Repository navigation
spec: the kernel startup-orchestrator contract (IStartupOrchestrator, PluginStartupResultSchema) is declared, exported and documented, and implemented by nothing #16059
Description
Activity
- addedenhancementNew feature or requestNew feature or requestpriority:p2Medium: important, M3Medium: important, M3
on Sep 6, 2026 分诊 ·
domain:spec/enhancement/priority:p2/needs-user-decision分诊席位。⛔ 不认领、不派发、不写代码、不合并、不裁决 decision-box 卡。⛔ 本 session 是
claude-opus-5,CONTRACT_REVIEW_TIER硬闸要求 fable。origin/main@932acc3d,2026-09-06T04:09Z。「零实现」我独立复现了,并把卡的零收得更紧
读数 结果 两个声明文件存在 ✅ packages/spec/src/contracts/startup-orchestrator.ts、packages/spec/src/kernel/startup-orchestrator.zod.tsIStartupOrchestrator在packages/spec之外2 个文件 ⭐ 那 2 个是什么 packages/metadata/CHANGELOG.md与packages/services/service-analytics/CHANGELOG.md—— 都是发布历史,不是代码CONTROL:同符号在 packages/spec内6 个文件 ⇒ grep 起火 orchestrateStartup在 spec 之外同样只有那 2 个 CHANGELOG ⇒
packages/spec之外零个实现、零个生产调用点。 卡的读数成立,而且我把「那两个非零命中是什么」也报出来了——⛔ 免得复算的人看到 2 就以为有实现。而真正启动插件的是另一条链(我也验了):
packages/core/src/kernel.ts:371 const result = await this.startPluginWithTimeout(plugin); packages/core/src/kernel.ts:668 private async startPluginWithTimeout(plugin: PluginMetadata): Promise<PluginStartupResult> { packages/core/src/plugin-loader.ts:91 export interface PluginStartupResult {⇒ core 用的是它自己的
PluginStartupResult(plugin-loader.ts:91),kernel.ts:7从./plugin-loader.js导入它。⭐ 两个声明只共享一个名字。 core 既不 import 也不引用 spec 的契约。⭐ 定级 p2 —— 卡把危害的性质说得最准
The cost here is not a crash, it is a false map. A consumer — an AI-maintained one especially, which is the population ADR-0087 designs for — reads the exported contract and the reference docs and concludes there is an orchestrator seam to implement against or to receive results from. There is not. Whatever it builds against that shape can never be reached by the running kernel.
⇒ 而且这不是一个隐蔽的内部符号:它被导出(
api-surface/contracts.json:157)并且有一整页参考文档(content/docs/references/kernel/startup-orchestrator.mdx)。⭐ 三个面(导出、文档、schema)都在说「有这个 seam」,而运行时没有。不给 p1:没有东西坏掉——没有人在用它,所以也没有人被它坑过(就本仓可测范围而言)。
⚠️ 仓外消费方未测,而它恰恰是导出且有文档的面。⇒ 若测得任何仓外消费方已按它实现,立即 p1。为什么是
needs-user-decision两条处置方向相反,卡说得对,只有维护者能选:
- 强制它 —— 让 kernel 的启动路径实现这个声明的契约,或产出
PluginStartupResultSchema真能 parse 的值。⭐ 这是 spec 自己的文档已经承诺的方向。 - 退役它 —— 按 enforce-or-remove 移除契约与 schema,并带上「移除一个已发布、已文档化的面」所要求的 ADR-0087 台账处置。
⚠️ 两条的成本差很大:路线 1 要动packages/core的启动路径(startPluginWithTimeout的返回形状与 spec 的plugin/ 可序列化 error 投影 /health成员对不上——卡把差异列全了);路线 2 是一次 ADR-0087 退役,走spec-property-retirement剧本。⭐ 一条给裁决者的输入:两个
PluginStartupResult的字段差异本身就是证据——core 的带pluginName: string、活的error?: Error、timedOut?: boolean;spec 的带plugin对象、可序列化 error 投影、health。⇒ 它们不是「同一个东西的两次声明」,是两个不同的设计。选 1 意味着 core 要迁到 spec 的设计上(含health,那是个新概念);选 2 意味着承认 spec 那个设计从未落地。车道
domain:spec/ 类型enhancement两条处置的落点都在
packages/spec(路线 1 还会外溢到packages/core)⇒domain:spec。卡自己也说「This is apackages/specquestion」。
enhancement:两条都不修既有错误行为——今天的行为是「什么都不做」。⛔ 我不预挂needs:contract-review:路线 2 移除已发布面(Clause-② yes),路线 1 不一定;预挂等于替裁决者选边。⛔ 与 #15820 不合并
卡划得对:#15820 刻意窄,只对齐
packages/core里的一个字段名,不碰packages/spec。本卡是那次对齐让人看见的更大问题。⇒ 不合并。
Generated by Claude Code
- 强制它 —— 让 kernel 的启动路径实现这个声明的契约,或产出
Ruling recorded — option 3, re-declare the contract as the shipped shape (director seat, decision batch #60, 2026-09-06)
Maintainer reply, verbatim: 「同意」 (all five batch #60 recommendations adopted).
Ruling. The spec keeps a startup-result contract, and it describes what the kernel actually produces. Neither "enforce the never-landed design" (option 1) nor "remove the contract" (option 2) is adopted.
Execution.
PluginStartupResultSchemainpackages/spec/src/kernel/startup-orchestrator.zod.tsis rewritten to the shapepackages/core/src/plugin-loader.ts'sPluginStartupResultships today (pluginName, a serialisable error projection oferror,timedOut);packages/corethen imports the type from the spec instead of declaring its own, so the two cannot drift again.IStartupOrchestrator/orchestrateStartup,StartupOptionsSchema(includinghealthCheck),HealthStatusSchemaandStartupOrchestrationResultSchemaare retired under the ADR-0087 ledger disposition for a published, documented surface — nothing implements or consumes them, andhealthis a concept with no probe system behind it. Follow thespec-property-retirementplaybook for the baselines, docs and pin tests.content/docs/references/kernel/startup-orchestrator.mdxis rewritten to describe the current contract (whatstartPluginWithTimeoutreturns and whentimedOutis set), not the retired orchestrator.- Land after core:
PluginStartupResult.startTimecarries an elapsed duration, and the spec contract for the same result declares it asduration#15820 (which aligns one field name in core) so the two PRs do not fight over the same declaration. - Clause-② yes (a published exported surface is removed): the landing PR carries
needs:contract-review; the changeset states the retirement and the re-declared shape. If any out-of-repo consumer of the retired interface is found during the round, record it here before removing — a deprecation window becomes a maintainer question.
Labels:
needs-user-decision→pm:queue. Ledger on #12708 (batch #60).
Generated by Claude Code
Claim:
domain:specexecution seat, sessionsession_01KB5PFtxuy1x3dcR5gxudx6, PM loop R2, at 2026-09-15T14:05Z.
Branch:claude/issue-16059-startup-orchestrator-shipped-shape
Clause-②: yes — this retires five declared, exported and documented spec surfaces and rewrites a sixth to a different shape. Implementation and contract review are separate agents atCONTRACT_REVIEW_TIER;needs:contract-reviewhangs on the card and on the draft PR in one stroke the moment the PR exists, and this seat never self-judges.
Thread-read: body + every comment read to the last page, including the maintainer ruling at #16059 (comment) (batch #60, option 3, maintainer: 同意).Why this lane claims a card whose file surface crosses domains
The ruling bundles the schema rewrite, the five retirements and the documentation rewrite into one landing — "so the two cannot drift again" is a same-PR requirement, not a sequencing note — so the work does not split along domain lines. The triage seat that would normally name the owning lane is vacant. This is the cross-domain exception path: one lane PM claims, and declares the file surface here.
Declared file surface
In this lane (
domain:spec, by the anchoring rule):packages/spec/src/kernel/startup-orchestrator.zod.ts+.test.tspackages/spec/src/contracts/startup-orchestrator.ts+.test.ts- new retirement entries under
packages/spec/src/migrations/entries/**, registered inpackages/spec/src/migrations/registry.ts - generated surface files, regenerated by the repo's own tooling and never by hand:
packages/spec/api-surface/{kernel,contracts}.json,packages/spec/export-origins/{kernel,contracts}.json,packages/spec/declaration-map/kernel.json packages/spec/src/type-alias-convention.pin.test.ts(names the retired symbols)- gate class, which the anchoring rule routes here:
scripts/check-startup-registry-verdict.mjs,scripts/startup-registry-verdict.baseline.json,packages/lint/src/lint-startup-registry-verdict{,.test,.corpus.test}.ts
Crossing out of this lane, claimed under the exception:
content/docs/references/kernel/startup-orchestrator.mdx(domain:devx) — the documentation rewrite the ruling namespackages/core/src/plugin-loader.ts(domain:engine) — read as the source of the shipped shape. Edited only if the rewrite cannot be expressed without it, and the PR body says so if it is.
⛔ Out of surface, fenced to other in-flight work:
packages/spec/src/shared/retired-key.ts(the tombstone constructor — #17502's declared surface; call it, do not edit it) andpackages/lint/src/index.ts(PR #18198 has it open). If the work turns out to need either, stop and report rather than edit.Targeted in-flight check (the cross-domain exception path requires it)
Read at 2026-09-15T14:03Z over the in-flight cards of every domain this surface touches —
domain:spec#17751, #17502, #17396;domain:devxnone;domain:enginenone;domain:cli#18265 — plus open PR #18198, whose 47 files were enumerated from the API rather than from its description.Result: no overlap on any file above, with two recorded exceptions.
packages/spec/src/migrations/registry.tsis in PR feat(types,triggers,service-automation,runtime,cli,spec,lint)!: package-authored scheduled work is a deployment decision, off by default #18198's file list. It is a shared file and not aSINGLE_CLAIM_PATHSentry, so this is ordinary concurrency: whichever lands second resolves the conflict. Both sides append distinct entries.packages/lint/src/index.tsis in PR feat(types,triggers,service-automation,runtime,cli,spec,lint)!: package-authored scheduled work is a deployment decision, off by default #18198's file list and is fenced out above.
#17502's claim comment declares
packages/spec/src/shared/retired-key.tsandpackages/metadata-protocol/src/protocol.ts; #17751's ispackages/spec/src/ui/chart.zod.ts. Neither intersects this surface.Landing-order prerequisite: satisfied, verified against GitHub
The ruling says "Land after #15820". #15820 read fresh: closed/completed at 2026-09-06T23:21:56Z. Read from the card's current state, not from the ruling's description of it.
Generated by Claude Code
Correcting my own claim comment, before anything is dispatched
Two things in the claim at 14:05Z (comment 5681528222) need to be firmer than I wrote them.
1.
packages/core/src/plugin-loader.tsis mandatory surface, not conditional. I wrote that it is "read as the source of the shipped shape. Edited only if the rewrite cannot be expressed without it." The ruling is stronger than that — #16059 (comment), verbatim:PluginStartupResultSchemainpackages/spec/src/kernel/startup-orchestrator.zod.tsis rewritten to the shapepackages/core/src/plugin-loader.ts'sPluginStartupResultships today (pluginName, a serialisable error projection oferror,timedOut);packages/corethen imports the type from the spec instead of declaring its own, so the two cannot drift again."
packages/corethen imports the type from the spec instead of declaring its own" is an instruction, and it is the half that makes the landing durable — a spec rewritten to match core, with core still declaring its own copy, re-opens the same drift the next time either moves. Sopackages/core/src/plugin-loader.tsis declared surface for this card, under the same cross-domain exception as the docs page, and a PR that leaves core declaring its ownPluginStartupResulthas not executed the ruling.2. The retirement follows a named playbook. The ruling says "Follow the
spec-property-retirementplaybook for the baselines, docs and pin tests." That skill is.claude/skills/spec-property-retirement/SKILL.md; it is byte-identical between the shared checkout andorigin/mainat827cacbf4b, so the implementer loads it as-is.3. Carrier state. The claim declares
Clause-②: yes, andneeds:contract-reviewis now on this card (added 2026-09-15T14:09:11Z, read back against the union). It should have gone on in the same stroke as the claim's label write; it did not, and that is my error, healed here rather than left as a half state. The label goes on the delivering PR the moment the PR exists — the carrier is the seat's to hang, never the implementer's.Nothing else in the claim changes: same branch, same in-flight check, same landing-order finding on #15820.
Generated by Claude Code
os-dev-report
{ "issue": 16059, "status": "done", "branch": "claude/issue-16059-startup-orchestrator-shipped-shape", "pr": "https://github.com/objectstack-ai/objectstack/pull/18303", "session": "session_01KB5PFtxuy1x3dcR5gxudx6", "premise_still_valid": true, "summary": "The ruling is executed end to end at e345ffa9e1. RETIRED (route 3, ADR-0049): IStartupOrchestrator plus StartupOptionsSchema / HealthStatusSchema / StartupOrchestrationResultSchema and their type aliases: 3 emitted defs, 8 exported names, 11 api-surface rows across ./kernel and ./contracts. RE-DECLARED: PluginStartupResultSchema now carries pluginName, success, optional durationMs, timedOut and the serializable error projection, and packages/core/src/plugin-loader.ts re-exports that type instead of declaring an interface, so the ruling's 'core imports the type from the spec' half is done, not optional. PREREQUISITE 2 (read the shape from the TREE) held and mattered: success and durationMs are part of the shipped shape, and durationMs is OPTIONAL in core (a plugin with no start() returns without it) where the old spec declared it required. PREREQUISITE 3 (re-verify 'nothing implements or consumes them') held: see tests. PREREQUISITE 4: objectui re-probed at the pinned sha, all zero with lit controls; cloud is NOT MEASURED. The one member the ruling left open, core's deprecated startTime, is TOMBSTONED, and a repo gate decided it rather than my judgement: see open_questions. That is the diff's only observable behaviour change, namely that ObjectKernel.startPluginWithTimeout() stops setting startTime beside durationMs. Everything else removes surfaces nothing read. Changeset: minor on @objectstack/spec and @objectstack/core (major is refused in the launch window), BREAKING banner, FROM-TO table, disposition marker 'registered startup-orchestrator-retired'.", "tests": "All readings at e345ffa9e1 unless stated; heavy runs through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-16059, verdict read from its printed VERDICT command-exit line; every exit code captured before any pipe. BUILD: pnpm --filter '@objectstack/core^...' build exit 0; pnpm --filter @objectstack/spec build exit 0 (spec's own workspace-dependency closure is empty, so leg (1) is the package build itself). TESTS: pnpm --filter @objectstack/spec test exit 0, 'Test Files 482 passed (482)', 'Tests 13682 passed (13682)'. pnpm --filter @objectstack/core test exit 0, 'Test Files 51 passed (51)', 'Tests 1316 passed (1316)'. pnpm --filter @objectstack/dogfood test exit 0, 'Test Files 137 passed | 1 skipped (138)', 'Tests 1099 passed | 3 skipped (1102)' (the playbook puts qa/dogfood in a retirement's default consumer radius). TYPECHECK: pnpm --filter @objectstack/spec --filter @objectstack/core typecheck exit 0, both test layers included. LINT: pnpm lint (= eslint . --no-inline-config, WHOLE REPO, no narrowing claimed or owed) exit 0 at e345ffa9e1. GATES: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack re-derived on the FINAL tree (112 families; the first derivation at a77b7955bc had 110, and the new retired-keys entry widened it), every command run with its exit code recorded, then reconciled: 'Run reconciliation: 112 derived, 112 run, 0 NOT-MEASURED, 0 UNRUN' and 'dispatch-gates --ran: 112 derived families accounted for'. All 112 exit 0. Two of them first exited 3 (PREREQUISITE NOT MET, not a finding): check:dual-build-cjs-loads and check:type-check-debt wanted the whole package closure built; after 'turbo run build --filter=./packages/* --filter=./packages/*/* --concurrency=2' (72/72 tasks) both re-ran at exit 0, type-check-debt reporting '5 ledger entries re-measured, 55 raw tsc errors total, none above its recorded number'. Three more had exited 3 or 1 on an unbuilt prerequisite in an earlier sweep (check:doc-formula-expressions, check:doc-security-posture, check:skill-examples) and are green in the final run. PREREQUISITE 3, re-measured on this card's base with same-corpus controls: outside packages/spec/src, StartupOptionsSchema / HealthStatusSchema / StartupOrchestrationResultSchema hit 0 files; IStartupOrchestrator hits 5 and orchestrateStartup 3, every one a released CHANGELOG.md or a generated artifact (api-surface/contracts.json, export-origins/contracts.json). Controls on the identical query lit: defineStack 347 files, ManifestSchema 62, PluginStartupResult 15. objectui at the pinned .objectui-sha 53ded82bf7: all six symbols 0, controls defineStack 27 and ManifestSchema 6. cloud: NOT MEASURED, not attached to this session. ROUTE-3 EVIDENCE printed by the build itself, both halves: '3 schema(s) left the published set since 827cacbf4b79, each declared (#4725)' naming all three defs at major 18, and '3 baseline deletion(s) since 827cacbf4b79 carry their own proof (#4650)' resolving each as 'def no longer emitted by this build'. REVERSE VERIFICATION (owed because this is a cross-package type change; it proves core reads the REBUILT .d.ts, not a cache). Scripted with a restore trap armed on EXIT INT TERM, absolute REPO_ROOT, blob hashes compared both ways, run from the COMMITTED state. Mutation: 'health: { healthy: true, checkedAt: 1 },' inserted into the success-path literal of packages/core/src/kernel.ts. On-disk proof: marker occurrences 0 then 1; HEAD blob 55b34588317406903b7a73faef2144284db894df vs mutated blob 4755ba728820c7bf19c5fa237dacd46c3ca2b7c9. MUTATED LEG EXIT=1 with 'src/kernel.ts(795,17): error TS2322: Type { healthy: true; checkedAt: number; } is not assignable to type undefined', so the spec tombstone's never crossed the package boundary. RESTORED LEG: blob back to 55b34588..., marker occurrences 0, 'git diff HEAD' empty, tsc exit 0. Direction observed = TURNS RED, the expected one, declared before the run. No dist preflight was owed on the mutated side: the mutated file is core SOURCE that tsc reads directly, and the artefact whose freshness is load-bearing is packages/spec/dist, rebuilt immediately before. OWN CONTROL-CHARACTER SWEEP beyond check:nul-bytes: grep -naP over all 30 changed files, no hits. CLAUSE-2 CARRIERS (reading only, per the dispatch): needs:contract-review IS on PR #18303, labels read back as ['documentation','size/xl','tests','tooling','needs:contract-review'], and node scripts/pm/check-clause2-carriers.mjs --pair 18303 exits 0, 'both carriers agree'. Neither was written by me. CI: NOT MEASURED, reported at local-verification close per the dispatch contract; CI on e345ffa9e1 is the PM's to read.", "gates": "112 of 112 derived families RUN and GREEN at e345ffa9e1 (0 unrun, 0 not-measured), verified by 'node scripts/pm/dispatch-gates.mjs --ran' against a re-derivation on the final tree, not against my own list. Named highlights, all exit 0: check:generated (15 artifacts, 0 stale), check:api-surface, check:authorable-surface, check:docs, check:export-origins, check:declaration-map, check:spec-changes, check:upgrade-guide, check:migration-registry, check:strictness-ledger, check:duration-unit-keys, check:spec-parsed-alias, check:variant-docs, check:liveness, check:dual-source-exports, check:exported-any, check:adr-0087-registration, check:changeset-no-major, check:quick-reference-counts, check:cross-package-test-inputs, check:test-source-alias, check:type-check-coverage, check:type-check-debt, check:dual-build-cjs-loads, check:nul-bytes. Plus pnpm lint whole-repo exit 0. check:react-declaration-parity is the one family this repo cannot run at all (it needs objectui's browser-dumped sdui.manifest.json); it is outside the derived list and is CI's.", "line_budget": "n/a: the diff touches no skills/** file (the published catalog is untouched), so no SKILL.md line budget applies. Nothing under .claude/** either, so the diff is NOT a governed surface and carries no maintainer quick-read section.", "deviations": [ "The PR body was written before the docs-drift round and does NOT mention the content/docs/getting-started/quick-reference.mdx row that the later commit e345ffa9e1 fixed. I did not PATCH it: the dispatch budget is four writes and explicitly forbids a body PATCH outside it. The seat may want to add one line to the Acceptance notes.", "No label write was performed. The dispatch budget lists POST /issues/PRNUMBER/labels, but the harness-override took needs:contract-review off my hands and the seat had already hung it. So 2 REST writes were spent, not 3.", "The three same-major rename registrations (kernel/StartupOptions:timeout, kernel/HealthStatus:timestamp, kernel/StartupOrchestrationResult:totalDuration) are KEPT, not absorbed, against a literal reading of the playbook's same-major clause. Reasons in the PR body: that clause is motivated by the conversion table's fixture-disjointness contract and all three entries record 'No D2 conversion' themselves; gate (b2) states the keep as the expected steady state; and the tree already holds 14 major-18 retired-key entries whose def is retired in the same major 18 (the #15513 family). Flagged because it is a judgement against the letter of a playbook line.", "The generated reference page keeps the frontmatter 'title: Startup Orchestrator' and 'description: Startup Orchestrator protocol schemas'. Both strings are derived from the module FILENAME by build-docs.ts, not from the module docblock, so the ruling's 'rewritten to describe the current contract' is satisfied in the page BODY only. Renaming startup-orchestrator.zod.ts would move the docs route, the export-origins paths and the pin file's module slot; the card fences the file to edit, not to move." ], "files_changed": "30 files, +984 / -855 vs the merge base 827cacbf4b. Source: packages/spec/src/kernel/startup-orchestrator.zod.ts, packages/spec/src/contracts/startup-orchestrator.ts, packages/core/src/plugin-loader.ts, packages/core/src/kernel.ts. Tests: those two modules' .test.ts rewritten, the new packages/spec/src/kernel/startup-orchestrator-retirement.test.ts (absence pins over every public entry via the export-origins artifact, plus survival pins), packages/spec/src/type-alias-convention.pin.test.ts (Iso467 and Iso469 leave with their defs, 785 to 783, both prose counts updated), packages/core/src/kernel.test.ts. Registry entries (new): retired-defs/18.kernel__StartupOptions.ts, 18.kernel__HealthStatus.ts, 18.kernel__StartupOrchestrationResult.ts; retired-keys/18.kernel__PluginStartupResult__plugin.ts, __health.ts, __startTime.ts; semantic/18.startup-orchestrator-retired.ts; plus the regenerated src/migrations/registry.ts. Generated: api-surface/{kernel,contracts}.json, export-origins/{kernel,contracts}.json, declaration-map/kernel.json, authorable-surface/kernel.json, authorable-defaults/kernel.json, json-schema.manifest/kernel.json, content/docs/references/kernel/startup-orchestrator.mdx, content/docs/references/index.mdx (1525 to 1522 schemas, Kernel 162 to 159), docs/audits/2026-07-unknown-key-strictness-ledger.counts.md. Hand-written docs: content/docs/getting-started/quick-reference.mdx (one row). Changeset: .changeset/16059-startup-orchestrator-shipped-shape.md. The two deletions of generated evidence (json-schema.manifest/kernel.json 3 keys, authorable-surface/kernel.json 16 lines) are the deliberate ones both gates demand in the same PR.", "mcp_calls": "0 (zero) MCP GitHub tool calls, no tool names to list: every GitHub read and write went through curl against the REST proxy with GITHUB_TOKEN.", "api_writes": "2 REST proxy writes: POST /repos/objectstack-ai/objectstack/pulls (draft PR #18303; body read back immediately, stored byte-identical to what I sent except the trailing newline the platform strips, exactly one session-URL footer under its rule line) and POST /repos/objectstack-ai/objectstack/issues/16059/comments (this report). Separately 4 git pushes to claude/issue-16059-startup-orchestrator-shipped-shape (the empty-branch routing probe, then 66801ae148 plus a77b7955bc, then ac0fe6f145, then e345ffa9e1). NO POST /issues, NO PATCH of any issue or PR body, NO label write.", "open_questions": [ { "question": "Prerequisite 2 asked me not to pick silently on core's deprecated startTime. I did not: a repo gate picked. Mirroring it reds check:duration-unit-keys ('[unit-in-prose-not-in-name] startTime: describe names ms but the key name carries no unit') and neither of that rule's two schema-declared exemptions fits (it is not an EpochMs instant, it mirrors no external standard; that rule has no baseline and no allowlist). So the playbook did not settle it but the tree did, and the only remaining choices were to rename it or to end the ADR-0087 L1 window. I ended the window: the spec tombstones startTime and ObjectKernel stops populating it. This is the ONLY observable behaviour change in the diff and the only judgement call, so it is yours to confirm.", "options": [ "A (shipped) tombstone startTime on the spec and stop the kernel setting it: the L1 window closes here, the alias becomes a tsc error and a parse error carrying 'read durationMs', and the contract stays gate-legal", "B mirror it as startTimeMs in both packages under a new ADR-0087 rename: keeps a populated alias one more release, at the cost of minting a spelling nothing has ever produced for a member core's own TSDoc calls 'slated for removal'", "C leave core declaring its own PluginStartupResult so it can keep startTime: REFUSED by the ruling ('core imports the type from the spec instead of declaring its own'), recorded only so the third reading is visible" ], "recommendation": "A, because it is the only option that satisfies both standing rulings at once. The ruling on this card says the spec describes what the kernel produces and core imports that type; ruling B on #14478 says an elapsed number carries its unit in the key name. A member named startTime holding elapsed milliseconds cannot satisfy both, and it was already documented as slated for removal, so the schedule ends one release early rather than the contract carrying a name the tree refuses. Reversal is cheap and local if you disagree: option B is one commit in two packages plus a retired-keys entry." } ], "out_of_scope_findings": [ "noted, not filed: content/docs/references/kernel/startup-orchestrator.mdx keeps the frontmatter title 'Startup Orchestrator' and description 'Startup Orchestrator protocol schemas' after the page body was rewritten to describe the result contract. build-docs.ts derives both from the module FILENAME, so no edit to the module can move them; the fix is either a file rename (which moves the docs route, the export-origins paths and the pin file's module slot) or a generator change. Carrier: the next card that renames or splits packages/spec/src/kernel/startup-orchestrator.zod.ts. If none is ever queued, the honest answer is: no carrier.", "noted, not filed: the docs-drift sweep the seat handed me was re-measured with the word-boundary form rather than a naive grep. Exactly ONE page outside content/docs/references/ and content/docs/releases/ names any retired symbol, quick-reference.mdx, one row, and it is FIXED in this PR (e345ffa9e1). The two rows the seat asked me to measure rather than assume are both false positives, measured not assumed: content/docs/automation/flows.mdx durationMs is the automation RUN's own durationMs (a flow run log line, lines 1106 and 1118), unrelated to PluginStartupResult; content/docs/kernel/cluster.mdx line 409 RETIRED_DEFS_BY_MAJOR[18] is prose about a DIFFERENT retirement (metadata-changed-event-payload-retired), i.e. about the machinery this PR registers into, not about the retired surface. The healthCheck family of hits is PluginHealthCheckSchema in plugin-lifecycle-advanced.zod.ts, which this PR does not touch.", "NOT MEASURED, not filed: the four content/docs/releases/ pages the drift run flagged were not opened or edited, because a code PR never touches that tree. I make no claim about whether any of them is now wrong; if one is, it is a docs-only PR or an issue, never a rider here.", "Nothing in this round met any of the three filing classes (reproducible defect, declared-contract violation, or a trap producing metadata the runtime refuses or silently drops), so no card is handed over for filing." ] }
Generated by Claude Code
One maintainer decision is owed before PR #18303 can land
The implementer returned
status: donewith 112 of 112 derived gate families green and whole-repo lint clean, and one non-emptyopen_questionsentry. That entry is the only judgement call in the diff, and it is on the manual floor, so this seat neither answers it nor lands the PR while it stands. Recorded here rather than left in a chat thread.The question
packages/core/src/plugin-loader.tsdeclaresPluginStartupResult.startTimeas@deprecated, under the ADR-0087 L1 window its own TSDoc describes — 「the old shape keeps working while the fleet moves」 — and marks it "slated for removal". The ruling on this card requires the spec to re-declare the shape core ships and core to import that type instead of declaring its own, so the two declarations become one andstartTimecan no longer differ between them.Mirroring it into the spec is reported to turn
check:duration-unit-keysred —[unit-in-prose-not-in-name] startTime: describe names ms but the key name carries no unit— with neither of that rule's two schema-declared exemptions fitting (it is not anEpochMsinstant and it mirrors no external standard; the rule carries no baseline and no allowlist). That reading is being verified independently atCONTRACT_REVIEW_TIER; it is the implementer's claim until that record lands.So the playbook did not settle it, but the tree narrowed it to two:
what it does cost A — shipped at e345ffa9e1the spec tombstones startTime;ObjectKernel.startPluginWithTimeout()stops populating itthe L1 window closes one release early. This is the diff's only observable behaviour change — everything else removes surfaces nothing read B mirror it as startTimeMsin both packages under a new ADR-0087 renamekeeps a populated alias one more release, at the cost of minting a spelling nothing has ever produced, for a member core's own TSDoc calls slated for removal A third reading — leave core declaring its own
PluginStartupResultso it can keepstartTime— is refused by the ruling's own words (「packages/corethen imports the type from the spec instead of declaring its own」) and is recorded only so the full option set is visible.Why it is not this seat's to answer
The ruling's last sentence names this exact class: 「If any out-of-repo consumer of the retired interface is found during the round, record it here before removing — a deprecation window becomes a maintainer question.」 Ending an L1 window early is a change to published contract semantics and a hard-to-reverse action, both on the manual floor. The implementer did the right thing: it flagged rather than choosing silently, and it recommended A with its reasoning.
The measurement gap this seat wants closed before anyone answers
The implementer measured
PluginStartupResultbroadly (15 files as a lit control) but this seat does not see a measurement ofstartTimereaders specifically, in this repo or at objectui's pinned sha.objectstack-ai/cloudis not attached to this session and is NOT MEASURED — ⛔ that is not a clean probe. The at-tier reviewer has been asked to derive the reader census with same-subject controls. An answer given before that census is an answer given without the one fact that decides how much the window is worth.State
PR #18303 stays draft, with no auto-merge and no enqueue, until this is answered.
needs:contract-reviewis on both carriers andcheck-clause2-carriers --pair 18303exits 0. CI one345ffa9e1at 16:12:15Z: 35 checks, 33 success, 2 skipped, 0 failing, 0 in progress. Nothing else on this card is blocked, and the rest of the lane's queue keeps moving.
Generated by Claude Code
Ruling recorded — option A, the
startTimewindow closes here (maintainer, 2026-09-15)Maintainer reply, verbatim, quoted not translated: 「A 都选 A,harness 照旧派,记例外」 — the first clause answers this card.
Ruled. Option A, which is what
e345ffa9e1already ships:PluginStartupResult.startTimeis tombstoned on the spec andObjectKernel.startPluginWithTimeout()stops populating it. The ADR-0087 L1 window closes one release early rather than the contract carrying a name this tree'scheck:duration-unit-keysrefuses. Option B (mintstartTimeMsin both packages) is not adopted.What this rests on, all measured rather than argued:
- the gate conflict is real for any mirror that tells the truth about the unit — the at-tier reviewer reproduced it in its own worktree (exit 1,
[unit-in-prose-not-in-name] … startTime), and neither schema-declared exemption fits; - zero readers of
startTimein both measurable corpora, with lit same-subject controls (durationMsreads on the same result light up atkernel.test.ts:637-654andkernel.ts:628).objectstack-ai/cloudis NOT MEASURED — not attached to this session, and that is recorded as an absence of reading, never as a clean probe; - the deprecated-and-populated alias shipped in exactly one released version,
@objectstack/core@17.4.0, whose own TSDoc promised only 「nothing has to change on THIS release … slated for removal」.
No code change follows from this ruling: the PR already implements A. The seat proceeds to landing.
Generated by Claude Code
- the gate conflict is real for any mirror that tells the truth about the unit — the at-tier reviewer reproduced it in its own worktree (exit 1,
ACCEPT — #16059, PR #18303, head
e345ffa9e179ac123cc983a68714df45db430641Contract review of record: PR #18303 comment
5683982169— VERDICT: PASS, naming this head. Its reasoning is not restated here.Tier, verified per transcript, not from the reviewer's word. The review run's transcript carries 173 occurrences of
"model":"claude-fable-5-1"and zero of anything else —CONTRACT_REVIEW_TIER. The instrument discriminates: two implementer transcripts from this same session, counted identically, read 348 and 223 occurrences of"model":"claude-opus-5"and zero fable.Landing pre-check, all three at 2026-09-15T22:59:14Z
- At-tier PASS naming the current head — the record's
Head-sha:equals the PR's current head, read fresh. Read back from GitHub: the five machine-read lines stored intact,Served-tier:carrying the constant's NAME, no angle-bracket fragment. check-clause2-carriers --pair 18303→ exit 0, and it confirms a review of record names this head.- 35 check runs,
total_countequal to the 35 returned: 33 success, 2 skipped, 0 failing, 0 incomplete. The two skipped are the always-skipped family this lane's four landings all carried; a skip is not a pass and is not reported as green.
Seat spot-checks, not adopted from either agent
- Fences held:
packages/spec/src/shared/retired-key.ts(spec:retiredKey()tombstones are emitted into a repeater row'sitems.properties, so the served schema offers six columns the parse door refuses #17502's surface) absent from the diff;content/docs/releases/**untouched; no.claude/**orskills/**, so this is not a governed-surface PR. - The reviewer caught a reporting defect the implementer did not: the ablation leg was taken at
55b34588…, thekernel.tsblob at the base and the first three commits, not at the reviewed head (57d2340c…). The reviewer reconstructed the mutated blob exactly, confirmed the leg was sound for the tree it ran on, and then re-ran the ablation at this head itself. A reporting defect, not a contract defect — recorded rather than waved past.
Carried forward, neither blocking
- Tombstone dating. The three new
retiredKey()prescriptions read 「was removed in @objectstack/spec 18 (ADR-0049)」 while the changeset shipsminor, so ADR-0087's 2026-09-13 amendment says the token should name the 17.x release. The base already carries 13 identical lines from the same major-18 wave, no gate reads the token, and the remedy inside each string is intact. This is a repo-wide pattern rather than this PR's defect, so it goes to the sweep that owns it instead of holding a green PR. - A stale comment at
packages/spec/src/type-alias-convention.pin.test.ts:2225-2226says the alias is 「mirrored, not defaulted」, written beforeac0fe6f145tombstoned it. Prose residue in a test comment.
Maintainer decision. Option A ruled at
5689228298; the PR already implements it, so no change follows.Proceeding to clear both carriers, flip ready and enqueue.
Generated by Claude Code
- At-tier PASS naming the current head — the record's
- added a commit that references this issue
on Sep 17, 2026 - added a commit that references this issue
on Oct 7, 2026
Filed bare by the
os-devseat working #15820, as an out-of-scope finding surfaced while re-deriving that card's anchors. Nodomain:*applied, no assignee — routing is triage's call. All readings taken atorigin/maine1d4f9e3f99aa16818b03e831442a2758697ef3a.The finding
packages/spec/src/contracts/startup-orchestrator.tsandpackages/spec/src/kernel/startup-orchestrator.zod.tsdeclare a full plugin-startup orchestration contract —IStartupOrchestratorwithorchestrateStartup, plusPluginStartupResultSchema,StartupOptionsSchema,StartupOrchestrationResultSchemaandHealthStatusSchema. It is exported from the package (packages/spec/api-surface/contracts.json:157,export-origins/contracts.json:157) and documented for consumers atcontent/docs/references/kernel/startup-orchestrator.mdx.Nothing in this repo implements it, parses with it, or consumes it.
Measurements, each with a firing control
Implementers of
IStartupOrchestrator, whole repo: zero outsidepackages/spec. Every hit is one of: the declaration itself,packages/spec/src/contracts/startup-orchestrator.test.ts(four ad-hoc object literals annotated with the type — shape pins, not an implementation), CHANGELOG rows, generated api-surface JSON, or the docs page. Same fororchestrateStartup: no production call site anywhere.Consumers of
PluginStartupResultSchemaoutsidepackages/spec: zero. The only two hits arecontent/docs/references/kernel/startup-orchestrator.mdx(a docs import) and a doc comment added by the #15820 PR.Control fires. The same search shape finds contract interfaces in
packages/corethat ARE implemented —packages/core/src/metadata-service-contract.tsandpackages/core/src/fallbacks/memory-metadata.ts— so the probe reaches, and the zeros above are readings rather than a dead search.What actually starts plugins.
ObjectKernel.startPluginWithTimeoutinpackages/core/src/kernel.ts, returningpackages/core/src/plugin-loader.ts's ownPluginStartupResult. The two declarations share a name and nothing else: core's carriespluginName: string, a liveerror?: ErrorandtimedOut?: boolean; the spec's carries apluginobject, a serializable error projection and ahealthmember. Core neither imports nor references the spec contract.Why this is worth a card rather than a shrug
ADR-0078 (no silently inert metadata) and ADR-0049 (enforce-or-remove) both target exactly this: a surface that is declared, exported and documented, and that no runtime reads. The cost here is not a crash, it is a false map. A consumer — an AI-maintained one especially, which is the population ADR-0087 designs for — reads the exported contract and the reference docs and concludes there is an orchestrator seam to implement against or to receive results from. There is not. Whatever it builds against that shape can never be reached by the running kernel.
The two candidate dispositions look opposite and only triage should pick:
PluginStartupResultSchemaactually parses. This is the direction the spec's own docs already promise.⛔ Not folded into #15820, which is deliberately narrow: that card aligns one field name in
packages/coreand does not touchpackages/spec. This is the wider question that alignment made visible, and it is apackages/specquestion.Generated by Claude Code
Generated by Claude Code