refactor(spec)!: delete the seven cron-typed positions outright — no tombstone, no D2, no D3 (#16320, ADR-0049, the 直接删 ruling) - #17146
Conversation
…087 entries, pins Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
…e docs, fix cache carrier fixture Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
…tire-cron-typed-positions
📓 Docs Drift Check10 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 134 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 242836640091a8f74b03eed451c8a7d601fc63ac && git checkout 242836640091a8f74b03eed451c8a7d601fc63ac
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 143c715a99f5e663e331158a786367d7a1a41657 2095e4e918299c68601788247e1302fd63e55ef9 && git checkout -B drift-repro 143c715a99f5e663e331158a786367d7a1a41657 && git merge --no-ff 2095e4e918299c68601788247e1302fd63e55ef9
node scripts/docs-audit/affected-docs.mjs --json 143c715a99f5e663e331158a786367d7a1a41657 |
…tire-cron-typed-positions
…he two missed fixtures, harden pins Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
…tire-cron-typed-positions
…nector D3 twin (class pin `retired-key-migrate-sentence`) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
…tire-cron-typed-positions
…tire-cron-typed-positions
…tire-cron-typed-positions
⛔ Do not merge at this head —
|
| side | commit | what |
|---|---|---|
origin/main |
5f392f04c |
feat(spec): ADR-0112 error envelope gains a producer-side refusal declaration … (PR #17090, card #16335) |
| this branch | 0fe47980f |
wip(spec): move export constants below the module docblock, regenerate docs, … |
So the conditions for the silent drop are met, and a merge at this head can land main having quietly lost PR #17090's half of that generated file — with zero conflict markers and every gate green, which is the whole reason that script exists.
The exposure is exactly one file — measured, not estimated
branch files changed: 42 main files changed: 73
both-sides intersection: 1
content/docs/references/api/export.mdx ← and it is the os-regen one
Nothing else in this diff is contended. packages/spec/authorable-surface/*.json and the other four content/docs/references/** pages this branch regenerated are merge=os-regen too, but main has not touched them since the merge base, so step 2 of the script (take main's side only for os-regen paths the branch has not edited) has nothing to take there.
What the head needs before a merge
bash scripts/pm/os-regen-merge.sh, run inside this branch's worktree — steps 1–3 mechanically (merge origin/main, per-file side selection in the worktree only, commit the merge first), then the regen chain and the generated-artifact gates it prints as step 4, including its assertion that PR #17090's implementation body still exists on the merged tree by quoted-exact git grep against origin/main. ⛔ Not a rebase, ⛔ not a force-push, and ⛔ not gen:schema while the tree is still in MERGE state — the script's header records that the latter silently rolls the authorable-surface anchor back to the branch's fork point while staying authentic, so every gate passes over an undone advance.
This seat dispatches that round as soon as a dev slot frees — three are in flight against the maintainer's cap of 3. This comment exists so the blocker is on the PR rather than in a seat's memory: ⛔ not mergeable as-is, notwithstanding what the mergeability field says.
Card #16320 stays pm:dispatched and assigned until this PR merges.
Correction (2026-09-10T01:45Z) — one label, and one number that has since moved
⭐ Mislabelled sha, corrected above. The opening line called 4261fbc8 origin/main. It is the merge base, not main's tip at that time. Every load-bearing use of it in this comment was already as the merge base and is unaffected; only the opening label was wrong. Caught by the dev running the round, not by me.
main files changed: 73 is stale. origin/main advanced while this comment stood; re-measured during the round it reads 123. The reading that mattered did not move: the both-sides intersection is still exactly one path, and it is still the merge=os-regen one.
✅ Resolved — and the silent drop was REAL, measured rather than predicted
The regen-merge round landed: merge commit fb230b840, repair commit 4041edb9b, now the PR head.
⭐ The loss this comment predicted was confirmed empirically, not assumed away: the merge commit's export.mdx blob came out byte-identical to the pre-merge branch tip (d53a4ab90) and carried 0 of PR #17090's refusal rows — exit 0, no conflict markers, one side gone. Step 2 restored main's side and the regeneration re-derived this branch's cron tombstones on top; the repair commit lands the 6 recovered rows and nothing else, so the page now carries both intents. The survival assertion ran on 5 quoted-exact symbols with a control leg proving it can detect the drop (59 pre-repair vs 65 on main and post-repair).
⛔ Still not mergeable at this instant, for the ordinary reason: CI on 4041edb9b is mid-flight (read 01:43Z, newest run per check name: 12 success, 2 skipped, 17 in progress, 0 red). This comment's blocker is discharged; the head still owes a green run.
Generated by Claude Code
…tire-cron-typed-positions
The os-regen merge driver resolved content/docs/references/api/export.mdx with exit 0 and no conflict markers while silently dropping origin/main's side: the merge commit's blob was byte-identical to this branch's tip and carried 0 of PR #17090's `refusal` envelope rows. scripts/pm/os-regen-merge.sh step 2 restored main's side in the worktree, and `gen:schema && gen:docs` re-derived this branch's cron-retirement tombstones on top. The merged page now carries both intents. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
✅ The promised round is done — head is green again and this is safe to merge by hand
This is the round I committed to in What it found — and it is quieter than last time, for a measurable reason⭐ The both-sides Both survival assertions passed with control legs, which is what makes them assertions rather than hopes:
⛔ A premise in my own order was false, and it was the dangerous kindI wrote that The dev worked out why, and the correction matters more than the fact: that exit-1 shape is conditional on step 2 having taken main's side of at least one path. With an empty both-sides intersection, step 3 prints "(merge left no additional changes to commit)", makes no ordinary commit, and no pre-commit ⭐ The dev caught its own vacuous probe, which is the reason I trust the greenIts first survival probe for #17257 used a symbol paraphrased from the PR title instead of lifted from the tree. It read 0 on ⇒ ⛔ A survival assertion whose probe matches nothing is not a passing assertion; it is a broken instrument reporting silence. That is the same rule that has corrected me five times tonight, applied by a dev to its own work before anyone asked. The reading my order asked for and the report omitted — measured by this seatThe governed file's line ratchet (net ≤ 0, never more than +2): ⇒ Net 0. ⛔ The merge moved nothing there. The report did not carry this number, so I measured it rather than assume it — an omitted reading is not a satisfied one. Nothing about the PR's substance changedStill the same reviewed work with its at-tier PASS on record. The Generated by Claude Code |
维护者裁决已录:直接删,不做墓碑
席位在第一问后给出了相反的推荐(保留 connector 家族的 D2 与墓碑、只削三个够不着作者的家族),并明确写出了代价:「删掉 connector 那个 D2 ⇒ 今天能加载的 stack manifest 明天加载不了。这不是提示变差,是数据坏掉。」 维护者在看到该代价后重申「直接删」。⇒ 按字面执行,⛔ 席位不再复议。 裁决覆盖面(按字面,⛔ 不外推)本裁决只点名 #17146。⛔ 它不自动覆盖另外两张受管退役 PR(#17249 / #17298)——那两张各自的墓碑与账本行照现状,除非维护者另裁。 这张 PR 上要被删掉的东西(读自其 42 文件差异,2026-09-10T07:00Z)
⇒ 这张 PR 会从 42 个文件大幅收缩。按「创业阶段不扩散」那一轴,这是改善:每一个墓碑都是一份永久义务,而这次删掉的是十二份。
|
…tire-cron-typed-positions
…tombstone, no D2, no D3 Executes the maintainer ruling of 2026-09-10 on this PR: 直接删. The previous round retired the seven positions the tombstone way — `retiredKey()` at every site, seven `RETIRED_KEYS_BY_MAJOR[18]` entries, one D2 conversion for the connector family and five D3 semantic entries. The maintainer ruled that whole kit out on the reading that customers do not upgrade major by major in order, after the seat had recommended the opposite in writing and stated the cost. So the keys simply stop existing: - 7 `migrations/entries/retired-keys/18.*.ts` deleted - 5 `migrations/entries/semantic/18.*-retired.ts` deleted - `conversions/registry.ts`: `connector-sync-schedule-removed` (D2) deleted, and its id dropped from step 18's `conversionIds` - `migrations/registry.ts` regenerated — the generated regions collapse - the six schema files keep only the deletion half; no `retiredKey()` survives. Each site keeps a source comment recording what left, why nothing read it, and that `Job.schedule.expression` is the one cron slot the platform evaluates - `authorable-surface/`: the four rows that existed are deleted rather than marked `[RETIRED]`; the build's own #4650 proof 2 carries each of them (the def is not reachable from the 26 metadata-type roots) - the pin test is rewritten around what a bare deletion actually produces: the authored value is accepted and STRIPPED, and no ADR-0087 surface names any of the seven — with a lit and a dark control on every absence reading - the changeset is rewritten for the smaller change Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH Co-authored-by: Claude <noreply@anthropic.com>
`CACHE_WELL_FORMED` and `DR_PLAN_WELL_FORMED` are the ATTRIBUTION CONTROLS of every pin in this file — each site asserts that the same document without the deleted key still parses, so a fixture that does not parse turns the control into a second failure mode and the pin proves nothing. Retyping them from memory rather than carrying them across produced exactly that: `DistributedCacheConfig` needs `tiers` and `invalidation`, and `DisasterRecoveryPlan` takes no `name`. Both are restored to the bytes they carried before, and the suite is green. Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH Co-authored-by: Claude <noreply@anthropic.com>
一条只有合并人需要知道的门禁事实(与裁决无关,不复议)这张 PR 之所以能让
这一条实测为假。 同一批模块上的探针( 已按发现单独立卡:#17356(未指派,不带 对这张 PR 的意义,一句话:删除本身是维护者裁决,证明 2 的真假不改变裁决;它改变的只是「这张 PR 的绿灯里有多少是门禁真的看过的」。 这四行删除若按证明 2 的字面失效来判,该走的路是「墓碑」,而墓碑正是裁决删掉的东西 —— 所以这里没有第三条路可选,只有一件事该做:合并人知情。 ⛔ 本卡不在本 PR 内修,本 PR 不因它改变任何一行。 Generated by Claude Code |
Seat disposition on the 直接删 rework — head
|
Contract review of head
|
| claim | verdict | |
|---|---|---|
| the seat's ruling record | "a manifest that loads today fails to load" | false — computed for the tombstone route, not the ruled one |
the round's ## 维护者速读 |
"silently dropped, no error, no warning, no greppable trace" | true at the parse layer, false end-to-end — the CLI speaks |
| this review | parse strips silently; os validate/os build name the key; --strict refuses; os migrate meta lists nothing |
the measured one |
⭐ Worth recording as a shape: two independent, careful readings both under-measured the same fact, in opposite directions, because each stopped at the first layer that answered. The seat stopped at the schema; the round stopped at the parse.
Also to fix — the clause-② characterisation is incomplete: seven widenings, not two
The reviewer enumerated all seven, and three are required-key removals rather than one — the export pair's requiredness left with the key, and api/ScheduledExport:schedule.cronExpression is already pinned in this PR's own test at :243-244 while going unnamed in the body and changeset. Clause-②: yes stands and is if anything stronger.
Verified, and not to be re-opened
- Claim 1 — non-strict, stripped: PARTIALLY true. All five schema files: 0 hits for
.strict(|strictObject|.passthrough(|.catchall(. Probe at head:DataSyncConfigSchema.safeParse({schedule: X})accepts and strips for a cron string, a{dialect:'cel'}envelope, and the number42. Other doors checked and ruled out:DeclarativeConnectorEntrySchema'ssuperRefinenever touchessyncConfig;ObjectStackDefinitionSchemaisstrictObjectat the top level only;/meta/connectorbinds the same entry schema;saveMetaItemadds no schema door. The CLI lint is the door that speaks — M1. - Claim 3 — no reader: TRUE. Sweep excluding tests/fixtures/CHANGELOGs:
syncConfig0,CacheWarmup0,BackupConfig0,DisasterRecoveryPlan0, the export pair 0,ScheduleState0.cronExpressionreads 4 — all the report-schedule API, a different position not among the seven. Lit control (the croner path inservice-job/trigger-schedule) = 6 lines; dark control = 0. ⭐ And the reviewer discarded its own first sweep because that one's lit control read 0 — an invalid pathspec. Exactly right. - Claim 4 — the merge preserved both sides: TRUE, independently reproduced. 19
merge=os-regenlines = 18 real patterns + 1 comment (the round's 18 is correct). Branch touched 8 routed, main 14, both 0. Raw both-touched set is 2 files (migrations/registry.ts,shared/expression.zod.ts), neither routed. And the decisive one:git merge-tree --write-treeof the two parents → exit 0, no conflicts, andgit diff <tree> 3a881f3b6c= 0 files. ⭐ The mechanical 3-way reproduces the commit exactly, so nothing was dropped on either side. On a branch that has already lost a side silently once, that is the reading that closes it. - Claim 5 — ADR-0087: gate satisfied (re-run: 338 self-test assertions, exit 0), substance correct. ADR-0087
:596refusesno-migration-prescriptiononly when the body carries a migration prescription; the "What to write instead" block is forward guidance, not a FROM→TO rewrite.⚠️ But its premise sentence "refuses nothing" is false per M1 (--strictrefuses) — reword with the rest. The conclusion holds only because the ruling forbids registering an entry, and the reviewer notes the house rule this ruling overrides is explicit in the retirement skill (「永不从非 strict schema 上裸删一个键」) — recorded, ⛔ not relitigated. - Claim 6 — [finding] the authorable-key deletion gate's reachability BFS reports
integration/DataSyncConfigunreachable from the metadata-type roots — it is two hops from theconnectorroot, so #4650 proof 2 waives a tombstone for a key a stack manifest really authors #17356: TRUE, and the reviewer named the root cause the card left open. UnderOS_EAGER_SCHEMAS=1, BFS fromgetMetadataTypeSchema('connector')does reachDataSyncConfigSchema(438-node closure). From "all roots" it does not — becauselistMetadataTypeSchemaTypes()returns 26 types and deliberately excludesUNREGISTERED_KIND_SCHEMAS(connector/webhook/sharing_rule, per meta: bind Zod schemas for webhook / connector / sharing_rule WITHOUT registering the kinds — close the unvalidatedPUT /metawrite hole (#2657 audit, option A) #6245). The gate's docblock claims it covers "the schemas a metadata document is actually parsed against", but/meta/connectorandstack.connectors[]parse against an unenumerated root. ⇒ root enumeration, not an edge or def-key-map bug. Being added to [finding] the authorable-key deletion gate's reachability BFS reportsintegration/DataSyncConfigunreachable from the metadata-type roots — it is two hops from theconnectorroot, so #4650 proof 2 waives a tombstone for a key a stack manifest really authors #17356. Impact here: the proof-2 waiver is a false waiver, but the outcome (no tombstone) is what was ruled anyway, so no assertion in this PR is undermined.
Advisory
A2 — contracts/export-service.ts drops the required cronExpression: string member from ScheduleExportInput, a published runtime TS interface on IExportService. The changeset's "no def leaves the public surface" is true but this member's departure is unmentioned. No consumer outside packages/spec. Being named.
A3 — the regenerated content/docs/references/integration/connector.mdx carries generator drift unrelated to this PR (strategy?: → strategy: on defaulted keys); main's copy was stale relative to its generator and check:docs is green at head. ⛔ Left alone; noted so nobody reads it as a schema change.
What both the seat and the round missed
The CLI unknown-key lint · three required-key removals rather than one · the #17356 root cause · the ScheduleExportInput member.
Next
A must-fix round is dispatched: reword the four M1 sites, name all seven widenings, name A2, note A3 in one line, and add the root cause to #17356. ⛔ No code change — the deletions, the merge and the disposition all stand. needs:contract-review stays on until the seat clears it against the corrected head.
Generated by Claude Code
…es, and re-count clause ② as seven A CONTRACT_REVIEW_TIER review measured this branch's head and found the cost statement the maintainer signs off on by merging to be factually wrong. Four sites said the strip is silent. It is not: `lintUnknownAuthoringKeys` (#3786) walks every stack collection whose entry schema is strip-mode, and `connectors` is one, so for `connectors[].syncConfig.schedule` — the one deleted position a stack manifest reaches — `os validate` and `os build` NAME the dropped key and `os validate --strict` EXITS 1 on it. Measured at head on an otherwise-clean stack: without the key 0 warnings / exit 0, with it 1 warning / exit 1. The truth is more favourable to authors than what the maintainer was told, but it is not the sentence they were asked to confirm. Corrected at all four sites: the changeset, `migrations/registry.ts`'s step-18 rationale, two comments in `cron-typed-positions-retirement.test.ts`, and the PR body's 维护者速读. The accurate statement is per channel: the PARSE strips silently, `os validate` / `os build` name the key, `--strict` refuses, `os migrate meta` still lists nothing. Same round, two more corrections and no behaviour change: * Clause ② is SEVEN widenings, not two, and THREE of them are required-key removals (`ScheduleState.cronExpression`, `ScheduledExport.schedule.cronExpression`, `ScheduleExportRequest.schedule.cronExpression`). Measured on both legs: the five schema files restored to the merge-base on disk, one probe run, restored to HEAD and re-run, with a byte-identity check after the restore. 14 cases, 14 refusals on the base leg, 14 accept-and-strip on the head leg. * The changeset now names the departure of the required `cronExpression: string` member from `ScheduleExportInput` — a published TS interface on `IExportService`. "No def leaves the public surface" is true and does not cover a member. Sweep: zero consumers outside `packages/spec`. ⛔ No behaviour changed: the seven deletions, the merge and the ADR-0087 disposition are untouched, and the 直接删 ruling is not re-litigated. Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH Co-authored-by: Claude <noreply@anthropic.com>
ACCEPT — must-fix round, head
|
Fixes #16320
Clause-②: yes
Reworked on the maintainer ruling of 2026-09-10 recorded on this PR (comment 5614475165): 直接删. The previous generation retired the seven cron-typed positions the tombstone way —
retiredKey()at every site, sevenRETIRED_KEYS_BY_MAJOR[18]entries, one D2 conversion for the connector family, five D3 semantic entries. The maintainer ruled that whole kit out, after the seat had recommended the opposite in writing and stated the cost. The kit is deleted; the keys simply stop existing. 42 files became 29.本轮(合约复审 must-fix)只改措辞,不改一行行为代码。 复审在这条 PR 的 head 上实测出:四处文本把这次删除的代价写成「静默」,而它不是——
os validate/os build会点名被丢掉的键,os validate --strict会直接退出 1 拒收。维护者是靠「按下合并键」来签这段代价陈述的,所以那四处被更正:PR 正文的〈维护者速读〉、changeset、migrations/registry.ts的 step-18 rationale、cron-typed-positions-retirement.test.ts的两处注释。同轮把条款②的放宽从「两处」更正为七处(其中三处是必填键消失),并在 changeset 里点名ScheduleExportInput.cronExpression这个已发布 TS 成员的离场。⛔ 七处删除、合并与 ADR-0087 处置本身不重开。The diff touches
skills/objectstack-formula/SKILL.md⇒ GOVERNED: draft only, a human merges. Never ready, never queued, never auto-merged, never approved by a seat.维护者速读(草稿)
改了什么。 七个从来没有引擎读过的 cron 位置被直接删除(
api/export.zod.ts×2、automation/execution.zod.ts、integration/connector.zod.ts、system/cache.zod.ts、system/disaster-recovery.zod.ts×2)。没有retiredKey()墓碑,没有RETIRED_KEYS_BY_MAJOR[18]条目,没有 D2 conversion,没有 D3 semantic 条目,protocol-18 迁移链里不留任何一行。authorable-surface/里原有的四行随键一起删掉。为什么改。 执行维护者 2026-09-10 的裁决「直接删」,理由是「我们的客户也不会按照你的设想的版本按顺序升级」。⛔ 本轮不复议该裁决。
风险与代价(含回滚)。 请在按下合并键之前看这一段。
DataSyncConfig.schedule是七个位置里唯一从 stack manifest 够得着的:stack.zod.tsconnectors[]→connector.zod.tssyncConfig→schedule。1504dd1d77上实测):DataSyncConfigSchema不是.strict(),zod 丢掉该键并返回success: true——ADR-0104 命名的形态。这一层确实不告诉任何人。os validate:退出码 0,但点名这个键。 实测输出:⚠ connectors.sap_erp.syncConfig.schedule: 'schedule' is not a declared connector key, so its value is dropped at load.(--json的warnings里是同一句)。对照组(同一份 manifest 去掉该键)少的正是这一行。os validate --strict:退出码 1,拒收。 在一份其余全干净的 stack 上实测:不带该键 = 0 条警告、exit 0;带该键 = 1 条警告、exit 1。⇒ CI 里跑--strict的流水线会直接拒绝升级后的 manifest,直到作者删掉这个键。os build:同样点名,印在Undeclared authoring keys (1) — dropped at load (#3786)之下。os migrate meta:什么都不列(实测--from 16与--from 17两次,输出里没有这七个位置的任何一条)。这一条是「直接删」这条路线真正放弃的东西:没有升级处方。lintUnknownAuthoringKeys(排查「手抄 spec 清单 + "keep in sync" 注释」模式:一天内确认三例,全部曾静默漂移 #3786)遍历每一个 entry schema 为 strip 模式的 stack 集合,connectors: 'connector'就在那张表里。所以七个位置中唯一从 manifest 够得着的那个,恰好落在这条会说话的通道上;另外六个 manifest 根本够不着,对它们来说 parse 层的静默剥除就是全部。--strict下直接拒绝」。对作者的可见性比上一版正文所说的更高;比裁决评论当时假设的(「墓碑负责响亮拒绝」)低一档,但并没有低到无声。抵消的一点:这个 cron 从来就没有引擎执行过,所以运行时行为没有任何变化——该值删除前后同样惰性。tsc是第五条通道:用Connector等类型标注的 TypeScript 作者会在那个键上拿到编译错误。88aeee32bf、1504dd1d77),b16c221a89是墓碑版的最后一个提交。回到墓碑路线 = 回退这两个 commit,不需要重做任何东西。席位意见。(留空,归维护者)
你要做的。 只有一件:确认上面那份四通道的实测代价可接受——parse 层静默丢弃、
os validate/os build点名该键、os validate --strict退出 1 拒收、os migrate meta无处方——然后手工合并。⛔ 不要入队,⛔ 不要挂 auto-merge——skills/**在差异里。删掉了什么(逐项,对照裁决表)
migrations/entries/retired-keys/18.*.tsmigrations/entries/semantic/18.*-retired.tsmigrations/registry.ts的生成区域gen:migration-registry重生成,净 −346 行;step18.conversionIds手工去掉connector-sync-schedule-removedconversions/registry.ts的 D2retiredKeycron-typed-positions-retirement.test.ts.changeset/cron-typed-positions-retired.md每个删除点在源码里留一条注释,记下删了什么、为什么从来没人读它、真正生效的机制是
Job.schedule.expression。前提核验 —— 裁决表逐行按本分支的 head 复核
裁决表是 2026-09-10T07:00Z 从 42 文件差异读出来的。逐行复核(基点
c47edbb217,墓碑版 tipb16c221a89):七个 retired-key 条目文件、五个 semantic 条目文件、D2 条目、六个 schema 文件的删键+墓碑对、516 行的 pin 测试、136 行的 changeset —— 全部原样存在,没有一行在裁决之后被别人动过。migrations/registry.ts当时读作 +351/−1,合并origin/main之后读作 +352/−1(一行来自 main 的无关条目),不影响任何一步。一处实测证伪:裁决评论把「删掉 D2 ⇒ manifest 加载失败」当作既定代价。那句话在墓碑保留的前提下为真;在「直接删」下为假——但也不是静默。实测:parse 层丢弃,
os validate/os build点名该键,os validate --strict退出 1。上一版正文把它写成「没有任何可 grep 的痕迹」,那是本轮更正的对象。详见上面的〈风险与代价〉。这一项按证据报告,⛔ 不作为复议。合并
origin/main—— 驱动路径两侧都没丢分支落后 15 个 commit,用
bash scripts/pm/os-regen-merge.sh合入(merge commit3a881f3b6c,脚本 exit 0)。风险清单从.gitattributes现读,不从上一次事故推:18 条merge=os-regen模式。git diff TOMBSTONE_TIP MERGE_COMMIT -- PATH空;main 独有的 14 条,git diff MAIN_TIP MERGE_COMMIT -- PATH空。packages/spec/authorable-surface/system.json在基点→分支上读到 2+/2−(非零,证明这个读数器看得见改动)。熄灭对照:虚构路径packages/spec/authorable-surface/zzz-not-a-real-category.json在同一读法下读到 0 行、且不在 HEAD 里(证明 0 是真的 0,不是读数器坏了)。$GIT_DIR/os-regen-pending不存在,与「驱动从未被调用」一致。门禁 —— 跑了什么,读数是什么
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack在本轮 head 上推出 114 条命令(脚本自报 STALE TREE:该树至少落后origin/main4 个提交,14 个它据以推导的文件在那段区间里变过 ⇒ 「只多不少」的保证在此不成立,增量归 PM 在 CI 上读)。本轮差异只有三个文件(changeset、migrations/registry.ts、pin 测试),全部是措辞。下面这些在本轮最终 commit2095e4e918上跑过,其余留给 CI。pnpm --filter @objectstack/spec buildcheck-dts-emitted: @objectstack/spec - 34/34 declared declaration file(s) present.pnpm --filter @objectstack/spec check:generated✓ All 15 generated artifacts are up to date.(⇒registry.ts的 step-18 rationale 是手工维护的文本,不在生成区域)pnpm --filter @objectstack/spec check:spec-changesspec-changes.json is up to date.pnpm --filter @objectstack/spec exec vitest run src/cron-typed-positions-retirement.test.ts src/migrationsTest Files 2 passed (2)·Tests 136 passed (136)pnpm --filter @objectstack/spec typecheckpnpm --filter @objectstack/spec check:test-typecheckOK — test layer compiles; 54 files / 261 errors / 145 pinned signatures(与上一轮同数,未动棘轮)node scripts/check-adr-0087-registration.mjs --base origin/main✓ 1 declared-breaking changeset(s), each carrying an ADR-0087 disposition.—— 提交之后跑的(该门禁从 git 读 changeset)node scripts/check-adr-0087-registration.mjs --self-test338 assertions over real temp git reposnode scripts/check-changeset-no-major.mjs --base origin/main✓ This diff introduces no major bump.node scripts/check-empty-changeset.mjs --base origin/main✓ No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added).pnpm check:changeset-gate-self-testspnpm check:nul-bytesOK (scanned 8172 text file(s); no raw ASCII control bytes);另对三个改动文件单独跑了grep -naP控制字符扫描,0 命中node scripts/check-skills-token-ratchet.mjs✓ 36 authored bundle file(s) within their ceilings(本轮未触skills/**)node scripts/check-spec-docblock-symbol-anchors.mjspnpm check:doc-authoring46 published skill files clean·15184 customer-facing string(s) across 878 spec sources cleanpnpm check:pm-governed-merges·pnpm check:pm-governed-prosenpx eslint . --no-inline-config --format json2095e4e918上跑的)一件维护者应当知道的门禁事实
check:adr-0087-registration与 AGENTS.md 的「breaking changeset 必须带 FROM → TO 映射」在这条路线下互相矛盾,实测两次:## FROM → TO标题,该门禁拒绝not-required (no-migration-prescription)(实测 exit 1,证据行from-to-label),要求改为registered ENTRY_ID—— 而裁决恰恰规定一个条目都不注册。type-surface-only这条豁免被它自己的no-spec-diff谓词挡住(本 diff 就在packages/spec里)。本 PR 走的是通过那一条,并且在 changeset 正文里明写为什么没有 before/after 映射:直接删在非 strict schema 上既不拒绝也不转换,元数据升级者无事可做,
os migrate meta无事可列 —— 这句话是真的,不是为了绕门禁。差别在于:门禁的机械判据读的是标题里的那个字符串,而不是那段话的意思。这一点作为发现报给 PM,不在本 PR 内修。Clause ② —— 重判:仍然是
yes,放宽是七处,不是上一版说的两处判据(
lanes/spec.md):放宽接受集或扩大公开面即条款②,收窄不触。上一版正文只点了两处,漏了五处;其中三处是必填键的消失,不是一处。两条腿都实测,不是从 diff 推的。 把 5 个 schema 文件按 merge-base(
cca1dc0bfb1e)整文件还原到磁盘,用同一个探针跑一遍(base 腿),再还原成 head 重跑(head 腿);还原后逐文件git hash-object与HEAD:PATHblob 比对,5/5 相同,git diff HEAD为空。两腿各带 5 条阳性对照(well-formed fixture 在两腿都success=true)。automation/ScheduleState:cronExpressioninvalid_union@cronExpression拒绝cronExpression: 42invalid_union@cronExpression拒绝api/ScheduledExport:schedule.cronExpressionschedule: {}invalid_union@schedule.cronExpression拒绝api/ScheduleExportRequest:schedule.cronExpressionschedule: {}invalid_union@schedule.cronExpression拒绝integration/DataSyncConfig:schedule{dialect:'template',source:…}invalid_union@schedule拒绝schedule: 42invalid_union@schedule拒绝system/CacheWarmup:schedulesystem/BackupConfig:schedulesystem/DisasterRecoveryPlan:testing.schedule12@testing.schedule)14 个用例,base 腿 14 次
success=false,head 腿 14 次success=true且键在 parse 后一律不存在。⇒ 七处全部是放宽,Clause-②: yes成立。公开面另有一处收窄需要点名(A2):
contracts/export-service.ts的ScheduleExportInput删掉了必填成员cronExpression: string,那是IExportService.scheduleExport的入参类型,一个已发布的 TS 接口(两个名字都在api-surface/contracts.json里)。上一版 changeset 只写了「no def leaves the public surface」——那句话为真,但盖不住这个成员。本轮已在 changeset 里点名。清扫读数:packages/spec之外本仓 0 处引用ScheduleExportInput/IExportService(阳性对照:同一次清扫里同类符号在packages/spec之外有命中),objectui 0 处(对照:该仓 1326 个文件 import@objectstack/spec)。cloud仓不在本容器的检出里 ⇒ 该仓 NOT MEASURED。pin 测试固定七处的 head 腿行为。
needs:contract-review标签仍在 PR 上,⛔ 本轮不摘。skills/**读数(受管面,净减)skills/objectstack-formula/SKILL.md行数check:skills-token-ratchet的计数,天花板 6002 未动)skills/**全部SKILL.md行数之和)改动是一处表格单元格的删减(
cron方言的 Carriers 列去掉三个已退役的载体,保留Job.schedule.expression),不是扩写。生成文档的一行说明(不作处理)
重新生成的
content/docs/references/integration/connector.mdx里带着几行与本 PR 无关的生成器漂移(有默认值的键从strategy?:变成strategy:):main上那份副本相对它自己的生成器是陈旧的,本 PR 只是跑了一次生成器把它对齐。check:docs在 head 上是绿的。⛔ 本 PR 不处理它,记在这里只是为了别把它读成 schema 变更。一致性
本裁决按其字面只覆盖这张 PR。⛔ 本 PR 不触碰、不对齐、也不建议对齐任何其他退役 PR。
本轮由 Claude Code 会话
session_01MkQhmuuJAVDjmeWNixwDDH产出。署名以正文散文形式记在这里,而不是尾部页脚:实测本仓的裸 RESTPATCH /pulls在写 PR 正文时无条件追加一条裸署名页脚 —— 同一通道两向受控实测,送出体带前置横线的页脚块与不带横线的单行页脚,两次都被追加,结果都是两条。所以正文自身不再携带页脚,页面上那一条是平台追加的,恰好一条。(AGENTS.md〈GitHub mutates body BYTES〉:耐久归属写进正文散文或评论,⛔ 不循环重贴页脚。)Generated by Claude Code