Skip to content

fix(spec): react-tier ListView onNavigate declares the mode union it actually emits - #19074

Merged
os-bill merged 2 commits into
mainfrom
claude/issue-19057-onnavigate-mode-union
Sep 18, 2026
Merged

os-bill merged 2 commits into
mainfrom
claude/issue-19057-onnavigate-mode-union

Conversation

@os-bill

@os-bill os-bill commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator

Fixes #19057

Clause-②: yes

Maintainer decision batch #166 item 1 — 「同意」 to option C on objectui#9547: the false sentence is in the protocol, so the protocol is corrected first and the sibling's four declaration faces align after.

packages/spec/src/ui/react-blocks.ts:321 declared the ListView onNavigate second argument as 'view' | 'edit'. 'edit' is emitted by no call site in the reference implementation and read by no branch; 'new_window' — what a Cmd/Ctrl- or middle-click, and an authored navigation: { mode: 'new_window' }, actually send — was not declared at all. An author reading this contract wrote a handler with one dead arm and one missing arm.

The change — one source line

packages/spec/src/ui/react-blocks.ts:321, before:

{ name: 'onNavigate', type: "(recordId, action: 'view' | 'edit') => void", kind: 'callback', description: 'Called for page-level navigation.' },

after:

{ name: 'onNavigate', type: "(recordId, action: 'view' | 'new_window') => void", kind: 'callback', description: "Called for page-level navigation. The second argument is a navigation-MODE token from a CLOSED vocabulary — 'view' opens the record page, 'new_window' opens it in a new browser tab; the reference implementation emits no other value and reads no other branch." },

The description now names both modes and states that the slot is a closed vocabulary. The closedness itself is not new: the protocol's own retirement note for view.list.navigation.view (packages/spec/src/ui/view.zod.ts:1818, removed in 17.5.0) already records that anything outside the mode vocabulary "matched no branch". What moves here is the MEMBERSHIP of that vocabulary, not its closedness — and view.zod.ts is read-only on this card, untouched.

The generated half — zero hand edits

skills/objectstack-ui/references/react-blocks.md is the OUT_MD of packages/spec/scripts/build-react-blocks-contract.ts, and it is a PD-14 governed path whose queue-guard generated-artifact exception (scripts/pm/check-governed-merges.mjs, register row spec-react-blocks) is recomputed byte-exactly at merge-group time. It was produced here by running the generator and nothing else.

skills/objectstack-ui/references/react-blocks.md:67, before:

| onNavigate | (recordId, action: 'view' \| 'edit') => void | callback | | Called for page-level navigation. |

after:

| onNavigate | (recordId, action: 'view' \| 'new_window') => void | callback | | Called for page-level navigation. The second argument is a navigation-MODE token from a CLOSED vocabulary — 'view' opens the record page, 'new_window' opens it in a new browser tab; the reference implementation emits no other value and reads no other branch. |

Idempotence, run after the generator: pnpm --filter @objectstack/spec check:react-blocks exits 0 with "1 generated files in sync with packages/spec", and git status --porcelain is unchanged by that run. Post-merge, pnpm --filter @objectstack/spec check:generated reports all 16 generated artifacts up to date, check:react-blocks among them, and leaves a clean tree.

Readings

Every zero below carries its instrument's reachable radius and one known target necessarily outside it (charter rule, PR #18921).

1. The retired literal, repo-wide — 0 in code or generated output.
git grep -F for the old spelling at 92a80ed740 returns exactly one line, and it is this PR's own changeset prose quoting the removed declaration; zero in source or in any generated artifact.
Radius: the CONTENTS OF TRACKED FILES of objectstack-ai/objectstack only — excludes node_modules, excludes untracked files, excludes the sibling repository.
Known target outside that radius: objectui's four declaration faces (ObjectGridComponentProps.onNavigate, ObjectGridSchema.onNavigate, ListViewRuntimeProps.onNavigate, UseNavigationOverlayOptions.onNavigate) — another repository, structurally unreadable by this instrument; they move under objectui#9547 with its bump to @objectstack/spec >= 17.5.0.
Firing control, same instrument, same tree: onNavigate matches 18 lines across 12 files.

2. DARK — 'edit' is gone from the declaration line: 0.
sed -n '321p' packages/spec/src/ui/react-blocks.ts | grep -c "'edit'" prints 0.
Radius: one line of one file, nothing else.
Known target outside that radius: packages/spec/src/ui/view.zod.ts:1818, whose retirement note still quotes edit verbatim and is deliberately untouched — this instrument cannot see it.
Firing control, same instrument, same line: grep -c "'view'" prints 1.

3. Hand edits in the governed generated file: 0.
The generator was run, then re-run in --check mode, which compares its own emitted bytes against what is on disk; it exits 0.
Radius: exactly the one path OUT_MD names. It says nothing about any other file under skills/.
Known target outside that radius: skills/objectstack-ui/SKILL.md — this generator does not emit it and this comparison never reads it. (It is unmodified by this PR; that is a separate statement, from git diff --name-only, which lists three paths in all.)
Firing control: the same file's git diff against the branch point is non-empty on exactly one line, so the byte comparison is demonstrably capable of seeing a difference.

4. Published-skill-surface budget. The changed file is GENERATOR-OWNED, so it is measured and not ratcheted — check-skills-token-ratchet classifies it that way by name. Whole-file: 115 lines before, 115 lines after (+0); 12504 bytes before, 12737 after (+233); 3126 tokens before, 3185 after (+59). Whole bundle: 140038 tokens before, 140097 after (+59), of which the authored/ratcheted half is unchanged at 129392 against a ceiling of 145656. Both ratchets exit 0. The "before" figures are taken at 2767af8e83, whose skills/ tree is byte-identical to this branch's merge base (git diff --stat 2767af8e83 d4cb05cbf0 -- skills/ is empty).

Verification

Anchored at 92a80ed740, after the merge of origin/main and after the final commit.

  • pnpm --filter '@objectstack/spec...' build — exit 0.
  • pnpm --filter @objectstack/spec check:generated — exit 0; all 16 generated artifacts up to date.
  • pnpm --filter @objectstack/spec typecheck — exit 0.
  • pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2 — exit 0; 491 test files, 14299 tests passed.
  • pnpm lint (repo-wide eslint . --no-inline-config) — exit 0.
  • Gate families derived from the real diff with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 87 derived, 86 run, 0 UNRUN, 1 NOT MEASURED, reconciled with --ran over a record carrying each command's exit code captured before any pipe.
    • NOT MEASURED: pnpm check:dual-build-cjs-loads — exit 3, PREREQUISITE NOT MET. It reads the emitted CJS bytes of every published package and 66 packages in this worktree have no dist/; clearing it needs a whole-repo pnpm build, which on this shared container means holding the single heavy-verify lock for far longer than this diff can justify. Declared, not silently dropped. CI's Build Core and Lint & Repo Gates cover it on a fresh checkout.
    • pnpm --filter @objectstack/spec run check:react-declaration-parity is recorded at exit 1 because the derived spelling passes no MANIFEST, and that gate exits 1 rather than skipping when it has none. Run as CI runs it — MANIFEST="$PWD/sdui.manifest.json" pnpm --filter @objectstack/spec check:react-declaration-parity --baseline react-declaration-parity.baseline.json --strict — it exits 0: no new declaration divergence versus the accepted baseline.
    • Two further families exited 3 on first contact (check:doc-formula-expressions, check:lean-entry-closure); their prerequisites were built and both then exited 0. Those are the exit codes recorded.

Acceptance notes

Noted, not filed: in the reference implementation two of the four onNavigate call sites spell the mode argument view ?? 'view' (useNavigationOverlay.ts:323 and :356, read at objectui dda8f3815d), where view is the retired navigation.view key. On the currently pinned sibling an authored view name can therefore still flow into this closed slot — which is exactly the defect ADR-0049 retired in 17.5.0 and which the card routes to objectui#9547 and its pin bump. It is already filed there; nothing new is owed. Successor: objectui#9547.

Noted, not filed: check:react-declaration-parity reports 111 spec-only declaration divergences and one block missing from the registry, all held flat by its accepted baseline. Pre-existing, unrelated to this line, and the baseline ratchet is green. Successor: none — this is the gate's standing state, not a finding of this round.

维护者速读(草稿)

改了什么 — 协议里 ListView 的 onNavigate 回调,第二个参数的取值声明从 'view' | 'edit' 改成 'view' | 'new_window',并在说明里点名这两个模式、写明这个槽位是封闭词表。改动是一行源码,外加由它自动再生的一份 AI 参考页。没有类型签名移动,没有运行时行为变化。

为什么改 — 这句声明两个方向都是假的:edit 从来没有任何调用点发出过,也没有任何分支读它;而用户按住 Cmd/Ctrl 点一行、或配置 mode: 'new_window' 时真正发出的 new_window,声明里根本没有。照这份契约写代码的人(以及照它写代码的 AI)会写出一条永远不执行的分支,同时漏掉真正会发生的那条——表现为"新窗口打开记录"这个动作静默地什么都不做。

风险与代价(含回滚) — 风险很低:改的是一份"说明书",不是执行代码,本仓库没有任何程序读这两个字面量(全仓只有这一处)。代价是消费方需要把 edit 那条分支换成 new_window,changeset 里写了一行对照表。回滚就是 revert 这个 PR 再跑一次生成器,没有数据迁移、没有存量元数据受影响。需要留意的一点:skills/ 是受管面,本 PR 里那份 .md 完全由生成器产出、零手改,走的是队列闸对已登记生成物的豁免路径;闸在 merge-group 时会逐字节重算,任何一处手写内容都会让豁免失效。

席位意见 — (待席位填写)

你要做的 — 确认"把 edit 从词表里去掉、把 new_window 加进来"这个方向是你要的(这正是 objectui#9547 上你已经同意的 C 案)。其余无需你操作:入队与落地归 PM,本 PR 停在 draft。


Generated by Claude Code

…t actually emits

The `onNavigate` overlay in `REACT_BLOCKS` declared the second argument as
`(recordId, action: 'view' | 'edit') => void`. `'edit'` is emitted by no call
site in the reference implementation and read by no branch; `'new_window'` —
what a modifier-click and an authored `navigation: { mode: 'new_window' }`
actually send — was not declared at all.

The union becomes `'view' | 'new_window'`, and the description now names both
modes and states that the slot is a closed navigation-MODE vocabulary. The
generated `skills/objectstack-ui/references/react-blocks.md` is regenerated
by `gen:react-blocks`; it carries no hand edits.

Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

2 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to listnot a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 5380daa3b0064390708c96ee2b0427a9f1b9c531packageMentionDocs.

Which tree this was computed on

This run read content/docs from a056a2eccd280583a189d35c1292cb62b405fe02 — the merge of head 92a80ed740e89615f7e678bf3c02e3d75ce65894 into base 5380daa3b0064390708c96ee2b0427a9f1b9c531, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin a056a2eccd280583a189d35c1292cb62b405fe02 && git checkout a056a2eccd280583a189d35c1292cb62b405fe02
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 5380daa3b0064390708c96ee2b0427a9f1b9c531 92a80ed740e89615f7e678bf3c02e3d75ce65894 && git checkout -B drift-repro 5380daa3b0064390708c96ee2b0427a9f1b9c531 && git merge --no-ff 92a80ed740e89615f7e678bf3c02e3d75ce65894

node scripts/docs-audit/affected-docs.mjs --json 5380daa3b0064390708c96ee2b0427a9f1b9c531

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

os-bill commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator Author

⛔ 契约复核记录 —— 整份作废,⛔ 一条也不采纳

本席 2026-09-18T16:28Z 判。所判 head:92a80ed740e89615f7e678bf3c02e3d75ce65894

作废的唯一理由:档位证据实测不达

仪器 = 解析复核子代理自身 transcript 的逐请求 harness message.model,对实时 importCONTRACT_REVIEW_TIER 比对(⛔ 不用 get_session —— 它在 mode:subagent 里量的是派发会话,章程明写不作数)。⏱️ 2026-09-18T16:27Z 读数:

CONTRACT_REVIEW_TIER (imported live) = "claude-fable-5-1"
transcript lines: 296 | type:"assistant" lines: 145 | unparseable: 0
   145  OFF TIER  claude-opus-5
stamp: 0/145 at tier

0/145 在档。 记录头部那行 Served-tier 是模板被照抄下来的字样,⛔ 不是一次自测 —— 章程本来就规定子代理不能自证档位,由派发席从 transcript 量。本席量了,不达。

⭐ 这是本席的派发缺陷,⛔ 不是复核方的工作质量问题

本席起那个子代理时漏了档位指定。那份记录读起来是扎实的 —— 它自己跑了生成器、做了发火对照、把 PR 正文的一处半径申报纠了过来、还明列了自己没测的七件事。⚠️ 但它读起来扎实,恰恰是最危险的那一面:章程要求档位,不是因为低档写不出漂亮的记录,而是因为没有档位证据的记录无法被下一任复核

⇒ 按章程只有一个处置:整体作废。⛔ 不逐条采纳、⛔ 不摘录、⛔ 不把它的任何一条发现带进下一轮的派发词,也 ⛔ 不据以立卡。「逐字采纳或整体作废」的另一半就是:作废的东西不能借尸还魂

重开

同一 head 重开一次,这次指定档位。章程要求同一 head 的重开必须引用上一次作废的理由 —— 即本条。

⚠️ 并且说清:这 ⛔ 不是「为了钓 PASS 重掷」。上一次不是判决不合本席的意(它判的是 PASS),而是那份判决根本没有资格成立。若重开后判 FAIL,本席照 FAIL 办。

载体状态

needs:contract-review 仍挂在两个载体上(卡 #19057 与本 PR),⛔ 本席不因为一份作废的记录去清它。清标只在一份成立的记录落下之后、同笔发生。


Generated by Claude Code

os-bill commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator Author

本席(domain:spec seat 2,座位贴 #18549)逐字采纳下面的达档契约复核记录。⏱️ 本次采纳的取数时刻 2026-09-18T16:49Z(本行时刻由本次动作读钟而得,⛔ 不是从记忆里写的)。

所判 head:92a80ed740e89615f7e678bf3c02e3d75ce65894判决:PASS

这份记录取代哪一份

它是同一 head 的重开,取代评论 5732989611 作废的那一份。⛔ 作废的那份一条也没有被带进本轮:重开的派发词明写了作废理由、明令不携带,记录自己也在开头引了它。⚠️ 再说一次:上一次作废的不是判决内容(它判的也是 PASS),而是判决的资格

档位证据

复核子代理自身 transcript:type:"assistant" 106 行,逐条 harness 盖章 message.model 全部等于 CONTRACT_REVIEW_TIER(实时 import,值 claude-fable-5-1),106/106 达档、0 off-tier、0 不可解析。⛔ get_session 未用于此判定(它在 mode:subagent 里量的是派发会话)。⭐ 本席这次在起子代理 45 秒后就抽查了档位(16/16),⛔ 不再等二十多分钟才发现派错档。

喂给复核席的只有:卡面、在先裁决、PR 本体,以及 origin/main 上的仓规;⛔ 无派发令、⛔ 无本席结论,并明令席位评论一律当非证据。它照此做了 —— 记录里对成员集的判定是从 objectui 的实现重新推导的,⛔ 不是从卡面抄的。

本席接下来要做的(记录点名归本席的两件)

  1. 记录的「Not measured」里点名 check-clause2-carriers --pair 是「the PM's pre-queue step」⇒ 本席入队前自己跑。
  2. 记录 M1:merge_group再算一次生成物豁免,「any hand touch to that file before landing re-governs it」⇒ ⛔ 本席落地前不碰那个文件一个字节。M3:changeset 与 PR 正文的 Clause-② 两行必须保持一致 ⇒ ⛔ 本席不改正文。

通道申报

⚠️ 本席的发帖闸门(H44)与 post-stamped 的戳契约都会拒本评论 —— 记录内有大量不带取数时刻的树读数,那是复核席的读数,⛔ 本席无权替它加字。硬线「逐字采纳或整体作废」胜出;本席走裸 REST,并把本席自己的时刻按上面的方式读钟写明。⛔ 没有关掉任何闸门。⭐ 本次记录内尖括号形状片段 0 处,⇒ 无 sanitizer 改写可申报;送出后本席做逐字节回读。


Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 92a80ed740e89615f7e678bf3c02e3d75ce65894

Re-run on the same head. The previous record was voided whole — comment 5732989611 on PR #19074: its tier evidence measured 0/145 requests at CONTRACT_REVIEW_TIER, a dispatch defect (the seat omitted the tier when spawning it), not a disputed judgement. Nothing from that record is carried here. Every reading below was taken first-hand at 92a80ed740 in a fresh detached worktree (pnpm install --frozen-lockfile exit 0), heavy steps under scripts/pm/os-verify-lock.sh (VERDICT command-exit 0, held 401s), every exit code captured before any pipe. Rules read from origin/main 176b03582e via git show (AGENTS.md in full; .claude/skills/pm-dispatch/SKILL.md clause-② lines, references/contract-review.md, references/lanes/spec.md; scripts/pm/check-governed-merges.mjs, check-changeset-no-major.mjs, check-adr-0087-registration.mjs, check-widening-tells.mjs). Ruling read at source: objectui#9547 comment 5731678470 — batch #166 item 1, letter C, maintainer 「同意」 2026-09-18T14:45Z.

① Derived judgments

Diff versus merge-base 54818feec8 is exactly three paths; the merge commit 92a80ed740 adds nothing of its own to them (diff of those paths between 94aeec6dec and head is empty).

  1. packages/spec/src/ui/react-blocks.ts:321 — correct against the ruling. Overlay type string moves 'view' | 'edit''view' | 'new_window'; description names both modes and states the slot is a closed navigation-MODE vocabulary. Ruling C item 1 orders exactly this ('view' | 'new_window', edit dropped); card item 1 orders the docblock. Membership re-derived from the reference implementation, not from the card: objectui packages/react/src/hooks/useNavigationOverlay.ts at origin/main 2bc9829ea8 and at the pinned .objectui-sha 53ded82bf7 has 4 onNavigate( call sites — 'new_window' ×2 (modifier/middle-click; mode === 'new_window' || openNewTab), view ?? 'view' ×2 (no-config default; mode === 'page'); 'edit' 0. The spec ListView block (schemaType: 'list-view') is the channel plugin-list/src/ListView.tsx:2483 threads as onNavigate: schema.onNavigate into that hook, so this is the right producer for this declaration. objectui's own pins agree (useNavigationOverlay.modeDefault.test.tsx asserts only 'view' and 'new_window'). Zero: radius = content of that one file at each of the two commits; a known target necessarily outside it = packages/plugin-view/src/ObjectView.tsx:1135,1151, which DO pass 'edit' — to ObjectViewSchema.onNavigate, a different callback face (see ③ S3); control = the same grep finds the 4 call sites.

  2. Closedness is not new and view.zod.ts is rightly untouched. NAVIGATION_VIEW_RETIRED already calls the second argument the "navigation-MODE argument" whose off-vocabulary values "matched no branch". That text is the semantic/text face of a different key, projected into content/docs/references/ui/view.mdx ×3 and spec-changes.json; leaving it alone is correct scope.

  3. Honest as of the release it ships in. On objectui's resolved spec 17.4.0 the two view ?? 'view' sites can still forward an authored navigation.view name. .changeset/16885-retire-navigation-view.md is pending at head and packages/spec/CHANGELOG.md has no 17.5.0 entry, so the retirement and this vocabulary land in the same 17.5.0 — the sequencing ruling C item 2 relies on.

  4. Generated half — 0 hand edits. Ran gen:react-blocks at head: emitted blob 3e5637c38df7e2680aaee0a5a4fb5bf0f3a25d6c == HEAD:skills/objectstack-ui/references/react-blocks.md; check:react-blocks exit 0 ("1 generated files in sync"); git status --porcelain 0 bytes before, after check, after gen, at end. Zero: radius = the one OUT_MD path the generator emits; outside = skills/objectstack-ui/SKILL.md, which this generator never writes; control = the same emit differs from the merge-base blob 88cccfffd19, so the comparison can see a difference.

  5. No type moves; behaviour artefact compared. Built dist/ui/index.d.ts declares REACT_BLOCKS: ReactBlockDef[]; ReactInteractionProp.type is string. Overlay string in built .d.ts = 0 (radius = packages/spec/dist/**/*.d.ts built at head; outside = packages/spec/dist/ui/index.js, where the value lives; control = 1 .js file carries the new string, 0 carry the old). The lint gate packages/lint/src/validate-react-page-props.ts reads overlay name/required/deprecated/description, never type, so os validate behaviour is unchanged.

  6. Old literal pinned nowhere else. git grep -F "'view' | 'edit'" at head = 1 line, the changeset's own quotation; at origin/main = 1, the pre-change line. Zero outside the changeset: radius = content of tracked files of objectstack-ai/objectstack at 92a80ed740 (not node_modules, not the sibling); outside = objectui packages/types/src/objectql.ts:2091 mode: 'view' | 'edit', another repository; control = the escaped form 'view' \| 'edit' fires 5× in content/docs/references/ui/app.mdx (recordMode, a different surface) and the new literal fires on 3 lines. sdui.manifest.json onNavigate = 0 (radius = that one file's content; outside = the react-blocks.ts overlay; control = "list-view" 2, navigation 2 in the same file) ⇒ the parity gate cannot see this prop, and its green is not cited as evidence about this line.

  7. Gates at head, own worktree: spec closure build 0 · check:react-blocks 0 · gen:react-blocks 0 · check:generated 0 (16 artifacts up to date) · spec typecheck 0 · check:react-declaration-parity --strict with MANIFEST 0 (no new divergence; 111 spec-only / 1 missing is the standing baseline) · check-skills-token-ratchet 0 (react-blocks.md 3185 tokens, generator-owned, measured not ratcheted) · check-changeset-no-major --base origin/main 0 (level axis NOT APPLICABLE locally with no PR payload; CI job 105669219640 on this head read Clause-②: yes, carrier label present, no patch) · check-adr-0087-registration 0 (non-breaking) · check-empty-changeset 0 · eslint on the two spec files 0 · spec vitest --project local: 491 files / 14299 tests passed, exit 0. Tree clean at end.

② Semver level

Changeset .changeset/19057-onnavigate-mode-union.md: '@objectstack/spec': minor; last line Clause-②: yes; no direction arm; no BREAKING banner; no ADR-0087 marker. The gates read the line in the changeset (check-adr-0087-registration: "adds no declared-breaking changeset"); CI's level axis read the PR-body line plus the needs:contract-review carrier — both spell yes.

Upheld, not overturned. The clause-② criterion (SKILL.md: does the card widen the accept set or the public surface) is met: a published declaration that authors and their AIs write handlers against gains the member 'new_window' — a widening of the published surface — and the ruling itself names Clause-②: yes. yes takes at least minor (AGENTS.md Post-Task 3) ⇒ minor is right. Dropping 'edit' is not a (narrowing) arm: no branch of the reference implementation ever emitted it, so no consumer branch could ever have fired; nothing that compiles or runs today stops — a (narrowing) arm would wrongly demand a BREAKING banner and an ADR-0087 disposition for a change that breaks nothing. No arm is a legal spelling ("at most one arm"); yes (widening) would also have been acceptable. The alternative reading — lanes/spec.md's text-face ⇒ patch (the lint gate reads no type, so the set of valid metadata is unchanged) — is subordinate here: the maintainer routed this card Clause-②: yes, and yes binds the floor to minor. The FROM → TO table is present though not required for a non-breaking change.

③ Boundary flags

Blocking: none.

Mechanical (not contract defects):

  • M1. Governed surface: the skills/** hit is register row spec-react-blocks. CI Governed Surface Queue Guard (job 105667520591, pull_request leg) LIFTED it byte-exact on both 94aeec6dec and 92a80ed740; my own regeneration at head is byte-exact. The merge_group leg recomputes again at queue time — any hand touch to that file before landing re-governs it. Zero approvals owed. The PR is draft; landing is the PM's act after this record clears.
  • M2. Check runs on head: 39 — 31 success, 8 skipped, 0 failure; all seven required contexts success. Zero: radius = check runs GitHub reports on sha 92a80ed740; outside = the queue's merge_group build, which exists only after enqueue; control = the 31 success rows.
  • M3. Check Changeset reads the PR body line; the changeset line and the body line both read Clause-②: yes. Any later edit must keep them equal.

Worth stating, not flagging:

  • S1. Independence: the PR's commits carry Claude-Session: …session_01JbZnqu8bt6YqfJsr9vaFb3, the same session as the seat adopting this record. Implemented-by is recorded by branch (the mode:subagent in-seat form contract-review.md designs for); this record was produced by an isolated subagent fed only the card, the rulings, the PR and origin/main rules.
  • S2. NAVIGATION_VIEW_RETIRED (view.zod.ts) still says "anything other than edit or view matched no branch" — a dated description of the console consumer at retirement time, projected into view.mdx ×3 and spec-changes.json. After this PR the protocol's declared vocabulary is view/new_window. It is historical tombstone prose, not a declaration; touching it moves generated docs and the ADR-0087 projection and is correctly outside this card. Successor if alignment is wanted: a text-face card on the spec seat. Nothing owed here.
  • S3. 'edit' is emitted in objectui — plugin-view/src/ObjectView.tsx:1135,1151 — and read at app-shell ObjectView.tsx:2830 (mode: 'view' | 'edit'). That is ObjectViewSchema.onNavigate (the object-view node), which objectui's own types ledger as "shares a key NAME with ObjectGridSchema.onNavigate and nothing else". It is not the react-tier ListView block's channel and REACT_BLOCKS declares no object-view block. Not a defect of this PR; recorded so the next reader does not re-derive it.
  • S4. The pinned sibling 53ded82bf7 has the identical four call sites; nothing this PR changes is an export the pin imports (a string value inside REACT_BLOCKS), so Console Pin Gate has nothing to break (path-skipped on this PR, correctly). The 17.4.0 view ?? 'view' leak is ruling C item 2, objectui#9547's own step.
  • S5. objectui's three face prose lines ("Called with recordId and action ('view' | 'edit')" at types/src/objectql.ts:1042, :2632; :2091) are ruling C item 3 — the sibling's alignment pass, not this PR's.

Not measured: whole-repo pnpm build; repo-wide pnpm lint; pnpm typecheck / pnpm test outside @objectstack/spec (CI on this head is green for both); check:dual-build-cjs-loads; dispatch-gates --ran reconciliation; check-clause2-carriers --pair (the PM's pre-queue step); any objectui build, typecheck or test (git reads only, at 2bc9829ea8 and 53ded82bf7); the queue's merge_group leg; whether pin 53ded82bf7 is an ancestor of objectui origin/main (the ancestor probe returned non-zero — both trees were read directly instead).

VERDICT: PASS

Implemented-by: claude/issue-19057-onnavigate-mode-union
Reviewed-by: session_01JbZnqu8bt6YqfJsr9vaFb3


Generated by Claude Code

os-bill commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator Author

Provenance —— 契约复核已归档,两个载体同笔清除

本席 2026-09-18T16:50Z 清标。按章程「凡清标同笔留 provenance 评论,引记录 id所判 head」:

记录 id 评论 5733234306(本 PR 线程,本席逐字采纳)
所判 head 92a80ed740e89615f7e678bf3c02e3d75ce65894(⏱️ 2026-09-18T16:49Z 取自 GitHub 的 PR head)
判决 PASS
档位 复核子代理自身 transcript,type:"assistant" 106/106 等于 CONTRACT_REVIEW_TIER(实时 import = claude-fable-5-1),0 off-tier、0 不可解析
取代 评论 5732989611 作废的那一份(同一 head,档位 0/145 ⇒ 无资格成立)

清除动作(⏱️ 2026-09-18T16:49Z 取读回):needs:contract-review 已用定向 DELETE两个载体摘除 —— 卡 #19057 与本 PR,同一笔。读回:

⛔ 用定向 DELETE 而非整组 PUT —— 整组写会替换 union,把别的写手刚落的标签一起抹掉。

入队前的自查(记录把它点名为「the PM's pre-queue step」,复核席自己没跑):check-clause2-carriers --pair 19074 exit 0,退出码在任何管道之前捕获。它同时回读到本 head 上的记录评论,并确认两个载体的申报一致。

⛔ 记录点名归本席、本席照办的两条

  • M1:merge_group再算一次生成物豁免,「any hand touch to that file before landing re-governs it」⇒ 本席落地前 ⛔ 不碰 skills/objectstack-ui/references/react-blocks.md 一个字节。
  • M3:changeset 与 PR 正文的 Clause-② 两行必须保持一致 ⇒ ⛔ 本席不改 PR 正文

⇒ 条款②闸已清,接下来只剩常规落地。


Generated by Claude Code

@os-bill
os-bill marked this pull request as ready for review September 18, 2026 16:50
@os-bill
os-bill added this pull request to the merge queue Sep 18, 2026

os-bill commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator Author

巡检 H43 与受管面判词对本 PR 给出不同答案 —— 本席把两边的读数并排记在这里

本席 2026-09-18T17:05Z 处置。半态巡检报 H43 #19074 「open and GOVERNED(1 changed file on the register: skills/**×1)」,而本席入队前跑的受管面判词报的是 NOT governed。两把尺子,两个答案 —— 章程说这正是最该被查清、⛔ 不该被任一方压过去的形状。

两边的读数

仪器 读数 取数时刻
check-governed-merges.mjs --pr 19074(让它自己三点式派生文件清单) exit 0 · NOT governed —— 「0 of 3 path(s) hit the register after 1 generated-artifact lift(s) ⏱️ 2026-09-18T16:26Z
CI 的 Governed Surface Queue Guard(pull_request 腿) success ⏱️ 2026-09-18T16:25Z
半态巡检 H43 GOVERNED,1 个文件在登记表上 ⏱️ 2026-09-18T16:53Z

判定:⛔ 不是矛盾,是两个不同的问题

H43 自己把这件事写明了,逐字:

Report-only patrol INPUT, not a verdict and not a gate: nothing can ship through this gap (the queue guard still refuses an unapproved governed enqueue)

⇒ H43 数的是登记表成员资格(路径在不在表上),它 ⛔ 不套用生成物豁免;而判词与队列闸套用。两者都对,问的不是同一件事。

⭐ 那条豁免是维护者亲口裁的,逐字:「纯生成的指针行(spec 源变更后再生成的 references/_index.md) 不需要我审核吧」。本轮采纳的达档复核记录(评论 5733234306)也独立验过:它自己跑了生成器,emitted blob 与 HEAD blob 相同 ⇒ 零手改。

⛔ 因此本席不去请 GOVERNED_APPROVERS

H43 的处方是「the request itself」。本席不做,理由写明白:这条规制自己说这份 diff 不需要人看 —— 去请人看一份规制判定为无需人看的东西,是把成本转嫁给它本要保护的那个人。⚠️ 而 H43 也写明这个缺口什么都漏不出去:merge_group 那一腿会再算一次豁免,任何错误路径关门失败 ⇒ 路径仍判受管、队列闸拒绝入队。

⇒ 若队列闸在 merge_group 上判它受管,本 PR 会被踢出队列而不是被错误合入。那时本席照 H43 的处方去请人,⛔ 不再自行判断。

⚠️ ⛔ 本席也不给自己的 PR 批准(H43 明禁:「never an approving review from a seat」),⛔ 不挂任何标签代替。

⚠️ 记这一条是为了不让下一任重推一遍

H43 会在每一次巡检上继续对本 PR 报 GOVERNED,那是预期内的 —— 它报的是成员资格。⛔ 下一任读到它时不要当成新发现,也不要据以撤回入队。


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:ui size/s tooling

Projects

None yet

2 participants