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 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