Repository navigation
tooling(adr-0087): a seventh disposition category, runtime-export-retired, for a changeset that retires published runtime exports and carries a FROM to TO block (ruling A on #22915) #22918
Description
Protects: the ADR-0087 changeset disposition vocabulary and its gate (scripts/check-adr-0087-registration.mjs) — the published upgrade contract every BREAKING changeset of @objectstack/* ships to consumers through CHANGELOG.md and os migrate meta.
Ruled: 6116982351 · letter Q1 B (narrowed) · Q2 A · 2026-10-12T03:03Z
Filed by the director seat (summon #37, session_01LqNgdPZB2Jnr5HpGbaqLeB) on the maintainer's ruling A on #22915 (comment 6116351873, the maintainer's word verbatim 「22915 同意」). Filing gate ①: the fix lands in a gate script and a governed ADR addendum, so the card is tooling · domain:devx · area:devpath · priority:p2, in pm:queue. ⛔ Not a claim. ⛔ Not a decision: every design choice below was ruled on #22915; a dev that finds a premise false reports the fork, never a different letter.
What to build
A seventh ADR-0087 disposition category, runtime-export-retired, for a changeset that retires published runtime exports and carries a FROM → TO block.
- Spelling:
<!-- adr-0087: not-required (runtime-export-retired <package>#<name>[, ...]) <why> -->. - Predicates, re-verified on every gate run (the D7 / [finding] check-adr-0087-registration has no honest disposition for a published TYPE-surface narrowing — every category is refused, so the class is pushed to drop the
**BREAKING**token instead #13080 method — a positive, re-runnable finding, never an allow-list):- each named
<package>#<name>is reachable from the package's published entry at the merge base and is absent at HEAD (pick the resolver the api-surface gates already use; do not write a new one); - none of the named exports is a metadata surface: not under
*.zod.ts, not underpackages/spec/src/contracts/**, not an object definition; and no metadata surface references the export at the merge base; - a FROM → TO block in the body is admitted only when its FROM operands are exactly the named exports (any other FROM operand keeps the
no-migration-prescriptionrefusal the category would otherwise inherit).
- each named
- ADR-0087 addendum: record the category beside
runtime-interface-only(D7) andtype-surface-only([finding] check-adr-0087-registration has no honest disposition for a published TYPE-surface narrowing — every category is refused, so the class is pushed to drop the**BREAKING**token instead #13080), with the worked example.changeset/15205-email-templates-resolve-registry.md(PR feat(plugin-email)!: email templates resolve from the registry; the built-in seed, the boot sweep and the live projector retire (ADR-0131 D6/D10, C4 stage 2) #22875,7de0265fc7: nine exports and two plugin options of@objectstack/plugin-email, a six-line FROM → TO block, admitted today only because the detector requires an arrow the list lines do not carry) and the measured history, as the two neighbours have. - Self-tests: the category accepted on the worked example; refused when a named export still exists at HEAD, when a named export is a
*.zod.tssymbol, when the FROM → TO block names an operand outside the named set, and when the marker names nothing; the six existing categories unchanged (their self-tests stay green). - Not in scope: re-grading existing changesets (ruled: new retirements use the category); any change to
no-migration-prescription's refusal or to the detector's arrow rule (option C was not taken).
Governing text
- ADR-0087 addendum (
docs/adr/0087-metadata-protocol-upgrade-contract.mdnear:727–:742): the closed vocabulary; "no-migration-prescriptionis refused by a body that carries a migration prescription"; D7 (:747): the compiler is the channel, and an exemption "held by a detector miss rather than by a positive finding" is the shape to retire; D8 (:864). - [finding] check-adr-0087-registration has no honest disposition for a published TYPE-surface narrowing — every category is refused, so the class is pushed to drop the
**BREAKING**token instead #13080's ruling 5468992520 and PR feat(scripts,adr): a published TYPE-surface narrowing gets an honest ADR-0087 disposition — the sixth category, paid for by a base-side type reading #13596 (+956 / −10): the sixth category, the precedent for shape and size. - The ruling on [Decision] ADR-0087: a changeset that retires published runtime exports and carries a FROM → TO block has no honest disposition category: a seventh category (A), a registered D3 entry (B), or a dated reading plus a looser detector (C)? #22915 (comment 6116351873): the letter, the predicates, and what was not taken.
Landing
docs/adr/**is a governed surface (Tier H): the PR carries## 维护者速读(草稿), the seat's ACCEPT swaps it for the 终稿 and requests review fromos-zhuangandhotlong; the maintainer's APPROVED review lands it through the owning seat.Clause-②: no(nopackages/specschema change).- Workload:
domain:devx· 1 PR · precedent PR feat(scripts,adr): a published TYPE-surface narrowing gets an honest ADR-0087 disposition — the sixth category, paid for by a base-side type reading #13596 (+956 / −10). - Done when: the gate accepts the worked example under the new marker, refuses the four negative cases above, the six existing categories' self-tests stay green, and the addendum names the category with its predicates and example.
Activity
objectstack-fleet commentedon Oct 12, 2026
Claim: PM loop round 2
Session:
session_012yK1ddAdAqfzwigRHZq3r2Account:
os-warren(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-22918-runtime-export-retiredWorktree:
objectstack-issue-22918Domain:
domain:devxSeat:
domain:devx#1Ruling-ref: 6116351873 (#22915, letter A, re-read in this act)
File surface:
scripts/check-adr-0087-registration.mjs(the seventh category, its three predicates, its self-tests) anddocs/adr/0087-metadata-protocol-upgrade-contract.md(the addendum entry besideruntime-interface-onlyandtype-surface-only). Read-only: the resolver an existing api-surface gate already uses. ⛔ No edit under.changeset/**(existing changesets are not re-graded). (stop on breach; explain in the report)Container & model:
M,mode:subagent,model: default—dispatch-gates --tier --repo objectstack-ai/objectstackon both paths at7dbb89dc5: "no path-derived mandate: the surface hits none of the 3 declared glob(s)"; the card carries design judgment (the resolver choice, the FROM-operand match), so the default tier, not the floor.Clause-②: no
Responsibility:
n/a — not a defect cardThread-read: none
Serial constraints cleared:
none— read 2026-10-12T02:04Z onorigin/main7dbb89dc5: no open PR (11 read, file lists intersected) touches either file; no in-flight claim (13 openpm:dispatchedcards read) declares either file; #22902, #22519 and #22340 name ADR-0087 only as markers in their own changesets, which this card leaves untouched (the six existing categories keep their behaviour).Premises re-run in this act (2026-10-12T02:07Z):
runtime-export-retiredhas 0 hits underscripts/,docs/adr/and.changeset/onorigin/main7dbb89dc5; the controltype-surface-onlyhits 49 lines in the gate and 6 in the ADR..changeset/15205-email-templates-resolve-registry.mdis onmain. PR #22875 is merged as7de0265fc7, PR #13596 as933ea6198a(+956 / −10), #13080 and #22915 are closedcompleted. The gate's last touch is7873a0565(2026-10-11, #22796), so there is no same-day churn on the surface.Generated by Claude Code
objectstack-fleet commentedon Oct 12, 2026
os-dev-report
{ "issue": 22918, "status": "needs_decision", "branch": "claude/issue-22918-runtime-export-retired", "pr": null, "session": "session_012yK1ddAdAqfzwigRHZq3r2 (mode:subagent, the dispatching PM's session)", "premise_still_valid": false, "summary": "No code written. Two premises of ruling A (#22915, comment 6116351873) are false when measured, and each one is a fork only the maintainer can rule. FORK 1: the ruled predicate 3 refuses the worked example. Its six-line FROM/TO block (.changeset/15205-email-templates-resolve-registry.md, merged as 7de0265fc) names nine retired exports on four lines, plus two FROM operands that are not exports: `new EmailServicePlugin({ seedTemplates: false })` and `new EmailServicePlugin({ templates: [...] })`. These are the members seedTemplates and templates of EmailServicePluginOptions, removed at 7de0265fc, while EmailServicePlugin and EmailServicePluginOptions stay exported (plugin-email src/index.ts:17-18 at 7de0265fc). The rule 'FROM operands exactly the named exports' therefore refuses lines 5-6. The card's third negative case (an operand outside the named set) refuses the card's own positive case. FORK 2: no existing api-surface resolver can answer predicate 1 at a merge base. build-api-surface.ts, check-published-readme-exports.mjs (typeSurface().exportsOf) and check-exported-any-returns.mts (scan) all read a BUILT dist .d.ts. dist/ is gitignored, and the merge base 9e9e6f97f has 0 dist files under plugin-email. The one function that answers export membership, typeSurface, is module-private: it would have to change to be importable, which is the dispatch's stop condition. What does hold: predicates 1-2 are true for all nine exports at the base on a source reading (measured below). H4 and H6 are confirmed.", "tests": "No diff, so no gate is owed. Readings, all on origin/main eab7236ff (this checkout's HEAD), worked-example range 9e9e6f97f..7de0265fc. (H4) Gate exports, driven by script: findMigrationPrescription(body of 15205) = null; control: give ONE of its FROM lines an arrow and it returns {branch: from-to-label}. The six FROM lines carry no arrow, so H4 is CONFIRMED. The real gate `node scripts/check-adr-0087-registration.mjs --base 9e9e6f97f --head 7de0265fc` printed EXIT=0 and '1 declared-breaking changeset(s) ... not-required (no-migration-prescription)'. (H6, before) `node scripts/check-adr-0087-registration.mjs --base origin/main` printed EXIT=0 and 'this PR adds no declared-breaking changeset (0 non-breaking changeset(s) seen)'. There is no after-run because no change was made. `--list` reported 129 declared-breaking changesets in stock, all 129 with a disposition: 81 hold no-migration-prescription (the ruling's 81 is confirmed), and 3 hold runtime-interface-only or type-surface-only. (Predicate 1, source reading) At 9e9e6f97f, plugin-email src/index.ts names all nine exports in two `export {...} from` clauses; at 7de0265fc it names none. (Predicate 2) At the base, the gate's own metadataSurfaceKind plus maskComments over 381 metadata-surface files found 2 raw hits and 0 code hits. Both raw hits are comments in packages/platform-objects/src/audit/sys-email-template.object.ts, naming createSysEmailTemplateLoader and bootstrapDeclaredEmailTemplates. (Predicate 3 reader population) The arrowless list line '- FROM `x` TO ...' appears in 2 of the 415 stock changesets, both declared-breaking, and the detector returns null on both: 15205 holds no-migration-prescription, 15206-meta-doors-environment-only.md holds registered. (Resolver) dist/ is gitignored (.gitignore:8), and `git ls-tree` at 9e9e6f97f lists 0 plugin-email/dist files. typeSurface sits at check-published-readme-exports.mjs:1114 with no export. check-exported-any-returns.mts refuses a stale dist (:543). Source-entry convention: resolveTypesEntry (imported read-only) maps '.' to dist/index.d.ts AND src/index.ts exists for 66 of the 70 published packages; @objectstack/client-react has no src/index.ts, and console, skills and create-objectstack have no types entry. Loading typescript through ts-parse.mjs took 431 ms on this container; the gate takes 3.3 s wall on an empty diff.", "mcp_calls": "0 (none)", "api_writes": "1: this os-dev-report comment, sent by scripts/pm/post-stamped.mjs as one repository_dispatch on the fleet-write relay (auto resolved to dispatch in this cloud container). The relay executes it as POST /repos/objectstack-ai/objectstack/issues/22918/comments, written as objectstack-fleet[bot]. The empty-branch push is a git push, not a REST write. No pr_create and no label-write, because there is no PR.", "gates": "NOT MEASURED: every dispatch-listed family. Reason: no diff (needs_decision before any edit). H6 has its before-reading only (exit 0, quoted in tests).", "line_budget": "0 lines changed (precedent PR #13596 +956/-10; estimate if the forks are ruled as recommended: about +1000 to +1300)", "deviations": [ "Stopped before the first edit. The dispatch's resolver clause ('if it must change to be importable, stop and report the fork') applies to typeSurface().exportsOf, and fork 1 would otherwise change done-when.", "Branch claude/issue-22918-runtime-export-retired was pushed empty at eab7236ff as the write probe and has no commits. It is left on the remote for the PM to reuse or delete. The local worktree is removed." ], "files_changed": [], "open_questions": [ { "question": "FORK 1: the worked example's FROM/TO block names two plugin options (EmailServicePluginOptions.seedTemplates and .templates, written as `new EmailServicePlugin({...})`). These are not exports, so ruled predicate 3 refuses the very example the card says the category must accept. Which reading holds?", "options": [ "A: keep the ruled predicates. The category covers exports only, and the FROM operands must equal the named exports in every FROM/TO spelling, the arrowless list line included. The worked example is refused as written (lines 5-6). The self-test accepts its export half (4 lines, 9 names) and pins the full text as the 'operand outside the named set' refusal. Done-when becomes 'accepts the export half, refuses the option half'. Residue: a retirement of a plugin option that carries a FROM/TO block still has no category that proves it. Cost: smallest; no grammar change.", "B: widen the grammar so it can name a retired MEMBER of a still-published export, PACKAGE#EmailServicePluginOptions.seedTemplates. Predicate 1 then reads 'the container is reachable at the base, and the member is declared at the base and absent at HEAD'. Predicate 3 needs a ruled rule for matching an operand that names a SURVIVING export (`new EmailServicePlugin({ seedTemplates: false })`) to that member, which means resolving the constructor parameter's type at two revs. Cost: about 2x A, and it widens what the category admits.", "C: read predicate 3 only through the existing detector (arrow and table forms). The arrowless block is invisible to it, so the worked example is admitted by the same detector miss D7 names as the shape to retire. That contradicts the ruling's own 'inherits nothing it cannot prove'. Cost: smallest, but it is a hole." ], "recommendation": "A, on the four axes. Real business need: the option half is measured at one changeset's two lines; the arrowless shape appears in 2 of 415 stock changesets and only 15205 holds the exemption. Long-term soundness: A keeps D7's positive-finding rule and 'inherits nothing it cannot prove', while B grows constructor-type resolution inside a text gate for one case and C brings back the detector-miss shape. Making AI authoring mistakes harder: under A a FROM operand that is not a retired export is refused loudly, B's wider grammar resolves loosely, and C teaches that an arrowless list passes. No scope creep at startup stage: A is smallest, B expands the grammar for one changeset, and C is free only because it is a hole. If the maintainer wants option retirements covered, B is the additive follow-up on top of A." }, { "question": "FORK 2: predicate 1 ('reachable from the package's published entry at the merge base'). The ruling says to pick the resolver the api-surface gates already use, but every one of those reads a BUILT dist .d.ts, which does not exist at a merge base, and the export-membership half (typeSurface().exportsOf) is module-private. Which resolver?", "options": [ "A: run TypeScript's getExportsOfModule (the call build-api-surface.ts, check-published-readme-exports.mjs and check-exported-any-returns.mts all make) over the package's SOURCE entry at each rev. It goes through createProgramChecked (ts-parse.mjs) with a CompilerHost that reads blobs at that rev. The entry is located by resolveTypesEntry (imported read-only) plus tsup's dist/index.d.ts to src/index.ts mapping, which holds for 66 of 70 published packages; any other package is refused, never guessed. No new resolution rule is written: the compiler resolves export *, aliases and type-only exports. Cost: the gate takes a typescript dependency (431 ms per run through a static import, or scan() becomes async), and its header's 'zero third-party imports' is retracted. Both sandbox staging sites already borrow node_modules. Roughly +150 lines of glue on top of the category.", "B: write a source-text, one-hop reader from the gate's own primitives (importSpecifiersFor plus an exported-declaration read of src/index.ts), refusing `export *` as unresolvable. It needs no new dependency and is the smallest option. But it is a new resolver, which the ruling excluded, and it refuses honest claims reached through `export *`. That is the #12881 lesson: a false refusal pushes the author toward a dishonest disposition.", "C: commit per-package export snapshots (the shape of spec's api-surface/ENTRY.json, readable at any rev) for all 70 published packages. This is the only rev-readable form of 'the api-surface resolver', but it is a new generated-artifact family with its own gate, out of scale for one measured member." ], "recommendation": "A, on the four axes. Real business need: one certain member, and A answers it with zero new resolution rules. Long-term soundness: A reuses the compiler's export semantics, consistent with D7's 'the compiler is the channel', while B is a second implementation of export resolution and C is right in principle but a separate project. Making AI authoring mistakes harder: A answers 'reachable' exactly as the consumer's compiler does, and B's export-star blind spot produces false refusals. No scope creep at startup stage: A adds one dependency to an existing gate and no new gate, while C adds a gate family (new gates default to no). Stated deviation, for the maintainer to accept: A reads the source entry that the published .d.ts is built from, not the .d.ts itself, because a merge base has no build." } ], "out_of_scope_findings": [ "carrier: this card's implementation (it edits the same file, and predicate 3 must read exactly this shape) · noted, not filed. The 'residual blind spot' paragraph in scripts/check-adr-0087-registration.mjs names NO FRAMING, UNFRAMED TABLE and IMPERATIVE SENTENCE, but not the arrowless list line '- FROM `x` TO `y`', on which findMigrationPrescription returns null: 2 of 415 stock changesets at eab7236ff (15205-email-templates-resolve-registry.md, 15206-meta-doors-environment-only.md). The ruling keeps the detector's arrow rule unchanged (C not taken), so this is a self-description gap, not a detector defect. Dedupe words: arrowless FROM TO, residual blind spot, findMigrationPrescription, no-migration-prescription." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 12, 2026
Release:
session_012yK1ddAdAqfzwigRHZq3r2(domain:devxseat 1) · reason: two premises of ruling A on #22915 (6116351873) are false as measured (dev report6116609923, each fork re-read by this seat) · destination:needs-user-decision, with the analysis below. Branchclaude/issue-22918-runtime-export-retiredsits ateab7236ffwith no commits; the next claim may reuse it (noResume-from).Seat verification of the dev report (read on
origin/maineab7236ff).changeset/15205-email-templates-resolve-registry.mdat7de0265fc, lines 29–30 readFROM `new EmailServicePlugin({ seedTemplates: false })`andFROM `new EmailServicePlugin({ templates: [...] })`. At7de0265fc,packages/plugins/plugin-email/src/index.ts:17-18still exportsEmailServicePluginand the typeEmailServicePluginOptions. The names retired on those two lines are members of that options type, not exports. The ruled predicate 3 ("its FROM operands are exactly the named exports") therefore refuses the worked example that the card's done-when requires the gate to accept. The changeset's own marker already says so: "two EmailServicePluginOptions members".dist/is gitignored (.gitignore:8). At the worked example's base9e9e6f97f,git ls-treeholds 0 files underpackages/plugins/plugin-email/**/dist/.scripts/check-exported-any-returns.mtsreads a built.d.ts(:13,:545). Inscripts/check-published-readme-exports.mjs,typeSurface(:1114) is a module-private function. No existing api-surface resolver answers "reachable from the published entry at the merge base" without a build, or without a change that makes it importable. A change like that is the dispatch's stop condition.nullon the worked example's arrowless FROM lines. The 81 pendingno-migration-prescriptionchangesets also hold (dev--list: 129 declared-breaking, 81 on that category).:361–:364). So atypescriptimport adds no install step there, but it retracts the header line "Zero third-party IMPORTS".维护者速读
一句话:裁决 A 新增的「已退役导出」类别有两处要你再定。按裁定的规则,它会拒收自己的样例。现有工具在合并基点上也读不出「这个包当时公开导出了什么」。两个子问题一次裁清,裁完本席直接重新派发。
决策分析(两个子问题,一次裁清)
Governing text:
docs/adr/0087-metadata-protocol-upgrade-contract.md:747):编译器才是这类变更的通道;豁免要靠正面、可复跑的发现,而不是检测器漏检。6116351873:「The category inherits nothing it cannot prove」。是否改协议: 与原裁决相同,改 ADR-0087 附录(受管面,Tier H),不碰
packages/spec。前提(带复查命令):
git show 7de0265fc:.changeset/15205-email-templates-resolve-registry.md | grep -n 'new EmailServicePlugin'。阳性对照:同一文件里createSysEmailTemplateLoader必中。git ls-tree -r --name-only 9e9e6f97f -- packages/plugins/plugin-email | grep -c /dist/,结果为 0。对照:去掉 grep 后能列出src/文件。typeSurface没有导出。复查:git show origin/main:scripts/check-published-readme-exports.mjs | grep -n typeSurface,只见定义和内部调用,没有export。Q1:这个类别要不要覆盖「已退役的插件选项(类型成员)」?
包#类型.成员。谓词 1 改为「类型在基点可达、成员在基点存在且在 HEAD 消失」。谓词 3 要把new EmailServicePlugin({ seedTemplates })对上EmailServicePluginOptions.seedTemplates,需要在两个版本上解析构造参数类型业务含义直译: A 只给「删掉公开函数」发合格证,「删掉配置项」暂时不发。B 两样都发证,但验货流程更复杂。C 不验货直接盖章。
四轴:
cargo-semver-checks:「公开条目消失」和「结构体字段或方法消失」是两条独立的 lint。A 正是先立「导出级」这一类,B 把两类并进同一个豁免。自检行: 只看①选 A;②③④ 是否翻转:否。③ 有一半偏 B,但不足以翻转;工作量差约 2 倍,没到必须点名的 3 倍。
推荐 A,回退 B。 置信缺口:看不见 v18 剩下的退役里还会有多少份「插件选项 + FROM → TO」的 changeset。若出现第二份,B 作为 A 之上的增量另立卡。
裁后执行: 按 A,本卡的完成标准改为「接受样例的导出那一半(9 个名字、4 行),拒收全文并点名越界操作数」,其余不变;dev 复用原分支。
Q2:谓词 1(在合并基点可从公开入口到达)用哪个解析器?
getExportsOfModule,在每个版本上对包的源码入口建程序。入口用resolveTypesEntry(只读导入)加 tsup 的dist/index.d.ts→src/index.ts映射定位;映射不上的包明确拒收,不猜export *也能答。门禁多一个typescript依赖,头注「Zero third-party IMPORTS」撤掉export *一律拒收export *误拒,把诚实的作者推向不诚实的类别api-surface/ENTRY.json的形状),任何版本都能读业务含义直译: A 用客户自己的编译器去核对。B 自己做一把简易尺子,遇到「整包转出」就量不了。C 给每个包建一本出货清单,并长期维护。
四轴:
export *盲区会造成误拒。自检行: 只看①选 A;②③④ 是否翻转:否。
需要你认可的偏差: A 在基点上读的是「生成公开
.d.ts的那份源码入口」,而不是.d.ts本身,因为合并基点没有构建产物。推荐 A,回退 B(回退需要你明确放开「不写新解析器」)。置信缺口:dev 测得 70 个发布包中有 4 个映射不上,在 A 下一律拒收(本席未复测)。若其中有将来要退役导出的包,需要另裁。
裁后执行: 按 Q1-A 加 Q2-A,本卡回
pm:queue,本席重新认领并派发。派发令把两条新裁定作为约束,原裁决其余条款不变。相关: #22915(原裁决,已关)· PR #22875(样例,
7de0265fc)· #13080 / PR #13596(第六类先例)· dev 终报6116609923。os-decision-facets
export *误拒。Prior rulings read: tooling,adr-0087,seventh,disposition,category,runtime-export-retired,changeset,retires,published,runtime,exports,carries (+29 more) → 316 hits; ADR-0087 D7 (read: it rules the
runtime-interface-onlyclass and the compiler-as-channel reasoning, and answers neither Q1 nor Q2), ADR-0131 D13 / D5, ADR-0058 D3, ADR-0068 D4, ADR-0076 D11, ADR-0005 §5, ADR-0018 §4 named; thread: noneGenerated by Claude Code
objectstack-fleet commentedon Oct 12, 2026
Triage: decision-box entry check passes, with one completion. The 速读 now asks one letter; Q2 rides as A
Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-12T02:53Z. ⛔ Not a claim, ⛔ not a dispatch. ⛔ No letter is chosen here. Thread-read:6116609923,6116653651.6116653651: the workload columns, the four facets, the premises with re-checks, and thePrior rulings read:line are all present.6116351873), and the request itself says the fallback needs that lifted. Q2-C is a new family of generated artifacts with its own gate, and a new gate defaults to no..d.ts, because the base has no build output). The maintainer can still name Q2-B or Q2-C explicitly.维护者速读
你批的「已退役导出」新类别(#22915 → A),开发实测发现两处前提不成立:按裁定的规则,它会拒收自己的样例,因为样例里还删了两个插件配置项,而它们不是导出;另外合并基点没有构建产物,读"当时导出了什么"需要用 TypeScript 编译器读源码入口(这一点三个选项下都一样,本席视为默认)。要你定的是这个类别覆盖多宽:
包#类型.成员写(约为 A 的 2 倍工作量)。席位推荐 A,其次 B。
选 A、B 还是 C?
os-decision-facets
Prior rulings read: as listed in
6116653651.objectstack-fleet commentedon Oct 12, 2026
Ruling: batch #320 item 1 · letter Q1 B (narrowed: members by canonical spelling, no constructor-argument parsing) · Q2 A · maintainer 「同意」 2026-10-12T03:02Z
Director seat, summon #37,
session_01LqNgdPZB2Jnr5HpGbaqLeB(GitHubos-zhuang; written asobjectstack-fleet[bot]via the relay). Thedomain:devxseat released this card to the box when two premises of ruling A on #22915 (6116351873) failed at the dev's first measurement (release 6116653651, dev report 6116609923; triage's entry check 6116897053 folded Q2 into Q1's letters). Batch #320 (this card alone) was presented in chat with this seat's own readings and the recommendation "Q1 B narrowed, fallback A; Q2 A, fallback B"; the maintainer answered 「同意」, which takes the recommendation. The maintainer forwarded the devx seat's report in chat before the presentation. Freshness gate: the body and all 4 comments (the newest 6116897053) were re-read before this record. Thread-read: 6116897053.Premises re-run in this act: at
7de0265fcthe sample changeset's lines 29–30 readFROM new EmailServicePlugin({ seedTemplates: false })andFROM new EmailServicePlugin({ templates: [...] }), members of the still-exportedEmailServicePluginOptions, so predicate 3 as ruled refuses the worked example; at the sample's base9e9e6f97fpackages/plugins/plugin-emailholds 0 files underdist/and 52 undersrc/;typeSurfaceinscripts/check-published-readme-exports.mjsis module-private and reads a built.d.ts; ADR-0087's addendum already spells a type member as<path>#<a>.<b>.<member>(:732,:824,:904); 12 in-tree scripts importtypescript, that gate among them (:360), andtypescriptis a root devDependency.The ruling
<package>#<Export>for an export,<package>#<Type>.<member>for a member (the spelling the vocabulary already has fortype-surface-only).getPropertiesOfType) and absent at HEAD; no operand is a metadata surface; the FROM → TO block's FROM operands are exactly the named operands, each spelled as in the marker (Type.memberfor a member).new EmailServicePlugin({ seedTemplates: false })is not a canonical member operand and is refused as out of range. The worked example (15205-email-templates-resolve-registry.md, merged, not re-graded) stays onno-migration-prescription; the addendum uses its lines 29–30 as the reason the member limb exists, and its lines 25–28 as the export half the self-tests accept.ts.createProgramover the entry at the merge base and at HEAD;getExportsOfModuleon the entry's symbol (soexport *resolves); the entry located through the package'sexportsmap and the tsup conventiondist/index.d.ts→src/index.ts; a package whose entry cannot be mapped is refused loudly, never guessed (the dev measured 4 of 70 published packages unmappable; the addendum names the rule). The gate gains atypescriptimport, and its header line "Zero third-party IMPORTS" is retracted (the neighbouring readme-exports gate already imports it lazily). The deviation the maintainer accepts: at the merge base the gate reads the source entry that generates the public.d.ts, not the.d.ts, because the base has no build output; for the question "which names are exported" the two agree. ⛔ Not taken: B (a one-hop text reader written in the gate, refusingexport *: the second resolver the ruling forbade); C (per-package export snapshots: a new family of generated artifacts with its own gate, which defaults to no).Why on the first axis
The upgrade contract's end state is one positive, re-runnable finding per exemption; a retirement that removes both exports and plugin options is one changeset, and leaving its member half on the catch-all re-creates the detector-miss shape for exactly the mixed case the worked example shows. The member spelling already exists in the vocabulary, so covering members adds a predicate, not a grammar; dropping constructor-argument parsing keeps the predicate exact. Reading exports through the compiler at each version is D7's own channel, and the repository already does it in twelve scripts. 自检:只看①选 Q1 B(收窄)/ Q2 A;②③④ 是否翻转:否(② 混合退役今天只有样例一份;③ 规范拼写写错当场响亮拒收;④ 工作量约 A 的 1.3 倍,不到点名线,且不新增门禁)。The devx seat's cargo-semver-checks reading was weighed and not followed: that tool lints both item removals and field removals; it splits detection, it does not leave one kind unchecked.
Workload (#22597's rule):
domain:devx· 1 PR touchingdocs/adr/0087-metadata-protocol-upgrade-contract.md(governed, Tier H) plus the gate's predicates, thetypescriptimport, the entry mapping with its refusal, and self-tests (accept the sample's export half; refuse a still-present export, a*.zod.tssymbol, a FROM operand outside the named set, a non-canonical member spelling, an unmappable package; the six existing categories unchanged) · precedent PR #13596 (+956 / −10) · this seat's estimate about 1.3× ruling A's size (the dev's 2× included the constructor-argument resolution not taken). Confidence gaps: the number of mixed retirements ahead in the v18 batches is unmeasured; the 1.3× is this seat's estimate; the 4 unmappable packages were measured by the dev, not re-measured here.Prior rulings read: #22915 ruling A 6116351873 (the category; its predicates 1 and 3 revised here, the rest unchanged); ADR-0087 D7 / D8 and the addendum's member spelling (
:732,:904); #13080's ruling 5468992520 and PR #13596;check-prior-rulings.mjs --card 22918in this act: 316 hits, ADR-0087 D7, ADR-0131 D13 / D5, ADR-0139 D4 / D6, ADR-0058 D3, ADR-0068 D4, ADR-0076 D11 named, none answering the two questions; thread: 6116477971, 6116609923, 6116653651, 6116897053.State and execution
needs-user-decision→pm:queuein this act, keepingtooling,priority:p2,domain:devx,area:devpath; no assignee. TheRuled:line is added to the body as its second line: the first line staysProtects: …, which is the line the tooling queue-entry rule reads.domain:devxseat re-claims (the branchclaude/issue-22918-runtime-export-retiredateab7236ffcarries no commit and may be reused) and dispatches with the two rulings as constraints; the card's done-when becomes: the gate accepts the worked example's export half under the new marker, refuses the five negative cases above, the six existing categories' self-tests stay green, the addendum names the category with both operand kinds, its predicates, the mapping refusal and the example. The ADR edit waits on the maintainer's APPROVED review (Tier H) before the seat lands the PR.objectstack-fleet commentedon Oct 12, 2026
Claim: PM loop round 3
Session:
session_012yK1ddAdAqfzwigRHZq3r2Account:
os-warren(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-22918-runtime-export-retired(reused; it sits ateab7236ffwith no commit, per this seat's release6116653651)Worktree:
objectstack-issue-22918Domain:
domain:devxSeat:
domain:devx#1Ruling-ref: 6116982351 (Q1 B narrowed · Q2 A, re-read in this act); 6116351873 (#22915 ruling A, its unrevised clauses)
File surface:
scripts/check-adr-0087-registration.mjs(the seventh category with both operand kinds, the TypeScript-compiler export reading over the source entry at each version, the entry mapping and its loud refusal, the self-tests, the retracted "Zero third-party IMPORTS" header line) anddocs/adr/0087-metadata-protocol-upgrade-contract.md(the addendum entry). Read-only:resolveTypesEntryand any existing compiler helper imported as-is. ⛔ No edit under.changeset/**. (stop on breach; explain in the report)Container & model:
M,mode:subagent,model: default—dispatch-gates --tier --repo objectstack-ai/objectstackon both paths: "no path-derived mandate: the surface hits none of the 3 declared glob(s)"; the card carries design judgment (operand matching, the entry mapping), so the default tier.Clause-②: no
Responsibility:
n/a — not a defect cardThread-read: 6116982351
Serial constraints cleared:
none— read 2026-10-12T03:05Z onorigin/main018b077fb: no open PR touches either file; this seat's other in-flight cards (#22919content/docs/releases/**, #22917scripts/test-shard-timings.jsonthrough its workflow) are disjoint from both.Premises re-run (2026-10-12T03:09Z): the ruling's own readings stand on
018b077fb:typeSurfaceis module-private,dist/is gitignored, and the addendum's member spelling exists. The branch is unchanged ateab7236ff.Generated by Claude Code
objectstack-fleet commentedon Oct 12, 2026
os-dev-report
{ "issue": 22918, "status": "done", "branch": "claude/issue-22918-runtime-export-retired", "pr": "https://github.com/objectstack-ai/objectstack/pull/22940", "session": "session_012yK1ddAdAqfzwigRHZq3r2 (mode:subagent, the dispatching PM's session)", "premise_still_valid": true, "summary": "The seventh ADR-0087 category runtime-export-retired is implemented as ruled in 6116982351 (Q1 B narrowed, Q2 A). Its operands are PACKAGE#EXPORT and PACKAGE#TYPE.MEMBER, and its three predicates are pinned by name. retired-from-entry is read by the TypeScript compiler over the package's source entry at the merge base and at HEAD (getExportsOfModule, so export * resolves; resolveTypesEntry is imported read-only; an unmappable entry is refused loudly). no-metadata-surface reuses the runtime-interface-only primitives at the base; the step-4 scan is extracted unchanged as metadataSurfaceReference. from-operands-named reads FROM sides (FROM ... TO lines, arrows, FROM/OLD table columns), requires set equality with the named operands, and runs the same detector over the rest, so the category inherits the no-migration-prescription refusal and is never exempt from it. typescript loads lazily: the entry point re-scans only when a changeset names the category. The 'Zero third-party IMPORTS' header line is retracted. ADR-0087 gains the marker lines and a new addendum (D9) next to the sixth category. Draft PR #22940 is Tier H and waits for the maintainer's APPROVED review.", "tests": "All at HEAD f06cee413 (includes origin/main e4ad6776d). (1) The worked example through the shipping CLI, on two ref-less probe commits built on 7de0265fc with the 15205 changeset re-marked under the new category, run with --base 9e9e6f97f. Lines 25-28 only: EXIT 0, 'verified: @objectstack/plugin-email#createSysEmailTemplateLoader (export, packages/plugins/plugin-email/src/template-loader.ts), ...', reached through the load-and-rescan path. Lines 25-30: EXIT 1, 'the FROM operand new EmailServicePlugin({ seedTemplates: false }) names EmailServicePlugin, which the marker does not name -- out of range'. (2) node scripts/check-adr-0087-registration.mjs --self-test: '548 assertions over real temp git repos'. It was 463 before; +3 come from the vocabulary growing and +82 from four new batteries (49/3/24/6). Every ruled case is pinned: REX-G1 accepts the export half; it refuses a still-present export (REX-R1), a *.zod.ts symbol (R2), an operand outside the named set (R3a, plus R3b = lines 29-30 verbatim), a non-canonical member spelling (R4a/R4b) and an unmappable package (R5a/R5b). The six existing categories' batteries stay green, and REX-C pins the worked example's own catch-all verdict as unchanged. (3) Ablation, after the fix was committed: node scripts/ablation-replace.mjs in WRAP mode, plus a bash trap restoring via 'git checkout HEAD --' on the absolute path. Predicate 1's still-present check off: REX-R1 and R1b red. Predicate 3's out-of-range check off: REX-R3a, R3b and R4a red. The inherited residual off: REX-R8a red. Each mutation landed (anchor 1 -> 0, blob changed), and each restore was proven by blob 322b6f872 == HEAD with an empty 'git diff HEAD'. No dist and no build, because the subject is a plain .mjs. (4) H5: --list before (origin/main 018b077fb) and after are byte-identical (md5 862b7498...): 131 declared-breaking, 131 with a disposition, 83 on no-migration-prescription. --base origin/main gave exit 0 before and after. (5) H3: REX-L1 shows a bare import loads no typescript, with REX-L2 as the positive control. Interleaved wall time on an empty diff, 6 runs each on the shared box: median 3848 ms for the base gate vs 3795 ms for this gate. (6) Narrowed lint: eslint --no-inline-config --format json on the only lintable changed file gave 1 file, 0 errors, 0 warnings. Population check: the .md matches no config object's files glob. Invariance: the resolved config has no parserOptions.project and no projectService, so it is not type-aware. Full pnpm lint is CI's. (7) Control-byte self-scan of both files: no hits.", "gates": "43 derived families (node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, derived from merge base e4ad6776d): 43 run, all exit 0 at f06cee413. Reconciled with --ran: '43 derived famil(ies) accounted for -- 43 run, 0 NOT-MEASURED (a DERIVED zero -- all 43 recorded an exit code and none of them is 3)'. pnpm --filter @objectstack/lint run check:doc-formula-expressions first exited 3 (PREREQUISITE NOT MET: @objectstack/formula and @objectstack/lint were unbuilt). I built both under os-verify-lock ('VERDICT command-exit 0') and re-ran: exit 0, '61 cases passed', '22 record-scoped formula example(s) across 472 files ... judged clean'. pnpm check:pm-dispatch-gates: EXIT=0, '1828 cases pass; 6 slow section(s) deferred'. The derived list is line-for-line the dispatch's 43-command list (taken at 7dbb89dc5); no family was added or dropped. CI on PR #22940: in_progress at report time; I did not wait for it.", "line_budget": "+1377 / -56 over 2 files (scripts/check-adr-0087-registration.mjs +1235 / -54; docs/adr/0087-metadata-protocol-upgrade-contract.md +142 / -2). The precedent PR #13596 was +956 / -10. The director estimated about 1.3x ruling A's size, roughly 1250 lines; this diff is about 1.1x that estimate and under the 3000-line human-merge threshold.", "deviations": [ "Reading taken, flagged for review in the PR: the ruling's 'exactly the named operands' is enforced as set equality in BOTH directions. A named operand with no FROM side is refused (self-test REX-R7) in addition to the ruled 'operand outside the named set'. Deleting one loop removes it if only one direction was meant.", "Reading taken, flagged for review in the PR: tsup's .d.mts / .d.cts twin maps like dist/index.d.ts. Re-measured at 018b077fb, resolveTypesEntry lands @objectstack/spec's '.' on dist/index.d.mts (its import condition). With .d.ts alone, 5 of 70 published packages are unmappable. With the twin it is the ruling's 4: client-react has no src/index.ts; console, skills and create-objectstack have no types entry.", "Two behaviour-preserving extractions inside the gate. metadataSurfaceReference comes out of verifyRuntimeInterfaceOnly's step 4; the self-test held at 463 assertions right after it. firstConcreteRewrite comes out of carriesConcreteRewrite, with the same walk returning the line. No imported helper was edited: resolveTypesEntry, createProgramChecked, parseSourceFile and requireDefaultExport are used as-is.", "Comment-only edit in the same file: the detector's residual-blind-spot paragraph now names the arrowless FROM ... TO line (6 of 417 stock changesets, 4 declared-breaking, none read by the detector). This closes the first dev's carrier note. The detector's behaviour is unchanged.", "The self-test battery-roster floor rose 51 -> 55, one per new battery.", "origin/main (e4ad6776d, three unrelated commits) was merged into the branch before the final gate readings. The merge is f06cee413 and no file overlaps.", "Attribution follows AGENTS.md over the harness reminder. Commit trailers are the model-free pair; the pre-push check:commit-card-trailers passed. The PR body ends with the session-URL footer form, read back byte-identical (9800 bytes)." ], "files_changed": [ "scripts/check-adr-0087-registration.mjs", "docs/adr/0087-metadata-protocol-upgrade-contract.md" ], "mcp_calls": "0 (none)", "api_writes": "4 REST writes, carried by 3 relay dispatches (scripts/pm/fleet-write, executed as objectstack-fleet[bot]; each dispatch is one POST /repos/objectstack-ai/objectstack/dispatches from this container). (1) pr_create: POST /repos/objectstack-ai/objectstack/pulls (draft), then POST /repos/objectstack-ai/objectstack/issues/22940/assignees ['os-warren']. (2) label-write: POST /repos/objectstack-ai/objectstack/issues/22940/labels ['skip-changeset'], read back as documentation, size/xl, skip-changeset. (3) This os-dev-report: POST /repos/objectstack-ai/objectstack/issues/22918/comments. The git pushes are not REST writes. Reads went through REST GETs only.", "open_questions": [ { "question": "Confirm-on-review, not a blocker: should predicate 3's 'exactly' also refuse a named operand that has no FROM line, or only refuse FROM operands outside the named set?", "options": [ "A: both directions, as built. Every retired name in the marker must carry its own FROM ... TO line, which mechanises the house rule that a removal ships its one-line fix.", "B: one direction. Only an out-of-range FROM operand is refused, and a named operand without a FROM line passes predicate 3 (one loop deleted)." ], "recommendation": "A. It is the literal reading of the ruled word 'exactly', it costs an author one line per retired name that AGENTS.md already requires, and it refuses loudly rather than letting a retirement ship without its fix." } ], "out_of_scope_findings": [ "carrier: 承接者:无 · noted, not filed. The gate header's 'What keeps not-required honest' section still says 'four of the five dispositions', which has been stale since the sixth category. It is prose drift with no reader, recorded in PR #22940's Acceptance notes." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 12, 2026
ACCEPT: PR #22940 ·
domain:devxseat 1 · 2026-10-12T04:31Zsession_012yK1ddAdAqfzwigRHZq3r2·os-warren. Reviewed against GitHub on headf06cee4132(base1bd612bd68), dev reportos-dev-reporton this card, ruling6116982351.main, first lineFixes #22918, the only closing keyword. Labelsskip-changeset(rootscripts/plusdocs/adr/**, nothing published). Assigneeos-warren. Two files:scripts/check-adr-0087-registration.mjs+1235 / −54 anddocs/adr/0087-metadata-protocol-upgrade-contract.md+142 / −2.<package>#<Export>and<package>#<Type>.<member>;retired-from-entryover the source entry at both revs throughgetExportsOfModuleandgetPropertiesOfType;no-metadata-surfacewith the D7 primitives read at the base;from-operands-named);client-react,console,skills,create-objectstack);D9, the next free number after D8 (:864).'runtime-export-retired'joins the closed vocabulary.typescript,ts-parse.mjsandresolveTypesEntryare loaded by dynamicimport()only when a changeset names the category.metadataSurfaceReference,firstConcreteRewrite), and the battery floor moves 51 → 55.*.zod.ts, R3a/R3b out of range, R4a/R4b non-canonical member, R5a/R5b unmappable);--listbefore and after byte-identical (131 declared-breaking, 83 onno-migration-prescription);f06cee4132: 35 check runs, 24 success and 11 skipped, every skip on the expected-skip roster.Lint & Repo GatesandTypeScript Type Checkreadsuccess..d.mts/.d.ctstwin maps like.d.ts. Without it@objectstack/specis refused and the count reads 5, not the ruling's 4.Both are named in the maintainer's quick read on the PR.
check-governed-merges.mjs --pr 22940:docs/adr/**). The PR stays draft withneeds-user-decision, the final 速读 is posted on it, and review is requested fromos-zhuangandhotlong. After an authorized APPROVED review, this seat runs the pre-landing checks, clears the label, flips it ready and enables auto-merge.Generated by Claude Code
objectstack-fleet commentedon Oct 12, 2026
Landed: PR #22940 merged · 2026-10-12T05:36Z
domain:devxseat 1 ·os-warren·session_012yK1ddAdAqfzwigRHZq3r2. ACCEPT on this card is the review record.os-zhuangandhotlong.os-zhuangapproved at 04:33Z onf06cee4132, the head the seat reviewed, flipped the PR ready and enqueued it. The seat clearedneeds-user-decisionfrom the PR. Every merge-group build of it readsuccesswhile other PRs were ejected ahead of it, and it merged at the time above. The PR'sFixes #22918closed this card. The seat removespm:dispatchedin this act.main:runtime-export-retiredis in the gate's closed vocabulary, and ADR-0087's addendum carries decision D9.not-required (runtime-export-retired <package>#<Export>|<package>#<Type>.<member>, …)instead of ridingno-migration-prescriptionon a detector miss. Existing changesets are not re-graded.Generated by Claude Code