Repository navigation
fix(spec): re-read four sharing proof-registry reasons now that sharing_rule is governed - #18797
Merged
os-bill merged 1 commit intoSep 17, 2026
Conversation
…falsified `sharing_rule` became a governed metadata type when #18587 seeded packages/spec/liveness/sharing_rule.json, so the four sharing-related `blockedReason` entries in proof-registry.mts — plus one comment on `rls-check-post-image` carrying the same sentence — were recording a reason that had stopped being true. Each entry is re-read against what its proof ACTUALLY exercises, not swept: - bu-hierarchy-sharing, sharing-rule-org-scoped-listing and sharing-rule-criteria-required never author the spec shape (they call SharingRuleService.defineRule on the booted kernel, or POST a runtime body to /api/v1/sharing/rules), so they stay unbound — for a reason that is true. - declarative-rbac-seeding DOES author it (showcase defineSharingRule → bootstrapDeclaredSharingRules → the asserted sys_sharing_rule row), so it is recorded as a real ADR-0054 §3 binding candidate and deferred to that separate act: adoption is a ledger act, since every cited row must carry `proof`. No `bound` flag and no `ledgerBindings` change; no published bytes move. Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3 Co-authored-by: Claude <noreply@anthropic.com>
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
This was referenced Sep 17, 2026
os-bill
marked this pull request as ready for review
September 17, 2026 21:59
This was referenced Sep 17, 2026
os-bill
deleted the
claude/issue-18589-proof-registry-stale-blocked-reason
branch
September 17, 2026 22:19
os-bill
pushed a commit
that referenced
this pull request
Sep 18, 2026
…e sharing_rule ledger note The `_note` of `packages/spec/liveness/sharing_rule.json` quoted the `declarative-rbac-seeding` entry's `blockedReason` VERBATIM. PR #18797 (`ac720a9865`) rewrote that reason, so the quoted string stopped existing in the very file the note sends a reader to. The substance was never wrong — the seeding does falsify the entry's original premise — so this replaces the quotation rather than the judgement: cite the registry and the stable `declarative-rbac-seeding` id, state the substance in the note's own words, and quote nothing. Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3 Co-authored-by: Claude <noreply@anthropic.com>
This was referenced Sep 24, 2026
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 28, 2026
…ey it requires (objectstack-ai#18816) Fixes objectstack-ai#18747 Clause-②: no ## Where the two defects actually are on `main` The card's line numbers were taken on a branch head. Re-anchored by symbol against `origin/main` at the base of this branch (`034f5a3afd`), in `packages/spec/scripts/lib/dropped-refinements.ts`: | the card said | on `main` it is at | symbol | |---|---|---| | `:453` | **`:496`** | the first `throw new Error` in `readDroppedRefinementsBaseline` | | `:456-458` | **`:499-501`** | the `entry.sites` check and the second `throw new Error` | | module docblock `:68` | **`:66-70`** | the "Every entry carries a `reason`" sentence | | (not named) | **`:134-150`** | the `DroppedRefinementsEntry` docblock that contradicts it | ## Which side is true — measured from the consumers, not chosen The card's open question was whether the refusal should name `sites` or the shape should really carry `count`/`reason`, and which docblock governs. Every consumer that reads this ledger answers `sites`, and none of them reads `count` or `reason` at all: | consumer | reading | |---|---| | `DroppedRefinementsEntry` (the shipped interface) | one member: `readonly sites: readonly string[]` | | `packages/spec/dropped-refinements.baseline.json` | 243 entries; **243** carry `sites`, **0** carry `count`, **0** carry `reason` | | `checkDroppedRefinements` | reads `entry.sites` only — set difference plus a length compare | | the `unreasoned` refusal | fires on `entry.sites.length === 0`, and the gate prints "carry an empty `sites` list" | | `build-schemas.ts` remedy text (both the undeclared and the miscounted arms) | prints the corrected entry as `"sites": [ ... ]` | | the ledger's own `description` field | documents `sites` and nothing else | | `dropped-refinements.test.ts` "the committed ledger" | asserts `entry.sites.length` per entry and that `measured.droppedRefinementSites` equals their sum | So `sites` is the contract and the `DroppedRefinementsEntry` docblock governs. The two wrong texts are both residue of the module this one was copied from: in `packages/spec/scripts/lib/unemitted-schemas.ts` the entries really are `{ cause, reason }`, its refusal really does say so, and its gate really does require a non-empty `reason` (`entry.reason.trim() === ''`). Copying the file carried the vocabulary across without the shape. ## LIT — the red leg, both messages verbatim The trap is a sequence, so the probe walks it: take a structurally broken ledger, read what the reader says the shape is, write that shape, and hand it back to the same function. **BEFORE** (the module restored to `origin/main`, the rest of the tree unchanged): ```text [step 1] input: {"entries": []} REFUSED dropped-refinements.baseline.json: "entries" must be an object of key -> { count, reason } [step 2] the ledger an author writes by FOLLOWING step 1 input: {"entries":{"system/TraceSamplingConfig":{"count":1,"reason":"zod projects no custom check"}}} REFUSED dropped-refinements.baseline.json: entry "system/TraceSamplingConfig" needs a `sites` array of path strings ``` The repair written from the message is refused by the **same function**, four lines below the message that prescribed it. **AFTER**: ```text [step 1] input: {"entries": []} REFUSED dropped-refinements.baseline.json: "entries" must be an object of key -> { sites: string[] } [step 3] the ledger an author writes by FOLLOWING step 1 input: {"entries":{"system/TraceSamplingConfig":{"sites":["properties.rate"]}}} ACCEPTED (1 entry/entries) ``` The mutation leg and the restore leg were each proved on disk (the deleted text present and the injected text absent, then the reverse), and the restore was verified byte-identical to `HEAD` by `git hash-object` (`9615f4e0c0…` both sides) with an empty `git diff HEAD` and an empty `git status --porcelain`. The probe itself lives outside the repository and nothing of it is committed. ## DARK — a legitimate ledger passes on both legs, and nothing else moved | reading | BEFORE | AFTER | |---|---|---| | the real committed ledger through `readDroppedRefinementsBaseline` | ACCEPTED — 243 entries, 737 sites | ACCEPTED — 243 entries, 737 sites | | `pnpm --filter @objectstack/spec test` | 487 files / **14051** passed, 0 failed | 487 files / **14055** passed, 0 failed | | `dropped-refinements.test.ts` | 23 tests | 27 tests | The `+4` is exactly the four tests this PR adds. The BEFORE row is a real run, not arithmetic: both files were checked out at the merge base, the suite was run, and both were restored and re-verified byte-identical to `HEAD`. Nothing else in the repository pins either message — `git grep "must be an object of key"` returns exactly two hits, this one and `unemitted-schemas.ts`'s own (which is correct for its own shape, and is the live control on that grep). ## The pin is a closed loop, not a wording match Error prose is not pinned here on its spelling; what is pinned is the **named subject** and the property that makes this class of defect a trap: whatever the refusal names has to be what the reader then accepts. Four cases in `packages/spec/scripts/dropped-refinements.test.ts`: 1. the shape diagnostic names `sites`; 2. a ledger written to that shape is then ACCEPTED — the loop closes; 3. **LIT CONTROL** — the shape the old diagnostic named is refused, and that refusal still says `sites` (without this leg the first two pass on a reader that accepts anything); 4. the shape diagnostic names no key the entry shape does not have. The docblock half has no pin, deliberately: no consumer parses a docblock, and a source-text assertion over prose is a gate that fails on rewording rather than on regression. ## Also in this diff, declared - The module docblock's **shrink-only bullet** said the ratchet re-checks "a recorded `count`" — the same contradiction as the `reason` sentence the card names, in the paragraph above it, against the same `DroppedRefinementsEntry` docblock ("The unit is the SITE and not a count, deliberately"). Repaired in place under the bounded exemption: same defect class as this card, same file, mechanical, the corrected form already fixed by the entry docblock, no new verification surface, and no other claim holds any `dropped-refinements*` path (measured across all 26 open `claude/issue-*` PRs, with `proof-registry.mts` reading out for objectstack-ai#18797 as the live control). - `packages/spec/scripts/dropped-refinements.test.ts` was listed read-only on the claim. It is written here, and only to carry this card's own regression pin. ## The card's second open question — is there a third site? `unemitted-schemas.ts`, the sibling the docblock calls itself "Identical to", was read: it has the same defect **nowhere**. Its docblock claim, its refusal text, its interface and its ledger all agree on `{ cause, reason }`. It is the correct template, not a second instance. ## Changeset — measured, not inferred from the path `npm pack` on `packages/spec` after a full build, then grep over the packed bytes (142,490,031 of them): | reading | hits | |---|---| | tarball entries under `package/scripts/` | **0** | | `DROPPED_REFINEMENTS_BASELINE_FILE` in the packed bytes | **0** | | `readDroppedRefinementsBaseline` | **0** | | `must be an object of key -> { sites: string[] }` | **0** | | positive control — entries under `package/src/` | 203 | | positive control — entries under `package/json-schema/` | 1530 | | positive control — `x-dropped-refinements` in the packed bytes | 486 | | positive control — `ObjectSchema` in the packed bytes | 965 | `packages/spec/scripts/**` is absent from the package's `files[]`, and the build after this change leaves `git status` clean, so no `dist/` byte moves either. Nothing this diff changes publishes from any released package, so it carries `skip-changeset` rather than a changeset. ## Verification - `pnpm --filter @objectstack/spec build` — exit 0 (34/34 declaration files present). - `pnpm --filter @objectstack/spec test` — exit 0, 487 files / 14055 tests. - `pnpm --filter @objectstack/spec typecheck` — exit 0, including `tsconfig.scripts.json` (which is what compiles the edited file) and the test layer. - `node scripts/pm/dispatch-gates.mjs --commands` derived **57** families from the change set; all 57 were run and reconciled with `--ran`: **54 exit 0**, **3 exit 3 = NOT MEASURED** (`check:dual-build-cjs-loads`, `check:lean-entry-closure`, `check:type-check-debt` — each refuses its own prerequisite because only `packages/spec` is built in this worktree; all three read built output, which this diff cannot move, and CI builds the full closure). - `pnpm check:nul-bytes` exit 0, plus a direct control-character scan of both edited files — no match, with a live non-zero control on a file that carries one. - `origin/main` was merged in before this PR was opened (clean, no `os-regen` deferral). --- _Generated by [Claude Code](https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3)_ Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 28, 2026
…e sharing_rule ledger note (objectstack-ai#18994) Fixes objectstack-ai#18801 Clause-②: no ## What was wrong The `_note` of `packages/spec/liveness/sharing_rule.json` quoted the `declarative-rbac-seeding` proof-registry entry's `blockedReason` **verbatim**, and named the file to find it in. PR objectstack-ai#18797 (`ac720a9865`) rewrote that reason, so the quoted sentence stopped existing in the very file the note sends a reader to. **The judgement was never wrong.** The seeding does falsify the entry's original premise — the rewritten reason on the entry now records exactly that, as a real ADR-0054 §3 binding candidate held back by the adoption act alone. Only the quotation rotted, which is why this is p3 and why the fix replaces the quote rather than the verdict. ## Shape chosen: A-2 — stop quoting verbatim The card preferred A-2 and left the choice to the dev, because the real question is whether a reader can still **locate** the entry once the quote is gone. Measured, not assumed: | reading | result | |---|---| | `id: 'declarative-rbac-seeding'` declarations in `proof-registry.mts` | **1** | | ...out of all `id:` declarations in that file (firing control for the predicate) | **42** | | `declarative-rbac-seeding` occurrences in that file | **6**, across **5** lines | So the id is a unique key *within the registry* and grepping it lands a reader on the entry. A-1 would have bought a pointer with the same expiry date as the last one: the entry's reason is prose owned by another card's author, and this note has now been broken by a rewrite of it once already. Three things worth stating about the shape: - **The old premise is paraphrased, deliberately not re-quoted.** A paraphrase of a premise that has already been retired cannot rot — the text it describes is frozen in history and nothing will rewrite it again. Re-quoting it would also have re-introduced the exact string this card exists to remove. - **It is the house pattern in the same directory.** `liveness/api.json` and `liveness/qa.json` both cite `proof-registry.mts` by name and claim, and quote none of its prose. This file was the outlier. -⚠️ **Nothing mechanically asserts those ids unique** — there is no uniqueness assertion in `proof-registry.test.ts` or anywhere in `packages/spec/scripts/liveness/`. The id's durability as an anchor is a measured fact about today's tree, not an enforced invariant. See the acceptance note below. ## Acceptance readings All taken at `dc1202c21b` with a **fold-proof** predicate: whitespace folds and TypeScript `' + '` concatenation seams are dissolved before matching, because the registry splits every reason across source literals mid-phrase and a line-oriented grep reads a false zero there. The predicate carries a self-test — three synthetic samples that must each read 1 through a fold or a seam, plus a negative control that must read 0 — and all four behaved as declared on every run, so the zeros below are measurements rather than a broken regex. Needles are written **in full**; corpus is all 8,912 tracked text files via `git ls-files`. **Firing control, same run** — a zero alone is not a reading: | needle (in full) | result | |---|---| | `not on a per-type authorable property` | **0** repo-wide | | ⭐ FIRING CONTROL `declarative-rbac-seeding` | **21** hits in 9 files, same run, same predicate | **Uniqueness, re-taken** — the card's claim was the filing dev's reading and had not been re-run. All three old spellings, before and after: | old spelling (in full) | on `main` | after | |---|---|---| | `not a governed metadata type` | 1 — `packages/spec/src/ai/knowledge-source.zod.ts:106` | 1, unchanged | | `not as a property of a governed metadata type` | 0 | 0 | | `not on a per-type authorable property` | 1 — `packages/spec/liveness/sharing_rule.json:3` | **0** | The claim holds, with the shape made precise: the three spellings do not all hit this one site. Spelling 3 was the only one on the `_note`; spelling 1's single hit is on an unrelated file — `KnowledgeSource` is documented as not being a governed metadata type, nothing to do with sharing rules — and it is deliberately untouched; spelling 2 was already absent. **Substance preserved.** The rewritten `_note` still asserts, in its own words, that the seeding falsifies the entry's original premise, and now says what that premise was and that objectstack-ai#18587 supplied the per-type coordinate it claimed was missing. The sentence was replaced, not deleted. **DARK.** `check:liveness` exits 0 on both legs and its output is **byte-identical** before and after, reporting `sharing_rule 17 classified (live 16, planned 1)` either way. The BEFORE leg is a real measurement, not a no-op: the old quotation was confirmed back on disk (1 occurrence) before that run, and the restore was proven by blob hash matching `HEAD`, an empty `git diff HEAD`, and 0 occurrences afterwards. **Verdicts untouched.** The read-only fence was drawn by kind, not by path: every `status`, `verifiedAt`, `evidence`, `producer` and per-row `note` in the file is byte-identical to `main`. Asserted structurally, not by eyeball — the edit script parses both versions and requires every field except `_note` to compare equal. ## Changeset: a `patch`, measured rather than defaulted `packages/spec`'s `files[]` ships `liveness`, so this file is published content. `npm pack --dry-run --json`, with controls in both directions: - **275** published entries, and `liveness/sharing_rule.json` is among them. - **Positive control**: 39 `liveness/*.json` ledgers ship — the measurement can see a spec-owned data file when one ships. - **Negative control**: **0** entries under `scripts/`, and `scripts/liveness/proof-registry.mts` is not published — which is why objectstack-ai#18797 correctly took `skip-changeset`, and why this card cannot. Published bytes move, and what moves is precisely the pointer a consumer follows, so `skip-changeset` does not apply by its own criterion. A `patch` changeset is written. Precedent for the shape: `.changeset/13272-liveness-cloud-citations-verifiedat-anchors.md`, a `patch` for a liveness-ledger evidence/prose change with no verdict moving. ⛔ No `skip-changeset` label is applied, deliberately — it is an opt-out that would exempt this PR from the very check the changeset satisfies. ## Verification Run in a dedicated worktree at `dc1202c21b`, after merging `origin/main` (which moved `packages/spec`) and rebuilding. - **Gates**: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived **55** commands. All 55 run with exit codes landed to disk first, then reconciled with `--ran`: **54 run green, 1 NOT MEASURED, 0 unrun**. - The one NOT MEASURED is `pnpm check:dual-build-cjs-loads` — recorded exit **3**, `PREREQUISITE NOT MET`, its own words: *"this gate reads built output, and some package has no dist"* across 87 packages. It needs a whole-repo build and is owned by CI's `Build Core`. Exit 3 is neither a pass nor a failure by that gate's design. - `pnpm check:lean-entry-closure` first read the same exit 3; its prerequisite named exactly one package, so `@objectstack/objectql` was built and it was re-run to a real verdict — 2 published conditions measured from a real load, admitted set held exactly. - **Tests**: `pnpm --filter @objectstack/spec test` — **489 files, 14209 passed**. The five liveness-ledger test files were also run on their own: 146 passed. - **Typecheck**: `pnpm --filter @objectstack/spec typecheck` — OK. - **Generated artifacts**: `pnpm --filter @objectstack/spec check:generated` — all 16 up to date after the merge. - **Control characters**: `check:nul-bytes` green, plus a direct scan of the edited file for the wider control-byte class — no hits. - **Lint, narrowed and the narrowing proven** — the three readings, not an assertion: 1. **Population, read from eslint's own config**: every `files:` selector in `eslint.config.mjs` is `{ts,tsx,mts,cts,js,jsx,mjs,cjs}`. Neither `.json` nor `.md` is selected by any of them. 2. **Count, read from `--format json`**: eslint over exactly the 2 changed paths reports on 2 files, 0 errors, each with its own message *"File ignored because no matching configuration was supplied."* 3. **Invariance for untouched files**: type-aware linting is not enabled anywhere — `eslint.config.mjs` states it carries no `parserOptions.project` and no typed rules *for ANY file* — so this diff cannot move the verdict on a file it does not contain. The repo-wide `eslint .` sweep is CI's run and is unaffected by these two paths. ## Acceptance notes Out of scope for this card, filed nowhere and recorded here instead: - `noted, not filed:` the `HIGH_RISK_CLASSES` ids in `packages/spec/scripts/liveness/proof-registry.mts` are not asserted unique anywhere — no check in `proof-registry.test.ts` or its siblings. This is an observation, not a reproducible defect, a contract violation or an authoring trap, so it is not one of the three filing classes. It is worth writing down only because this PR's argument for A-2 rests on the id being a durable anchor, and that rests on a convention rather than on a gate. **Who will meet it:** the next seat to add or rename a `HIGH_RISK_CLASSES` entry — the same file this card was forbidden to edit. Not acted on here. - `noted, not filed:` the same id string is declared a second time in the tree, at `packages/qa/dogfood/test/authz-conformance.matrix.ts:322`. That is deliberate — the conformance matrix names the same proof — and it makes the id a cross-file join key rather than a collision. Recorded so a later reader who greps the id repo-wide and finds two declarations does not read it as drift. **Who will meet it:** anyone following the new `_note` pointer with a repo-wide grep instead of a registry-scoped one. ## Pushback on the brief Reported rather than quietly worked around, per the round convention: 1. **The A-2 wording in the brief mis-attributes the rewrite.** It prescribes saying the entry's reason *"was updated (by objectstack-ai#18587)"*. Measured: objectstack-ai#18587 (`e0d05538c0`) seeded the ledger and put `sharing_rule` in `GOVERNED`, which supplied the coordinate; the `blockedReason` text itself was rewritten by **objectstack-ai#18797** (`ac720a9865`, `Fixes objectstack-ai#18589`). Writing objectstack-ai#18587 as the rewriter would have planted a second wrong pointer in the sentence that exists to stop wrong pointers. The note names objectstack-ai#18797 as the rewriter and objectstack-ai#18587 as what supplied the coordinate. This is a one-token correction inside the shape the brief chose, so it was implemented rather than handed back. 2. **The PM's "5 hits" and this PR's "6" are the same reading.** `declarative-rbac-seeding` occurs 6 times across 5 lines of the registry — line 519 carries it twice. A line count and an occurrence count, not a disagreement. 3. **The base moved twice during the round.** The brief's readings were at `2265bb0a5e`; the worktree was cut at `a484966407`, and `origin/main` reached `d8b12fca97` before the gate list could be derived. Every reading in this PR was re-taken, and `origin/main` was merged in because `dispatch-gates` refused to answer from the stale tree — correctly, since all five of its gate-defining files had moved across that range. 4. **objectstack-ai#18800 was re-taken at the start of work**, as instructed: `state=open`, `assignees []`, labels `pm:queue` / `domain:spec` / `priority:p3`, 0 comments — nobody holds it, so this round does not collide. --- 🤖 Generated with [Claude Code](https://claude.com/claude-code) _Generated by [Claude Code](https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3)_ --- _Generated by [Claude Code](https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #18589
Clause-②: no
What was wrong
packages/spec/scripts/liveness/proof-registry.mtsrecorded fourblockedReasonentries whose stated reason rested on one premise: sharing rules are authored at STACK level andsharing_ruleis not a governed metadata type, so there is no ledger entry to ratchet. PR #18587 (landed ase0d05538c0) seededpackages/spec/liveness/sharing_rule.jsonand putsharing_rulein the gate'sGOVERNEDarray — which makes that premise false. A recorded WHY that has silently stopped being true is one level up from the keys this ledger governs.A fifth carrier the card did not enumerate is fixed in the same pass, because the DARK control is a file-level reading: the
rls-check-post-imageentry's code comment carried the same sentence ("stack-level sharing rules are not a governed metadata type, so onlycheckbinds").What changed — per entry, read against what each proof ACTUALLY exercises
No
boundflag and noledgerBindingsentry changes. This PR changes recorded reasons only.bu-hierarchy-sharingstack.kernel.getService('sharingRules').defineRule({… criteria, recipientType, recipientId …}, SYS): the RUNTIME column shape.SharingRuleSchemaandbootstrapDeclaredSharingRulesare not on its path, so no authorablesharing_rule.*key is written. BindingsharedWith.typehere would be the owner-anchor/allowTransfer mistake.sharing-rule-criteria-requiredcondition/api/v1/sharing/rules. Its own header states the mechanism: "The endpoint plucks its body field-by-field intoSharingRuleService.defineRule;SharingRuleSchemais never on that path." The ledger coordinate now exists; this proof is still not evidence for it.declarative-rbac-seedingdefineSharingRule(examples/app-showcase/src/security/sharing-rules.ts:condition,sharedWith: { type, value },object,name),bootstrapDeclaredSharingRulesseeds them, and the proof asserts the landed row (object_name,recipient_type,recipient_id, and the CEL tocriteria_jsontranslation). Adoption is a separate ADR-0054 §3 act — see "Why the binding is not in this PR".sharing-rule-org-scoped-listingPOST /sharing/rules(the criteria-required shape), and what the file pins is a READ-SCOPE filter insideSharingRuleService, not the behaviour of any authored key.rls-check-post-image(comment)sharing_rule.conditionIS a governed entry since #18587, so that half is no longer un-bindable for want of a coordinate; onlycheckbinds here because adopting it is its own ADR-0054 §3 act with its own candidate question.Why the binding is not in this PR
declarative-rbac-seedingis a real candidate, and adopting it is a ledger act, not a registry act:BOUND_PROOF_PATHSmakescheck-liveness.mtsrequire the matchingproofon every citedsharing_rule.jsonrow (report.proofMissing), andproof-registry.test.ts's wiring suite asserts the same from the other side (it also needs asharing_rulerow in itsledgerFormap).packages/spec/liveness/sharing_rule.jsondeliberately claimsproofon no row — its own_note: "Noproofis claimed on any row here: binding a high-risk class is a separate ADR-0054 §3 act, one class at a time, and it is filed rather than slipped in." That file is read-only under this card's declared file surface, and WHICH of the five exercised props the class owns is a decision of its own (conditionis also exercised byshowcase-d3-d4-capabilities). Reported for filing instead.The ledger reading the seat could not verify
The card's "17 classified (16 live, 1 planned)" is the GATE's count, and it is correct as such — but it is not the number of authored rows. Read first-hand from
packages/spec/liveness/sharing_rule.jsonand fromcheck-liveness.mts --dump sharing_rule:live(name,label,description,object,active,accessLevel,sharedWith.type,sharedWith.value,condition) + 1planned(type, the one-memberSharingRuleTypediscriminator).livebyFRAMEWORK_FIELDS(_lock,_lockReason,_lockSource,_lockDocsUrl,_provenance,_packageId,_packageVersion).pnpm --filter @objectstack/spec check:livenessprints.Acceptance controls
LIT — the premise really is false (symbol/array membership, not a substring grep): parsing the
GOVERNEDsymbol out ofcheck-liveness.mtsreads length 39,includes('sharing_rule')true at index 36; negative controlssharing_rules/sharing/not_a_metadata_typeall read false. The gate's own runtime leg agrees: it printssharing_rulein "governed types:" and emits the per-type rowsharing_rule 17 classified (live 16, planned 1), which only exists because the loop iteratesGOVERNED.DARK — the assertion now reads 0, with a non-zero control. The predicate folds the TypeScript string-concatenation seams (
' + ') before matching, because the reasons are split across source literals mid-phrase; a line-oriented predicate reads a false zero there. It deliberately uses no POSIX ERE bracket spelling, which is the other false-zero trap.6de7a2d6e6(control)not a governed metadata typenot as a property of a governed metadata typenot on a per-type authorable propertyno ledger entry to ratchet, scoped to the four sharing entriesThe predicate carries its own self-test (a synthetic split-literal sample carrying all three spellings must read 3; it does, on both runs), so the zero is a measurement and not a broken regex.
Changeset:
skip-changeset, measured not inferrednpm pack --dry-run --jsoninpackages/spec: 275 published entries, 0 underscripts/, andproof-registry.mtsis not among them. Positive control on the same reading: 41liveness/*.jsonledger files ARE published, so the measurement can see a spec-owned data file when one ships. This diff therefore moves zero published bytes.Verification
Run on
968d6e0a55, in a dedicated worktree:pnpm --filter @objectstack/spec test— 486 files, 14015 passed, 1 skipped.pnpm --filter @objectstack/spec exec vitest run scripts/liveness/proof-registry.test.ts— 39 passed (the registry-invariant and wiring suite).pnpm --filter @objectstack/spec typecheck— exit 0 (tsc --noEmit+check:scripts-typecheck+check:test-typecheck).pnpm --filter @objectstack/spec check:liveness— exit 0; counts unchanged.pnpm lint(repo-wideeslint . --no-inline-config) — exit 0. Full population, no narrowing to declare.node scripts/pm/dispatch-gates.mjs --commandsfor this diff: 46 of 50 green, includingcheck:nul-bytes,check:published-files,check:cross-package-test-inputs,check:test-source-alias,check:pm-governed-merges,check:adr-0087-registration.check:dts-closure,check:dual-build-cjs-loads,check:lean-entry-closure,check:sourcemap-no-sources-content— each exits 3, PREREQUISITE NOT MET ("nothing was swept … NOT a pass and NOT a finding") because a fresh worktree has nodist/for any of the 81 packages. They read built output repo-wide; this diff changes a liveness script that is in no package's build inputs and in nofiles[]. Declared to CI, where the closure is built.Acceptance notes (noted, not filed)
bootstrapDeclaredSharingRulesthreadslabel: r.label ?? r.name, so an unauthoredlabelstores the rule NAME rather than staying empty. That is the ledger's recorded behaviour for thelabelrow, not a defect — noted only because it is the sort of thing a future binding decision touches. Successor: none — no PR or person is heading into that file for this reason.Generated by Claude Code