Skip to content

feat(spec)!: retire the seven cron-typed positions nothing evaluated — export schedules, ScheduleState.cronExpression, DataSyncConfig.schedule, CacheWarmup.schedule, backup / DR-test schedules (#16320, ADR-0049) - #17146

Draft
os-bill wants to merge 10 commits into
mainfrom
claude/issue-16320-retire-cron-typed-positions
Draft

feat(spec)!: retire the seven cron-typed positions nothing evaluated — export schedules, ScheduleState.cronExpression, DataSyncConfig.schedule, CacheWarmup.schedule, backup / DR-test schedules (#16320, ADR-0049)#17146
os-bill wants to merge 10 commits into
mainfrom
claude/issue-16320-retire-cron-typed-positions

Conversation

@os-bill

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

Copy link
Copy Markdown
Collaborator

Fixes #16320

Clause-②: yes

Executes the maintainer ruling on #15954 (director decision batch #56, 2026-09-06, 「其他同意」 on the per-family recommendation): option A — retire — per family for the seven cron-typed positions nothing evaluated, under ADR-0049 enforce-or-remove, by the spec-property-retirement playbook. The PromptTemplate marking half is the sibling card and is not touched here. Clause-②: yes ⇒ this PR carries needs:contract-review. The diff touches skills/objectstack-formula/SKILL.mdGOVERNED: draft only, a human merges — never ready, never queued, never auto-merged by a seat.

Per family — which shape, and why (the table a reviewer checks first)

"Per family" is a ruling, not a figure of speech: the tree forces one split, and the two ways to get it wrong (a conversion omitted for the connector family; the os migrate meta sentence written on all seven) have no gate, so the pin test asserts the split in both directions.

family positions (RETIRED_KEYS_BY_MAJOR[18] spelling) reachable from a stack manifest ADR-0087 shape os migrate meta --from 17 sentence
export schedules api/ScheduledExport:schedule.cronExpression, api/ScheduleExportRequest:schedule.cronExpression (nested spellings — neither has an authorable-surface row of its own; …:schedule is the row) no — an API contract nothing serves (POST /api/v1/data/export/schedules has no server route; IExportService has no provider) D3 semantic export-schedule-cron-retired; no D2 absent
flow schedule state automation/ScheduleState:cronExpression (was REQUIRED — the requiredness leaves with the key, so timezone / status / nextRunAt now describe a cadence the row no longer declares; they stay, the ruling retires the position not the def) no — runtime state D3 semantic schedule-state-cron-expression-retired; no D2 absent
connector sync integration/DataSyncConfig:schedule yesstack.zod.ts connectors: z.array(DeclarativeConnectorEntrySchema)connector.zod.ts syncConfig: DataSyncConfigSchemaschedule D2 conversion connector-sync-schedule-removed (mapCollection(stack, 'connectors', …), toMajor: 18, retiredFromLoadPath: true, one strip per connectors[] entry that authored the key, fixture with one authoring connector beside two that keep identity) wired into step18.conversionIds; plus the D3 twin connector-sync-schedule-retired — added on the seat's route-(b) ruling (card comment 5602588780), because the #15954 letter names this family's D3 entry as the carrier of the author-population reading. The D2 stays the mechanical half (the strip); the D3 carries what no conversion can — the cadence the author meant, and the out-of-repo population this repo cannot measure present — and true of the tool, because the chain has a seam that sees the key
cache warmup system/CacheWarmup:schedule no — plugin TS configuration D3 semantic cache-warmup-schedule-retired; no D2 absent
backup / DR testing system/BackupConfig:schedule, system/DisasterRecoveryPlan:testing.schedule (nested spelling) no — operator configuration D3 semantic disaster-recovery-schedules-retired; no D2 absent

Route on every position: retiredKey() tombstone, NOT plain deletion — none of the five schemas is .strict(), so a bare deletion would be a silent strip (ADR-0104). Registered under 18, not 17 (v17.0.0 was cut; launch-window convention). Not in scope, as the card names: CronSchedule.expression (croner), KnowledgeRefreshPolicy.cron (experimental by design), Object.titleFormat, the PromptTemplate pair. Nothing under content/docs/releases/**.

Premise check — the card is a clue; each mechanism assumption re-derived on this tree

assumption (pre-dispatch reading, comment 5595844387) measured at 78e53b188
only ONE of seven reachable from a stack manifest holdsstack.zod.ts:685connector.zod.ts:955 DeclarativeConnectorEntrySchema:799 syncConfig; the other six have no runtime consumer outside their own file. One refinement: ScheduledExport has a type-only consumer, contracts/export-service.ts (IExportService.scheduleExport() returning a Promise of ScheduledExport, plus a hand-written ScheduleExportInput.schedule.cronExpression: string). No reader, so the route does not change — but the tsc sweep reaches it; see "Beyond the literal seven"
gate (b) checks registration, not conversions; the chain-replay test only sees a conversion that exists holds — nothing red with the conversion omitted, hence the per-family pins
only 4 of 7 have an authorable-surface row holdsgen:schema flipped exactly four rows to [RETIRED] (automation/ScheduleState:cronExpression, integration/DataSyncConfig:schedule, system/BackupConfig:schedule, system/CacheWarmup:schedule); the three nested ones take the nested registry spelling
line drift — execution.zod.ts:526 REQUIRED, cache.zod.ts:183 with an inert 'scheduled' enum member, DR :58 / :255 holds — all seven re-located by text, never by line
D7 ledger: a tombstoned cover is deleted with a comment holds, and goes further: all seven covers of cron-declared-unwired leave discovery at once, so the whole row is deleted and replaced by a comment recording what it measured; the two notes that cited it (cron-job-schedule, cron-knowledge-refresh) and the cel-flow cover comment are re-worded. No state: 'removed' precedent exists in that ledger (zero rows), so the comment form was chosen
stale text in SYNC_ARCHITECTURE.md and the @example blocks holds:51, :99, :190-198, :239 and the three @example lines re-worded / dropped
both deferrals discharged; shared/expression.zod.ts out of the write set holds — untouched
no open PR touches the five schema files, the D7 ledger or the skill; #16778 regenerates migrations/registry.ts holds — merged origin/main via bash scripts/pm/os-regen-merge.sh (4 incoming commits, none touching this branch's files; #16778 had not landed at merge time, so the registry region needed no reconciliation — regenerated from entries/ afterwards anyway)

Census — nothing authors any of the seven positions (each zero has a lit control)

population pattern hits control
every package outside packages/spec, examples/**, skills/**, apps/**, docs/**, hand-written content/docs/** the seven def names + syncConfig.schedule 2, both prose about a different thing: docs/adr/0122-…:73 (a dated record of the parsed connector shape — an ADR, not edited here) and docs/qa/platform-checklist/FOLLOW-UPS.md:448 (ScheduleStateSchema.status) the same pattern with packages/spec included lights the four declaring files (15 / 8 / 9 / 17 lines)
the same roots, bare word cronExpression (tests and changelogs excluded) \bcronExpression\b 6 mentions, all a DIFFERENT key (corrected on review — the first census counted 4 and missed the last two): the report-schedule cronExpression of plugin-reportsreport-service.ts:636, rest-server.ts:12541, client/src/index.ts:5812,5820 (4 code sites), plus docs/qa/platform-checklist/areas/dashboards.json:1068,1079 (2 QA-checklist prose lines, same key, POST /api/v1/reports/:id/schedule). Evaluated by that plugin, not one of the seven positions — the conclusion is unchanged the declaring api/export.zod.ts / execution.zod.ts light when packages/spec is included
the same roots, syncConfig syncConfig 0 outside the D7 ledger's own comments connector.zod.ts lights
objectui at the pinned 53ded82bf7a4 (fetched into an owned ref, not the checkout's 3fbdd4a) the seven names + syncConfig 0 authors — the syncConfig hits are the react offline hook's own key (useOffline.ts, ui/offline.zod.ts), ScheduleState appears once in ROADMAP.md prose ConnectorSchema / defineStack light 5 files
out-of-repo stacks NOT MEASURED from this repo — stated in the connector entry, not claimed zero

Consequence for the no-text-pin decision: the key names are shared with LIVE keys (Job.schedule, plugin-reports' cronExpression), so a tree-scoped text absence pin would either false-positive on those or have to be file-scoped, which the playbook forbids. The tree-wide guard here is tsc: every one of the seven keys is typed never, so any .ts author of one fails to compile — measured by the fixture sweep below.

Fixture triage — every test that authored one of the keys, re-judged (not batch-respelled)

fixture what it pinned disposition
api/export.test.ts (7 sites, two describes) the envelope normalization of schedule.cronExpression — the branch this PR deletes replaced: the well-formed fixtures drop the key (schedule: {} still parses; timezone still defaults to UTC), the normalization assertion becomes a timezone assertion, and the refusal moves to the dedicated pin
automation/execution.test.ts should reject missing required fields that a ScheduleState without cronExpression THROWS — now false replaced: the same document now not.toThrow()s (the positive half, so the former requiredness cannot quietly come back); the other two required-key refusals stay
automation/execution.test.ts (3 more sites) the key in valid fixtures dropped, with an not.toHaveProperty('cronExpression') on the parsed state
integration/connector.test.ts:238, system/disaster-recovery.test.ts:61,168,193, contracts/export-service.test.ts ×3 the key in valid fixtures dropped
system/cache.test.ts should accept scheduled warmup the envelope normalization of schedule beside strategy: 'scheduled' replaced: strategy: 'scheduled' still parses (an enum value the ruling did not name), schedule is absent from the parse
integration/connector-author-shape.test.ts (⚠ missed by the first round — CI-red, :455,:461,:465) the ADR-0122 phase-2 flip, measured on syncConfig.schedule — the one key whose TYPE differed between Connector (author) and ConnectorParsed re-judged, not respelled: the key is gone from the probe literal, so the flip is now measured on the defaults alone (always the larger half — the parse supplies direction, realtimeSync, conflictResolution, batchSize, deleteMode), plus a positive not.toHaveProperty('schedule') on the parsed connector. The refusal itself is owned by the dedicated pin
shared/typed-expression-envelope-dialect.test.ts (⚠ missed by the first round — CI-red, :177,:197) the roster of stack-reachable typed slots (three: jobs[].schedule.expression, connectors[].syncConfig.schedule, objects[].titleFormat) and an invalid_union dialect verdict at the connector path re-judged: the roster shrinks to two HERE rather than a stale control quietly passing a cron through a slot that no longer exists. The case stays as the tombstone it now is — all three former inputs (foreign envelope, cron envelope, bare string) draw invalid_type at connectors.0.syncConfig.schedule with the prescription, explicitly NOT the dialect verdict, and a control asserts the same connector minus the key still parses

Beyond the literal seven — one consequential edit, declared

packages/spec/src/contracts/export-service.ts ScheduleExportInput.schedule.cronExpression: string — a hand-written TS input interface, not a zod position, not on the authorable surface, not in the D7 ledger. With ScheduledExport.schedule.cronExpression typed never, an IExportService.scheduleExport(input) that still DEMANDED a cron would ask the provider for a cadence its own return type refuses. The line is removed with a comment; the three test fixtures that returned it are dropped by the tsc sweep. No behaviour changes: IExportService has no provider binding (its own header records that).

Reverse verification — the pins can fail

All four legs run from the COMMITTED state, each mutation proven on disk (grep -c of the injected and the removed text), each restore proven by git checkout HEAD -- FILE then git hash-object FILE equal to the HEAD blob and git diff HEAD --quiet. The pin test imports every schema relatively from src (no dist on the resolution path), so no rebuild leg is owed. Head 78e53b188, lock-held.

leg mutation prediction observed
M1 system/cache.zod.ts: CacheWarmup.schedule back to a live z.string().optional() pin RED RED — 3 of 20: the base-schema refusal, the DistributedCacheConfig.warmup carrier refusal, the tsc-channel runtime sweep; restored, blob ab8192e19ad2 == HEAD
M2 conversions/registry.ts + migrations/registry.ts: the connector D2 conversion removed from CONVERSIONS_BY_MAJOR[18] AND from step18.conversionIds — the predicted no-gate failure conversions.test.ts + migrations.test.ts stay GREEN; pin RED exactly that — 308/308 existing tests green with the conversion gone; pin RED on "the connector family converts (D2) and is wired into the step-18 chain"; both files restored, blobs ca59db23c710 / 9151550ffd73 == HEAD
M3a integration/connector.zod.ts: the os migrate meta --from 17 sentence stripped from the connector prescription pin RED RED — 4 of 20: the base site and all three carriers (Connector.syncConfig, DeclarativeConnectorEntry, stack.connectors[]); restored, blob 5dcf7b727bb0 == HEAD
M3b system/cache.zod.ts: the well-formed house sentence APPENDED to a non-conversion prescription — the mirror error class-wide wording pin (retired-key-migrate-sentence.test.ts, repo project) stays GREEN; this PR's pin RED exactly that — 14/14 green on the class pin (it holds wording, not placement), pin RED on both CacheWarmup.schedule sites; restored, blob ab8192e19ad2 == HEAD. A first M3b reading ran the class pin under --project local, where it is not included, and read a no-files exit as red; re-run under --project repo before this table was written

Tree after every leg: git status --porcelain empty.

Contract review round 2 — the seat's two binding items, and where each landed

The domain:spec seat's contract-review verdict (card comment 5602588780) returned FAIL with two binding items. Both are discharged on this head; the seat's own route choice is followed literally, not re-argued.

B1 — CI was RED: two spec tests still authored the retired key, and neither was in the first round's hand-picked 13-file run. Both are re-judged individually (the disposition rows are in the Fixture triage table above), and the lesson is taken at the level it belongs to: this round runs the package suite in FULL (pnpm --filter @objectstack/spec test, all 471 local files), never a hand-picked list — picking files is precisely the mechanism that let these two through.

B2 — route (b), per the seat's ruling. The #15954 letter says the connector family's D3 entry "says so and names the measured zero in-repo authors and the NOT-MEASURED out-of-repo population". So:

  • the D3 semantic entry 18.connector-sync-schedule-retired is added beside the D2 conversion, with acceptanceCriteria;
  • the reverse assertion expect(step.semantic.filter(/sync-schedule|connector-sync/)).toEqual([]) — which had encoded the deviation as contract — is deleted, inverted to assert the twin is present (toEqual([SEMANTIC_TWIN_ID]));
  • and the part the ruling actually cares about: the population sentence now sits on fields that PROJECT. It previously lived only in three non-projecting code comments. It is now on the D3 entry's reason (projects to spec-changes.json rationale, the upgrade guide's "Why not automatic", and os migrate meta's why:) and acceptanceCriteria (projects to "Done when" and verify:), and the D2's one projecting field, summary, names the twin as the carrier of the residue. A code comment projects nowhere, which is why the sentence is no longer only in one.

⛔ The underlying convention question — whether a retirement a D2 already expresses still owes a D3 twin — is not re-opened here. The seat filed it as decision card #17152; this PR does not wait on it (governed, draft-only, never auto-merged, so the objection window exists by construction).

Verification

Everything below at head e5e87ccba — this branch merged with origin/main (4261fbc80) through bash scripts/pm/os-regen-merge.sh, which took the branch's bytes for the eight generated artifacts it had hand-edited and main's for none (main moved no os-regen path in this window). No artifact needed regenerating afterwards, so there is no regeneration commit.

  • FULL package suite — the binding B1 item. pnpm --filter @objectstack/spec test (= vitest run --project local): 471 test files, 13222 tests, all passed, exit 0, under the shared verify lock (os-verify-lock.sh VERDICT command-exit 0, held 763s on a contended box). ⛔ No file was hand-picked. Reconciliation of the run against the config: vitest list --filesOnly collects 499 files, 471 [local] + 28 [repo], and the package's test script is --project local — so 471 run is the whole local project, exactly. Both files the review named are in it: [local] src/integration/connector-author-shape.test.ts and [local] src/shared/typed-expression-envelope-dialect.test.ts.
  • The repo tier, which pnpm test does not run (28 files the reconciliation above exposed): vitest run --project repo28 files, 410 tests, all passed, exit 0, lock VERDICT command-exit 0. This is where the class pin retired-key-migrate-sentence.test.ts lives, the one the second predecessor's commit was written against.
  • pnpm --filter '@objectstack/spec^...' build (dependency closure) and pnpm --filter @objectstack/spec build — both exit 0.
  • pnpm --filter @objectstack/spec check:generated — exit 0, ✓ All 15 generated artifacts are up to date. Nothing stale, so nothing regenerated (the wrapper's --fix is deliberately narrow and had no work).
  • pnpm --filter @objectstack/spec typecheck (tsc --noEmit + check:scripts-typecheck + check:test-typecheck) — exit 0.
  • Gates, derived not hand-fed. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no path list; the script derives the change set itself — 42 paths vs merge base 4261fbc80; the --repo assertion checked against this checkout's origin and holds) → 110 families. Every one run, each exit code redirected to its own log and captured before any reading (eval "$cmd" >> log 2>&1; ex=$? — never across a pipe). Reconciled: --ran✓ 110 derived famil(ies) accounted for — 110 run, 0 NOT-MEASURED, exit 0.
    • 109 green on the first pass. The one non-zero was pnpm check:type-check-debt exit 3 — PREREQUISITE NOT MET, which is NOT MEASURED, never a red: its self-test and coverage legs passed (76/80 packages type-checked), and the --re-measure leg OOM'd because tsc inherits the caller's NODE_OPTIONS ceiling, which the gate's own output flags as tighter than its process limit. Re-run with that ceiling raised to 8192 (the gate's prescribed repair — it refuses to record 0 rather than report a false green): exit 0, --re-measure: OK — 5 ledger entr(ies) re-measured, 55 raw tsc error(s), none above its recorded number, lock VERDICT command-exit 0. CI's own Type Check · debt ledger is likewise green on this head.
  • pnpm lint narrowed, and the narrowing PROVEN a measurement rather than a skip — the three readings, all required: (1) population read from eslint.config.mjs itself, not guessed — eslint . minus its NEVER_LINTED set; (2) file count read from --format json output — 31 lintable files of this branch's 42-path diff, 0 errors, 0 warnings, exit 0, --no-inline-config; (3) invariance — the config declares no parserOptions.project and no typed @typescript-eslint rules (its own comment at :328 states this), so type-aware linting is off and this diff cannot move any rule's verdict on an untouched file. The narrowing therefore excludes nothing.
  • Governed skill budget (net ≤ 0, never more than +2): skills/objectstack-formula/SKILL.md 454 → 454 lines, net 0git show origin/main:... and git show HEAD:... both 454. The single changed line removes three retired carriers from the cron row's Carriers cell and leaves Job.schedule.expression (canonical); table shape and the template row untouched.
  • PR CI, read on this head, newest run per check name (⛔ not the raw run list, ⛔ not the required subset): 34 distinct check names — 32 success, 2 skipped (Console Pin Gate, Packed-tarball smoke (opt-in)), 0 failure, 0 in_progress. The two the review measured RED at 78e53b188Test Core and Test Core (1/6) — are both success, as are Lint & Repo Gates and Type Check · workspace, which were still running when it read.
  • Declared to CI, not run here: the full pnpm test / pnpm typecheck farm beyond @objectstack/spec, check:react-declaration-parity (needs objectui's manifest — an on-demand gate whose trigger is a pin bump), and the merge-queue rebuild.

验收备注

维护者速读(草稿)

改了什么:把七个"声明了 cron 表达式、但平台从来没有任何东西去执行它"的位置退役:导出计划(ScheduledExport / ScheduleExportRequestschedule.cronExpression)、流程调度状态(ScheduleState.cronExpression)、连接器同步(DataSyncConfig.schedule)、缓存预热(CacheWarmup.schedule)、备份与容灾演练(BackupConfig.schedule / DisasterRecoveryPlan.testing.schedule)。每个位置改成 retiredKey() 墓碑:作者再写这个键,TypeScript 编译报错、运行时解析报错,错误信息本身就是迁移说明。按 #15954 裁决"按家族选 A(退役)"执行;PromptTemplate 那一对是姊妹卡(选 B 标记),本 PR 不碰。 契约复核后按席位裁定补齐:连接器家族(七个位置里唯一能从 stack manifest 写到的那个)同时带 D2 机械转换与 D3 语义条目,并把"仓内实测零作者、仓外人口无法测量"这句话从只存在于代码注释,移到了会真正投影给作者的字段上(os migrate metawhy: / verify:、升级指南、spec-changes.json)。

为什么改:这七个键都被解析成 cron 信封,但没有任何调度器读它们(D7 表达式一致性台账把它们全记为 unevaluated)。作者写了 schedule: '0 2 * * *' 会以为每晚备份,实际什么都不会发生——这是 ADR-0049 明令禁止的"声明了却不执行"的假合规。平台真正会执行的唯一 cron 槽位是 Job.schedule.expression,所有墓碑说明都把作者引向它。

风险与代价(含回滚):公开契约收窄(Clause-②: yes),但仓内、示例、技能、文档、objectui 固定版本均无作者写过这七个键(全部带对照组实测);仓外的客户栈无法从本仓测量,连接器家族因此带了 D2 转换,os migrate meta --from 17 会列出机械修改。@objectstack/specminor + BREAKING 横幅(启动窗口惯例)。改动同时触碰已发布的受管技能 skills/objectstack-formula/SKILL.md:只在 cron 行的 Carriers 单元格里删掉三个已退役载体,行数净 0、token −22,不改表结构、不碰 template 行。回滚:整个 PR revert 即可,退役没有删除任何 def、没有迁移任何存储数据,无需反向数据迁移。

席位意见:达档契约复核 PASS(裁决在卡 #16320 评论 5609546402,熔断 90 条 claude-fable-5-1 零其它值)。两条必办项都讨清,而且复核是从「怎么作弊」的方向进去的:它专门查了「把测试主体删掉换绿灯」这一种修法,结论是两处 pin 的主体都保住了——作者形状 pin 改用 defaults 量翻转后,把两个别名对调仍会让两个探针同时变红,⇒ 它没有失去分辨力。人口句现在落在 reason / acceptanceCriteria 上,三条投影通道逐行读源码核过。

⚠️ 一件合并前必须知道的事,与契约无关:并写邻接。single-writer 门只声明 .objectui-sha,所以它的绿灯不覆盖两处生成物邻接——#17090(已在合并队列,共用 content/docs/references/api/export.mdx)与 #16778(open,共用 packages/spec/src/migrations/registry.ts)。#17090 会先落,所以合并本 PR 前请先合 origin/main 并用 bash scripts/pm/os-regen-merge.sh 重生成,⛔ 不手改生成物。

⚠️ 一处诚实的限制:step-18 的条目在 PROTOCOL_VERSION = '17.0.0' 的当下不投影到任何已发布工件(既有 step-18 条目同样为 0,step-17 条目为 2/1),os migrate meta --from 17 不带 --to 18 时链为空。这是启动窗口惯例、不是本 PR 的缺陷,但正文没说,记在这里免得你按「已经能看到」验收。

⛔ 本席翻 ready、未入队、未挂 auto-merge、未提交批准 review。受管面的人工合并与达档复核是两道叠加的保障,前者不替代后者。

你要做的:这是受管面 PR,需要你人工合并(不进队列、不自动合并);合并前只需确认"按家族拆分"那张表与你在 #15954 的裁决意图一致。


🤖 Generated with Claude Code

https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH


Generated by Claude Code

…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
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

29 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ⚠️ 10 changed file(s) yielded no anchor (packages/spec/authorable-surface/automation.json, packages/spec/authorable-surface/integration.json, packages/spec/authorable-surface/system.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

What this run could not see
  • 10 changed file(s) yielded no anchor (packages/spec/authorable-surface/automation.json, packages/spec/authorable-surface/integration.json, packages/spec/authorable-surface/system.json, …) — pages documenting those are invisible to this run
  • 9 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 215 client-bound route-ledger rows — the other 155 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 155: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 100 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

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

Which tree this was computed on

This run read content/docs from 12bf4fedd355a9a28fb8aeb5ab30af6fdd1ab8fe — the merge of head e5e87ccba3dea43cc7189ef724fc4edea0a8a8a3 into base 4261fbc80e67b1715d62e02417f959430ad2666d, which is what actions/checkout gives a pull_request run. Not the PR head.

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

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 12bf4fedd355a9a28fb8aeb5ab30af6fdd1ab8fe && git checkout 12bf4fedd355a9a28fb8aeb5ab30af6fdd1ab8fe
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 4261fbc80e67b1715d62e02417f959430ad2666d e5e87ccba3dea43cc7189ef724fc4edea0a8a8a3 && git checkout -B drift-repro 4261fbc80e67b1715d62e02417f959430ad2666d && git merge --no-ff e5e87ccba3dea43cc7189ef724fc4edea0a8a8a3

node scripts/docs-audit/affected-docs.mjs --json 4261fbc80e67b1715d62e02417f959430ad2666d

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

os-bill commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator Author

⛔ Do not merge at this head — mergeable: clean is a false green on this PR

domain:spec seat, session_01MkQhmuuJAVDjmeWNixwDDH, 2026-09-10T00:41Z. Readings below are on PR head e5e87ccba3dea43cc7189ef724fc4edea0a8a8a3 and origin/main 4261fbc8… fetched at that time.

The API says this PR is fine:

GET /pulls/17146  →  mergeable: true, mergeable_state: "clean"

That reading is computed by plain git, and one file in this diff is not merged by plain git.

The measurement

$ git check-attr merge -- content/docs/references/api/export.mdx
content/docs/references/api/export.mdx: merge: os-regen
$ git check-attr merge -- packages/spec/src/data/hook.zod.ts        # control
packages/spec/src/data/hook.zod.ts: merge: unspecified

.gitattributes:153 routes content/docs/references/** to merge=os-regen. What that driver does is stated by scripts/pm/os-regen-merge.sh's own header, verbatim:

Paths routed to merge=os-regen in .gitattributes merge with exit 0 and zero conflict markers while SILENTLY DROPPING one side's changes — only a full regeneration exposes the loss.

Both sides have edited that file since the merge base (4261fbc80e67b1715d62e02417f959430ad2666d):

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.


Generated by Claude Code

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

Labels

documentation Improvements or additions to documentation protocol:system size/xl tests tooling

Projects

None yet

2 participants