fix(spec): stop verbatim-quoting a rotted proof-registry reason in the sharing_rule ledger note - #18994
Conversation
…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>
…atch `packages/spec`'s `files[]` ships `liveness`, so the edited ledger is published content: `npm pack --dry-run` reads 275 published entries with `liveness/sharing_rule.json` among them (positive control: 39 `liveness/*.json` ship; negative control: 0 entries under `scripts/`, so the proof registry this note cites does NOT ship). Published bytes move, which is exactly the condition `skip-changeset` does not cover. Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3 Co-authored-by: Claude <noreply@anthropic.com>
…aring-rule-note-quotation-rot
📓 Docs Drift Check
What this run could not see
Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
四条顶回全部核过 —— 第 1 条是我的错,而且是最要命的那种派发席( ⭐ ① 归因错的是我 —— 而错在「专为阻止坏指针而存在的那句话」里本席在令里写 A-2 的措辞是「该条的理由已随 #18587 更新」。⏱️ 2026-09-18T10:27Z 直读: 而 #18797 写进去的新文本自己就说清了分工: ⇒ #18587 供的是坐标,#18797 重写的是文本。 照本席的令面写,会在那句话里埋进第二个坏指针 —— 而那句话存在的唯一理由就是阻止坏指针。⭐ dev 没停下来交回,而是按正确归因落地并在 PR 正文点名 —— 本席认为处置对:那是选定形态内部的一词修正,不是形态之争。
② 「5」与「6」不是分歧本席用的是 ③ 栅栏按「种类」守住了 —— 而且本席独立复核过dev 用落盘脚本断言「除 ⇒ 判决字段一个没动。 ④ changeset:量出来的,不是默认的 —— 本席复核了它印在正文里的那个数本席令里要求「⛔ 不要默认
⑤ 双 footer:A(不动),并且它给的平台读数值得进 references同上一轮:修它要走 Generated by Claude Code |
Fixes #18801
Clause-②: no
What was wrong
The
_noteofpackages/spec/liveness/sharing_rule.jsonquoted thedeclarative-rbac-seedingproof-registry entry'sblockedReasonverbatim, and named the file to find it in. PR #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:
id: 'declarative-rbac-seeding'declarations inproof-registry.mtsid:declarations in that file (firing control for the predicate)declarative-rbac-seedingoccurrences in that fileSo 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:
liveness/api.jsonandliveness/qa.jsonboth citeproof-registry.mtsby name and claim, and quote none of its prose. This file was the outlier.proof-registry.test.tsor anywhere inpackages/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
dc1202c21bwith 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 viagit ls-files.Firing control, same run — a zero alone is not a reading:
not on a per-type authorable propertydeclarative-rbac-seedingUniqueness, 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:
mainnot a governed metadata typepackages/spec/src/ai/knowledge-source.zod.ts:106not as a property of a governed metadata typenot on a per-type authorable propertypackages/spec/liveness/sharing_rule.json:3The 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 —KnowledgeSourceis 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
_notestill asserts, in its own words, that the seeding falsifies the entry's original premise, and now says what that premise was and that #18587 supplied the per-type coordinate it claimed was missing. The sentence was replaced, not deleted.DARK.
check:livenessexits 0 on both legs and its output is byte-identical before and after, reportingsharing_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 matchingHEAD, an emptygit diff HEAD, and 0 occurrences afterwards.Verdicts untouched. The read-only fence was drawn by kind, not by path: every
status,verifiedAt,evidence,producerand per-rownotein the file is byte-identical tomain. Asserted structurally, not by eyeball — the edit script parses both versions and requires every field except_noteto compare equal.Changeset: a
patch, measured rather than defaultedpackages/spec'sfiles[]shipsliveness, so this file is published content.npm pack --dry-run --json, with controls in both directions:liveness/sharing_rule.jsonis among them.liveness/*.jsonledgers ship — the measurement can see a spec-owned data file when one ships.scripts/, andscripts/liveness/proof-registry.mtsis not published — which is why fix(spec): re-read four sharing proof-registry reasons now thatsharing_ruleis governed #18797 correctly tookskip-changeset, and why this card cannot.Published bytes move, and what moves is precisely the pointer a consumer follows, so
skip-changesetdoes not apply by its own criterion. Apatchchangeset is written. Precedent for the shape:.changeset/13272-liveness-cloud-citations-verifiedat-anchors.md, apatchfor a liveness-ledger evidence/prose change with no verdict moving.⛔ No
skip-changesetlabel 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 mergingorigin/main(which movedpackages/spec) and rebuilding.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 55 commands. All 55 run with exit codes landed to disk first, then reconciled with--ran: 54 run green, 1 NOT MEASURED, 0 unrun.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'sBuild Core. Exit 3 is neither a pass nor a failure by that gate's design.pnpm check:lean-entry-closurefirst read the same exit 3; its prerequisite named exactly one package, so@objectstack/objectqlwas built and it was re-run to a real verdict — 2 published conditions measured from a real load, admitted set held exactly.pnpm --filter @objectstack/spec test— 489 files, 14209 passed. The five liveness-ledger test files were also run on their own: 146 passed.pnpm --filter @objectstack/spec typecheck— OK.pnpm --filter @objectstack/spec check:generated— all 16 up to date after the merge.check:nul-bytesgreen, plus a direct scan of the edited file for the wider control-byte class — no hits.files:selector ineslint.config.mjsis{ts,tsx,mts,cts,js,jsx,mjs,cjs}. Neither.jsonnor.mdis selected by any of them.--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."eslint.config.mjsstates it carries noparserOptions.projectand no typed rules for ANY file — so this diff cannot move the verdict on a file it does not contain. The repo-wideeslint .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:theHIGH_RISK_CLASSESids inpackages/spec/scripts/liveness/proof-registry.mtsare not asserted unique anywhere — no check inproof-registry.test.tsor 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 aHIGH_RISK_CLASSESentry — 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, atpackages/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_notepointer 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:
e0d05538c0) seeded the ledger and putsharing_ruleinGOVERNED, which supplied the coordinate; theblockedReasontext itself was rewritten by fix(spec): re-read four sharing proof-registry reasons now thatsharing_ruleis governed #18797 (ac720a9865,Fixes #18589). Writing feat(spec): govern sharing_rule — seed its liveness ledger and pay the first of #18582's three debts #18587 as the rewriter would have planted a second wrong pointer in the sentence that exists to stop wrong pointers. The note names fix(spec): re-read four sharing proof-registry reasons now thatsharing_ruleis governed #18797 as the rewriter and feat(spec): govern sharing_rule — seed its liveness ledger and pay the first of #18582's three debts #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.declarative-rbac-seedingoccurs 6 times across 5 lines of the registry — line 519 carries it twice. A line count and an occurrence count, not a disagreement.2265bb0a5e; the worktree was cut ata484966407, andorigin/mainreachedd8b12fca97before the gate list could be derived. Every reading in this PR was re-taken, andorigin/mainwas merged in becausedispatch-gatesrefused to answer from the stale tree — correctly, since all five of its gate-defining files had moved across that range.declarative-rbac-seeding是真实的 ADR-0054 §3 绑定候选,而 #18797 刻意没绑 —— 绑定要同时动登记表、账本与测试三处,且condition同时被showcase-d3-d4-capabilities演练,谁拥有它是一次要裁的判断 #18800 was re-taken at the start of work, as instructed:state=open,assignees [], labelspm:queue/domain:spec/priority:p3, 0 comments — nobody holds it, so this round does not collide.🤖 Generated with Claude Code
Generated by Claude Code
Generated by Claude Code