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
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 Check29 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 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 |
…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.
Generated by Claude Code
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-retirementplaybook. ThePromptTemplatemarking half is the sibling card and is not touched here.Clause-②: yes⇒ this PR carriesneeds:contract-review. The diff touchesskills/objectstack-formula/SKILL.md⇒ GOVERNED: 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 metasentence written on all seven) have no gate, so the pin test asserts the split in both directions.RETIRED_KEYS_BY_MAJOR[18]spelling)os migrate meta --from 17sentenceapi/ScheduledExport:schedule.cronExpression,api/ScheduleExportRequest:schedule.cronExpression(nested spellings — neither has an authorable-surface row of its own;…:scheduleis the row)POST /api/v1/data/export/scheduleshas no server route;IExportServicehas no provider)export-schedule-cron-retired; no D2automation/ScheduleState:cronExpression(was REQUIRED — the requiredness leaves with the key, sotimezone/status/nextRunAtnow describe a cadence the row no longer declares; they stay, the ruling retires the position not the def)schedule-state-cron-expression-retired; no D2integration/DataSyncConfig:schedulestack.zod.tsconnectors: z.array(DeclarativeConnectorEntrySchema)→connector.zod.tssyncConfig: DataSyncConfigSchema→scheduleconnector-sync-schedule-removed(mapCollection(stack, 'connectors', …),toMajor: 18,retiredFromLoadPath: true, one strip perconnectors[]entry that authored the key, fixture with one authoring connector beside two that keep identity) wired intostep18.conversionIds; plus the D3 twinconnector-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 measuresystem/CacheWarmup:schedulecache-warmup-schedule-retired; no D2system/BackupConfig:schedule,system/DisasterRecoveryPlan:testing.schedule(nested spelling)disaster-recovery-schedules-retired; no D2Route 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, thePromptTemplatepair. Nothing undercontent/docs/releases/**.Premise check — the card is a clue; each mechanism assumption re-derived on this tree
78e53b188stack.zod.ts:685→connector.zod.ts:955DeclarativeConnectorEntrySchema→:799 syncConfig; the other six have no runtime consumer outside their own file. One refinement:ScheduledExporthas a type-only consumer,contracts/export-service.ts(IExportService.scheduleExport()returning a Promise ofScheduledExport, plus a hand-writtenScheduleExportInput.schedule.cronExpression: string). No reader, so the route does not change — but the tsc sweep reaches it; see "Beyond the literal seven"gen:schemaflipped 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 spellingexecution.zod.ts:526REQUIRED,cache.zod.ts:183with an inert'scheduled'enum member, DR:58/:255cron-declared-unwiredleave 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 thecel-flowcover comment are re-worded. Nostate: 'removed'precedent exists in that ledger (zero rows), so the comment form was chosenSYNC_ARCHITECTURE.mdand the@exampleblocks:51,:99,:190-198,:239and the three@examplelines re-worded / droppedshared/expression.zod.tsout of the write setmigrations/registry.tsorigin/mainviabash 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 fromentries/afterwards anyway)Census — nothing authors any of the seven positions (each zero has a lit control)
packages/spec,examples/**,skills/**,apps/**,docs/**, hand-writtencontent/docs/**syncConfig.scheduledocs/adr/0122-…:73(a dated record of the parsed connector shape — an ADR, not edited here) anddocs/qa/platform-checklist/FOLLOW-UPS.md:448(ScheduleStateSchema.status)packages/specincluded lights the four declaring files (15 / 8 / 9 / 17 lines)cronExpression(tests and changelogs excluded)\bcronExpression\bcronExpressionofplugin-reports—report-service.ts:636,rest-server.ts:12541,client/src/index.ts:5812,5820(4 code sites), plusdocs/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 unchangedapi/export.zod.ts/execution.zod.tslight whenpackages/specis includedsyncConfigsyncConfigconnector.zod.tslights53ded82bf7a4(fetched into an owned ref, not the checkout's3fbdd4a)syncConfigsyncConfighits are the react offline hook's own key (useOffline.ts,ui/offline.zod.ts),ScheduleStateappears once inROADMAP.mdproseConnectorSchema/defineStacklight 5 filesConsequence 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 istsc: every one of the seven keys is typednever, so any.tsauthor 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)
api/export.test.ts(7 sites, two describes)schedule.cronExpression— the branch this PR deletesschedule: {}still parses;timezonestill defaults toUTC), the normalization assertion becomes atimezoneassertion, and the refusal moves to the dedicated pinautomation/execution.test.tsshould reject missing required fieldsScheduleStatewithoutcronExpressionTHROWS — now falsenot.toThrow()s (the positive half, so the former requiredness cannot quietly come back); the other two required-key refusals stayautomation/execution.test.ts(3 more sites)not.toHaveProperty('cronExpression')on the parsed stateintegration/connector.test.ts:238,system/disaster-recovery.test.ts:61,168,193,contracts/export-service.test.ts×3system/cache.test.tsshould accept scheduled warmupschedulebesidestrategy: 'scheduled'strategy: 'scheduled'still parses (an enum value the ruling did not name),scheduleis absent from the parseintegration/connector-author-shape.test.ts(⚠ missed by the first round — CI-red,:455,:461,:465)syncConfig.schedule— the one key whose TYPE differed betweenConnector(author) andConnectorParseddirection,realtimeSync,conflictResolution,batchSize,deleteMode), plus a positivenot.toHaveProperty('schedule')on the parsed connector. The refusal itself is owned by the dedicated pinshared/typed-expression-envelope-dialect.test.ts(⚠ missed by the first round — CI-red,:177,:197)jobs[].schedule.expression,connectors[].syncConfig.schedule,objects[].titleFormat) and aninvalid_uniondialect verdict at the connector pathinvalid_typeatconnectors.0.syncConfig.schedulewith the prescription, explicitly NOT the dialect verdict, and a control asserts the same connector minus the key still parsesBeyond the literal seven — one consequential edit, declared
packages/spec/src/contracts/export-service.tsScheduleExportInput.schedule.cronExpression: string— a hand-written TS input interface, not a zod position, not on the authorable surface, not in the D7 ledger. WithScheduledExport.schedule.cronExpressiontypednever, anIExportService.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:IExportServicehas 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 -cof the injected and the removed text), each restore proven bygit checkout HEAD -- FILEthengit hash-object FILEequal to the HEAD blob andgit diff HEAD --quiet. The pin test imports every schema relatively fromsrc(nodiston the resolution path), so no rebuild leg is owed. Head78e53b188, lock-held.system/cache.zod.ts:CacheWarmup.scheduleback to a livez.string().optional()DistributedCacheConfig.warmupcarrier refusal, the tsc-channel runtime sweep; restored, blobab8192e19ad2== HEADconversions/registry.ts+migrations/registry.ts: the connector D2 conversion removed fromCONVERSIONS_BY_MAJOR[18]AND fromstep18.conversionIds— the predicted no-gate failureconversions.test.ts+migrations.test.tsstay GREEN; pin REDca59db23c710/9151550ffd73== HEADintegration/connector.zod.ts: theos migrate meta --from 17sentence stripped from the connector prescriptionConnector.syncConfig,DeclarativeConnectorEntry,stack.connectors[]); restored, blob5dcf7b727bb0== HEADsystem/cache.zod.ts: the well-formed house sentence APPENDED to a non-conversion prescription — the mirror errorretired-key-migrate-sentence.test.ts,repoproject) stays GREEN; this PR's pin REDCacheWarmup.schedulesites; restored, blobab8192e19ad2== 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 repobefore this table was writtenTree after every leg:
git status --porcelainempty.Contract review round 2 — the seat's two binding items, and where each landed
The
domain:specseat'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 471localfiles), 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:
18.connector-sync-schedule-retiredis added beside the D2 conversion, withacceptanceCriteria;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]));reason(projects tospec-changes.jsonrationale, the upgrade guide's "Why not automatic", andos migrate meta'swhy:) andacceptanceCriteria(projects to "Done when" andverify:), 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 withorigin/main(4261fbc80) throughbash 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.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.shVERDICTcommand-exit 0, held 763s on a contended box). ⛔ No file was hand-picked. Reconciliation of the run against the config:vitest list --filesOnlycollects 499 files, 471[local]+ 28[repo], and the package'stestscript is--project local— so 471 run is the wholelocalproject, exactly. Both files the review named are in it:[local] src/integration/connector-author-shape.test.tsand[local] src/shared/typed-expression-envelope-dialect.test.ts.repotier, whichpnpm testdoes not run (28 files the reconciliation above exposed):vitest run --project repo— 28 files, 410 tests, all passed, exit 0, lock VERDICTcommand-exit 0. This is where the class pinretired-key-migrate-sentence.test.tslives, the one the second predecessor's commit was written against.pnpm --filter '@objectstack/spec^...' build(dependency closure) andpnpm --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--fixis deliberately narrow and had no work).pnpm --filter @objectstack/spec typecheck(tsc --noEmit+check:scripts-typecheck+check:test-typecheck) — exit 0.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 base4261fbc80; the--repoassertion checked against this checkout'soriginand 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.pnpm check:type-check-debtexit 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-measureleg OOM'd because tsc inherits the caller'sNODE_OPTIONSceiling, 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 VERDICTcommand-exit 0. CI's ownType Check · debt ledgeris likewise green on this head.pnpm lintnarrowed, and the narrowing PROVEN a measurement rather than a skip — the three readings, all required: (1) population read fromeslint.config.mjsitself, not guessed —eslint .minus itsNEVER_LINTEDset; (2) file count read from--format jsonoutput — 31 lintable files of this branch's 42-path diff, 0 errors, 0 warnings, exit 0,--no-inline-config; (3) invariance — the config declares noparserOptions.projectand no typed@typescript-eslintrules (its own comment at:328states 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.skills/objectstack-formula/SKILL.md454 → 454 lines, net 0 —git show origin/main:...andgit show HEAD:...both 454. The single changed line removes three retired carriers from thecronrow's Carriers cell and leavesJob.schedule.expression(canonical); table shape and thetemplaterow untouched.success, 2skipped(Console Pin Gate,Packed-tarball smoke (opt-in)), 0failure, 0in_progress. The two the review measured RED at78e53b188—Test CoreandTest Core (1/6)— are bothsuccess, as areLint & Repo GatesandType Check · workspace, which were still running when it read.pnpm test/pnpm typecheckfarm 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.验收备注
noted, not filed:CacheWarmup.strategykeeps the enum member'scheduled', whose describe still reads "scheduled (cron)". It is a VALUE, not a position the ruling names, it was exactly as inert before (nothing readsCacheWarmupat all), and enum-value narrowing is invisible to all four ratchets; the tombstone comment and the semantic entry both say so. filed as spec:CacheWarmup.strategykeeps the'scheduled'member after its cron left — a declared warmup cadence with no key and no engine (ADR-0049 enum residue of #16320) #17157 (boundary flag 1).noted, not filed:ScheduledExport.schedule/ScheduleExportRequest.scheduleare now REQUIRED objects whose only key is an optionaltimezone;ScheduleState.timezone/status/nextRunAtdescribe a cadence no longer declared. The ruling retires the cron position, not the blocks; the wholeExportJobApiContractsfamily has zero consumers and is a separate ADR-0049 question. filed as [Decision] the export-job API contract family (ScheduledExport/ScheduleExportRequest/ExportJob…) has zero consumers and, after #16320, an emptyscheduleblock — retire the family, keep the contract, or build the scheduler? #17158 (boundary flag 2) — opened as aneeds-user-decisioncandidate, ⛔ not decided here.noted, not filed:docs/adr/0122-schema-type-alias-naming-convention.md:73sayssyncConfig.scheduledemands the envelope in the parsed state — a dated record of what the shape was when that ADR was written; an ADR is governed and is not rewritten for a later retirement. 承接者:无.noted, not filed: theetl-pipeline-layer-retiredsemantic entry'sreplacementtext (spec-changes.json:739, upgrade guide:443) says connector-attached sync "IS parsed and executed"; on this treesyncConfighas no reader outsidepackages/spec, so "executed" is a stale claim in a registered, projected ledger string. Not this card's family; correcting a released migration-ledger sentence is its own docs-only change. filed as spec:etl-pipeline-layer-retiredD3 entry says connector-attached syncIS parsed and executed— nothing executessyncConfig(stale claim in a projected ledger string, #5781 class) #17159 (boundary flag 4).../objectuicheckout sits at3fbdd4a, not the pinned53ded82; the census above was taken at the pin by fetching it intorefs/os-dev/issue-16320-pin(a ref this session owns, deleted after the reading), never by moving the shared checkout.维护者速读(草稿)
改了什么:把七个"声明了 cron 表达式、但平台从来没有任何东西去执行它"的位置退役:导出计划(
ScheduledExport/ScheduleExportRequest的schedule.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 meta的why:/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/spec走minor+ 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上,三条投影通道逐行读源码核过。.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重生成,⛔ 不手改生成物。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