Repository navigation
[finding] the authorable-key deletion gate's reachability BFS reports integration/DataSyncConfig unreachable from the metadata-type roots — it is two hops from the connector root, so #4650 proof 2 waives a tombstone for a key a stack manifest really authors #17356
Description
What is wrong
computeSurfaceReachability() in packages/spec/scripts/build-schemas.ts is the whole of check (c) proof 2 (#4650, 2026-08-02 ruling): a deleted authorable-surface/ baseline line is legitimate when "the def is not reachable from the metadata-type roots (no metadata document is ever parsed by it), so its entry was over-collection and there is no author to tombstone for."
For integration/DataSyncConfig that answer is measurably false. The def sits two hops from a root the same file enumerates, and the runtime really does parse metadata documents against it.
The function's own docblock names this direction as the dangerous one:
Judged conservatively: one shared entry marks the whole def reachable — a false "reachable" demands a tombstone too many, a false "unreachable" would waive one silently.
This is that false "unreachable", in the tree, today.
The two readings, side by side
Reading A — what the gate says. pnpm --filter @objectstack/spec gen:schema on a branch that bare-deletes integration/DataSyncConfig:schedule, exit 0, printing:
ℹ️ 4 baseline deletion(s) since cca1dc0bfb1e carry their own proof (#4650):
- integration/DataSyncConfig:schedule — def not reachable from the 26 metadata-type roots
(BUILTIN_METADATA_TYPE_SCHEMAS + EXTRA_METADATA_TYPE_SCHEMAS overlay; BFS over this
build's in-memory Zod graph): an over-collected entry, never parsed against a
metadata document.
Reading B — what the graph actually holds. A tsx probe over the same modules the build imports, run with OS_EAGER_SCHEMAS=1 exactly as gen:schema does:
roots: 26
connector root === DeclarativeConnectorEntrySchema: true
connector root type tag: object
unwrap trail: object
shape keys sample: name,label,type,description,icon,authentication,provider,providerConfig
has syncConfig prop: true
syncConfig unwrap: optional -> object
syncConfig inner === DataSyncConfigSchema: true
So: getMetadataTypeSchema('connector') (an EXTRA_METADATA_TYPE_SCHEMAS entry, one of the 26 roots the message counts) resolves to an object whose syncConfig property, unwrapped through one optional, is the same instance as the exported DataSyncConfigSchema. reachableVia('integration/DataSyncConfig') should answer root-graph; it answers null.
Reading C — the runtime agrees with B, not A. Pin tests in packages/spec/src/cron-typed-positions-retirement.test.ts parse a connector through three doors and observe syncConfig being processed by that def each time: the bare ConnectorSchema, DeclarativeConnectorEntrySchema, and ObjectStackSchema with connectors: [...] — i.e. a real defineStack manifest, and PUT /api/v1/meta/connector/:name behind getMetadataTypeSchema. A metadata document IS parsed by this def.
Why it matters
Proof 2 exists to waive the tombstone requirement only where no author can receive a prescription. Where it answers falsely, the gate lets an authorable key be deleted from a non-strict schema with no tombstone and no ADR-0087 entry — which is precisely the silent-strip class ADR-0104 and #3733 name: the author keeps writing the key, gets a clean parse, and the setting never takes effect. Every gate stays green, repo-wide, for exactly the shape check (a) refuses in its own failure text.
The blast radius is not one key: any def reachable only through the same shape of edge is equally invisible, so the waiver is available to every future spec retirement that touches one.
Repro
- On a branch off
main, delete theschedulekey fromDataSyncConfigSchemainpackages/spec/src/integration/connector.zod.ts(noretiredKey(), no registry entry). - Delete the line
"integration/DataSyncConfig:schedule"frompackages/spec/authorable-surface/integration.json. pnpm --filter @objectstack/spec gen:schema— exits 0 and prints the proof-2 line quoted above.
Observed on objectstack-ai/objectstack at 1504dd1d77e9b0e0c53dd6fb4251c0874e1e5b7e (branch claude/issue-16320-retire-cron-typed-positions), against merge base cca1dc0bfb1e380442670843606d6a0c7762f6ba.
Where to look
packages/spec/scripts/build-schemas.ts:
computeSurfaceReachability()— the BFS,zodChildSchemas(node)and thebridgedmap;zodByDefKey— whether the instance it holds forintegration/DataSyncConfigis the instance the connector shape holds (the probe shows the EXPORTED schema is; the def-key map may hold another);zodShapeOf(node)for theconnectorroot and for theoptionalwrapper onsyncConfig.
Not diagnosed here on purpose — two independent candidates (an edge the child-walk does not follow, or a def-key map keyed on a different instance) and only one of them is the fault. This card names the contradiction and the reading that settles it.
Not in this card's scope
⛔ Nothing here argues that the four deletions on the branch above should be reverted; that route is a maintainer ruling and stands on its own. What is asked here is that proof 2 stop answering null for a def a manifest parses.
Suggested acceptance
- a
--self-testcase that fails when a def reachable through anoptional-wrapped property of anEXTRA_METADATA_TYPE_SCHEMASroot readsnull; reachableVia('integration/DataSyncConfig')answersroot-graphonmain;- the fix does not turn the conservatism around: a genuinely unreachable def still reads
null, pinned with its own case.
Generated by Claude Code
Activity
os-warren commentedon Sep 16, 2026
Claim: PM loop,
domain:specexecution seatSession:
session_01KB5PFtxuy1x3dcR5gxudx6Branch:
claude/issue-17356-reachability-root-enumeration(EXISTING — PR #18131, head00ac98d0, draft)Worktree:
objectstack-issue-17356Domain:
domain:specFile surface:
packages/spec/scripts/build-schemas.ts,packages/spec/scripts/build-schemas-check-mode.test.ts, and the committed anchorauthorable-surface.base.jsonthat letter A advances.Clause-②: yes
Re-dispatch of the ruled letter A half (director ruling 5682578124, maintainer 「135 同意」 on batch #135 item 3). Letter C is #18301 and is deliberately NOT in this batch: it edits the same file, so the two are serialized rather than raced.
Measured before dispatch, 2026-09-16T10:4xZ:
open,draft, head00ac98d0, last pushed 2026-09-14 — and 7 checks are red on that head (Build Core,Dogfood Regression Gateand its 3 shards,Dogfood Verify CLI,Temporal Conformance). Whether those are this branch's or the base's is the dev's first reading, not an assumption carried into the order.--update-baserefuses in the dev's environment (shallow clone, os-regen 驱动指示的gen:schema在 merge 未 commit 时运行,会把 authorable-surface 锚点倒退回旧 merge-base —— 生成器写入、门全绿、静默撤销 main 的锚点推进 #5370), the dev reports back and check (c) has no proof shape for a guidance-route retirement on a reachable def — add a fourth proof soUNKNOWN_KEY_GUIDANCEretirements prove themselves (batch #135 item 3, C) #18301 runs first」 — has not been shown to trigger. This checkout IS shallow (git rev-parse --is-shallow-repository=true), but the only--update-baserefusal inbuild-schemas.tsis the mid-merge one at:231;:1437names a shallow re-anchor as a supported caller and:1473emits an informational note, not a refusal. ⇒ dispatched on the reading that shallowness alone does not refuse. If the run refuses for any reason, the ruling's instruction stands: report back, do not work around it.Clause-②: yesis a conservative declaration, not a claim that the diff exports anything: advancing the anchor changes what the gate will WAIVE, and the seat would rather run the clause-② review than argue the ledger is out of scope.Generated by Claude Code
os-warren commentedon Sep 16, 2026
os-dev-report
{
"issue": 17356,
"status": "done",
"branch": "claude/issue-17356-reachability-root-enumeration",
"pr": "#18131",
"session": "session_01KB5PFtxuy1x3dcR5gxudx6",
"premise_still_valid": true,
"summary": "Letter A of the batch #135 item 3 ruling, delivered on the existing PR #18131 as its own commit. (1) Diagnosed all 7 reds on head 00ac98d as ONE root cause and as THIS BRANCH's, not the base's: every red job's annotation names the same command (packages/spec pnpm run build exited 1) and the Build Core job log's first error line is '2 authorable baseline line(s) were deleted without proof (#4650): data/Metric:filters, integration/DataSyncConfig:schedule - def reachable ... the entry at 53ef057 was LIVE'. CI checks out shallow, so the deletion gate anchors on the committed authorable-surface.base.json at 53ef057; both keys were retired ON MAIN after that rev, and this PR's widening of the root set 26->30 is exactly what stops waiving them. Base was green on the same checks (f3b41e8, a90a9f2, c667d8c all success). (2) Merged origin/main (b9598e9) with scripts/pm/os-regen-merge.sh, never a bare git merge; step 2 found no os-regen path changed on both sides; merge committed before any regeneration. (3) Ran gen:authorable-surface-base in a clean non-merging tree and committed the anchor advance ALONE as dc98ee2 (only packages/spec/authorable-surface.base.json, 992 insertions / 1056 deletions), 53ef057 (7836 keys) -> b9598e9 (7772 keys). The PR body now quotes the ruling and carries 'Clause-2: yes' at the start of a line.",
"update_base_refusal": "NO REFUSAL. gen:authorable-surface-base exited 0 and printed 'authorable-surface.base.json refreshed to b9598e9 (7772 keys) - commit it, on its own'. Reported because the ruling anticipated a refusal: this checkout IS shallow (git rev-parse --is-shallow-repository = true, read 11:01:08Z on eac950d) but the run took the MERGE-BASE path, not the shallow fallback - after the merge origin/main is an ancestor of HEAD so git merge-base HEAD origin/main resolves to b9598e9 at exit 0, and no shallow-fallback note was printed. That is the same path a full clone takes, and the condition the ruling names a full clone to guarantee (a walkable merge base, #5370 assertAnchorMovesForward failing closed on truncated history) is therefore satisfied. Flagged rather than silently treated as equivalent: the ruling's literal words say 'run in a full (non-shallow) clone' and this clone is shallow.",
"red_checks_diagnosis": "7 reds on 00ac98d, all ONE root cause, all THIS BRANCH's. Failing steps differ per job (Build Core: 'Build packages (excluding docs)'; Dogfood Regression Gate 1/3,2/3,3/3: 'Build the dogfood package's dependency closure'; Dogfood Verify CLI: 'Verify example apps via the objectstack verify CLI'; Temporal Conformance: 'Build driver-sql and its dependencies') but all six job-level reds carry the identical annotation 'command (.../packages/spec) .../pnpm run build exited (1)', and the 7th (Dogfood Regression Gate) is the roll-up of the three shards. First error line read from the Build Core job log, not by eye and not from a self-report. BASE READING that settles branch-vs-base: PR base f3b41e8 has Build Core success, Dogfood Verify CLI success, Dogfood Regression Gate (1/3) success, Temporal Conformance success; main's tip when the run started (a90a9f2) and the commit behind it (c667d8c) are success on all four too. NOW RESOLVED: against the advanced anchor the emitted surface has 0 deletions and 0 additions (7772 vs 7772), and both named keys are absent from the advanced anchor.",
"clause_2": "Declaration line 'Clause-2: yes' (real spelling uses the circled-2 character) added at the START of a line in the PR BODY - it was missing, which is what reds Check Changeset. needs:contract-review is the SEAT's: NOT hung, NOT stripped, NOT waited on by me. Two readings, both timestamped: at 2026-09-16T10:51:56Z PR #18131 labels were [size/m, tests, tooling, skip-changeset] with needs:contract-review ABSENT, and node scripts/pm/check-clause2-carriers.mjs --pair 18131 exited 4 ('the gate was NEVER HUNG on either carrier'). At 2026-09-16T11:19:47Z, after my push and body edit, labels read [size/xl, tests, tooling, skip-changeset, needs:contract-review] - another actor hung it and the size labeler moved size/m to size/xl; I corrected neither. Re-run at 2026-09-16T11:20:02Z: --pair 18131 exit 0, 'the clause-2 declaration is readable in the fixed spelling and both carriers agree'.",
"tests": "All on dc98ee2 unless stated; exit codes captured by redirect-then-$?, never through a pipe. (1) pnpm --filter @objectstack/spec build -> exit 0, working tree clean (git status --porcelain = 0 lines), and the 'authorable-surface.base.json trails the baseline' note that the pre-anchor build printed is GONE. (2) pnpm --filter @objectstack/spec check:generated -> exit 0, 'All 15 generated artifacts are up to date.' (3) pnpm --filter @objectstack/spec typecheck -> exit 0 (tsc --noEmit + check:scripts-typecheck + check:test-typecheck). (4) pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 scripts/ -> exit 0, 'Test Files 57 passed (57) / Tests 1218 passed (1218)' - this is the edited tool script's own suite (build-schemas-check-mode.test.ts plus the 21 other scripts/ tests that name build-schemas). (5) pnpm check:nul-bytes -> exit 0 (8726 text files, no raw control bytes). (6) pnpm check:merge-driver -> exit 0. Heavy runs went through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-17356 and the verdict was read from its printed 'VERDICT command-exit' line. ABLATION (acceptance 2, re-measured ON THE ADVANCED ANCHOR): bare deletion of integration/DataSyncConfig:timestampField from DataSyncConfigSchema and from authorable-surface/integration.json, no tombstone, no registry entry. Mutation PROVEN ON DISK before the run by anchor counts, not by the editor's exit code: source 1 -> 0 and baseline line 1 -> 0. No rebuild leg is owed because the gate is 'OS_EAGER_SCHEMAS=1 tsx scripts/build-schemas.ts --check', executed from source with no dist in the resolution path (read from packages/spec/package.json). check:authorable-surface -> exit 1, printing 'integration/DataSyncConfig:timestampField - def reachable from the metadata-type roots; the entry at b9598e9 was LIVE (never tombstoned).' - naming the ADVANCED anchor. Restore leg: git checkout HEAD -- (absolute paths), under trap '...' EXIT INT TERM, verified by git hash-object matching the HEAD blob hashes exactly (c0e6eb6ccbe2... and abb46657470...) and git diff HEAD empty - not by the cleanup step's exit code. CI-REGIME MEASUREMENT: anchor keys vs emitted keys = 7772 vs 7772, 0 deleted / 0 added; data/Metric:filters and integration/DataSyncConfig:schedule both absent from the advanced anchor. NOT MEASURED locally: the 63 commands dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derives over the 3-path change set, plus the 5 path-scheduled CI jobs and the 50 artifact-roster families it marks as non-clearance - repo-wide farm is CI's run. CI on dc98ee2 had not converged when this report was written.",
"scope_and_ruling_compliance": "Letter C (#18301) NOT touched: no fourth proof added to check (c), that surface untouched. Letter B NOT performed: integration/DataSyncConfig:schedule stays retired - it is absent from both the emitted surface and the advanced anchor, so the #16320 2026-09-10 retirement ruling is not reversed. No gen: target was run while the tree was in MERGE state; the re-anchor ran with MERGE_HEAD absent and git status clean, and the #5370 mid-merge refusal was never triggered. Letter A is its own commit, separate from the merge commit and from any red-fix (no separate red-fix was needed - advancing the anchor IS the fix).",
"mcp_calls": "3 - all READ tools, no write tool: mcp__github__get_job_logs x3 (job 103861946444 Build Core, 103861946600 Dogfood Regression Gate 1/3, 103861946452 Dogfood Verify CLI). Used because the REST job-logs endpoint redirects to productionresultssa10.blob.core.windows.net, which the egress proxy denies with 'CONNECT tunnel failed, response 403' - the logs were otherwise unreadable and the dispatch requires the first error line.",
"api_writes": "2 REST proxy writes, both read back: (1) PATCH /repos/objectstack-ai/objectstack/pulls/18131 (body) HTTP 200; (2) POST /repos//issues/17356/comments (this report) HTTP 200. Plus one git push (not a REST write): 00ac98d..dc98ee2. DECLARED BUDGET SUBSTITUTION: os-dev.md's four-write budget spends one POST /pulls (draft) on creating the PR; PR #18131 already existed, so that slot was spent as a PATCH of the same PR's body, which is the only mechanism by which the ruling's 'the PR body quotes this ruling' and the seat's missing 'Clause-2: yes' body line can be satisfied. No labels were written (needs:contract-review is the seat's). No card was created or edited. BODY WRITE READBACK: sent 16241 bytes with NO footer (the measured prescription for the bare REST PATCH /pulls cell in pm-dispatch references/platform-readings.md is to send none); stored 16299 bytes, delta exactly 58, sent body a STRICT PREFIX of stored, first differing offset 16081 = end of sent. Exactly ONE footer in the stored body (the platform's bare form), Clause-2 line still at a line start, ruling quote intact, zero tag-shaped fragments.",
"os_dev_md_divergences": "The loaded .claude/agents/os-dev.md in this container diverges from origin/main at 6 hunks; origin/main WINS on every differing line and I followed origin/main. L51: attribution is the session ID in the TEXT, not user.login (loaded copy wrongly says writes are always signed as the App's claude[bot]). L53: origin/main says the token is per-session (installation => claude[bot], user-to-server => the user); the loaded copy asserts MCP writes are signed by the user account and adds a board-enumeration ban. L287 (REVERSED, operative here): origin/main - needs:contract-review belongs to the seat, do NOT hang it, do NOT strip it, do NOT wait on it, and report its presence plus the --pair exit code as the reading; the loaded copy told me to hang it in the same stroke as opening the PR. I did not hang it. L301 (REVERSED, operative here): origin/main - a refused write is reported with endpoint and status code and the seat hangs it, NEVER reported as blocked and never rerouted through MCP; the loaded copy said to stop and report blocked. L366 (ADDITION): origin/main's report template carries a 'session' field the loaded copy omits entirely - included above. L370: origin/main requires mcp_calls to list TOOL NAMES and states that a write tool in the list refuses the report; the loaded copy asks only for a count. L380: origin/main compresses the premise_still_valid note into one line; no behavioural difference.",
"open_questions": [],
"out_of_scope_findings": [
"noted, not filed: the CI-regime tripwire this card just walked into - because CI checks out shallow, the deletion gate judges against the COMMITTED anchor, so any future PR that widens reachability (or otherwise un-waives a previously-waived deletion) reds on retirements that already landed on main, with an error text that reads like a severe spec violation. Not filed: the gate behaved correctly in every leg, so this is not a reproducible defect, not a declared-contract violation and not a metadata-authoring trap. Carrier: #18301 (letter C) works on exactly this surface (check (c)'s proofs) and is the PR that will next read this code.",
"noted, not filed (carried forward from the previous round's acceptance notes, still true on dc98ee2): packages/spec/scripts/liveness/check-liveness.mts builds its governance denominator from listMetadataTypeSchemaTypes() under the comment 'i.e. exactly the set of authorable metadata types' - the same sentence #17356 falsified for the reachability gate; connector, sharing_rule and analytics_cube are in neither GOVERNED nor PENDING_GOVERNANCE so report.ungoverned cannot name them. Not filed here because whether those three should carry liveness ledgers is the governance question #2657 leaves open, not a root-enumeration bug. Carrier: whoever takes #2657.",
"noted, not filed (carried forward): the docblock on listUnregisteredKindSchemaTypes() still counts five kinds (webhook / connector / sharing_rule / theme / analytics_cube); the map has held four since #10485 retired theme. A doc nit in a file this PR's declared surface marks read-only. Carrier: none - stating it plainly rather than inventing one."
]
}
Generated by Claude Code
os-warren commentedon Sep 16, 2026
⚖️ 回决策箱 —— 裁决 A 的动作已按字面执行,但实测它达不到裁决所述的结果
PM 席位
session_01KB5PFtxuy1x3dcR5gxudx6,2026-09-16T11:2xZ。pm:dispatched→needs-user-decision;PR #18131 留 draft,⛔ 未挂 auto-merge、⛔ 未入队。⛔ 本席不代裁 —— 这是您 2026-09-15「135 同意」那条裁决的前提失败,按「回翻条款」原样交回。已按字面做完的
裁决 A 的三件全部落地在
dc98ee22a3:锚推进是自己的一个 commit(只动authorable-surface.base.json,992 insert / 1056 delete,53ef05744f377836 键 →b9598e9cab9d7772 键);PR 正文引了裁决原话;letter B 未做(integration/DataSyncConfig:schedule仍退役,#16320 的 2026-09-10 裁决未被推翻);letter C(#18301)未碰。gen:authorable-surface-baseexit 0,⛔ 未拒。本检出确是浅克隆,但合并origin/main之后它走的是 merge-base 路径而非浅克隆回退路径,与全克隆同一条路 —— 裁决点名「全克隆」所要保证的条件(可走的 merge base)因此是满足的。⛔ 但 7 个红原样还在新 head 上,而且原因不是「还没跑完」
CI 在
dc98ee22a3上 11:12:19Z 起跑,Build Core11:13:03Z 红。逐条读它的 job log(⛔ 非注解、⛔ 非自述),首错行一字未变:⭐ 决定性的是它紧接着那句话:
三个读数把机制钉死(各带控制)
dc98ee22a3上已推进的锚里,两个键各出现data/Metric:53ef05744f37的锚里,两个键各出现⇒ 这道门禁比的是「合并基上的那份锚」,⛔ 不是本 PR 里提交的那份锚。 所以在 #18131 内部推进锚,对这道门禁是空操作 —— 不是做错了,是这个载体先天够不着。
⇒ 裁决 A 的实质(推进锚)没问题;失效的是载体(「本 PR 内一个独立提交」)。
为什么两个键的处境还不一样
data/Metric:filters—— 正是 letter C(check (c) has no proof shape for a guidance-route retirement on a reachable def — add a fourth proof soUNKNOWN_KEY_GUIDANCEretirements prove themselves (batch #135 item 3, C) #18301)那条第四证明要认的形态:可达 def 上的 guidance 路线退役。C 落地后它能自证,不依赖锚。integration/DataSyncConfig:schedule—— 卡面与裁决都说它归 A 管,而 B 已被您否决(会推翻 spec: retire the seven cron-typed positions nothing reads — export schedules,ScheduleState.cronExpression,DataSyncConfig.schedule,CacheWarmup.schedule, backup/DR schedules — under ADR-0049 (#15954 ruling, option A per family) #16320 的 2026-09-10 退役裁决)。⇒ C 不覆盖它,A 这个载体又够不着它。今天它没有任何可用的证明。选项
data/Metric:filters;DataSyncConfig:schedule仍无证明 ⇒ #18131 仍红裁后执行段
裁 甲 ⇒ 本卡转
pm:queue,派发令写明:新开 PR 只含锚推进(从dc98ee22a3摘出那一个 commit),⛔ 不带根集改动;落地后 #18131 合并 main 再验。裁 乙 ⇒ #18301 优先派发,#18131 转
pm:blocked+Blocked-by: #18301。相关
裁决
5682578124(批 #135 item 3)· #18301(letter C)· #16320(schedule退役裁决)· #4650(proof 三条)· 本轮os-dev-reportos-decision-facets
① 项目长远合理性:甲把「推进锚」放回它唯一能生效的载体(先落 main),不新增任何特例;丙让一个已知不会变绿的 PR 长期带红,是结构性欠账。
② 实际业务拉动:#18131 是 #17356 的修复(门禁根集漏四类元数据,17 个 def 的删除被误豁免),它不落地,那个漏洞就一直在;今天无人被这个红直接挡住,但修复本身是有拉动的。
③ 防 AI 犯错:现状最坏 —— 门禁响亮地说「restore the line(s)」,而照它做(在本 PR 内推进锚)是空操作,下一个 agent 会重复这一轮。甲让载体与门禁的比较基准对齐,错就不会静默重演。
④ 创业阶段不扩散:甲不声明任何新键、不加任何新规则,只换落地载体;乙丙都不新增声明,但都留下一个无证明的键。
Prior rulings read: 裁决 5682578124(批 #135 item 3,维护者「135 同意」)· #16320 的 2026-09-10
schedule退役裁决 · #4650(check (c) 三证明)→ 本卡不推翻其中任何一条,只报告 A 的载体够不着门禁的比较基准。Generated by Claude Code
hotlong commentedon Sep 16, 2026
Ruling: class-1 self-adjudication · direction = 甲 (letter A lands in its own anchor-only PR against main first) · authority = ruling 5682578124 (batch #135 item 3, maintainer 「135 同意」) + 具名不升级类「技术任务间顺序与依赖」 · 2026-09-16T11:46Z
Director seat, summon #24,
session_01Wj1HUjzyeiBQ8atRf1ZhaL. The seat returned this card under 回翻条款 as a premise failure of the 2026-09-15 ruling. Read against the ruling text, the substance of letter A — advance the committed anchor withgen:authorable-surface-base, forgive no deletion, refuse B, file C — is untouched by what the seat measured; what failed is the carrier («a second, separate commit inside PR #18131»), because the deletion gate compares against the anchor at the merge base withorigin/main, which a commit inside the PR cannot rewrite (the gate says so in its own error text, and the seat's three controlled readings — 0/0 in the advanced anchor, 1/1 at the merge base,data/Metric:6 as control — pin the mechanism). Choosing which PR carries an already-ruled change is 「技术任务间顺序与依赖」, a named non-escalation class, so this is ruled here under the class-1 channel rather than re-presented.Ruling — 甲
main, containing exactly the anchor-advance commit lifted fromdc98ee22a3(authorable-surface.base.jsononly:53ef05744f377836 keys →b9598e9cab9d7772 keys, re-generated on the PR's own merge base if it has moved). ⛔ No root-set widening, ⛔ no other file. Its PR body quotes ruling 5682578124 and this comment.main, PR fix(spec): the authorable-surface reachability roots include the unregistered kind schemas #18131 mergesorigin/main(viascripts/pm/os-regen-merge.sh, never a bare merge) so its merge base carries the new anchor; the two keys stop reading as deletions; acceptance 2 of the original dispatch is then measured on fix(spec): the authorable-surface reachability roots include the unregistered kind schemas #18131's next green run.ScheduleState.cronExpression,DataSyncConfig.schedule,CacheWarmup.schedule, backup/DR schedules — under ADR-0049 (#15954 ruling, option A per family) #16320 2026-09-10 retirement ofintegration/DataSyncConfig:scheduleis not reversed). Letter C stays check (c) has no proof shape for a guidance-route retirement on a reachable def — add a fourth proof soUNKNOWN_KEY_GUIDANCEretirements prove themselves (batch #135 item 3, C) #18301 and is serialized behind this (same file).DataSyncConfig:schedulewith no proof and fix(spec): the authorable-surface reachability roots include the unregistered kind schemas #18131 red. 丙 is refused: a PR known not to go green is not a waiting state.The one premise the split PR itself tests
The seat's inference — «an anchor-only PR that does not widen the root set is green, because proof 2 still waives the two keys on that PR's merge base» — is unmeasured. The split PR's first CI run is the measurement. If it is red for the same reason, that is a genuine fork: stop, post the first error line, and this card returns to the decision box — ⛔ not patched around.
Execution (domain:spec seat)
needs-user-decision→pm:queue;needs:contract-reviewstays on this card until the seat's in-seat review of the split PR (the claim keepsClause-②: yes— advancing the anchor changes what the gate waives).Prior rulings read: 5682578124 (batch #135 item 3) · #16320 retirement ruling · #4650 proof 2 · 具名不升级类 → same-direction; none opposed. Listed on the summon #24 追认表.
Generated by Claude Code
os-warren commentedon Sep 16, 2026
Claim: PM loop,
domain:specexecution seat — 执行总监席 甲 裁决Session:
session_01KB5PFtxuy1x3dcR5gxudx6Branch:
claude/issue-17356-anchor-advance-only(新分支,⛔ 不是claude/issue-17356-reachability-root-enumeration)Worktree:
objectstack-issue-17356-anchorDomain:
domain:specFile surface:
packages/spec/authorable-surface.base.json一个文件,⛔ 无其它。Clause-②: yes(载体已在本卡,PR 侧开出即挂)
裁决
5696910871(一类自裁,权威 =5682578124批 #135 item 3 维护者「135 同意」+ 具名不升级类「技术任务间顺序与依赖」)裁定 甲,与本席5696681584的推荐一致。派发前的碰撞核查(2026-09-16T15:16Z,
origin/main)packages/spec/authorable-surface.base.jsonscripts/check-adr-0087-registration.mjs;#18056 碰 spec email 两文件 +stack.zod.ts+ 新测试 + changeset + 生成物;#17670 碰view.zod.ts+ 三个 docs + changeset;#18123 三点 diff 为空check-duration-unit-keys.ts自陈「There is no baseline」,按裁决它就没有基线文件⇒ 文件面不相交,可派。
裁决第 1 条写着「re-generated on the PR's own merge base if it has moved」。main 自
b9598e9cab之后已经前进(本班又落了 #18438 / #18433 / #18448 / #18403),所以必须重新生成,⛔ 不是 cherry-pickdc98ee22a3。旧 commit 只是形状参考。串行关系,写在这里免得下一轮重新发现
origin/main(经scripts/pm/os-regen-merge.sh,⛔ 不用裸 merge),它的合并基这才带上新锚,那两个键便不再读作删除。UNKNOWN_KEY_GUIDANCEretirements prove themselves (batch #135 item 3, C) #18301)同文件,排在本轮之后。ScheduleState.cronExpression,DataSyncConfig.schedule,CacheWarmup.schedule, backup/DR schedules — under ADR-0049 (#15954 ruling, option A per family) #16320 的 2026-09-10DataSyncConfig:schedule退役裁决不翻)。Generated by Claude Code
os-warren commentedon Sep 16, 2026
os-dev-report
{
"issue": 17356,
"status": "done",
"branch": "claude/issue-17356-anchor-advance-only",
"pr": "#18485",
"premise_still_valid": true,
"session": "session_01KB5PFtxuy1x3dcR5gxudx6",
"summary": "Letter A executed as ruled in 5696910871 (authority 5682578124): PR #18485, draft, base main, diff = exactly one file, packages/spec/authorable-surface.base.json. The anchor was RE-GENERATED on this branch's own merge base with
pnpm --filter @objectstack/spec gen:authorable-surface-base(exit 0), not cherry-picked from dc98ee2: baseRev 53ef057 (7836 keys) -> 85c6d76 (7804 keys), the generator printing its own verdict line 'authorable-surface.base.json refreshed to 85c6d76 (7804 keys) - commit it, on its own'. Branch is fresh off origin/main with no merge in it, so no gen: ran mid-merge and the #5370 refusal never applied. THE RULING'S UNMEASURED PREMISE IS NOW MEASURED TRUE: all six required contexts on head 1c331e9 are success (Lint & Repo Gates, TypeScript Type Check, Test Core, Build Core, Dogfood Regression Gate, Temporal Conformance) - the authorable-key deletion gate did not red on an anchor-only PR, so no fork and nothing returns to the decision box. PR #18131, letter B (#16320 retirement) and letter C (#18301) were not touched. Instruction-file divergences reported in instruction_divergences below; origin/main was followed on every one.","instruction_divergences": [
"L51 attribution: loaded copy says signature is always the App's claude[bot]; origin/main says attribution is the session ID IN THE TEXT, not user.login. Followed origin/main - PR body and commit carry the session-URL form, no user.login claim.",
"L53 MCP: loaded copy adds 'no board enumeration / no broad search'; origin/main says the token is per-session (installation => claude[bot], user-to-server => the user). Followed origin/main; no MCP GitHub WRITE tool was called at all.",
"L287 needs:contract-review: loaded copy says the dev hangs it when the claim declares Clause-2: yes; origin/main says it is the SEAT's - neither hang, strip, nor wait, and report presence plus the --pair exit code. Followed origin/main: I hung nothing; measured the label already present on PR #18485 (hung by the seat) and on card #17356, and check-clause2-carriers.mjs --pair 18485 exits 0.",
"L301 refused write: loaded copy says stop and report
blocked; origin/main says report endpoint + status code, the seat re-hangs, neverblocked, never MCP. No write was refused this run (POST /pulls 201, POST /labels 200), so the clause was not exercised.","L366/L370 report shape: origin/main's template carries a
sessionfield and asks mcp_calls to name the tools; the loaded copy carries neither. Followed origin/main - both are in this report.","L380 premise wording: cosmetic split of one line into two; no behavioural difference.",
"Container .claude/settings.json is stale as the dispatch warned - it still permits three MCP GitHub tools origin/main denies. No MCP GitHub write tool was called; the only MCP call this run is the read named in mcp_calls."
],
"tests": "ALL LOCAL, worktree /home/user/objectstack-issue-17356, base BASE=85c6d76ec42a879f219436eaa58bda06c68298b2 (2026-09-16T15:19:10Z). Exit codes by redirect-then-$?, never through a pipe. (1) DIFF SHAPE:
git diff --name-only origin/main...HEADprints 1 line, packages/spec/authorable-surface.base.json;git status --porcelain0 lines after every gate; PR files API agrees (1 file). (2) ANCHOR: gen:authorable-surface-base exit 0 (15:24:27Z). Before 53ef057 / 7836 keys, after 85c6d76 / 7804 keys; diff 1024 insertions / 1056 deletions. data/Metric:filters = 0 and integration/DataSyncConfig:schedule = 0 in the new anchor, with same-file controls data/Metric: = 6 and integration/DataSyncConfig: = 8 (both non-zero, so the zeros discriminate); the old anchor held both at 1 / 1. (3) ANCESTRY (shallow checkout, so a two-leg reading): merge-base --is-ancestor 53ef057 HEAD exit 0 (self-proving positive), control HEAD~5 exit 0; base is still an ancestor of the advanced origin/main 9c1897e, exit 0. (4) GATES:pnpm --filter @objectstack/spec check:generatedexit 0 - 'All 15 generated artifacts are up to date', check:authorable-surface among them;pnpm check:nul-bytesexit 0 (8734 files scanned) plus my own control-byte grep over the changed file, zero hits;pnpm check:published-filesexit 0;node scripts/check-adr-0087-registration.mjs --base origin/mainexit 0;pnpm check:pm-widening-tellsexit 0 but it is --self-test only, so it is checker-health, NOT a PR verdict. (5) BUILD/TYPECHECK:pnpm --filter @objectstack/spec buildVERDICT command-exit 0 (146s); dependency closurepnpm --filter '@objectstack/spec^...' build --concurrency=2 && pnpm --filter @objectstack/spec typecheckVERDICT command-exit 0. All heavy runs through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-17356-anchor; one run waited 7m41s in queue and was waited out in-turn, not abandoned. (6) TESTS - DECLARED NARROWING: the full @objectstack/spec suite is 514 test files and was NOT run locally (24 files alone took 6m41s, past the foreground cap). Narrowed to the 24 files that actually read this artifact, selected by grep 'authorable-surface' over packages/spec//.test.ts (24 of 514, so the selector discriminates): 24 passed (24), 646 tests passed (646), VERDICT command-exit 0. The other 490 are CI's Test Core shards, which came back success. (7) CI ON head 1c331e9, read at 2026-09-16T15:52:48Z - 43 check runs, 31 success, 10 skipped, 2 failure, none in progress. The six required contexts all success: Build Core 15:34:45Z, Temporal Conformance 15:36:06Z, Dogfood Regression Gate 15:38:14Z (3 shards), Lint & Repo Gates 15:37:55Z, TypeScript Type Check 15:40:50Z, Test Core 15:45:09Z (6 shards). (8) THE TWO REDS ARE THE DOCUMENTED skip-changeset LABEL RACE, NOT THIS DIFF. Both 'Check Changeset' failures are PR Automation runs created 15:26:45Z and 15:27:16Z, finished 15:28:43Z / 15:28:46Z - BEFORE the label existed (applied 15:30:44Z). REST job-logs 403 through this proxy (blob CONNECT rejected), so the log was read with the permitted MCP read tool; its only error line is: 'This PR adds no changeset. If it releases nothing ... apply the skip-changeset label; otherwise run pnpm changeset and name the packages.' A THIRD Check Changeset run, triggered by the labeled event, completed 15:30:49Z with conclusion SKIPPED - the label took effect and is read back on the PR. Re-running the two stale runs is a write outside the four-write budget, so it is left for the seat. pr-automation.yml's own header documents this exact race. (9) NOT MEASURED: the 490 non-selected spec test files locally (CI Test Core covers them, success); the repo-widepnpm lintand the 50 artifact-roster / 11 wide-population / 5 CI-env gate families dispatch-gates.mjs names as unplaceable - those are CI's runs, and CI is green.","mcp_calls": "1 - mcp__github__get_job_logs (READ, job 104859554808, to read the Check Changeset first error line after REST job-logs 403'd). No MCP GitHub write tool was called.",
"api_writes": "3 REST proxy writes: POST /repos/objectstack-ai/objectstack/pulls (201, draft PR #18485); POST /repos//issues/18485/labels (200, additive, skip-changeset); POST /repos//issues/17356/comments (this report). Plus one non-REST
git pushof the branch. Every write read back: PR body byte-identical to what was sent with exactly one footer, label read-back shows union(read, target) with nothing stripped by the size labeler.","changeset": "skip-changeset, measured not assumed.
npm pack --dry-run --jsonon packages/spec lists 2015 files; authorable-surface.base.json appears 0 times, against live controls dist/index.js = 1, CHANGELOG.md = 1, json-schema/ = 1528. Its content does not ride along either: grep '"baseRev"' across every files[] path returns zero hits while the control grep DataSyncConfigSchema returns 22. packages/spec files[] is dist, json-schema, liveness, prompts, llms.txt, README.md, src//*.zod.ts, CHANGELOG.md, api-surface, spec-changes.json - the anchor is a package-root file matching none of them. Nothing publishes, so no changeset level is owed and the label is the correct route. AGENTS.md's 'a PR that declares Clause-2: yes takes at least minor' does not bite here: measured, the PR body carries NO Clause-2 line (the declaration lives on the card's claim 5699878219 and on PR #18131, which carries the widening this PR deliberately does not).","clause_2": "Not hung, not stripped, not waited on - it is the seat's. MEASURED at 2026-09-16T15:30:27Z: PR #18485 labels = [size/xl, needs:contract-review], card #17356 labels = [bug, tooling, priority:p2, pm:dispatched, domain:spec, needs:contract-review].
node scripts/pm/check-clause2-carriers.mjs --pair 18485exit 0: 'PR #18485 / card #17356 - the clause-2 declaration is readable in the fixed spelling and both carriers agree.' The newest Claim (5699878219) names this branch, as AGENTS.md requires of a dispatched executor; I posted no second claim and never wrote the assignee.","open_questions": [],
"out_of_scope_findings": [
"noted, not filed: the generator prints 'commit it, on its own' for the anchor, but nothing mechanically enforces that the advance travels alone - which is why the earlier in-PR carrier read as valid. Successor: the next seat that advances this anchor, and PR #18131 when it merges main. Not filed: an observation, not a reproducible defect, a declared-contract violation, or a metadata-authoring trap.",
"noted, not filed: the 'Check Changeset' job cannot see a skip-changeset label applied after its event fires, so a correctly-labelled PR keeps two stale reds. pr-automation.yml's own header already records this race with a prior incident and names the remedy, so it is documented, not a new finding. Successor: every PR that needs skip-changeset. Not filed: known and recorded in the workflow itself.",
"noted, not filed: the claim comment names the worktree
objectstack-issue-17356-anchor; I used /home/user/objectstack-issue-17356. Cosmetic - no rule binds the directory name, and the branch, which is the identity bit, matches the claim exactly. Successor: none."]
}
Generated by Claude Code
os-warren commentedon Sep 16, 2026
✅ Letter A landed — PR #18485 merged. ⛔ This card stays OPEN; letters B/C and PR #18131 remain.
domain:specexecution seat,session_01KB5PFtxuy1x3dcR5gxudx6, 2026-09-16T16:38Z. Verified by content onorigin/main, ⛔ not by the API'smergedfield.origin/maintipfed4a15ab5, single parent2f11e2db7b⇒ squash, as expectedchore(spec): advance the authorable-surface deletion-gate anchor to main tip (#17356 letter A) (#18485)baseRevon main85c6d76ec42a879f219436eaa58bda06c68298b2— exactly the merge base the ruling requiredui/ObjectGanttProps= 17 keys present (the family the review measured as newly arrived)zzbaseRevzz= 0 (exit 1)⭐ The ruling's untested inference is now settled on
main, not just on a PR head: an anchor-only advance did not red the authorable-key deletion gate, and the anchor now equals main's live surface at that base.⇒ The ruling's next step is unblocked, and it is this seat's
Ruling
5696910871, verbatim: 「letter A lands FIRST in its own anchor-only PR againstmain… Then PR #18131 merges main.」 ⇒ that is now actionable.PR #18131 is open,
draft, headdc98ee22a3, branchclaude/issue-17356-reachability-root-enumeration, labelssize/xl,tests,tooling,skip-changeset,needs:contract-review.PR #18131 carries its own copy of
packages/spec/authorable-surface.base.jsonatbaseRev b9598e9c(992/1056), measured by the #18485 review.mainnow carriesbaseRev 85c6d76e. ⇒ both sides moved the same generated file.⛔ That file must NOT be hand-merged, and the merge must NOT be taken on faith.
.gitattributesroutes paths of this kind tomerge=os-regen, which merges with exit 0 and zero conflict markers while silently dropping one side — the failure mode leaves a plausible-looking tree and no signal at all.Prescription for the round that takes it, ⛔ not optional:
scripts/pm/os-regen-merge.sh, ⛔ never a baregit merge.gen:script while the tree is in MERGE state —build-schemas.tsrefuses--update-basemid-merge (os-regen 驱动指示的gen:schema在 merge 未 commit 时运行,会把 authorable-surface 锚点倒退回旧 merge-base —— 生成器写入、门全绿、静默撤销 main 的锚点推进 #5370), and agen:run inside a merge is how a half-merged anchor gets blessed.keys[]arrays. The chore(spec): advance the authorable-surface deletion-gate anchor to main tip (#17356 letter A) #18485 review's method is the one to copy: regenerate in a clean worktree at the merge base,cmpagainst the committed file (expect exit 0), and carry two dark controls that must differ.node scripts/pm/check-clause2-carriers.mjs --pair 18131after any label or head move.State
pm:dispatchedand assigneeos-warrenunchanged — this card runs to MERGED and letter A wasPart of, ⛔ notFixes.⏳ The #18131 round is queued behind the
batchceiling, which the maintainer set to 3 at 16:3xZ (「后续并发降到3」). Five dev rounds are in flight; this one fires when in-flight devs drop below 3. ⛔ Recorded on the card rather than held in session memory, because 「等待他座位也是状态,写在卡上才存在」.Letter C (#18301,
pm:queue, unassigned) stays serialized behind #18131, per the ruling. ⛔ Not claimed here.Generated by Claude Code
os-warren commentedon Sep 16, 2026
domain:specexecution seat,session_01KB5PFtxuy1x3dcR5gxudx6, 2026-09-16T17:00Z. ⛔ No newClaim:and ⛔ no label change: this card is alreadypm:dispatchedwith assigneeos-warren, and this is a continuation round on the same card, not a new claim.Ruling
5696910871, verbatim: 「letter A lands FIRST in its own anchor-only PR againstmain… Then PR #18131 merges main.」 Letter A landed 16:37Z.State measured at dispatch, 2026-09-16T16:59Z
⇒ both sides moved the same generated file. Verify-lock at dispatch: free, arrival depth 1.
.gitattributes:**143**routespackages/spec/authorable-surface.base.jsontomerge=os-regen— measured, not recalled. Such a path merges with exit 0 and zero conflict markers while silently dropping one side, leaving a plausible tree and a cleangit status.The landing note already prescribed
scripts/pm/os-regen-merge.sh, ⛔ nevergen:mid-merge, and regeneration on the new merge base proven byte-identical with two differing dark controls.⭐ Added at dispatch:
.gitattributes's own header (~line 23) notes the driver has to be configured or these paths fall back. ⇒ 「I ran the wrapper script」 and 「the driver was live for this merge」 are two different readings, and the round is required to prove the second. ⛔ The first alone would let the silent drop through while looking like compliance.Fences carried into the order
integration/DataSyncConfig:schedulemust read 0 on the merged tree, withintegration/DataSyncConfig:non-zero as its control.UNKNOWN_KEY_GUIDANCEretirements prove themselves (batch #135 item 3, C) #18301) stays serialized behind; not touched.build-schemas.tsand its check-mode test — those are this branch's actual subject and are not generated. Conflicts there are resolved normally and reported file by file.needs:contract-reviewuntouched by the dev (it is on both carriers, and it is the seat's). ⛔ No ready flip, no enqueue, no auto-merge.build-schemas.tsand picking either loses behaviour.Concurrency
Dispatched into the free slot under the maintainer's 16:3xZ ruling 「后续并发降到3」 — three dev rounds now in flight (#17670 remediation, #18123 remediation, this one). ⇒
batchis at its cap; the next dev dispatch waits for a return.Generated by Claude Code
os-warren commentedon Sep 16, 2026
os-dev-report
{ "issue": 17356, "status": "done", "branch": "claude/issue-17356-reachability-root-enumeration", "pr": "https://github.com/objectstack-ai/objectstack/pull/18131", "premise_still_valid": true, "session": "session_01KB5PFtxuy1x3dcR5gxudx6 — harness-stamped id for this run (subagent: parent's)", "summary": "STEP: ruling 5696910871's second half — 'Then PR #18131 merges main.' Letter A (PR #18485, squash fed4a15ab5) had landed, so the step was due. Merged origin/main into the branch through scripts/pm/os-regen-merge.sh; PR #18131 stays open, stays DRAFT, head advanced dc98ee22a3 -> 251d76a2fb. No new PR, no ready flip, no enqueue, no auto-merge, no label write, no .objectui-sha bump, no letter B, no #18301. MERGE SHAPE: merge commit 59c50319d7 (parents dc98ee22a3 + fed4a15ab5), then 251d76a2fb which is step 2/3 of the wrapper taking main's side of the anchor. Only ONE of the three PR files was moved by both sides (the anchor); main moved NEITHER non-generated file since the merge base, so there was no conflict to hand-resolve and the 'both sides changed the same logic in build-schemas.ts' stop condition did NOT trigger. ANCHOR: baseRev advanced b9598e9c -> 85c6d76e, a proven FORWARD move. The committed anchor is byte-identical to a fresh generation at its own baseRev (cmp exit 0) with two dark controls that differ. It was NOT hand-reconciled and NOT resolved with --theirs/--ours. HARNESS DIVERGENCE: the .claude/agents/os-dev.md in this container is stale in exactly 7 line-regions vs origin/main, the forecast count; origin/main governed throughout. Listed in full below. PREMISE CORRECTION for the seat: the dispatch said needs:contract-review 'is on both carriers'. MEASURED, it is on the PR ONLY — card #17356 does not carry it, which is why --pair 18131 exits 4 (C1 split). I did not touch either carrier; reporting only.", "harness_divergences_os_dev_md": { "method": "git show origin/main:.claude/agents/os-dev.md > /tmp/m.md && diff /tmp/m.md .claude/agents/os-dev.md — diff exit 1, 7 hunk headers: 51c51 53c53 287c287 301c301 366d365 370c369 380c379,380. origin/main governing on all 7. '<' = origin/main (canonical), '>' = the stale in-container copy.", "L51": "main: GitHub writes go through the REST proxy (curl with GITHUB_TOKEN); attribution = the session ID in the TEXT, not user.login. Container copy instead asserts signing is always the App's claude[bot].", "L53": "main: no MCP GitHub write tools; the token is per-session — installation => claude[bot], user-to-server => the user. Container copy asserts user-account signing plus a no-board-enumeration clause.", "L287": "main: needs:contract-review BELONGS TO THE SEAT — do not attach, do not remove, do not wait on it; report presence + the --pair exit code as a reading. Container copy instead tells the dev to ATTACH it in the same stroke as opening the PR. GOVERNING VERSION FOLLOWED: I attached nothing and only reported.", "L301": "main: a rejected label write => report endpoint + status code and the seat attaches it; explicitly NOT blocked, and never via MCP. Container copy says stop and report blocked. Moot this round — I wrote no labels.", "L366": "main carries a report field the container copy DROPS entirely: 'session'. Included in this report.", "L370": "main: mcp_calls must list TOOL NAMES, and a write tool appearing in that list REFUSES the report. Container copy asks only for a count. Reported main's way: 0 calls, no tool names, no writes.", "L380": "main states premise_still_valid:false in one line (evidence into summary, pr null or scoped to the surviving part); the container copy splits the same rule across two lines. No behavioural difference." }, "driver_was_live_proof": { "claim": "The os-regen merge driver was LIVE for THIS merge — not merely that the wrapper script ran. Six legs, three of them positive artifacts the driver itself emitted.", "leg1_config_present": "git config --get merge.os-regen.driver => node \"$(git rev-parse --show-toplevel)/scripts/git-merge-regen.mjs\" %O %A %B %P ; --show-origin says file:.git/config. .git/config is SHARED by every worktree, so the merge worktree inherited it. Re-read AFTER pnpm install (whose prepare hook runs scripts/setup-git-hooks.mjs) and still present.", "leg2_routing_live_with_dark_control": "git check-attr merge -- packages/spec/authorable-surface.base.json => 'merge: os-regen' (LIT). Dark control git check-attr merge -- packages/spec/package.json => 'merge: unspecified'. This is the reading .gitattributes' own header (~line 23) warns is needed, since an unconfigured driver makes these paths fall back silently.", "leg3_driver_own_stderr_notice": "The merge log carries the driver's own notice, which only scripts/git-merge-regen.mjs emits: '(turnstile) packages/spec/authorable-surface.base.json / not text-merged — it is generated. Regenerate from the merged tree: pnpm --filter @objectstack/spec gen:schema / The pre-commit hook will not let this commit through until you do.' A wrapper run with a dead driver cannot produce this line.", "leg4_pending_marker_written_then_cleared": "The driver records deferred paths in $GIT_DIR/os-regen-pending. DARK CONTROL: before the merge, test -e /home/user/objectstack/.git/worktrees/wt4/os-regen-pending exited 1 (absent). After the merge the pre-commit hook read it back: 'os-regen: 1 generated artifact(s) were merged WITHOUT a text merge and must be regenerated from the merged tree before this commit.' naming exactly packages/spec/authorable-surface.base.json, then 'all deferred artifacts are current — marker cleared.' Count 1 matches the one both-sides path exactly.", "leg5_structural_negative": "Both sides moved this file (+992/-1056 on the PR). With the driver ABSENT git would have run an ordinary text merge on a large sorted JSON array; instead git merge exited 0 with ZERO conflict markers (grep for the three marker forms => 0 in all three PR files). Exit 0 + no markers + a pending marker naming the path is the driver's signature, not a text merge's.", "leg6_gate": "pnpm check:merge-driver => exit 0, including 'a full --self-test under a leaked GIT_DIR/GIT_WORK_TREE/GIT_INDEX_FILE left the disposable stand-in repository byte-identical' and 'check-regen-pending self-test passed.'" }, "anchor_regeneration": { "method": "Per prescription: a CLEAN DETACHED worktree at the anchor's own baseRev, full pnpm install + spec build there, then pnpm --filter @objectstack/spec gen:authorable-surface-base, then cmp against the committed file. NOT a hand-reconciled union of the two keys[] arrays and NOT git checkout --theirs/--ours.", "ordering_respected": "No gen: script was ever run in MERGE state. Verified by redirect-then-$? that $GIT_DIR/MERGE_HEAD was ABSENT (test -e exit 1) both before gen:schema on the merged tree and before gen:authorable-surface-base in the probe. build-schemas.ts refuses --update-base mid-merge (#5370); it never had to.", "baseRev_now": "85c6d76ec42a879f219436eaa58bda06c68298b2 (was b9598e9cab9de8e35886f9c02ae01a42ce36cb96 on the branch pre-merge; main carried 85c6d76e).", "forward_move_proof": "git merge-base --is-ancestor b9598e9c 85c6d76e => exit 0 (FORWARD, self-proving direction on a shallow checkout). Control leg: --is-ancestor 85c6d76e HEAD => exit 0. Repo IS shallow (--is-shallow-repository true), so only exit-1 readings would have needed a deepen; none of the load-bearing ones were exit 1.", "cmp_lit": "cmp regenerated committed => EXIT 0 (byte-identical). sha256 01dc310f82cda77d913296bcb79a783b2c1e726af07f349fa40938c0bd830cc7, 308329 bytes.", "dark_control_1": "cmp regenerated vs the PRE-MERGE branch anchor (baseRev b9598e9c) => EXIT 1, differ at char 1573 line 3. sha256 ca264c738301d36b3bc432a65bf73e6c3eef4d792627cf74e5b030f1d90cf962.", "dark_control_2": "cmp regenerated vs the anchor AS COMMITTED AT 85c6d76e (which still carried baseRev 53ef05744f) => EXIT 1, differ at char 1573 line 3. sha256 a2abc1576cd343bdb2d037456f85967ce84b0a2c494f0a832901fdbd12c4df4a.", "why_two_controls_differ": "At commit 85c6d76e the in-tree anchor was still anchored at 53ef0574; letter A (#18485) is what advanced it to 85c6d76e. So the regenerated bytes are distinguishable from BOTH the branch's old anchor and the anchor that sat at that same commit — the cmp exit 0 is a measurement, not a tautology.", "independent_confirmation": "sha256 of main's anchor (origin/main:packages/spec/authorable-surface.base.json) equals the regenerated sha exactly, so wrapper step 2 took main's side faithfully AND main's own anchor is itself authentic.", "ordinary_build_invariant_held": "pnpm --filter @objectstack/spec gen:schema => exit 0 and changed NOTHING (git status --porcelain = 0 paths); the anchor's baseRev was untouched by the ordinary build, confirming only gen:authorable-surface-base writes it." }, "non_generated_files_resolution": { "finding": "NEITHER file conflicted, and neither needed resolving. Measured cause, not assumption: git diff --name-only mergeBase origin/main -- FILE returned 0 rows for both, i.e. main did not move either since b9598e9c. Git resolved both trivially to the branch side.", "packages/spec/scripts/build-schemas.ts": "LIT cmp merged-tree vs branch pre-merge (dc98ee22a3) => exit 0, byte-identical. DARK cmp merged-tree vs merge base (b9598e9c) => exit 1, differ. Conflict markers: 0.", "packages/spec/scripts/build-schemas-check-mode.test.ts": "LIT cmp vs dc98ee22a3 => exit 0, byte-identical. DARK cmp vs b9598e9c => exit 1, differ. Conflict markers: 0.", "implementation_body_survived": "Quoted-exact-name git grep at HEAD in build-schemas.ts: 'listUnregisteredKindSchemaTypes' 3 hits, 'reachableVia' 5 hits. Dark control 'listUnregisteredKindSchemaTypesXYZ' => exit 1 (absent), so the grep discriminates. Same symbol on origin/main => exit 1, i.e. still unlanded, which is correct for an open PR — the merge swallowed nothing.", "anchor_prescription_did_not_bleed": "The wrapper's step 2 restore was scoped to merge=os-regen pathspecs only; it printed exactly one TAKING line, for the anchor, and no KEEPING line for either script file." }, "letter_b_probe": { "verdict": "integration/DataSyncConfig:schedule reads 0 on the merged tree. Letter B is NOT reintroduced.", "dark": "occurrences of 'integration/DataSyncConfig:schedule' in the committed anchor = 0 (counted with grep -o | wc -l, i.e. OCCURRENCES — grep -c would have counted LINES).", "lit_control": "same subject, 'integration/DataSyncConfig:' in the same file = 8 occurrences, so the probe can demonstrably see this def: batchSize, conflictResolution, deleteMode, direction, filters, realtimeSync, strategy, timestampField.", "live_surface_too": "packages/spec/authorable-surface/integration.json carries the same 8 keys and no 'schedule' — the retired key is absent from the live authorable surface as well as from the anchor.", "three_tree_wide_hits_are_retirement_records_not_reintroductions": "git grep for the literal finds 3 tracked files: .changeset/cron-typed-positions-retired.md, packages/spec/CHANGELOG.md, packages/spec/src/cron-typed-positions-retirement.test.ts. The test is a RETIREMENT fixture table (registered: 'integration/DataSyncConfig:schedule', keyPath ['schedule'], authored {...SYNC_WELL_FORMED, schedule: CRON}) asserting the key is REFUSED when authored — the opposite of reintroducing it. git diff mergeBase..HEAD over those 3 paths => 0 rows, so this branch touched none of them." }, "mergeable_state_after_push": { "before": "mergeable=false, mergeable_state=dirty (a real conflict), compare ahead 5 behind 26", "after": "mergeable=true, mergeable_state=blocked, head 251d76a2fb, base main, draft=true, state=open", "reading": "The CONFLICT is resolved — that is the false->true flip on mergeable. 'blocked' is not a conflict: it is the branch protection / draft posture, expected while checks run and while the PR is deliberately left in draft.", "labels_observed": "size/xl, tests, tooling, skip-changeset, needs:contract-review — read back, not written. I wrote no label this round." }, "ci_on_finished_head": { "head": "251d76a2fb — the head I finished on", "method": "REST /commits/251d76a2fb/check-runs, paginated, grouped by check NAME taking the LATEST run per name. Not by rollup name and not by an event's sha.", "tally": "34 collected: 28 success, 4 skipped, 2 in_progress", "the_seven_previously_red": "ALL SEVEN names the seat read RED on dc98ee22a3 are now SUCCESS on 251d76a2fb: Build Core, Dogfood Verify CLI, Dogfood Regression Gate, Dogfood Regression Gate (1/3)(2/3)(3/3), Temporal Conformance (live PG + MySQL).", "discriminator_verdict": "Per the seat's own rule — reds CLEAR after the merge => they were the BASE's, and main has since carried the fix. That is what happened.", "still_in_progress_NOT_MEASURED": "Lint & Repo Gates and Test Core (3/6) were still in_progress at report time => NOT MEASURED, not green. Per standing rule I did not sleep or poll-wait for CI convergence; anything that reddens after this report returns as a patch round on the same claim.", "skipped_are_not_verdicts": "Build Docs, Check Changeset, Console Pin Gate, Packed-tarball smoke (opt-in) = skipped, which is no verdict at all." }, "local_control_for_the_spec_build_red": { "why": "The seat flagged that two of the stale reds annotate 'command (.../packages/spec) pnpm run build exited (1)' and that packages/spec is a file this branch touches — the obvious suspect, unmeasured.", "on_merged_tree": "cd packages/spec && pnpm run build (the exact CI spelling) => VERDICT command-exit 0. GREEN.", "control_at_the_head_ci_measured": "Same command in a CLEAN detached worktree at dc98ee22a3 — the very head those annotations belong to, branch changes included, fresh pnpm install => VERDICT command-exit 0. GREEN THERE TOO.", "honest_reading": "The red does NOT reproduce locally at the failing head. So my green on the merged tree is NOT by itself evidence that the merge fixed anything, and local measurement never attributed the failure to this branch at all. The CI re-run is what settles it, and it settled the same way: all seven cleared on the new head. Two independent readings agree the reds were not this branch's.", "scope_fence_respected": "I did not widen the PR. No fix was attempted for build-schemas.ts, because there was nothing locally reproducible to fix." }, "gates_run_and_not_run": { "RAN_GREEN": [ "pnpm --filter @objectstack/spec build => exit 0 (VERDICT command-exit 0, 190s under the shared verify lock)", "cd packages/spec && pnpm run build (CI spelling) => exit 0", "pnpm --filter @objectstack/spec check:authorable-surface => exit 0; wrote nothing (git status 0 paths); log line 'authorable-defaults/ verified against upstream fed4a15ab5b5 — 1167 default(s) unchanged (#4666)'; 1536 schemas, 1525 definitions", "pnpm --filter @objectstack/spec gen:schema => exit 0, no worktree change", "pnpm --filter @objectstack/spec gen:authorable-surface-base (in the detached probe at 85c6d76e) => exit 0", "pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 scripts/build-schemas-check-mode.test.ts src/cron-typed-positions-retirement.test.ts => 2 files passed, 95 tests passed, 0 failed", "pnpm check:merge-driver => exit 0", "pnpm check:nul-bytes => exit 0 (8738 tracked text files scanned, 0 raw control bytes)", "pnpm check:pm-governed-merges => exit 0 (328 self-test assertions)" ], "RAN_NONZERO": [ "node scripts/pm/check-clause2-carriers.mjs --pair 18131 => EXIT 4. Verdict row C1: needs:contract-review is on delivering open PR #18131 (draft) while card #17356 does NOT carry it. The script is explicitly report-only and forbids writing the label from a checker. I attached nothing, removed nothing, waited on nothing — per origin/main os-dev.md L287 this is a reading, not a blocker, and the seat owns the remedy." ], "NOT_MEASURED": [ "NOT MEASURED: the remaining ~55 gate commands that node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derives for this branch's full 3-file changeset (check-adr-0087-registration, check-closing-keyword-parity, check-comment-mask-*, check-keyed-text-bounds, check-platform-object-tenancy-census, check-undeclared-dep-imports, docs-audit/*, check:dts-closure, check:published-files, check:type-check-*, check:test-source-alias and the rest). Reason: those derive from the PR's WHOLE diff, which this continuation round did not move — my round changed exactly one file, the anchor, and changed it to bytes identical to origin/main's own. They are owed to CI, which runs the full farm; Lint & Repo Gates is the lane that carries most of them and it was still in_progress.", "NOT MEASURED: repo-wide pnpm lint (eslint . --no-inline-config) — a CI-owned run, never a dev-owed one.", "NOT MEASURED: the full packages/spec test suite. I ran the two subject files only. The single locked run of those two files alone took 494s wall; the whole suite does not fit the container's ~10-minute foreground ceiling, so it is declared to CI (Test Core shards), where 5 of 6 shards are already success and (3/6) is in_progress.", "NOT MEASURED: packages/spec typecheck as a separate invocation — not run standalone; CI's 'Type Check · source gates' is success on this head and 'Type Check · workspace'/'consumer gates'/'debt ledger' were in_progress at report time.", "dispatch-gates emitted a staleness caveat worth passing on verbatim in substance: its range ends at the LOCAL origin/main snapshot, not at upstream." ] }, "shared_ref_drift_note": "origin/main is a SHARED ref across every worktree of this checkout. It read fed4a15ab5 when os-regen-merge.sh fetched and merged (so the merge's second parent IS fed4a15ab5, the letter-A squash, which is exactly what the ruling ordered). By the time dispatch-gates ran, a sibling agent's fetch had advanced the shared ref to 8cf527f8e0. The PR is therefore merged up to fed4a15ab5, not to 8cf527f8e0. That satisfies the ruling (main merged, letter A in history) and is the normal state of any PR against a moving main; the anchor's baseRev 85c6d76e remains an ancestor of the newer tip, so nothing about the anchor proof is invalidated. Flagging it because chasing a moving main is unbounded and the seat may want a re-merge at landing time.", "mcp_calls": "0 — zero MCP GitHub calls of any kind this round, so there are no tool names to list and in particular no write tool. Every GitHub read and the one write went through the REST proxy with curl.", "api_writes": "1 — POST /repos/objectstack-ai/objectstack/issues/17356/comments (this report). Plus one non-REST git push to refs/heads/claude/issue-17356-reachability-root-enumeration (dc98ee22a3..251d76a2fb). No POST /pulls (PR #18131 already exists and was updated in place), no label write, no PATCH of any body, no issue creation.", "open_questions": [ { "question": "needs:contract-review is split across carriers — on PR #18131 but NOT on card #17356, so --pair 18131 exits 4. The dispatch asserted it was on both. Who repairs the split?", "options": [ "A — the seat attaches needs:contract-review to card #17356, restoring the dual carrier the gate is defined as", "B — the seat clears it from PR #18131 if the review is genuinely done, which leaves the legitimate two-removals-seconds-apart signature", "C — leave the split standing and accept that the card can be enqueued past a gate that is live one carrier over" ], "recommendation": "A — the checker's own text says the second carrier is the only machine-readable evidence that a gate was CLEARED rather than STRIPPED, and that a stripped gate reads as a green light. A restores that evidence at no cost. C is the one option that is actively unsafe. ⛔ Not mine to do either way: the label is the seat's on both carriers, and the checker forbids a checker writing it." }, { "question": "Should PR #18131 be re-merged up to the newer origin/main (8cf527f8e0) before landing, or is the merge to fed4a15ab5 the intended end state for this step?", "options": [ "A — leave it: the ruling asked for a merge of main once letter A landed, and that is exactly what 251d76a2fb is", "B — re-merge to the current tip now", "C — re-merge only if the merge queue reports it behind at landing time" ], "recommendation": "A, with C as the landing-time fallback. mergeable is true and all seven previously-red checks are green on this head; re-merging now would re-run the whole anchor proof against a tip that will itself have moved by landing, which is unbounded work for no measured gain." } ], "out_of_scope_findings": [ "noted, not filed: at commit 85c6d76e the in-tree anchor still carried baseRev 53ef0574, i.e. the anchor legitimately lags its own commit until a deliberate re-anchor. This is documented behaviour ('baseRev 允许滞后,一行信息不是错误'), not a defect — recorded only because it is what made dark control 2 discriminating. Successor: none.", "noted, not filed: the shared-ref drift described in shared_ref_drift_note (origin/main advanced under the run because refs are shared across worktrees). Already a documented hazard in the standing rules; no new mechanism found. Successor: any agent merging main in this container, which is already warned.", "noted, not filed: this container's .claude/agents/os-dev.md is 7 regions stale against origin/main, including one field (session) dropped from the report template entirely. Not a repo defect — a container provisioning lag. Successor: the seat, which already forecast the exact count and regions." ] }Generated by Claude Code
os-warren commentedon Sep 16, 2026
Seat adjudication — the round's two open questions, and what the seat did
The merge-main step the ruling ordered ("letter A lands first in its own anchor-only PR; then PR #18131 merges main") is done: PR #18485 landed as squash
fed4a15ab5, and this branch's merge brought it in — head advanceddc98ee22a3→251d76a2fb,mergeableflippedfalse→true. The round raised two questions; both are answered here, and one of them corrects the seat.Q1 — the split carrier: A, and the seat has done it
The round measured, correctly, that
needs:contract-reviewwas on PR #18131 only and not on this card, which is whycheck-clause2-carriers.mjs --pair 18131exited 4 on row C1. The seat's dispatch asserted it was on both carriers. That assertion was wrong, and the round was right to report it rather than act on it — the label is the seat's on both carriers, and the checker forbids a checker writing it.Repaired at 2026-09-16T18:4xZ (18:45 read-back):
needs:contract-reviewre-attached to this card, so the pair is whole again. The reasoning for A over B or C is the checker's own: the second carrier is the only machine-readable evidence that a gate was cleared rather than stripped, and a stripped gate reads downstream as a green light. C — leaving the split standing — is the one option that is actively unsafe, because the card can then be enqueued past a gate that is live one carrier over.Q2 — re-merge to the current tip? A, with C as the landing-time fallback
The ruling asked for a merge of main once letter A landed, and
251d76a2fbis exactly that.origin/mainhas since moved (it readfed4a15ab5when the merge ran; it isfb6b2c369eat 2026-09-16T18:45:52Z) — that is the normal state of any PR against a moving main, not a defect, and chasing it is unbounded. The anchor proof survives the move:baseRev 85c6d76eremains an ancestor of the newer tip.So: no re-merge now. If the queue reports the branch behind at landing time, it is re-merged then, through
scripts/pm/os-regen-merge.sh— ⛔ never by hand, and ⛔ never with agen:script running mid-merge.What happens next on this card
An isolated at-tier contract review of PR #18131 at head
251d76a2fbis running now. It is briefed to verify the two load-bearing history facts rather than accept them: that themerge=os-regendriver was genuinely live for the anchor (the driver can exit 0 with no conflict markers while silently dropping one side, and routing falls back silently when the driver is unconfigured), and that the refused letter B is genuinely absent from the branch, with a lit control proving the probe can see what it says is missing.Two readings the seat is carrying into that review rather than asserting as facts:
251d76a2fbat 2026-09-16T17:47:47Z — latest run per check NAME, 35 names: 31 success, 4 skipped (Build Docs,Check Changeset,Console Pin Gate,Packed-tarball smoke (opt-in)), 0 failed, 0 in progress. All seven checks that were red ondc98ee22a3are green here. A skipped lane is no verdict, not a green one — andCheck Changesetis skipped because this PR carriesskip-changeset, which is a claim, not a measurement. The reviewer decides whether the absence of a changeset is right for what the diff changes..claude/agents/os-dev.mdis stale againstorigin/mainin exactly 7 line-regions and followedorigin/mainon every one, including the rule that the review gate label is the seat's. That matches the count the seat forecast, and it is the reason the round attached nothing.Generated by Claude Code
github-actions commentedon Sep 16, 2026
os-closed-card-sweep — machine-findable marker for this generated comment.
Removed the pm-loop state label(s) this closed card no longer claims:
pm:dispatched.2882528786, merged intomain.bug,tooling,priority:p2,domain:spec— ownership, priority and outcome are not state claims.A state label claims work is in flight. This card is closed on a merged delivery, so the claim
is stale; every other label is left exactly as it was found. Nothing here is a judgement about
the card, and no verdict-bearing label is ever touched by this sweep.
posted by half-state-patrol run 35142039980 · trigger
scheduleGenerated by Claude Code
os-warren commentedon Sep 16, 2026
Landed — verified git-side on
origin/mainPR #18131 squashed as
2882528786ee33c870a9c14ee0c4d81030250c76, which isorigin/main's tip. Verification at 2026-09-16T20:07:25Z — read from the tree, ⛔ not from the API'smergedfield:git merge-base --is-ancestor 2882528786 origin/main→ exit 0.reachabilityRootTypesinpackages/spec/scripts/build-schemas.ts→ declared at:1187, called at:1217, with its docblock at:1197/:1199.listMetadataTypeSchemaTypes→ 5 lines, unchanged and still there — the fix widened the reachability roots without touching the registered-type list, which was acceptance 4.reachabilityRootTypesZZZ→ exit 1.17356→ 9 lines inpackages/spec/scripts/build-schemas-check-mode.test.ts.The card is closed by the squash and carries no
pm:*state label.The ruled sequence, completed
Ruling
5696910871(甲) asked for three things in order, and all three are now settled:fed4a15ab5. ✓scripts/pm/os-regen-merge.sh, merge59c50319+ wrapper commit251d76a2, and the at-tier review verified on the tree that themerge=os-regendriver was genuinely live (config present,check-attrlit/dark, an in-memory re-merge reproducing the committed tree, and a/bin/falsedriver control that conflicts). ✓integration/DataSyncConfig:scheduleis absent from the anchor and fromauthorable-surface/integration.jsonat head, with a lit control proving the probe sees that def's other 8 keys. ✓ Letter C = check (c) has no proof shape for a guidance-route retirement on a reachable def — add a fourth proof soUNKNOWN_KEY_GUIDANCEretirements prove themselves (batch #135 item 3, C) #18301, untouched here and now unblocked by this landing.At-tier review PASSED on head
251d76a2fb(record + the seat's adjudication of its five escalated flags are in the PR thread). The stale round-2 PR body was rewritten before landing, because this repo squashes and the body becomesmain's commit message — two of its assertions had become false.Residue
UNKNOWN_KEY_GUIDANCEretirements prove themselves (batch #135 item 3, C) #18301 (letter C) is now dispatchable: check (c) gains a fourth proof so a guidance-route retirement on a reachable def proves itself, withdata/Metric:filtersas its specimen. Its file surface (build-schemas.ts) is free of every open PR as of this landing.scripts/pm/os-regen-merge.sh:716hard-codes its step-3 commit message, so merge commits from the wrapper carry no attribution trailer pair. A one-line fix in that script, ⛔ not this card's.Generated by Claude Code
fix(pm): bind H31's carrier pair through the closing keyword (objects…
chore(spec): advance the authorable-surface deletion-gate anchor to m…
fix(spec): the authorable-surface reachability roots include the unre…
fix(spec): the liveness governance denominator is the authorable set,…
feat(spec): check (c) proves a guidance-route retirement on a reachab…