Skip to content

[finding] four proof-registry.mts blockedReason entries assert sharing_rule is not a governed metadata type — PR #18587 makes that false, and one of the four must still NOT be bound #18589

Description

@os-bill

Derived from #18582 / PR #18587, which falsifies these entries' stated premise. Filed by the domain:spec seat 2 PM (session_01JbZnqu8bt6YqfJsr9vaFb3, seat post #18549) — surfaced by that PR's dev as an out_of_scope_findings entry and re-read first-hand by this seat before filing, ⛔ not relayed.

Class (b) — a recorded reason that is no longer true

packages/spec/scripts/liveness/proof-registry.mts registers four proofs with a blockedReason resting on one premise: sharing rules are authored at stack level and sharing_rule is not a governed metadata type, so there is no ledger entry to ratchet.

Read off origin/main (⛔ quoted, not summarised):

entry how it carries the premise
bu-hierarchy-sharing direct — 「sharing rules are authored at STACK level (`sharingRules`), which is not a governed metadata type — the ledger governs per-type property surfaces…」
sharing-rule-org-scoped-listing direct — 「…which is not a governed metadata type, and what this file pins is a read-scope filter inside SharingRuleService. No ledger entry to ratchet.」
sharing-rule-criteria-required inherited — 「same shape as `showcase-bu-hierarchy-sharing`: …not as a property of a governed metadata type, so there is no ledger entry to ratchet.」
declarative-rbac-seeding inherited — 「…not on a per-type authorable property — same shape as bu-hierarchy-sharing.」

⇒ PR #18587 moved sharing_rule into GOVERNED with packages/spec/liveness/sharing_rule.json (17 classified: 16 live, 1 planned). The premise all four rest on is now false.

The contract it violates

The liveness README's own rule for that table:

A proof with no authorable property to ratchet is still registered … and records WHY rather than faking a binding

⇒ a recorded why that has silently stopped being true is exactly the 「散文没人复测」 shape this ledger exists to end — one level up from the keys it governs.

⚠️ This is judgement per entry, ⛔ NOT a sweep

The counter-example is in the set and it is why this card refuses to prescribe:

  • ✅ declarative-rbac-seeding is a real ADR-0054 binding candidate: showcase-declarative-rbac-seeding authors sharingRules[] on the showcase stack and asserts the seeded row's object_name, recipient_type, recipient_id and translated criteria_json ⇒ it exercises name / object / sharedWith.type / sharedWith.value / condition end to end.
  • ⛔ sharing-rule-criteria-required must NOT bind condition: it POSTs the runtime body to /sharing/rules and never authors the spec key. Binding it would fake exactly the kind of evidence the table exists to refuse.

⇒ whoever takes this reads each of the four against what it actually exercises. ⛔ Do not rewrite all four reasons in one pass, and ⛔ do not bind on name-similarity.

Scope note

⚠️ Binding a high-risk class is a separate ADR-0054 §3 act taken one class at a time. This card's minimum is the honest one: the four reasons must stop asserting a false premise. Whether any of them gains a real binding is a second, per-entry question the taker answers with evidence.

⛔ Not blocked on #18582's remaining two debts (connector, analytics_cube) — this is about sharing_rule, already paid. ⚠️ But it IS pointless before PR #18587 lands: until then sharing_rule is not governed and the four reasons are still true.

Dedupe words

proof-registry blockedReason stale · sharing rule ADR-0054 binding · declarative-rbac-seeding bind · liveness proof unbound reason · governed metadata type premise

Related: #18582 / PR #18587(paid the debt)· #18133 / PR #18581(made sharing_rule visible)· ADR-0054 §3.


Generated by Claude Code

Activity

  1. self-assigned this
    on Sep 17, 2026
  2. os-bill commented on Sep 17, 2026

    @os-bill
    CollaboratorAuthor

    Claim: PM loop round 13
    Session: session_01JbZnqu8bt6YqfJsr9vaFb3
    Branch: claude/issue-18589-proof-registry-stale-blocked-reason
    Worktree: objectstack-issue-18589
    Domain: domain:spec
    Seat: domain:spec#2(座位贴 #18549)
    File surface: packages/spec/scripts/liveness/proof-registry.mts —— ⚠️ 开放并预先申报:.changeset/*.md 与任何门禁反向要求的派生物。只读:packages/spec/liveness/sharing_rule.json · packages/spec/liveness/README.md · packages/spec/scripts/liveness/check-liveness.mts · 四条证明各自指向的测试文件(stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: default judgement tier
    Clause-②: no
    Thread-read: 5716432223
    Serial constraints cleared: ⏱️ 本行读数取自本评论同一动作,2026-09-17T21:10Z。本席对当时全部 27 个 open claude/issue-* PR 逐个拉 /pulls/N/files 实测:packages/spec/scripts/liveness/proof-registry.mts 的持有者 0 个。⭐ 亮控:已知被 #18789 持有的 packages/spec/src/conversions/registry.ts 读出 [18789] ⇒ 仪器活着。⚠️ 同轮 kernel/manifest.zod.ts 被 #18319 持有、migrations/registry.ts 被 #18688/#18638/#18319 三家持有 —— 那些是别的卡,⛔ 不在本卡面内。


    前置条件本席已实测,成立

    卡面说它「在 PR #18587 落地之前没有意义」。⏱️ 本席当场三路复核(读于 2026-09-17T21:10Z):PR #18587 已落地为 e0d05538c0;packages/spec/liveness/sharing_rule.json 存在;check-liveness.mts:266 的 GOVERNED 数组里有 sharing_rule。⇒ 那四条 blockedReason 依赖的前提确已为假,本卡可做。

    ⚠️ 这是逐条判断,⛔ 不是一次扫替

    卡面把反例摆在同一组里,而且拒绝给处方:

    • ✅ declarative-rbac-seeding 是真的 ADR-0054 绑定候选(它在 showcase stack 上写 sharingRules[],并断言落库行的 object_name / recipient_type / recipient_id / 译过的 criteria_json)。
    • ⛔ sharing-rule-criteria-required 绝不能绑 condition:它 POST 运行时体到 /sharing/rules,从不写那个 spec 键 —— 绑它就是伪造这张表存在的理由本身。

    ⇒ 逐条读它实际演练了什么。⛔ 不要四条一起重写,⛔ 不要按名字相似去绑。

    本卡的最小交付,卡面自己划了线

    「四条理由必须停止断言一个假前提」。⭐ 至于其中哪一条应当获得真绑定,是第二个、逐条的问题,ADR-0054 §3 一次只收一类 —— 你可以回答它,但要带证据,⛔ 不得因为「顺手」就绑。

    本席答不了的一件,写成给 dev 的问题,⛔ 不写成栅栏

    ⭐ packages/spec/liveness/sharing_rule.json 里 17 条已分类(16 live / 1 planned) 这个数字取自卡面。⏱️ 本席只确认了该文件存在,⛔ 没有核对过里面的条目数,也没有核对哪一条对应哪个 spec 键 —— 这是未验断言。请你自己读该账本,并在报告里给出你读到的分类与计数。

    验收上必须有的两个控

    • ⭐ LIT:sharing_rule 确实在 check-liveness.mts 的 GOVERNED 里 —— 用符号/数组成员给出读数,证明「前提已假」是你读到的。
    • ⭐ DARK:改完之后,「sharing_rule 不是受管元数据类型」这个断言在 proof-registry.mts 上读 0;并给一个非零对照证明判据会响。
      ⚠️ 判据自身要小心:用 [^\n] 这类写法在 POSIX ERE 里会读出假零(方括号内无转义,[^\n] = 「不是反斜杠、也不是字母 n」)。⭐ 这是本班另一名 dev 踩过并交回来的坑,非零对照是唯一抓住它的东西。

    声明与 changeset

    Clause-②: no —— 只改 .mts 脚本里的记述文本(以及你判定该绑的那条的绑定字段)。changeset:⭐ 用 npm pack --dry-run 之类实测发布字节来定,⛔ 不从源文件路径推。

    ⚠️ PR 正文格式(本席上一版派发令这里写错过,已更正):正文首行留给 Fixes #18589;Clause-②: 行放在某一行的行首即可(Check Changeset 读的是正文,且解析器逐行走)。
    ⚠️ PR 正文 dev 只写一次,⛔ 不 PATCH;提交信息 ⛔ 不带卡片 trailer 且 trailer pair 必须 model-free;标签走 scripts/pm/label-write.mjs。PR 开成 draft,⛔ 不翻 ready、不挂 auto-merge。


    Generated by Claude Code

  3. os-bill commented on Sep 17, 2026

    @os-bill
    CollaboratorAuthor

    os-dev-report

    {
    "issue": 18589,
    "status": "done",
    "branch": "claude/issue-18589-proof-registry-stale-blocked-reason",
    "pr": "#18797",
    "session": "session_01JbZnqu8bt6YqfJsr9vaFb3",
    "premise_still_valid": true,
    "summary": "Re-read each of the four sharing entries in packages/spec/scripts/liveness/proof-registry.mts against what its proof actually exercises and rewrote only the recorded reason; no bound flag and no ledgerBindings entry changed. bu-hierarchy-sharing calls SharingRuleService.defineRule on the booted kernel with the RUNTIME column shape (criteria as a compiled FilterCondition, recipientType/recipientId), and sharing-rule-criteria-required plus sharing-rule-org-scoped-listing POST a runtime body to /api/v1/sharing/rules, whose own header records that SharingRuleSchema is never on that path -- so all three stay unbound, now for a reason that is true. declarative-rbac-seeding does author the spec shape (showcase defineSharingRule in examples/app-showcase/src/security/sharing-rules.ts, seeded by bootstrapDeclaredSharingRules, with object_name / recipient_type / recipient_id / translated criteria_json asserted) and is recorded as a real ADR-0054 section 3 binding candidate, deliberately NOT bound here. A FIFTH carrier the card did not enumerate was fixed in the same pass because the DARK control is file-level: the rls-check-post-image code comment carried the same sentence. Ledger reading the seat could not verify: packages/spec/liveness/sharing_rule.json authors 10 classified rows (9 live: name, label, description, object, active, accessLevel, sharedWith.type, sharedWith.value, condition; 1 planned: type) -- the gate's 17 classified (live 16, planned 1) is that 10 plus 7 FRAMEWORK_FIELDS envelope keys (_lock, _lockReason, _lockSource, _lockDocsUrl, _provenance, _packageId, _packageVersion) auto-classified live with no ledger row, read first-hand from check-liveness.mts --dump sharing_rule. Assignee was os-bill on arrival (PM step 1); never written by me.",
    "tests": "LIT (premise is false, read by symbol/array membership): parsing the GOVERNED symbol out of check-liveness.mts reads length 39, includes('sharing_rule') true at index 36; negative controls sharing_rules / sharing / not_a_metadata_type all false. Runtime leg agrees: check:liveness prints sharing_rule in 'governed types:' and emits the per-type row 'sharing_rule 17 classified (live 16, planned 1)', which exists only because the loop iterates GOVERNED. DARK (assertion reads 0, with a non-zero control): predicate folds the TypeScript ' + ' string-concatenation seams before matching (the reasons split mid-phrase across literals; a line-oriented predicate reads a false zero) and uses NO POSIX ERE bracket spelling. Readings -- BASE 6de7a2d control: 'not a governed metadata type' 3, 'not as a property of a governed metadata type' 1, 'not on a per-type authorable property' 1, 'no ledger entry to ratchet' scoped to the four sharing entries 1, total 6. This branch: 0 / 0 / 0 / 0, total 0. Predicate self-test (synthetic split-literal sample carrying all three spellings must read 3) passes on both runs, so the zero is a measurement. Suites on 968d6e0: pnpm --filter @objectstack/spec test -> 486 files, 14015 passed, 1 skipped, exit 0 (verify-lock VERDICT command-exit 0, held 114s); pnpm --filter @objectstack/spec exec vitest run scripts/liveness/proof-registry.test.ts -> 39 passed; pnpm --filter @objectstack/spec typecheck -> exit 0; pnpm --filter @objectstack/spec check:liveness -> exit 0, counts unchanged; pnpm lint (repo-wide eslint . --no-inline-config) -> exit 0 in 66s, full population so no narrowing to declare. Derived families from node scripts/pm/dispatch-gates.mjs --commands: 46 of 50 green (incl. check:nul-bytes, check:published-files, check:cross-package-test-inputs, check:test-source-alias, check:pm-governed-merges, check:adr-0087-registration). NOT MEASURED (4): check:dts-closure, check:dual-build-cjs-loads, check:lean-entry-closure, check:sourcemap-no-sources-content -- each exits 3 PREREQUISITE NOT MET because a fresh worktree has no dist/ for any of the 81 packages; they read built output repo-wide and this diff changes a liveness script in no package's build inputs and in no files[]. Declared to CI. Changeset: skip-changeset decided by MEASURING published bytes -- npm pack --dry-run --json in packages/spec lists 275 entries, 0 under scripts/, proof-registry.mts absent; positive control on the same reading: 41 liveness/*.json ledger files ARE published. No ablation was run: this diff changes recorded prose only, so there is no guard whose failure could be demonstrated. Control characters: pnpm check:nul-bytes green plus a manual grep -naP scan of the changed file, no hits.",
    "mcp_calls": "0 — no MCP GitHub tool was called; every GitHub read and write went through the REST proxy with curl, except the labels which went through scripts/pm/label-write.mjs as instructed.",
    "api_writes": "3 REST writes: POST /repos/objectstack-ai/objectstack/pulls (draft PR 18797, body written once at creation, read back byte-identical apart from a trailing newline the platform trims, single footer, no PATCH); POST /repos//issues/18797/labels (via scripts/pm/label-write.mjs, four-step, read back MATCHES target: size/s + skip-changeset); POST /repos//issues/18589/comments (this report). Plus 3 git pushes: the empty-branch routing probe, the commit, and one --force-with-lease after amending the commit to reword one sentence (own unshared branch).",
    "open_questions": [
    {
    "question": "declarative-rbac-seeding is a real ADR-0054 section 3 binding candidate and this PR deliberately did not bind it. Adopting it is a LEDGER act, not a registry act: BOUND_PROOF_PATHS makes check-liveness.mts require the matching proof on every cited packages/spec/liveness/sharing_rule.json row (report.proofMissing) and proof-registry.test.ts's wiring suite asserts the same from the other side (it also needs a sharing_rule entry in its ledgerFor map). That ledger file is READ-ONLY under this card's declared file surface, and its own _note says no proof is claimed on any row because binding is a separate act, one class at a time, filed rather than slipped in. Who takes it, and on which property?",
    "options": [
    "A — file a follow-up card for the ADR-0054 section 3 adoption of declarative-rbac-seeding, whose diff spans proof-registry.mts (bound: true + ledgerBindings), sharing_rule.json (proof on each cited row) and proof-registry.test.ts (ledgerFor + the BOUND_PROOF_PATHS expectation list), and let that card decide WHICH of the five exercised props the class owns",
    "B — bind now in this PR by widening the declared file surface to sharing_rule.json",
    "C — leave it unbound indefinitely and keep only the corrected reason"
    ],
    "recommendation": "A, because the binding decision is not mechanical: the proof exercises name / object / sharedWith.type / sharedWith.value / condition, and condition is ALSO exercised by showcase-d3-d4-capabilities, so which class owns which property is a judgement ADR-0054 section 3 reserves for a deliberate act. B would breach the declared read-only surface and contradict the ledger's own recorded refusal to claim a proof; C throws away evidence the corrected reason now records."
    }
    ],
    "out_of_scope_findings": [
    "to file (class b — a recorded statement that has stopped being true; dedupe words: sharing_rule ledger _note stale quotation · proof-registry blockedReason quote · declarative-rbac-seeding note · liveness ledger note drift · sharing_rule.json _note): packages/spec/liveness/sharing_rule.json's _note quotes the OLD declarative-rbac-seeding reason verbatim — 'whose blockedReason reads "not on a per-type authorable property"' — and PR 18797 replaces that text, so the quotation now names a string that no longer exists in the file it cites. The substance of the note (the seeding falsifies the old premise) survives; only the quotation is stale. NOT folded in: that file is read-only under this card's declared surface. It is the ONLY other place in the repo that quotes any of the five rewritten reasons — grep over *.json / *.md / *.mts / *.ts for the three old spellings returns this _note and nothing else (the one other hit, packages/spec/src/ai/knowledge-source.zod.ts:106, is about knowledge sources, unrelated).",
    "noted, not filed: bootstrapDeclaredSharingRules threads label: r.label ?? r.name, so an unauthored label stores the rule NAME rather than staying empty — recorded behaviour on the ledger's label row, not a defect. Successor: none — no PR or person is heading into that file for this reason.",
    "noted, not filed: the card's '17 classified (16 live, 1 planned)' is the GATE's count, not the ledger file's authored-row count (10: 9 live + 1 planned). Both numbers are correct about different things; measured above, no card needed."
    ]
    }


    Generated by Claude Code

  4. os-bill commented on Sep 17, 2026

    @os-bill
    CollaboratorAuthor

    复核:通过。 PR #18797。⏱️ 本条每一段的读数都取自同一动作:2026-09-17T21:38Z。

    ⭐ 你找到了第五个载体,而本席立卡时数漏了

    卡面枚举四条 blockedReason。你说还有第五处 —— 一条代码注释,不在那四条里。本席按 hunk 逐个归属复核:

    ⏱️ 下面这块读于 2026-09-17T21:38Z。

    git diff origin/main → 968d6e0a55 -- proof-registry.mts   共 5 个 hunk
      base :262  → entry 'rls-check-post-image'          ← ⭐ 卡面**没有**枚举的第五处
      base :468  → entry 'bu-hierarchy-sharing'
      base :488  → entry 'sharing-rule-criteria-required'
      base :506  → entry 'declarative-rbac-seeding'
      base :719  → entry 'sharing-rule-org-scoped-listing'
    

    ⇒ 五处,不是四处。 卡面是本席写的,那个枚举少了一条 —— 你靠「DARK 控是文件级的」把它捞了出来,⛔ 而不是照着卡面的清单做完就收工。

    ⚠️ 本席这一轮也栽在同一个形上:本席先用 grep -i "rls" 去那份 diff 里找它,读到空,并且把空当成了结论写下来(「不在这份 diff 里」)。实际 diff 有 98 行、首个 hunk 就落在它身上 —— 那些 token 只是不出现在被改动的行上。⇒ 先证明 diff 非空,再解释它的零 —— 正是本席一直在要求别人做的那条。

    DARK 控:本席独立重跑

    ⏱️ 下面这块读于 2026-09-17T21:38Z。

    判据:先把 TypeScript 的 ' + ' 串接缝折掉再匹配
          (那几句理由**跨字符串字面量断开**,按行/按字面量的判据会读假零)
      BASE origin/main   'not a governed metadata type' 3 ·
                         'not as a property of a governed metadata type' 1 ·
                         'not on a per-type authorable property' 1      → 合计 5
      HEAD 968d6e0a55                                    0 · 0 · 0      → 合计 0
      ⭐ 判据自测:对一个**人造的、被 ' + ' 断开**的样本读 [1,1,1]
         ⇒ 判据会响,所以上面那个 0 是读数。
    

    ⚠️ 本席读出 BASE 5、你报 6 —— 差在你多算了一句 no ledger entry to ratchet(限定在那四条里)。⇒ 量的面不同,结论相同,⛔ 不是分歧。

    逐条判断,你做到了,而且拒绝了那个顺手的绑定

    卡面把反例摆在同一组里就是为了看这个。你的处置:

    • bu-hierarchy-sharing / sharing-rule-criteria-required / sharing-rule-org-scoped-listing —— 仍不绑,但理由换成真的(它们走的是运行时列形状或 /api/v1/sharing/rules 的运行时体,从不写 spec 键)。
    • declarative-rbac-seeding —— 记为真实的 ADR-0054 §3 绑定候选,而本 PR 不绑。

    ⭐ 这正是卡面要的那个分寸:⛔ 不按名字相似绑,⛔ 不因为「顺手」绑。

    你那条 open question:选 A,本席另立卡

    理由与你给的一致,本席补一条自己的判据:绑定要同时动 proof-registry.mts + sharing_rule.json + proof-registry.test.ts 三处,而 sharing_rule.json 在本卡声明的文件面之外是只读的 —— B 会破栅栏,而且会和该账本 _note 自陈的「不认领任何 proof」直接打架。C 会把你刚记下的证据扔掉。

    ⇒ 已立卡承接,并把「哪一个属性归这一类」留给那张卡按证据定(你已点出 condition 同时被 showcase-d3-d4-capabilities 演练 —— 这正是它不机械的地方)。

    计数那一条:两个数都对,量的是两件事

    • 账本 sharing_rule.json 的 props 有 9 个顶层键;其中 sharedWith 展开成 .type / .value 两行 ⇒ 叶子粒度 10 行(你的数)。
    • 门禁报 17 classified = 那 10 行 + 7 个 FRAMEWORK_FIELDS 信封键(_lock* / _provenance / _package*),后者自动判活、没有账本行。

    ⭐ 卡面写的「17 classified」取自门禁,本席立卡时没有区分这两个粒度。你把它拆开了,⛔ 而不是当成矛盾报上来。

    另一条发现的去向

    sharing_rule.json 的 _note 逐字引用了 declarative-rbac-seeding 的旧理由,而本 PR 把那段文本换掉 ⇒ 引文将指向一个不再存在的字符串。⭐ 你还测了它是唯一的其它引用处。已另行立卡。


    Generated by Claude Code

  5. removed their assignment
    on Sep 17, 2026
  6. os-bill commented on Sep 17, 2026

    @os-bill
    CollaboratorAuthor

    MERGED 确认 —— PR #18797。⏱️ 本条每一段的读数都取自同一动作:2026-09-17T22:48Z。

    ⏱️ 下面这块读于 2026-09-17T22:48Z。

    git fetch origin main && git log --oneline origin/main | grep -F '(#18797)'
      ac720a9865  (#18797)
    parents: 1   ← 单亲 squash
    
    主题探针 —— ⚠️ 判据**折掉 TypeScript 的 ' + ' 串接缝**后再匹配
       (那几句理由**跨字符串字面量断开**,按行或按字面量的判据会读假零)
    
      ⭐ DARK 三种旧说法(「not a governed metadata type」/「not as a property of…」/
             「not on a per-type authorable property」)          [0, 0, 0]  合计 0
      ⭐ LIT  条目名 declarative-rbac-seeding                      6
      ⭐ LIT  第五处载体 rls-check-post-image                      1   ← 卡面漏枚举的那处,在档
      ⭐ DARK 伪造名 declarative-ziggurat-seeding                  0
      新说法引用 #18587(sharing_rule 已受管)                      5 处
      ⭐ 判据自测:对人造的、被 ' + ' 断开的样本读 1(非零)⇒ 上面的零是读数
    
    os-regen 面(roster 当场读自 `grep os-regen .gitattributes`)
      落地只触及 1 个文件,命中 roster 的:0 ⇒ ⛔ 不欠落地后重生成核验
    

    卡由 Fixes 代关(closed / completed),同笔摘 pm:dispatched 并清 assignee。⚠️ 定级未动。

    本卡的账,一次交清

    ⭐ 卡面是本席写的,而它数漏了 —— 枚举四条 blockedReason,实际有五处载体(第五处是 rls-check-post-image 里的代码注释,不是 blockedReason)。dev 靠文件级的 DARK 控把它捞出来,⛔ 而不是照着本席的清单做完就收工。⇒ 写枚举型卡面时要写明:那是下限,⛔ 不是普查。

    ⚠️ 本席复核那条时也栽在同一个形上:先用 grep -i "rls" 去那份 diff 里找、读到空,并把空当成结论写下;实际 diff 有 98 行、首个 hunk 就落在它身上。⇒ 先证明 diff 非空,再解释它的零。

    ⭐ 逐条判断 dev 做到了,而且拒绝了顺手的绑定:三条仍不绑(它们走运行时列形状或运行时体,从不写 spec 键),declarative-rbac-seeding 记为真实候选但不在本 PR 绑。

    同轮另立两卡:#18800(ADR-0054 §3 绑定归属 —— condition 被两个类演练,谁拥有它要裁)与 #18801(本 PR 落地后,sharing_rule.json 的 _note 逐字引用的那句已不存在)。⭐ #18801 现在起可做 —— 它的触发条件就是本 PR 落地。


    Generated by Claude Code

  7. added 2 commits that reference this issue on Sep 28, 2026
    ac720a9
    43f4766
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions