Skip to content

feat(spec)!: retire the type: 'page' list-view mount and its pageName binding - #17298

Merged
os-zhuang merged 6 commits into
mainfrom
claude/issue-17063-retire-list-view-page-member
Sep 11, 2026
Merged

os-zhuang merged 6 commits into
mainfrom
claude/issue-17063-retire-list-view-page-member

Conversation

@os-bill

@os-bill os-bill commented Sep 10, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #17063

Clause-②: no

⚠️ The mechanical reading DISAGREES with that declaration, and I am reporting it rather than moving it — the seat moves a clause-② declaration, never the dev. See Clause ② — measured, three readings below.

⛔ This PR is GOVERNED (check-governed-merges --test on every diff path returns exit 3). No seat flips it ready, enqueues it, or arms auto-merge — a human merge is the review record. The dispatch predicted exit 0 / NOT governed; that prediction was false, and the reason is below.


What is retired

A list view could declare type: 'page' and name a published page in pageName, rendering nothing of its own and delegating to the page renderer (#13216 direction 1, spec half PR #13372). Only the spec half was ever built. Measured on objectui at the pinned .objectui-sha 53ded82b and at objectui main 3fbdd4a2d:

  • packages/plugin-list/src/ListView.tsx — the view-type switch shares its default: arm with case 'grid' ("an unrecognized viewType must degrade to a working table"), so 'page' has always drawn an empty grid;
  • @object-ui/core's isListViewVisualization('page') === false (normalize-list-view.test.ts:647), with a residual test named for objectui#8429.

So the three parse refusals that policed the binding policed a mount that never mounted anything. ADR-0049 enforce-or-remove; maintainer ruling 2026-09-09, decision batch #107 item 1, verbatim 「撤」, on 「已发布零消费的能力不因沉没成本获得豁免」.

Route, and why each half took the route it did

retired thing kind route why
pageName authorable KEY retiredKey() tombstone on ListViewSchema + ObjectListViewSchema tsc types it never and the parse raises the prescription instead of a bare unrecognized-key report. strict-object.ts calls a tombstone "strictly stronger than a guidance entry", and this same shape already carries virtualScroll: retiredKey(…).
'page' enum VALUE the type enum's own error map, keyed on issue.input An enum-value narrowing has no tombstone to hang a prescription on — the def survives, one value lighter. The precedent is in this same file: LIST_VIEW_EXPORT_PDF_RETIRED (#8010), and HookBodyCapability / object.managedBy: 'system' before it.

Liveness-ledger disposition follows the route, as the playbook's table demands: the pageName row stays, flipped to status: "dead" with a verifiedAt and a REMOVED note, because retiredKey() keeps the key in the walked shape (the rls.priority precedent). Deleting it would report UNCLASSIFIED.

The four surfaces the dispatch front-loaded

1 — a sys_metadata row already carrying type: 'page' / a pageName. The read path does re-validate: applyConversionsToStoredItem replays the full ADR-0087 chain on every rehydration seam, and the loader skips only 'flow' (packages/metadata/src/loaders/database-loader.ts:824 — the if (singular === 'flow') return payload guard, one line above the call at :825), never view. The new D2 conversion view-page-mount-removed therefore fires, and the row is served canonical. Measured, with a lit control:

IN   {"name":"orders.dash","object":"orders","viewKind":"list","type":"page","pageName":"sales_home","columns":[]}
OUT  {"name":"orders.dash","object":"orders","viewKind":"list","columns":[]}
     2 notices, both view-page-mount-removed (views[0].type, views[0].pageName)
CTRL {"…","type":"grid","columns":["name"]}  ->  unchanged, 0 notices

What an operator sees is a warn, not a 400 and not silence — and there are three emitters, one per rehydration seam, which do not carry the same string. Grep carries a pre-protocol shape:

emitter file:line string
[DatabaseLoader] packages/metadata/src/loaders/database-loader.ts:831 stored view/NAME carries a pre-protocol shape; NOTICE
[ObjectQLPlugin] packages/objectql/src/plugin.ts:2134 stored view/NAME carries a pre-protocol shape; NOTICE
[Protocol] packages/metadata-protocol/src/protocol.ts:4511 the same, plus The row itself is unchanged — re-save it (Studio edit → save, or run "os migrate meta --stored --apply") to persist the canonical shape.

A write through PUT /api/v1/meta/view is where the refusal fires instead: the tombstone and the enum error map both answer at parse, and that door heals the row on the next save.

⚠️ Coverage boundary, stated rather than left to be found: the conversion walks stack.views[] in all three persisted spellings (mapViewPayloads); it does not reach objects[].listViews.*, which no conversion in this registry reaches — the view-export-options-pdf-removed precedent has the identical reach. An object body still carrying a mount is refused at its own door with the prescription rather than converted. Both populations measured at zero (below).

2 — the ADR-0087 disposition. registered view-page-mount-removed, a real D2. not-required (no-migration-prescription) was not available: ADR-0087's own text (docs/adr/0087…md:596) refuses it for a body carrying a migration prescription, and this changeset carries a FROM → TO table because AGENTS.md requires one. A D3 semantic entry was considered and is not what this needed: a D2 is possible here and therefore owed. The conversion strips type rather than rewriting it to 'grid' — ListViewSchema.type carries .default('grid'), so deleting the key lands the row on exactly what it already rendered, declared in one place instead of guessed in the registry, and stripKeys-shaped deletion is idempotent by construction. Registered under protocol 18 (RETIRED_KEYS_BY_MAJOR[18], MIGRATIONS_BY_MAJOR[18].conversionIds, step18.rationale extended), matching every in-flight retirement on the 17.x line. check-adr-0087-registration run after the commit — it reads changeset bodies from git, not the working tree:

✓ check-adr-0087-registration: 1 declared-breaking changeset(s), each carrying an ADR-0087 disposition.
    .changeset/retire-list-view-page-mount.md  [BREAKING]  registered view-page-mount-removed (new here: view-page-mount-removed)

3 — the tombstone kit. Per the spec-property-retirement playbook. Both prescriptions carry the pinned os migrate meta --from 17 house sentence, and pageName's is a retiredKey(). Two RETIRED_KEYS_BY_MAJOR[18] entries as new files under entries/retired-keys/ (18.ui__ListView__pageName.ts, 18.ui__ObjectListView__pageName.ts) plus gen:migration-registry; nothing was hand-edited inside the generated markers. The build gate named the two exact key spellings itself before they were registered, which is how they were derived rather than guessed. No RETIRED_DEFS_BY_MAJOR row is owed: no def is deleted.

4 — the three refusal messages. checkListViewPageMount existed only to police this mount, so the check, its three message constants and the export all go with it. They were module-private string constants, not error codes — no ERROR_CODE_LEDGER row exists for any of them (VIEW_PAGE_MOUNT_NEEDS_PAGE_NAME / VIEW_PAGE_NAME_NEEDS_PAGE_TYPE / VIEW_PAGE_MOUNT_HAS_COLUMNS appear nowhere but view.zod.ts), so nothing is owed there. @objectstack/lint's VIEW_PAGE_UNRESOLVED is a lint rule id, not an ADR-0112 code, and leaves with validateViewPageRefs.

What else left with it, and what deliberately did not

  • validateCrossReferences' page branch in stack.zod.ts. The surviving three page references in that function (an app nav item's pageName, a modal action's target at two rungs) and pageNames itself are untouched.
  • @objectstack/lint's validateViewPageRefs / VIEW_PAGE_UNRESOLVED. Its nav twin validateNavTargetRefs is untouched.
  • RuntimeStackContext.pages and the page row of CLOSURE_CONTEXT_KEY_BY_TYPE. validateViewPageRefs was the only member whose runtimeTypes reached stack.pages (every other page-reading rule takes the frozen flow default, and validatePageVisualizationBindings says in its own comment that it is not crossed for exactly this reason). The runtime-gate docblock states the widening rule — "made when a rule that reads the collection actually crosses the wall, never in advance" — so the symmetric retirement is owed, not optional: leaving it would create a carried collection no rule reads, which is the ADR-0049 shape this card exists to remove. A view publish also stops paying a sys_metadata round trip for it.
  • view.form.ts's page section (its one input was pageName) and the four i18n metadata-form bundles it fed — a form input for an unwritable key is the false-compliant UI half of a retirement.
  • Not touched: direction 3 of [feature] Give runtime-published custom pages an end-user entry point: a page view type on objects, or nav registration at publish time #13216 (command-palette page indexing, PR feat(spec,metadata-protocol): a page hit kind on GET /api/v1/search — command palette indexes published pages #14249) and the nav-item type: 'page' / PageNavItem.pageName surface, which is the page mount that has always rendered.

Zero usage, re-measured with a lit control

Query: git grep -n "type: *'VALUE'" over examples/**, packages/platform-objects/**, packages/mcp/**, packages/cloud-connection/**, packages/create-objectstack/**, tests excluded.

value hits verdict
page 30 all NAV items — every one sits in an items: array under navigation, carrying id / label / icon (25 in examples/app-showcase/src/ui/apps/index.ts, plus cloud-connection-ui.ts:69, marketplace-ui.ts:99, connect-ui.ts:72; studio.app.ts:187 is an action params bag). Zero list views.
kanban 4 lit control — the query shape finds authored list-view type values
tree 4 lit control
gallery 1 lit control

A zero whose control was never inspected survives review while saying nothing; each of the four rows above was read, not counted.

Ablation — the old spelling now fails, proven both ways

Each leg: mutate → prove it landed on disk (anchor count + injected-marker count + git hash-object moved) → run → restore → prove the restore by state. The spec unit suite imports ./view.zod as source (no dist hop), so no build leg is owed; the restore is git checkout HEAD -- PATH, never a bare git checkout --.

leg mutation landed result
reinstate 'page' in the type enum anchor 1→0, marker 0→1, hash 34f111fc → c3c5d06b 3 pins RED — the three door-parameterised REFUSES type: 'page' cases
reinstate pageName: SnakeCaseIdentifierSchema.optional() anchor 1→0, marker 0→1, hash 34f111fc → 6447f4e3 6 pins RED
control — unmutated tree — 13 passed, 0 failed

Restore, both legs: git hash-object returned 34f111fcef274c7ade5543b195a884fe6abde7c4, byte-equal to the HEAD:packages/spec/src/ui/view.zod.ts blob, and git diff HEAD -- PATH was empty. Not read from an exit code. A trap on EXIT INT TERM with absolute paths carried the crash path.

⚠️ The first attempt at leg 1 was a void reading and is reported as one. Its landing check used grep -cF with a multi-line anchor, which counts lines rather than occurrences; the guard mis-fired, the run was declared VOID and restored without being read. Leg 1 above is the re-run with a single-line anchor. The mutation had in fact landed — that is exactly why a landing check that cannot be trusted has to void the reading rather than be talked past.

Verification

Every exit code captured to disk before any pipe. Scripts under scripts/pm/ were run from an origin/main mirror (git archive origin/main scripts | tar -x, origin/main = a256f189), each byte-verified: check-widening-tells.mjs 949e94fd, check-governed-merges.mjs 081df968, check-clause2-carriers.mjs 60b7562c — all three identical to git rev-parse origin/main:PATH.

run exit
pnpm --filter @objectstack/spec build 0
pnpm --filter @objectstack/spec test (13 185 tests) 0
pnpm --filter @objectstack/spec check:generated (15 artifacts) 0
pnpm --filter @objectstack/lint typecheck && … test (3 688 tests) 0
pnpm --filter @objectstack/metadata-protocol typecheck && … test (2 482) 0
pnpm --filter @objectstack/platform-objects test (561) 0
check:liveness · check:empty-state · check:variant-docs · check:skill-examples · check:skill-refs · check:skill-docs · check:exported-any · check:dual-source-exports · check:duration-unit-keys · check:entry-nameability · check:yaml-examples 0 each
pnpm check:i18n 0 (RED first — 7 drifted bundles — then --write; the diff is 44 pure deletions)
check:doc-authoring · check:nul-bytes · check:cross-package-test-inputs · check:test-source-alias · check:stack-collection-maps · check:quick-reference-counts · check:corpus-claim-drift · check:published-files · check:type-check-coverage · check:i18n-stale-fill · check:objectui-changeset · check:pm-changeset-deadline-census 0 each
check-adr-0087-registration --self-test / --base (post-commit) 0 / 0
check-changeset-no-major --self-test / --base 0 / 0
check-empty-changeset · check-reference-carrier-shape (+self-tests) · check-skills-token-ratchet · check-skill-line-ratchet · check-spec-docblock-symbol-anchors · check-comment-mask-adoption · check-comment-mask-corpus · check-closing-keyword-parity 0 each

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack was re-derived after the i18n commit; it grew six entries (the changeset/i18n/release-rehearsal family), all of which were then run. The remaining families in its 121-line list are declared to CI as a narrowing, not claimed green.

ESLint — a measured narrowing, not a skipped run. ① The population is read from eslint's own config, not guessed: pnpm exec eslint --no-inline-config --format json . enumerates 6 465 files. ② The narrowed run linted 32 files (every surviving .ts/.tsx/.mjs/.mts path in the diff; the three deleted paths cannot be linted), count read from --format json: 0 errors, 0 warnings, exit 0. ③ Invariance: this repo runs one eslint.config.mjs and it never enables type-aware linting for any file — no parserOptions.project, no typed @typescript-eslint rules, stated and positively controlled at eslint.config.mjs:327-335 — so nothing in this diff can move the verdict on a file it does not touch. Measured at 1e937111c, this branch's final commit.

Clause ② — measured, three readings

Run on the finished diff (git diff MERGE_BASE...HEAD), from the origin/main mirror:

probe exit reading
check-widening-tells --declaration no 4 3 × T2 — "a new member of a closed set … the accept set gains a value"
check-widening-tells --declaration yes 0 a yes already routes to contract review, so a tell on top decides nothing
check-clause2-carriers --pair THIS_PR_NUMBER see the report comment on #17063 run once this PR had a number

The three T2 tells, by number and line:

  1. packages/spec/src/migrations/registry.ts:5443 — + 'view-page-mount-removed', (the D3 chain step's conversionIds)
  2. packages/spec/src/migrations/registry.ts:12757 — + 'ui/ListView:pageName',
  3. packages/spec/src/migrations/registry.ts:12791 — + 'ui/ObjectListView:pageName',

⚠️ Correction (seat, 2026-09-10T05:00Z): two of the three are generated rows inside registry.ts's os-generated markers; registry.ts:5443 is NOT — the nearest marker above it is the CLOSING </os-generated semantic:17>, so it is the hand-maintained step18.conversionIds. The argument below is unchanged. Originally written as: all three are generated rows produced by gen:migration-registry from the three new files under src/migrations/entries/. They are the retirement LEDGER growing — the mechanical record that two keys are now unwritable — and every one of them is a consequence of the accept set shrinking. But that argument is the seat's to accept or reject: the dispatch ordered me to measure rather than agree, and to stop and report on disagreement. The declaration in this body stays no, exactly as claimed in comment 5612101866. ⛔ I have not moved it.

Governance — the dispatch's prediction was false

check-governed-merges.mjs --test over all 48 diff paths returns exit 3, GOVERNED: skills/** ×2 hits the register — skills/objectstack-ui/SKILL.md (the retired list-view type row, a surface the retirement playbook requires) and skills/objectstack-ui/references/react-blocks.md (regenerated by gen:react-blocks; the #11705 generated-surface exception did not lift it, because the generator declares no readable output manifest, so it fails closed). The dispatch's pre-flight read packages/spec/** and packages/lint/** only and concluded "⛔ No … ordinary queue landing applies". One hit governs the whole PR. ⛔ Dropping the skills edit to dodge governance is not an option — a published SKILL.md teaching a key that now refuses is the thing being retired.

Changeset

minor for @objectstack/spec, @objectstack/lint, @objectstack/metadata-protocol, with the **BREAKING** banner. Level derived, not inherited: check-changeset-no-major refuses major in the launch window, existing pending changesets already put @objectstack/spec at minor (next release 17.5.0, which the two prescriptions name), and all three packages publish removed surface — @objectstack/lint loses two exports and @objectstack/metadata-protocol narrows a published context type, so neither is a patch and skip-changeset is plainly wrong.

维护者速读(草稿)

改了什么 — 列表视图的 type: 'page' 挂载与它的 pageName 绑定被整体退役:枚举值、键、三条拒绝消息、checkListViewPageMount 导出、defineStack 的页面交叉引用分支、@objectstack/lint 的 validateViewPageRefs、以及只为那一条规则而存在的 RuntimeStackContext.pages 实时页面全集。表单区块与四份 i18n 词条随之删除。应用导航项的 type: 'page' 是另一个键,原样保留。

为什么改 — 2026-09-09 裁决「撤」,依据 ADR-0049 enforce-or-remove 与「已发布零消费的能力不因沉没成本获得豁免」。spec 侧半边落了地,objectui 的渲染半边从未建成:实测 objectui 的 switch 用 default: 与 grid 共用分支,所以 page 视图一直画的是空表格,而三条解析拒绝守的是一个从未挂载过任何东西的挂载点。

风险与代价(含回滚) — 已存 sys_metadata 行不会炸:ADR-0087 的 D2 转换在每个再水化接缝上重放,剥掉两个键,行按规范服务;type 是剥除不是改写,靠 schema 自己的 grid 默认值落到它本来就渲染的样子。运营者看到的是三个接缝各一条 warn(字符串见上表),不是 400。已测编写面用量为零(带亮对照)。⚠️ objects[].listViews.* 不在任何转换的行走范围内 —— 那条路上的旧行在它自己的门上被带处方拒绝,而不是被转换。回滚 = 还原本 PR;墓碑与转换都是新增,不改动任何现存行为。

席位意见 — 达档契约复核返回 PASS WITH FINDINGS,无 must-fix;席位判定可以合。转录保险丝:120 个 harness 打戳的 claude-fable-5-1,零残留,对照词点亮。⚠️ 独立性说实话:复核者是本席位派出的子代理、继承本 session id ⇒ 席位内达档复核,⛔ 不是跨席位独立。⛔ 人工合并即审核记录,但 ⛔ 不替代契约复核 —— 上面那份就是那一道。

⭐ 两件请你在 diff 里特别看一眼的事。

一、这不是普通的收窄,它是一次真 D2 —— 但覆盖有边界,而边界的代价被测出来了。 存量 view 行会被自动改写(探针:type:'page' 视图 → 2 条 view-page-mount-removed notice;grid 对照 → 0 条;容器视图 → 3 条)。⛔ 但转换只走 stack.views[],够不到 objects[].listViews.*。复核实测了那条边界的实际代价:一条带 page mount 的存量 object 行会原样穿过转换链(0 条 notice),在自己的门被 ObjectSchema 拒绝,却仍然被注册,每个进程打一条 [metadata_spec_invalid] —— 供着,不治;而两处 prescription 都叫作者去跑一个够不到它的 os migrate meta。今天两个人群实测都是零生产者,所以不挡落地,但它是 ACCEPT 时要立的卡(view-export-options-pdf-removed 先例有同样的够不到)。

二、clause ② 的机械读数与本 PR 的声明冲突,我裁维持 no。 三条 T2 tell 全部落在退役台账的行上 —— 它们存在的原因恰恰是接受集缩小了。改判 yes 会让 clause ② 轴对语义上最明确的收窄报「扩大」,并成为此后每一次退役的先例。工具侧另立 #17300。

⚠️ 而我在裁决里把那三条 tell 都说成「生成的行」—— 错了一条。 registry.ts:5443 不在任何 os-generated 区域内(它上面最近的标记是闭合标记 </os-generated semantic:17>),它是手工维护的 step18.conversionIds。复核逮到,我自己在 head 上重测确认。已在裁决评论和 #17300 上就地更正 —— 因为 #17300 原本提的修法只按生成区域来判,会只覆盖三分之二的 tell,而每一次 D2 退役都会注册一个 conversion。

复核另纠了三处报告口径,都不动字节: runtime 读者的「唯一」少算了一个(validateVisibilityPredicates 也读 stack.pages,但 collectionEntries(undefined) 返回 [],无害,措辞应为「不把引用解析进 stack.pages」);消融计数 3/6/13 实为 4/4/14(方向对、守卫有效,只是记录要写真数);以及 #11705 的生成物例外其实抬得起 react-blocks.md,dev 读到的「fail closed」是未构建闭包导致的环境依赖结果。

受管面归属复核已重跑确认:exit 3,由 skills/objectstack-ui/SKILL.md 命中。 ⛔ 我当初的预检说「走普通队列」是错的 —— 我把检查跑在了卡片点名的路径上,而不是一次退役实际需要碰的路径上,而退役手册本身就要求动已发布的 skills 目录。⛔ 把 skills 拆出去躲开受管面这条路,dev 在我之前就拒了,理由对:一份教着「现在会在 parse 处被拒的键」的 SKILL.md,正是被退役的东西本身。

你要做的 — ① 判定 clause-② 的分歧:机械读数给出三条 T2(全部是退役台账的生成行),语义方向仍是收窄;移动声明是席位的事。② 本 PR 受治理(skills/**),需人工合并。③ ACCEPT 时按规则 3 在 objectui 立跨仓跟进:objectui#8429 的前提被本次退役消灭。


⚠️ Reminder, not an action: objectui#8429 is open and pm:blocked — it is the mount half that was never built, and this retirement kills its premise. ⛔ Nothing in objectui was filed or touched by this PR; that cross-repo follow-up is the seat's at ACCEPT.


Generated by Claude Code

…ame` binding

A list view could declare `type: 'page'` and name a published page in
`pageName`, delegating its whole rendering to the page renderer. Only the
spec half of that was ever built (#13216 direction 1, PR #13372). No
renderer ever routed the member — objectui's list-view switch shares its
`default:` arm with `case 'grid'`, and `isListViewVisualization('page')`
is false — so a page view drew an empty table where the page belonged,
and the three parse refusals policing the binding (`checkListViewPageMount`)
policed a mount that never mounted anything.

ADR-0049 enforce-or-remove; maintainer ruling 2026-09-09, decision batch
 #107 item 1.

- `pageName` becomes a `retiredKey()` tombstone on both list-view doors.
- `'page'` leaves the `type` enum; the enum's own error map carries the
  prescription, keyed on `issue.input` (the `exportOptions` 'pdf'
  precedent — an enum-VALUE narrowing has no tombstone to hang one on).
- `checkListViewPageMount`, its three refusal messages, the
  `validateCrossReferences` page branch, `@objectstack/lint`'s
  `validateViewPageRefs` / `VIEW_PAGE_UNRESOLVED`, and `view.form.ts`'s
  `page` section are removed with the mount.
- `RuntimeStackContext.pages` and the `page` row of
  `CLOSURE_CONTEXT_KEY_BY_TYPE` leave with the only crossed rule that
  read them: a view publish no longer gathers a live page universe.
- ADR-0087: D2 conversion `view-page-mount-removed` (protocol 18) strips
  both keys from stored rows and `os migrate meta --from 17` output;
  `type` is stripped rather than rewritten, since the schema defaults it
  to `grid` — exactly what the row already rendered.

The surviving page mount is the app navigation item
(`PageNavItem.pageName`), untouched.

Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
Co-authored-by: Claude <noreply@anthropic.com>
…tirement

`view.form.ts`'s `page` section and its `pageName` input are gone (#17063), so
the extracted metadata-form labels drop with them: the `page` section
label/description and the `pageName` label/helpText, across en/es-ES/ja-JP/zh-CN
plus the three source-hash bundles. Pure deletion — merge mode adds and
translates, it does not remove, so this is `check-i18n-bundles.mjs --write`
rewriting the default locale from source and dropping the orphaned keys.

Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/xl documentation Improvements or additions to documentation protocol:ui tests tooling labels Sep 10, 2026
@github-actions

github-actions Bot commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 4 package(s): @objectstack/lint, @objectstack/metadata-protocol, @objectstack/platform-objects, @objectstack/spec, touching 40 documentable anchor(s). ⚠️ 10 changed file(s) yielded no anchor (packages/lint/src/index.ts, packages/lint/src/validate-list-view-field-refs.ts, packages/spec/api-surface/ui.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/plugin-endpoints.mdx (via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in evaluateRuntimeAuthoringGate; a path literal on a changed line))
  • content/docs/concepts/metadata-lifecycle.mdx (via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in evaluateRuntimeAuthoringGate; a path literal on a changed line))
  • content/docs/kernel/services-checklist.mdx (via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in evaluateRuntimeAuthoringGate; a path literal on a changed line))
  • content/docs/protocol/objectui/index.mdx (via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in evaluateRuntimeAuthoringGate; a path literal on a changed line))
  • content/docs/ui/apps.mdx (via pageName (literal, a string literal in apply; a string literal in checkListViewPageMount; a string literal in sections))
  • content/docs/ui/forms.mdx (via /api/v1/meta/view (route, a path literal in REFERENCE_INTEGRITY_RULES; a path literal in evaluateRuntimeAuthoringGate; a path literal on a changed line))

⛔ 3 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v12.mdx (via ObjectListViewSchema (symbol, a top-level const))
  • content/docs/releases/v17/17-1.mdx (via ListViewSchema (symbol, a top-level const))
  • content/docs/releases/v17/17-3.mdx (via crm_contract (literal, a string literal in fixture))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 10 changed file(s) yielded no anchor (packages/lint/src/index.ts, packages/lint/src/validate-list-view-field-refs.ts, packages/spec/api-surface/ui.json, …) — pages documenting those are invisible to this run
  • 9 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 215 client-bound route-ledger rows — the other 155 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 155: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 100 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • 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 86c50528695a1d46649405bd47e81dae858127d7 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 86420ddc46d889a363265f7a771f94f850c7f7c0 — the merge of head 44fa8ad751326cb75ee4ea4acbcc349bdccc747f into base 86c50528695a1d46649405bd47e81dae858127d7, 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 86420ddc46d889a363265f7a771f94f850c7f7c0 && git checkout 86420ddc46d889a363265f7a771f94f850c7f7c0
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 86c50528695a1d46649405bd47e81dae858127d7 44fa8ad751326cb75ee4ea4acbcc349bdccc747f && git checkout -B drift-repro 86c50528695a1d46649405bd47e81dae858127d7 && git merge --no-ff 44fa8ad751326cb75ee4ea4acbcc349bdccc747f

node scripts/docs-audit/affected-docs.mjs --json 86c50528695a1d46649405bd47e81dae858127d7

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

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 86c50528695a1d46649405bd47e81dae858127d7 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

os-bill commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator Author

⛔ Correction — this PR is no longer mergeable as it stands. My "ready for a hand merge" is stale

domain:spec seat, 2026-09-10T05:47Z. Read live just now: mergeable_state: dirty — it conflicts with main.

⚠️ I told you this was ready for your hand merge, and that is no longer true. Do not press merge on it — git would refuse, but the wasted trip is mine to prevent, so here is the correction as soon as I saw it.

What happened, and it is my sequencing error

PR #17257 merged at 04:42:41Z, and it writes the same generated file this PR writes: packages/spec/src/migrations/registry.ts (this PR registers view-page-mount-removed there).

When I released the registry hold and enqueued #17257, I priced that decision against #17267 and #17146 — the two PRs I then knew shared that file — and I posted the sequencing for both. ⛔ I did not price #17298 into it, because it was still under review at that moment and I did not go back and re-check the file set after its diff was final. The hold I reasoned about carefully covered two of the three PRs it should have covered.

⇒ Nothing about the reviewed work changed. The at-tier PASS WITH FINDINGS stands, CI on this head reads 30 success / 4 skipped / 0 red, and the 席位意见 is filled. What changed is only that main moved underneath it.

What I am doing about it

A mechanical merge + regenerate round is dispatched now — the same operation #17267 and #17146 each took after #17257 landed, and it is the seat's, ⛔ not yours:

⛔ The PR stays draft throughout, and it stays yours to merge — the round makes that merge possible again, it does not perform it.

One thing worth keeping from this

⭐ The lesson is not "hold longer". It is that a hold priced against a file set is only as good as the moment it was priced — mine was correct when I wrote it and silently incomplete twenty minutes later, because a third PR's diff finalised in between. A file-set hold has to be re-derived at the moment of release, over every open PR in the lane, ⛔ not over the ones the seat happened to be thinking about.

I will carry that as a standing check rather than as a note, since this is the second time tonight a pre-check of mine was run over the wrong path set — the first being the governed-surface check on this very card, which read only the paths the card named and missed the skills/** hits that a retirement necessarily brings.


Generated by Claude Code

Brings the branch up to the landed main (8c9bd8f) so the governed PR is
mergeable again. Two hand-resolved hunks, both in
`packages/metadata-protocol/src/protocol.runtime-gate-stored-universe.test.ts`:

- the `@objectstack/lint` import — main widened it to
  `DASHBOARD_FILTER_FIELD_UNKNOWN, VIEW_PAGE_UNRESOLVED, WIDGET_MEASURE_UNKNOWN`
  while this branch removed the `VIEW_PAGE_UNRESOLVED` import along with the rule
  itself. Resolved to main's two surviving symbols; `VIEW_PAGE_UNRESOLVED` no
  longer exists in `packages/lint/src` on the merged tree, so keeping it would
  not compile.
- the helper block after `threeWidgetBoard` — main appended `filterBoard`
  (#16224) directly after `pageMountView`, which this branch deleted with the
  mount it modelled. Resolved by keeping `filterBoard` and dropping
  `pageMountView`; nothing references the latter on the merged tree.

Generated artifacts are regenerated in the follow-up commit, from the committed
merge rather than from a tree in MERGE state.

Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
Co-authored-by: Claude <noreply@anthropic.com>
…ropped

`packages/spec/api-surface/ui.json` and `packages/spec/export-origins/ui.json`
are routed to `merge=os-regen`, and both sides of the merge had edited them, so
the driver merged them with exit 0 while keeping one side. The dropped side was
main's: #17257 added four `*Parsed` type exports on the `ui` entry point
(`ElementDataSourceParsed`, `ObjectCalendarPropsParsed`,
`ObjectKanbanPropsParsed`, `ObjectMetricPropsParsed`), and the merge commit's
copy of both artifacts carried none of them.

Regenerated from the committed merge — `pnpm --filter @objectstack/spec build`
then the gen chain — so the content is re-derived from the merged source rather
than hand-reconciled. Both files gain exactly those four rows and nothing else;
every other generated artifact this branch touches (migration registry,
authorable surface, spec-changes, upgrade guide, liveness counts, react-blocks,
the reference docs) regenerated byte-identical to the merged tree.

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

os-bill commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator Author

ACCEPT — merge+regen round, head 59b2e5f0d

Reviewed by the domain:spec execution seat, session_01MkQhmuuJAVDjmeWNixwDDH, readings taken 2026-09-10T06:48Z UTC. ⛔ Governed surface: this is an acceptance record only — no ready-flip, no enqueue, no auto-merge, no approving review. The human merge remains the terminal step and remains the sign-off.

The dirty this round was dispatched for is gone. mergeable: true, mergeable_state: blocked (required checks pending), draft still true, 48 changed files.

⛔ Three false premises in my dispatch order, corrected on the record

The round's most valuable output is that it falsified my own order rather than executing it.

1 — I predicted registry.ts would conflict. It did not. It text-merged cleanly and check:migration-registry reports it current with no regeneration needed.

The files that actually lost a side were the pair routed to merge=os-regen: packages/spec/api-surface/ui.json and packages/spec/export-origins/ui.json. There the driver exited 0 and silently dropped MAIN's side — PR #17257's four new *Parsed ui exports (ElementDataSourceParsed, ObjectCalendarPropsParsed, ObjectKanbanPropsParsed, ObjectMetricPropsParsed) were absent from the merge commit's copy of both files, and are restored by 59b2e5f0d.

⇒ That is exactly the silent-loss shape this round exists to catch — caught in a file my order did not name, and it would have been missed by a round that only checked where I said to look. ⚠️ The lesson for the next merge order on a spec retirement branch: derive the prediction from .gitattributes, not from the last incident. I derived it from the #17146 incident and named the wrong file.

2 — I named five gates in their bare spelling. check:migration-registry, check:spec-changes, check:upgrade-guide and check:generated are not root scripts; they live in packages/spec. Run bare they exit 254 (ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL, "Command not found") — which is NOT MEASURED, not red, and pnpm's own "Did you mean check:query-options-erasure?" hint is actively misleading. Correct spelling: pnpm --filter @objectstack/spec check:…. Only check:i18n is a root script.

⚠️ This one is the more dangerous of the three: an order that teaches a dev to expect a specific non-zero exit is pre-authorisation to wave through an unrelated failure. It is the second time this stretch I have shipped that shape in an order.

3 — I asserted the skills bundle line reading could stay at 13531. It cannot and does not: md-only reads 13530 at HEAD. This PR's own contribution is −1 line; the other −1 is main's own skills/objectstack-ai/SKILL.md going 417→416 inside the same window. ⇒ Any published-bundle ratchet number quoted from before a merge is stale by construction; the per-file attribution is the durable form.

Evidence I checked before accepting

  • Entry-survival assertions ran with both a lit and a dark control. A probe that matches nothing proves nothing, and this round's own earlier legs had already produced one vacuous probe; the controls are what make the survival reading a reading.
  • Gate exits were landed to disk and read back, never taken through a pipe or from a bare exit variable.
  • Suites were run sequentially with each exit echoed, because pnpm --filter A --filter B test --concurrency=2 forwards --concurrency to vitest and dies (first attempt exit 1 — NOT MEASURED, correctly reported as such rather than as red). Green: @objectstack/spec 470 files / 13208 tests; @objectstack/lint 101 / 3688; @objectstack/metadata-protocol 173 passed + 2 skipped / 2486 passed + 10 skipped — that last one carries the hand-resolved file, green with main's whole The runtime authoring gate judges an OVERRIDDEN item's content from the registry copy, so an overlay that removes a measure still accepts widgets bound to it #16224 describe running. Typecheck across all three joined with &&, exit 0.
  • Declared narrowing, accepted: the repo-wide farm was not run locally. dispatch-gates.mjs --commands derives 121 commands for this 48-path change set; the round ran the order's named set plus the families its own mechanics implicate (merge-driver, nul-bytes, skills token ratchet, widening-tells, four spec generated-artifact gates). The rest is CI's, on this head.
  • No ablation, and none owed — nothing here is a new guard, so there is no trap/restore leg to report.

CI, read as newest run per check name

Read 2026-09-10T06:48Z from the check-runs endpoint for 59b2e5f0d, reduced to the newest run per check NAME — ⛔ not the raw run list, ⛔ not the required subset. 34 runs, 34 distinct names, so no name carried a stale duplicate: 31 success, 2 skipped, 1 in progress (Lint & Repo Gates), 0 failure, 0 cancelled, 0 timed out.

⇒ One job from converged. I will post again here when the last one lands, as promised — ⛔ that post will be a fresh reading, not this one restated.

Out-of-scope findings, dispositions

All three of the round's noted-not-filed items are correctly not-filed: two have no carrier (the pnpm 254 is loud and never a false green; the bundle arithmetic is arithmetic, not a defect), and the third — re-deriving a merge prediction from .gitattributes — is now recorded above, where the next order's author reads it.


Generated by Claude Code

os-bill commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator Author

Head 59b2e5f0d is green — as promised, a fresh reading

domain:spec execution seat, session_01MkQhmuuJAVDjmeWNixwDDH, read 2026-09-10T06:51Z. ⛔ Not a restatement of the acceptance comment above — re-read from the API at this timestamp.

Check runs on 59b2e5f0d, reduced to the newest run per check NAME (⛔ not the raw run list, ⛔ not the required subset): 34 runs, 34 distinct names, so no name carried a stale duplicate.

success 32
skipped 2 (Console Pin Gate, Packed-tarball smoke (opt-in))
failure / cancelled / timed out 0
still running 0

Lint & Repo Gates, the last one outstanding at the acceptance comment, completed success.

⚠️ One thing the check-run reading alone would have got wrong

The PR reads mergeable_state: unstable, not clean — and the check runs do not explain that, because it is not a check run. The combined commit status endpoint does:

combined state: pending | statuses: 1
  pending  Vercel  |  Vercel is deploying your app

⇒ The unstable is a pending Vercel preview deploy, a commit status and not a gate. Every gate on this head is green. ⛔ Recording it because a newest-run-per-name reading over /check-runs alone would have let me report "clean" while the API said otherwise — the two surfaces are different populations and the mergeability field reads both.

State

mergeable: true, mergeable_state: unstable (Vercel only), draft true, 48 changed files, labels unchanged. The dirty this round was dispatched for is gone and has not returned.

⛔ Still governed: no ready-flip, no enqueue, no auto-merge, no approving review by any seat. The hand merge is available again, and it remains yours.


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review September 10, 2026 12:47
@os-zhuang
os-zhuang added this pull request to the merge queue Sep 10, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Sep 10, 2026
os-litant pushed a commit that referenced this pull request Sep 10, 2026
…n rows as an accept-set gain

T2 is "the accept set gains a VALUE", and the ADR-0087 retirement ledger is a
list of values written BECAUSE an accept set shrank. Every retirement adds rows
to `RETIRED_KEYS_BY_MAJOR` / `RETIRED_DEFS_BY_MAJOR` and to a step's
`conversionIds`, so the mechanical clause-② axis read adverse on the one change
class whose direction is unambiguously narrowing: PR #17298 exits 4 with three
T2 rows against a declaration that is correct.

The repair is a LICENCE, minted only by an added line on one of two declared
input surfaces and checked by exact string identity:

  - `export const entry = '<row>';` under `packages/spec/src/migrations/entries/**`
    — the generator's own per-entry input (#7297), the exact string
    `gen:migration-registry` emits into the table; and
  - `id: '<id>',` added to `packages/spec/src/conversions/registry.ts` — the D2
    registration a hand-maintained `conversionIds` row refers to.

Neither a region nor a file is excluded. One of PR #17298's three rows sits in
the hand-maintained `conversionIds` array, outside every `<os-generated …>`
marker, so a position-keyed predicate covers two rows of three. A lookup in the
local tree was measured and is wrong rather than merely expensive: a retirement
registers its conversion in the same PR, so `view-page-mount-removed` reads 0
occurrences in any checkout of main against a positive control reading 1.

The licence is read AFTER #16943's replacement budget, so a block that removes a
member and adds both a tombstone and a genuine member still reports the genuine
one. Self-test 218 -> 245 cases, 27 of them a new battery whose firing half
comes first: the same row with no licence, a licence for a neighbouring row, a
licence spent on the wrong file, a genuine member beside a licensed one, and the
ordering against the budget.

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

os-bill commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator Author

Seat close-out — this PR is complete on the seat's side and is waiting on your hand, not on us. domain:spec seat, session_01MkQhmuuJAVDjmeWNixwDDH, 2026-09-10T22:52Z.

Four readings taken now, on the current head 59b2e5f0d298 — not carried from the 04:37Z round:

reading value
governed classification GOVERNED — 2 of 48 paths on GOVERNED_SURFACES: skills/objectstack-ui/SKILL.md, skills/objectstack-ui/references/react-blocks.md
checks, newest run per check NAME 34 checks, zero non-green
at-tier contract review PASS WITH FINDINGS, no must-fix — verdict, seat opinion and its independence limit are in ## 维护者速读(草稿) in the body
auto_merge null — ⛔ never armed by this seat

needs:contract-review is cleared in both places (card #17063 and here) because the review it gates has returned; the clear went through the same four-step read-back as hanging it did.

⛔ This seat does not merge it, enqueue it, arm auto-merge on it, or approve it — it is governed, and the terminal state named in the 04:37Z ruling is your own hand merge. The ready_for_review flip at 12:47Z was os-zhuang's, not this seat's; it is recorded and left alone.

⚠️ One thing the review found and this seat is carrying forward rather than hiding: the D2 conversion reaches stack.views[] but ⛔ not objects[].listViews.*. A stored object row with a page mount passes the conversion chain untouched (0 notices), is refused at ObjectSchema's own door, and is still registered, logging one [metadata_spec_invalid] per process — while both prescriptions send the author to an os migrate meta that cannot reach it. Both populations measure zero producers today, which is why it does not block landing; it is a card this seat files on ACCEPT, ⛔ not a silent residue.


Generated by Claude Code

os-bill commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator Author

⚠️ This PR has gone un-mergeable against main — mergeable_state: dirty, read 2026-09-11T00:47Z. domain:spec execution seat, session_01MkQhmuuJAVDjmeWNixwDDH. Nothing else about it changed: head 59b2e5f0d2, ready, 34 checks green on that head, contract review PASS on record.

⛔ Why this seat is not fixing it with a one-click update-branch

PUT /pulls/{n}/update-branch is normally the zero-file-write way for this seat to bring main in. ⛔ Not here. Derived from .gitattributes at merge time, ⛔ not recalled, this PR's 48 paths hit the merge=os-regen driver on 9 of them:

content/docs/references/api/metadata.mdx        ← .gitattributes:153  content/docs/references/**
content/docs/references/api/protocol.mdx        ←        "
content/docs/references/data/object.mdx         ←        "
content/docs/references/system/migration.mdx    ←        "
content/docs/references/ui/view.mdx             ←        "
packages/spec/api-surface/ui.json               ← :146   packages/spec/api-surface/**
packages/spec/authorable-surface/ui.json        ← :142   packages/spec/authorable-surface/**
packages/spec/export-origins/ui.json            ← :148   packages/spec/export-origins/**
skills/objectstack-ui/references/react-blocks.md← :156   (named explicitly)

⭐ That driver has been measured exiting 0 while silently dropping one side of a merge. Tonight's PR #17554 got a dedicated survivor probe for exactly this reason, and passed — but passing once is not a licence to merge nine generated artifacts unprobed on a governed PR. ⇒ Running update-branch here would produce a merge this seat could only trust by re-probing every one of those nine paths afterwards, and a silent drop in a published reference page or an api-surface roster is precisely the damage that is hard to see and easy to ship.

⇒ The right instrument is a round in a worktree: merge main, regenerate the generated files with the repo's own tooling (⛔ never by hand), validate, push — then re-read the governed classification and all four landing pre-checks on the new head, because both can move.

State

Card #17063 goes back to pm:queue with the assignee released: pm:awaiting-maintainer asserts only a human action remains, and that is no longer true — work is owed and nobody holds it. ⛔ Not a re-triage, ⛔ not a scope change, ⛔ nothing about the retirement itself is reopened. It is first in line for this seat's next free dev slot.

⛔ Unchanged regardless of who does the work: this PR is governed (2 skills/** paths on the register), so no seat merges it, enqueues it, arms auto-merge on it, or submits an approving review. It still ends in the maintainer's own hand merge.

维护者速读

这张 PR 现在和 main 冲突了,合不进去。⛔ 本席没有用一键 update-branch 去修,理由是:它这 48 个文件里有 9 个走 merge=os-regen 合并驱动(5 个已发布参考页、3 个 packages/spec 生成物、1 个 published skills 参考页),而那个驱动被实测过退出码 0 的同时静默丢掉一侧 —— 在一张受管 PR 上让它跑过九个生成物而不逐个验证存活,风险不对等。

⇒ 正确做法是起一轮开发:在 worktree 里合 main、用仓里自己的工具重新生成那些生成物(⛔ 不手改)、验证、推送,然后在新 head 上重跑受管面判定与四条落地前检。卡已退回队列等本席下一个空槽,⛔ 退役本身没有任何部分被重新打开。

⛔ 不变的是:这仍然是受管面 PR,任何座位都不合它、不入队、不挂 auto-merge、不提交批准 review —— 终局仍是你自己手动合并。


Generated by Claude Code

os-regen artifacts: main's side taken for the one path both sides moved
(content/docs/references/ui/view.mdx); regeneration follows as its own commit.

Three hand-written conflicts resolved as semantic merges, both intents stacked:
  packages/spec/src/ui/view.zod.ts     #16577 scope docblock + the retirement's
                                       rewritten attachment note
  packages/spec/src/ui/view.test.ts    #16577 module-scoped flattenUnionIssues +
                                       the retirement's door comment
  packages/spec/src/migrations/registry.ts  #16320 and #17063 step-18 paragraphs

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
…med test helper

Discharges the os-regen deferral recorded by the merge commit.

content/docs/references/ui/view.mdx — re-derived with `gen:docs` from the merged
sources on a known-good base, rather than accepting what the merge driver left.
The driver had exited 0 while keeping the branch's blob verbatim and silently
dropping main's side; the merge commit took main's side per
`scripts/pm/os-regen-merge.sh` step 2, and this regeneration re-derives the
retirement's own rows on top. Both sides are present in the result.

packages/spec/src/ui/view.test.ts — main added a third `describe.each` site using
`viewDoorsCarryingPageMountCheck`, which this branch had renamed to
`viewDoorsCarryingObjectLevelChecks`. Both edits merged without a textual
conflict and left a dangling identifier; the rename is propagated to main's new
block. No behaviour change on either side.

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

os-bill commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator Author

Merge round — this PR is mergeable again. No content of the retirement was reopened.

origin/main merged in as a real merge commit (no rebase, no amend, no force-push), so the existing checkout and the at-tier review's provenance stay valid.

  • New head 44fa8ad751 — was 59b2e5f0d2. mergeable: true (mergeable_state: blocked, the ordinary governed state — no longer dirty). 48 files, 6 commits.
  • d0937e229f the merge · 44fa8ad751 the regeneration, which discharged the os-regen deferral the merge recorded.

The merge driver did drop a side, and it was caught. The merge=os-regen register was re-derived from .gitattributes at merge time: 10 of the 48 paths are routed, not 9 — packages/spec/liveness/state-counts.md (.gitattributes:141) was missing from the recalled list. Only 1 of the 10 was changed on both sides, content/docs/references/ui/view.mdx, and there the driver exited 0 having kept the branch blob 347d5117 verbatim and discarded main's 6552cb19. Main's side was taken for that path per os-regen-merge.sh step 2, the branch's own bytes kept for the other 9, and everything regenerated with the repo's own tooling.

Survival probe — 0 of 10 artifacts failed, content readings with a lit control on every file, never an exit code. On view.mdx, occurrences via grep -o | wc -l (base / PR / main → merged): retirement tombstone rows 0/6/0 → 6; retired enum member 2/0/2 → 0; main's groupBy whitespace note 0/0/2 → 2; lit control 2/2/2 → 2; negative control → 0. Both sides present.

Three conflicts, all semantic merges with both intents stacked — no behaviour lost either way, so nothing needed a maintainer's choice: view.zod.ts, view.test.ts, migrations/registry.ts. One repair worth knowing: main added a third describe.each site naming viewDoorsCarryingPageMountCheck, which this branch had renamed — both edits auto-merged with no conflict marker and left a dangling identifier the package build cannot see. check:test-typecheck caught it (TS2304); the rename is propagated.

Re-measured on the new head, not carried:

No seat has merged, enqueued, approved or re-drafted this PR, and none will.


Generated by Claude Code

os-bill commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator Author

Merge round complete — this PR is mergeable again. New head 44fa8ad751, mergeable: true, mergeable_state: blocked (the ordinary governed-PR state, no longer dirty). domain:spec execution seat, session_01MkQhmuuJAVDjmeWNixwDDH, 2026-09-11T02:22Z. ⛔ No content of the retirement was reopened; the at-tier contract review (PASS WITH FINDINGS, no must-fix) stands.

origin/main abc4b83ce8 was merged in with a real merge commit — ⛔ no rebase, no amend, no force-push — then the generated artifacts were regenerated with the repo's own tooling. Three hand-written conflicts were resolved as semantic merges with both intents stacked and no behaviour dropped, so no maintainer choice was needed.

⭐ The merge=os-regen driver DID drop a side, and the probe is what caught it

This is the failure the seat has been probing for on every merge that touches a routed path. It happened here:

content/docs/references/ui/view.mdx   — the ONE of 10 routed paths changed on BOTH sides
  driver exit                 0
  kept                        the branch blob 347d5117, verbatim
  discarded                   main's 6552cb19

⇒ ⛔ A zero exit from that driver is not evidence the merge happened. The round took main's side for that path per os-regen-merge.sh step 2 (worktree only, hash-proven), kept the branch's own bytes for the other nine, and re-derived the retirement's rows on top.

Survival probed on all 10 routed paths, content readings with a lit control on every file — 0 of 10 failed. For the both-sides file, occurrences by grep -o | wc -l (⛔ not grep -c) against three reference trees:

                                base 8c9bd8f51f / PR 59b2e5f0d2 / origin-main   MERGED
PR tombstone rows                      0   /   6   /   0                          6
retired enum member                    2   /   0   /   2                          0
main's #groupBy whitespace note        0   /   0   /   2                          2
LIT CONTROL                            2   /   2   /   2                          2
negative control                       —                                          0

⇒ Both sides present, control lit, negative control dark. Filed as an incident record: #17602.

⭐ Two register readings moved — and quoting is not measuring

The routed-path list. The dispatch order recalled 9; re-derived at merge time it is 10 — packages/spec/liveness/state-counts.md (.gitattributes:141) was missing from the recalled list. The order told the round to re-derive rather than quote, and the re-derivation is what found the tenth.

The governed-path count, and here the seat must be careful about whose reading is whose:

reading count tree it was taken on
the round 1 of 48 (SKILL.md alone) its own worktree at the PR head, where gen:react-blocks recomputes byte-equal so the #11705 generated-surface exception lifts react-blocks.md
this seat, just now 2 of 48 (both skills/** paths) the shared checkout, whose tree is not the PR's — so the exception could not recompute and the path stays governed

⚠️ Both are correct for the tree each was taken on, and the seat's is the less meaningful one: --test's exception recomputes the generator against the tree under test, so running it from a checkout that is not the PR's head gives a conservative answer, ⛔ not the PR's answer. ⇒ The seat does not restate the round's 1 as its own measurement. The verdict is identical either way — GOVERNED, one hit governs the whole PR — so nothing about the landing path moves. Lit controls on the predicate, taken here: a known-governed path alone → exit 3; a known-ungoverned path alone → exit 0.

⚠️ A defect the merge created with NO conflict marker

Main added a third describe.each site naming viewDoorsCarryingPageMountCheck; this branch had renamed that identifier to viewDoorsCarryingObjectLevelChecks. Both edits auto-merged cleanly, exit 0, no marker, leaving a dangling identifier — and tsup does not type-check tests, so the package build stayed green. Only check:test-typecheck saw it (TS2304). Fixed in 44fa8ad751. ⇒ A rename on one side plus a new call site on the other is invisible to git's merge and to the build; the gate is what caught it.

Gates: 122 families derived from the actual 48-path change set, all 122 run, 122 run, 0 NOT-MEASURED, 0 UNRUN — a derived zero. 8 first returned non-zero and every one was a prerequisite refusal judged by substance, ⛔ not by code — ⚠️ and two of them arrived as exit 1, not 3 (check:skill-examples, check:dual-build-cjs-loads), exactly the trap this seat has been carrying all night. All 8 cleared once their prerequisites were met. check:generated → all 15 artifacts up to date. Tests: spec 13343, lint 3725, metadata-protocol 2486, platform-objects 561 — zero failures.

⛔ No ablation this round, and that is correct: a merge adds no guard, so there is nothing to prove can fail. The survival probe is the reading that stands in its place.

维护者速读

这张 PR 现在能合了(mergeable: true,冲突已解,落后 127 个提交已补上)。⛔ 退役内容一个字没重开,达档契约复核的 PASS 依然有效。

⭐ 要紧的一件事:merge=os-regen 驱动这次真的丢了一侧 —— 在唯一一个两侧都改过的路径 content/docs/references/ui/view.mdx 上,它退出码 0、保留了分支那一侧、把 main 那一侧扔掉了。已按 os-regen-merge.sh 第 2 步取回 main 的那一侧,并对全部 10 条受驱动路径做了逐件存活探测(每个文件都配亮对照),10 条全过。已按事故立卡 #17602。⇒ ⛔ 那个驱动的 0 退出码不构成「合并发生过」的证据。

⛔ 仍然是受管面 PR,终局仍是你自己手动合并 —— 任何座位都不合它、不入队、不挂 auto-merge、不提交批准 review。卡回 pm:awaiting-maintainer。


Generated by Claude Code

@os-zhuang
os-zhuang added this pull request to the merge queue Sep 11, 2026
Merged via the queue into main with commit d4f5232 Sep 11, 2026
35 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-17063-retire-list-view-page-member branch September 11, 2026 07:29
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 17, 2026
…n rows as an accept-set gain (objectstack-ai#17488)

T2 is "the accept set gains a VALUE", and the ADR-0087 retirement ledger is a
list of values written BECAUSE an accept set shrank. Every retirement adds rows
to `RETIRED_KEYS_BY_MAJOR` / `RETIRED_DEFS_BY_MAJOR` and to a step's
`conversionIds`, so the mechanical clause-② axis read adverse on the one change
class whose direction is unambiguously narrowing: PR objectstack-ai#17298 exits 4 with three
T2 rows against a declaration that is correct.

The repair is a LICENCE, minted only by an added line on one of two declared
input surfaces and checked by exact string identity:

  - `export const entry = '<row>';` under `packages/spec/src/migrations/entries/**`
    — the generator's own per-entry input (objectstack-ai#7297), the exact string
    `gen:migration-registry` emits into the table; and
  - `id: '<id>',` added to `packages/spec/src/conversions/registry.ts` — the D2
    registration a hand-maintained `conversionIds` row refers to.

Neither a region nor a file is excluded. One of PR objectstack-ai#17298's three rows sits in
the hand-maintained `conversionIds` array, outside every `<os-generated …>`
marker, so a position-keyed predicate covers two rows of three. A lookup in the
local tree was measured and is wrong rather than merely expensive: a retirement
registers its conversion in the same PR, so `view-page-mount-removed` reads 0
occurrences in any checkout of main against a positive control reading 1.

The licence is read AFTER objectstack-ai#16943's replacement budget, so a block that removes a
member and adds both a tombstone and a genuine member still reports the genuine
one. Self-test 218 -> 245 cases, 27 of them a new battery whose firing half
comes first: the same row with no licence, a licence for a neighbouring row, a
licence spent on the wrong file, a genuine member beside a licensed one, and the
ordering against the budget.

Claude-Session: https://claude.ai/code/session_01YKEjmbYNvYWJvWGSWx26zK

Co-authored-by: Claude <noreply@anthropic.com>
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/xl tests tooling

Projects

None yet

3 participants