You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
plugin-email, service-messaging: retire the render exports that take a template body with no save moment (renderTemplate, renderNotification) — #25082 ruling F2 B3 #25190
Filing gate ③: a task the maintainer directed through the director's ruling 6335348115 on #25082 (F2 B3, 「同意」): "domain:services seat 2 files one card retiring the two exports that take a template body with no save moment … under ADR-0087 runtime-export-retired, BREAKING, at once — no staged window." Filed by domain:services seat 2 (seat post #21118). ⛔ Not a claim.
Reader who acts:domain:services, the claiming seat.
renderTemplate, from packages/plugins/plugin-email/src/index.ts:88 (the template-engine.js re-export).
renderNotification, from packages/services/service-messaging/src/index.ts:92.
Whether interpolate (service-messaging/src/index.ts:93) goes with them is this card's first measurement. Today it has zero consumers in this tree outside tests, zero in objectstack-ai/hotcrm, and npm dependants are unmeasured.
Stays:setTemplateLoader, the plugin's own wiring point, not an authoring door.
Why:#25082's placement restriction judges a template where it is saved or registered. These exports take a body inline and render it with no save moment, so the restriction cannot see them (#25082's measurement 6334219164, R2). Retiring them leaves no door the restriction cannot see. It does not block #25082.
Declarations
ADR-0087 runtime-export-retired, BREAKING, with a FROM → TO block in the changeset and no staged window (the standing line 「暂时不考虑 v19,协议的调整原则上也是尽快,不考虑退役版本。」).
Claim: card thread · domain:services seat 1 (seat post #6021, project run by the Sales account) · p3 · B3 of ruling 6335348115 on #25082 (item 6) · 2026-10-22T18:41Z
Session: session_01CP5uvqW3aX2Tfwy6YAsNu5
Account: os-sales (the seat's linked user; the card's assignee)
Branch: claude/project-thread-gru6pg (the harness-pinned branch for this session; it names no issue, so the Duplicate Fix Guard's branch-name hint will not match — this claim is the identity record)
Worktree: objectstack-issue-25190, a linked worktree of the session's own container checkout
Domain: domain:services
Seat: domain:services#1 (seat post #6021)
File surface (read on origin/mainbc5daf0f2; stop on a breach and explain it in the report): packages/plugins/plugin-email/src/index.ts (the renderTemplate re-export of template-engine.js leaves the entry; requireVars, htmlToText, setTemplateLoader stay), packages/services/service-messaging/src/index.ts (renderNotification leaves the entry; interpolate per the first measurement below; NotificationTemplateStore, TEMPLATE_OBJECT, DEFAULT_LOCALE stay), one entry pin test in each package's src/, and one patch changeset for both packages. The module-internal functions stay: the mail service and the email and sms channels render through them from the stored-template doors. ⛔ Not packages/spec, packages/lint, packages/formula, content/docs/** or the plugin-email outlet pin test (PR #25212 holds it).
Container & model: M, card thread (in-session executor), model: default tier (dispatch-gates.mjs --tier: "no path-derived mandate"), effort: high; a CONTRACT_REVIEW_TIER review of record before the queue (a narrowing of the published surface)
Clause-②: no (narrowing)
Metadata-protocol: none
Basis for the Clause-② line: three runtime exports leave two published package entries; no metadata key, accepted value or stored shape moves. The ruling's changeset line: patch with the BREAKING banner and ADR-0087 runtime-export-retired, no staged window (the standing line quoted in the ruling).
First measurement, interpolate (asked by the card): on origin/mainbc5daf0f2, zero imports of interpolate, renderNotification or renderTemplate from either package specifier anywhere in this tree (tests included; every in-package caller imports the module file, not the entry). The card records zero in objectstack-ai/hotcrm; npm dependants are unmeasured. interpolate is the same door with no save moment as renderNotification (a body taken inline, rendered at once), and it encodes no hole at all, so it goes with them.
Responsibility: n/a — not a defect card (a ruled retirement).
Ruling-ref: 6335348115
Thread-read: none
Serial constraints cleared: of the 13 open PRs, #25212 (the email half, draft) is the one whose title names plugin-email; its file list was read and touches plugin-email only in text-field-html-outlets.pin.test.ts, outside this surface. The other titles name other packages (the Version Packages PR aside, which this diff does not edit). #25082's notification half landed as PR #25258.
Resume-from: none.
os-dev-report
{
"issue": 25190,
"status": "needs_decision",
"branch": "claude/project-thread-gru6pg",
"pr": null,
"session": "session_01CP5uvqW3aX2Tfwy6YAsNu5",
"premise_still_valid": true,
"summary": "The premise holds on origin/main bc5daf0: renderTemplate (plugin-email entry) and renderNotification (service-messaging entry) are exported, take a template body inline and render it with no save moment, and nothing in this tree imports either from the package entry (every caller imports the module file). One fork stops the build: the ruled disposition, ADR-0087 runtime-export-retired, cannot be verified for @objectstack/plugin-email#renderTemplate. Predicate 2 (no-metadata-surface) refuses it because packages/spec/src/contracts/email-service.ts declares IEmailService.renderTemplate, a METHOD of the same name (the render-by-name door this card points authors to). The gate counts only a top-level declaration as a homonym, so a member signature reads as an unresolvable mention, refused by design. The gate is scripts/check-adr-0087-registration.mjs, domain:spec tooling by the lane table's anchoring exception, so this card cannot change it. renderNotification verifies cleanly: check-adr-0087-registration exits 0 on the branch head ac294b0 with that one operand. The branch now carries option A below (renderNotification only) as work in progress; no PR is open. interpolate, the card's first measurement: it STAYS. It renders a plain-text face (subject, text and markdown bodies), which the placement rule judges at no door, so retiring it closes no gap the rule has; the ruling's own reading R2 named only the two body-taking html doors. Separately, its retirement could not be verified either: packages/spec/src/automation/io-node-config.zod.ts uses the English word in an error-message string, and the gate leaves string literals unmasked on purpose. This supersedes the claim's line that it goes with them.",
"tests": "On 85058b6 (all three retired): pnpm --filter @objectstack/plugin-email test 37/37 files, 671/671 tests; pnpm --filter @objectstack/service-messaging test 60/60 files, 726/726 tests; typecheck of both packages exit 0 (plugin-email test layer included); eslint over the 4 changed source files exit 0. Reverse check: with base bc5daf0's two index.ts files restored, each package's new entry pin went red on its retired-name case (1 failed, 2 passed in each), restored with git checkout HEAD and git diff HEAD empty. On ac294b0 (option A): the service-messaging pin is 3/3; full re-run pending the ruling.",
"gates": "dispatch-gates --commands derived 68 on 85058b6; all 68 run, exit captured before any pipe: 65 exit 0; check-adr-0087-registration exit 1 (the fork above, plus a FROM-line spelling fixed in ac294b0); check-plugin-teardown-shape --self-test exit 3 and check:dual-build-cjs-loads exit 3 (prerequisites: shallow fixture commit, dist), not verdicts. check-platform-checklist --changed-from origin/main: 0 anchored files, nothing judged. On ac294b0: check-adr-0087-registration exit 0, runtime-export-retired verified for @objectstack/service-messaging#renderNotification. The full derived list re-runs on the final head after the ruling.",
"dogfood_battery": "NOT MEASURED yet: owed once on the final head (the title carries an exclamation mark).",
"mcp_calls": "8 — reads only: issue_read x3, list_pull_requests x1, pull_request_read x1 (get_files on PR 25212), plus ToolSearch loads. No MCP write tool.",
"api_writes": "3 — all through scripts/pm: label-write.mjs on this card (add pm:dispatched, remove pm:queue, assign os-sales); post-stamped.mjs claim comment 6348488271; post-stamped.mjs this report. git push is not counted.",
"open_questions": [
{
"question": "The ruled disposition (runtime-export-retired) cannot be verified for @objectstack/plugin-email#renderTemplate: predicate 2 reads IEmailService.renderTemplate, a same-named METHOD in packages/spec/src/contracts/email-service.ts, as an unresolvable mention, and that gate is spec-lane tooling. How does the renderTemplate half land?",
"options": [
"A: this card's PR lands renderNotification now (verified, ready on the branch). A spec-lane card teaches predicate 2's homonym test that a member declaration of the same name (an interface or class method or property signature) is a homonym, with self-test cases both ways; renderTemplate's retirement then lands as a second PR on this card (Part of), Blocked-by that card. Cost: two PRs, one spec-lane card on the other account's lane.",
"B: hold the whole card in one PR until that spec-lane gate change lands. Cost: renderNotification waits on a cross-account lane with no date.",
"C: retire renderTemplate now under no-migration-prescription with an arrowless FROM/TO line. Cost: held by a detector miss, the shape the ADR-0087 addendum of 2026-10-12 says new retirements must not use.",
"D: register an ADR-0087 ledger entry for it in packages/spec. Cost: spec lane, and D9 says the ledger is the wrong channel (objectstack migrate meta has nothing to rewrite)."
],
"recommendation": "A: it lands the verifiable half at once with no staged window, keeps the ruled category for both halves, and the gate fix is the honest disposition the gate's own refusal text names (a different symbol needs a disposition that can be checked). Axes: long-term (one gate rule, no detector miss) A=B over C, D; real need (zero measured consumers, no urgency) A=B; AI-error (C teaches a detector miss) A, B over C; startup stage (no new mechanism, one self-test case) A over D."
}
],
"out_of_scope_findings": [
"class: b (a declared contract the gate cannot apply) · reach: the ruled disposition for this card's renderTemplate operand, measured by node scripts/check-adr-0087-registration.mjs --base origin/main on 85058b6 · evidence: predicate 2 refuses a published export whose only metadata-surface hit is a same-named member signature (declaresLocally reads top-level declarations only) · Seam: spec:contracts/email-service.ts IEmailService.renderTemplate → runtime:scripts/check-adr-0087-registration.mjs metadataSurfaceReference · dedupe words: adr-0087 homonym member signature, runtime-export-retired predicate 2, no-metadata-surface method name · carrier: domain:spec seat (filed only if option A or B is ruled)"
]
}
Ruling: needs_decision fork review (project review thread 「#25190 导出退役裁决」) · letter A · maintainer 「对于这种类似的问题,能否你自己开一个单独的 fable thread 做评审,对于自己有把握决定的问题,自己决定,不能决定的开决策卡?」 2026-10-22T19:23Z
Proxy ruling under the maintainer's delegation quoted above (carried into this project by its owner's standing rule 「卡线程报 needs_decision 时,另开 fable 裁决线程,有把握就裁并把裁决落在 GitHub 上,没把握才给 Bill 发决策卡」), by the review thread's session session_01S5StnzjNdynu8Bqqu13n3v (project thread), on the domain:services seat 1 card thread's needs_decision fork (claim 6348488271, report 6349141238, session session_01CP5uvqW3aX2Tfwy6YAsNu5). Marker: auto-adjudicated. ⚠️ Reversible on one word from the maintainer; nothing here forecloses that.
The question
The maintainer's ruling 6335348115 on #25082 (F2 B3, 「同意」) retires renderTemplate (@objectstack/plugin-email) and renderNotification (@objectstack/service-messaging) under ADR-0087 runtime-export-retired, BREAKING, at once. The card thread built it and the gate split the pair:
@objectstack/service-messaging#renderNotificationverifies: node scripts/check-adr-0087-registration.mjs --base origin/main exits 0 on the branch head ac294b00 (re-run by this thread against origin/maincc8d886: exit 0, "verified: @objectstack/service-messaging#renderNotification (export, packages/services/service-messaging/src/template-renderer.ts)").
@objectstack/plugin-email#renderTemplatecannot be verified: on 85058b6e (both retired) the same command exits 1 with "[predicate 2: no-metadata-surface] is false: packages/spec/src/contracts/email-service.ts (a spec contracts/** entry) mentions renderTemplate IN CODE while neither declaring nor importing it" and quotes the method signature line (re-run by this thread: exit 1, same line). The hit is IEmailService.renderTemplate, a METHOD SIGNATURE of the same name on the spec contract (email-service.ts:338): the gate's homonym test declaresLocally reads only top-level declarations (interface|type|class|enum|const|let|var|function NAME), so a member declaration falls through to "an unresolvable mention", and the gate refuses an unresolvable mention by design. The gate is domain:spec tooling (the lane table's anchoring exception); this project owns cli, services and ui only and does not edit it.
Forks as the card put them: A land renderNotification now, the spec lane teaches the homonym test that a same-named member declaration is a homonym, renderTemplate lands as a second PR on this card once that holds; B hold the whole card until then; C retire renderTemplate now under no-migration-prescription with an arrowless FROM/TO line; D write an ADR-0087 ledger entry for it in packages/spec.
Governing text, read at origin/maincc8d886
Ruling 6335348115 item 6: 「one card retiring the two exports that take a template body with no save moment … under ADR-0087 runtime-export-retired, BREAKING, at once — no staged window」; 「Whether interpolate goes with them is that card's first measurement」.
ADR-0087 D9, predicate 2 as the ADR words it: 「no-metadata-surface — at the merge base, no operand is declared in a *.zod.ts, under packages/spec/src/contracts/ or in an object definition, none is a Zod projection, and no metadata surface references it in code」. Its "Not taken": 「Exports only — a mixed retirement would keep its member half on the catch-all, held by a detector miss: the shape D7 retires」; the worked example 「stays on no-migration-prescription. New retirements use this category」; and its channel argument: 「the ledger has no metadata to rewrite」.
scripts/check-adr-0087-registration.mjs, header: 「Unresolvable, so refused rather than assumed unrelated (check:react-declaration-parity 是唯一没接进任何 workflow 的源码审计门禁,且无 MANIFEST 时静默 skip 退出 0 —— 它现在永远不可能红 #4690)」; the PackagePublishResult pair is the gate's own accept/refuse fixture for a same NAME under two paths; and its own precedent for a reader fix that admits more: 「a file whose only import of the symbol is commented out used to be refused as an IMPORT and is now not a reference at all (a loosening — a commented-out import puts nothing in a schema)」. metadataSurfaceKind: a *.test.ts under contracts/ is not a surface (self-test U5), so the method signature is the ONLY code hit for renderTemplate on any metadata surface at the base (the other two mentions in that file and the one in notification-template.object.ts are docblocks, masked).
.claude/skills/pm-dispatch/SKILL.md 〈升级与决策〉, the named non-escalation classes: 「技术任务间顺序与依赖;验证策略」 and 「既有门禁内部参数与盲区修复」; 〈状态机〉 references/state-machine.md: 「工已完而被门禁缺陷卡住者同用本态,⛔ 不设新标签、不重派;Blocked-by: 指向门禁卡」.
This project's standing rule from its owner: lanes spec / engine / skills belong to another account's project — this project files no card there and edits nothing there; it leaves a comment for that lane's seat.
Ruling: A
Why not C. D9 names the arrowless line held by a detector miss as 「the shape D7 retires」 and says new retirements use runtime-export-retired. C would also put the honest category on one half and a detector miss on the other for the same ruling — the mixed shape D9's "Not taken" exists to refuse.
Why not D. A retired runtime export is compiler-carried (D9): objectstack migrate meta, spec-changes.json and the upgrade guide would project an entry that rewrites nothing — false data in the one ledger this gate keeps true. It is also a packages/spec write from the wrong lane.
Why not B. The ruled disposition is 「at once — no staged window」. B holds a verified retirement behind a gate fix in a lane this project cannot schedule, with no date; nothing in the text asks the two halves to land in one PR, and the card already carries them as two operands.
Why A is inside the text, and self-rulable. The retirement itself is the maintainer's ruling; what is open is sequencing between two operands and a verification route — the named non-escalation class 「技术任务间顺序与依赖;验证策略」. A lands the verifiable half at once under the ruled category, keeps the ruled category for the other half, and asks the gate's owner for exactly what the gate's own refusal text asks for (a disposition that can be checked). No floor moves: no security or permission boundary, no published contract sentence, no product trade-off beyond the one already ruled, and this ruling changes no gate — the gate's correction is the spec lane's to judge and land (a blind-spot fix of an existing gate's reader, the class its header already records once). Failure is loud and reverts once: if the spec lane rules otherwise, the renderTemplate half comes back to the box as a fresh fork (item 4 below).
Fixed here, so the dev opens no second decision:
PR 1 — renderNotification. The branch claude/project-thread-gru6pg at ac294b00 is the shape: @objectstack/service-messaging only, one patch changeset with the BREAKING banner, Clause-②: no (narrowing), Metadata-protocol: none, marker not-required (runtime-export-retired @objectstack/service-messaging#renderNotification), FROM line spelled with the bare export name (the 85058b6e predicate-3 refusal was the "imported from" suffix on the FROM operand; ac294b00's spelling is the one to keep). Title fix(service-messaging)!: …. Body says Part of #25190 — ⛔ no closing keyword: the card stays open for PR 2. Merge origin/main first (the base bc5daf0f2 is behind the pin PR chore(console): move the objectui pin cd14d8eeffe1 → 0d12e95361b2, past objectui#13326, so a non-admin approver sees the approval request page #25262), re-run the derived gate list and check-adr-0087-registration on the final head, full dogfood battery once on that head (the bang title), CONTRACT_REVIEW_TIER review of record before the queue, hand-back to domain:services seat 1 ([PM seat] domain:services — 🟢 os-sales #6021) for ACCEPT.
interpolate stays. Ruling item 6 made it the card's first measurement, and the measurement decides: it renders the plain-text faces (subject, text, markdown), which the placement rule judges at no door, and R2 of 6334219164 named only the two html body-taking doors. Independently, its retirement is unverifiable today for the same predicate-2 reason (io-node-config.zod.ts names the English word in an error-message string, and string literals are unmasked on purpose). The claim's line "it goes with them" is superseded by the report; the changeset's "Unchanged: interpolate" line is right.
Hand-off to the spec lane — a comment, not a card from this project. The card thread posts ONE pointer comment on the domain:spec seat post ([PM seat] domain:spec · seat 2 — 🟢 os-bill #18549) carrying the out-of-scope finding as the report classifies it (class b · the ruled disposition for @objectstack/plugin-email#renderTemplate · Seam: spec:contracts/email-service.tsIEmailService.renderTemplate → scripts/check-adr-0087-registration.mjsmetadataSurfaceReference / declaresLocally · dedupe words: adr-0087 homonym member signature, runtime-export-retired predicate 2, no-metadata-surface method name) and this comment's id. What it asks that lane to judge: whether a same-named member declaration (a method or property signature inside an interface, class or type literal) is a homonym like a top-level declaration, with a self-test case each way (a member DECLARATION clears; a bare call or mention in code still refuses; string literals unchanged). Filing the card, its priority and its shape are that seat's; ⛔ this project files no domain:spec card and edits no scripts/ gate.
PR 2 — renderTemplate, premise-bound. Premise, falsifiable and measured before the branch is written: node scripts/check-adr-0087-registration.mjs --base origin/main exits 0 on a head whose changeset claims runtime-export-retired @objectstack/plugin-email#renderTemplate (the 85058b6e diff for plugin-email, rebased, is the shape; same banner, declarations and FROM spelling; requireVars, htmlToText, setTemplateLoader stay). While it exits 1 the half is not written. ⛔ If the spec lane rules that a member signature is NOT a homonym, or declines the change, nobody falls back to C or D and nobody retires renderTemplate behind another category: the half returns to the box as a fresh fork with that ruling cited. Whoever holds the services lane then re-claims this card for PR 2 (a new Claim: naming its branch), same disposition, same review tier, dogfood battery owed again (bang title).
Card state between the PRs. When PR 1 lands, the landing stroke (three readings, strip pm:dispatched, clear the assignee, a Release: line) moves the card to pm:blocked with a line-initial Blocked-by: #N naming the spec lane's gate card if that card exists (the state machine's 「门禁卡」 target; ⛔ not a Part of tracker), else to pm:on-hold with Restart-when: node scripts/check-adr-0087-registration.mjs --base origin/main exits 0 for the operand @objectstack/plugin-email#renderTemplate, and domain:services seat 1 rewrites it to pm:blocked once the spec card's number is known. The card closes only when PR 2 lands.
① Long-term soundness (weight ≥50%). Two years out there is one way a runtime export leaves a published package: the compiler-checked category, verified by a gate whose homonym test reads what TypeScript calls a declaration — including a member of a contract that happens to share a name with a function somewhere else. A keeps that one way for both halves and sends the reader's blind spot to its owner. C and D each add a second way that is false (a detector miss, or a ledger row that rewrites nothing). B keeps the one way but makes a maintainer-ruled 「at once」 wait on a lane with no date.
③ AI-error prevention. Under A, a consumer meets TS2305 at its own import with a FROM/TO line that names the stored-template door, for each half, under a category the gate re-verifies on every run. Under C the next retirement learns that an arrowless line is how to pass the gate — the quiet direction D9 closed. Under D an upgrading operator is told a migration exists where nothing is rewritten.
④ Startup no-sprawl. Two PRs and one self-test case in an existing gate, no new mechanism, no new gate, no staged window. B is the same diff later; C and D are smaller today and a permanent misclassification.
Only-① pick: A. Do ②③④ flip it: no. Effort of the chosen letter: PR 1 M, effort: high, this card thread, no new slot; PR 2 the same when its premise holds; the spec-lane half is that lane's to size.
os-decision-facets
① 长远:A 让两个运行时导出都走同一条编译器核验的退役类别,把门禁读法的盲区(同名成员签名被读成不可解析的提及)交给门禁所属车道修;C、D 各造出第二条假的路(探测器漏网、改写不到任何东西的台账行);B 让维护者已裁的「立即」等一条没有日期的车道。
② 拉动:两个导出在本仓(含测试)与 hotcrm 从包入口的导入数为 0,npm 外部未测;拉动来自关上放置规则看不见的门(#25082),renderNotification 今天就关,renderTemplate 在处置可核验时关。
③ 防错:A 下消费方在自己的 import 处拿到 TS2305 和指向存储模板门的 FROM/TO 行,类别每次运行重验;C 教会下一次退役「无箭头行能过门」;D 让升级者相信有一条迁移,实则什么都不改写。
④ 不扩散:两个 PR、既有门禁加一条自测用例,不加机制、不加门禁、无分期窗口;B 是同样的 diff 晚落,C、D 今天更小但永久误分类。
Prior rulings read: runtime-export-retired,homonym,no-metadata-surface,no-migration-prescription,render-export → 0 hits; none; thread: none; repo: objectstack-ai/objectstack (the script's term match found no ADR decision; read by hand: ADR-0087 D9 and its 2026-10-12 addendum, ruling 6335348115 on #25082, the card's claim 6348488271 and report 6349141238)
Execution after the ruling
The card thread continues under its claim 6348488271, same branch, same session; its next comment on this card carries a line-initial Ruling-ref: naming this comment's id and the pointer on [PM seat] domain:spec · seat 2 — 🟢 os-bill #18549 goes in the same act. ⛔ Ruling is not dispatching; the thread already holds its slot, and it frees when PR 1 merges.
The report's status moves from needs_decision to building under A; a fresh os-dev-report comes with PR 1, with the final-head gate list, the dogfood battery and mcp_calls reads-only as before.
Building under letter A · domain:services seat 1 card thread (claim 6348488271) · session_01CP5uvqW3aX2Tfwy6YAsNu5 · 2026-10-22T19:25Z · ⛔ Not a new claim.
PR 1 (renderNotification only) is being finished on claude/project-thread-gru6pg in the ac294b00 shape: origin/main merged in, the derived gate list, check-adr-0087-registration and the full dogfood battery on the final head, then a draft PR with Part of #25190 (no closing keyword) and a fresh os-dev-report.
interpolate stays (item 2), as the report 6349141238 says; the claim's line that it goes is superseded.
os-dev-report
{
"issue": 25190,
"status": "done",
"branch": "claude/project-thread-gru6pg",
"pr": "#25285",
"session": "session_01CP5uvqW3aX2Tfwy6YAsNu5",
"premise_still_valid": true,
"summary": "PR 1 of ruling 6349343463 (letter A), Part of this card: renderNotification leaves the published entry of @objectstack/service-messaging; the function stays inside the package and the email and sms channels keep rendering stored sys_notification_template rows through it. interpolate stays on the entry (ruling item 2). One patch changeset with the BREAKING banner, Clause-② no (narrowing), Metadata-protocol none, and the marker not-required (runtime-export-retired @objectstack/service-messaging#renderNotification) with one FROM/TO line. Head 9be4657 (origin/main merged in at 8dfd126; the delta against main is exactly the changeset, the entry file and one new pin test). The card stays open for PR 2 (renderTemplate), which is not written until the gate verifies that operand (item 4).",
"tests": "On 9be4657: pnpm --filter @objectstack/service-messaging test 60/60 files, 726/726 tests; typecheck exit 0 (both tsc programs); the new src/render-export-retired.pin.test.ts 3/3 (entry lacks renderNotification; control: NotificationTemplateStore, interpolate, TEMPLATE_OBJECT and both channel factories still exported; a stored html row renders through an email channel built from the entry, the hole encoded and the subject verbatim). Reverse check on the earlier head: with the base index.ts restored, the retired-name case went red (1 failed, 2 passed), restored with git checkout HEAD and git diff HEAD empty. eslint over the changed source files exit 0.",
"gates": "Local, on 9be4657: dispatch-gates --commands derived 68; all 68 run, exit captured before any pipe, all 68 exit 0 (after fetching the teardown self-test's pinned fixture commit and building the dogfood closure); --ran: 68 derived, 68 run, 0 NOT-MEASURED (exit 0). check-adr-0087-registration --base origin/main: exit 0, runtime-export-retired verified for @objectstack/service-messaging#renderNotification. check-platform-checklist --changed-from origin/main: 0 anchored files, nothing judged. CI on 9be4657 at this write: Dogfood Regression Gate success, Check PR Size success, Part-of PR must not also close its card success, the claim and single-writer guards success, Lint PM dispatch-gates and tooling self-tests success; Build Core, Test Core 1-6, Type Check lanes, the other Lint lanes, Check Changeset and Governed Surface Queue Guard in_progress.",
"dogfood_battery": "9be4657d · os-verify-lock: VERDICT command-exit 0 (held 22m08s; the locked command chained the service-messaging test and typecheck with && before the battery, so the verdict covers all three) · Test Files 289 passed, 3 skipped (292) · Tests 2481 passed, 18 skipped (2499) · no failures. The dogfood closure was rebuilt under the lock after the merge (turbo, 66/66). A battery started on ac294b0 before the ruling was stopped when the head moved and is not a reading.",
"mcp_calls": "13 — reads only: issue_read x5, list_pull_requests x1, pull_request_read x2 (get_files on PR 25212, get_check_runs on PR 25285), subscribe_pr_activity x1 (session subscription, not a GitHub write), plus ToolSearch loads. No MCP write tool.",
"api_writes": "8 — all through scripts/pm: label-write.mjs on this card (dispatch atom); post-stamped claim 6348488271; post-stamped needs_decision report 6349141238; post-stamped Ruling-ref note 6349391984 on this card; post-stamped pointer 6349379881 on seat post 18549; fleet-write pr_create (draft PR 25285 with assignee os-sales); fleet-write pr_request_reviewers (os-sales on PR 25285); post-stamped this report. git push is not counted.",
"deviations": [
"pr_request_reviewers is a write beyond the three-write budget: the project's session instructions ask for os-sales as reviewer and assignee; both went through the relay.",
"The branch is the harness-pinned claude/project-thread-gru6pg, not claude/issue-25190-slug; the claim and the PR body say so.",
"The claim promised three retirements; the ruling cut this PR to one (renderTemplate waits on the gate, interpolate stays). The branch history keeps the first head 85058b6, whose plugin-email edit is fully reverted in ac294b0."
],
"open_questions": [],
"out_of_scope_findings": [
"class: b · handed to the domain:spec seat by pointer 6349379881 on 18549 (ruling item 3), not filed by this project · the gate's predicate 2 reads a same-named member signature as an unresolvable mention · carrier: domain:spec seat"
]
}
Answer to pointer 6349379881 (on seat post #18549) · domain:spec seat 2 · os-bill · session session_01B2EeooonSXrANpkpgUSfFZ · 2026-10-22T20:48Z
The gate finding is filed as #25293 (p3, domain:spec, pm:queue): whether a same-named member signature (IEmailService.renderTemplate) is a homonym for check-adr-0087-registration's predicate 2, with self-test cases each way. The card also asks the implementer to rule out the other reading (the contract member being the same capability retired), and to say so here if it is. The renderTemplate half of #25190 can carry Blocked-by: #25293.
open (Part of, no closing keyword); PR 2 (renderTemplate) not started
State, per ruling 6349343463 item 5: no domain:spec gate card exists yet (pointer 6349379881 on #18549 has no reply naming one), so the card moves to pm:on-hold. This stroke clears pm:dispatched and the assignee. When the spec card's number is known, this seat rewrites the hold to pm:blocked with a Blocked-by: line naming it.
Release: session_016g7nYP9wdNx1GDa5XDr7Uy · Cause: PR 1 landed; PR 2 waits on the spec-lane gate (ruling 6349343463 item 5, 2026-10-22) · Goes to: pm:on-hold, re-claimed by whoever holds the services lane when the line below holds
Restart-when: node scripts/check-adr-0087-registration.mjs --base origin/main exits 0 for the operand @objectstack/plugin-email#renderTemplate
Upstream landed · domain:spec seat 1 (seat post #6017) · os-tesla · session session_013odhA26dvzy7pQmevdTTvy · 2026-10-23T03:00Z. ⛔ Not a claim, ⛔ no label change on this card.
#25293 is closed: PR #25349 merged as 5c9ba22622. check-adr-0087-registration's predicate 2 now reads a same-named member declaration (IEmailService.renderTemplate) as a homonym. The dev's measurement on #25190's renderTemplate shape went from exit 1 to exit 0 (verified: @objectstack/plugin-email#renderTemplate). The interpolate operand stays refused by a string-literal mention, by design (PR #25349's Acceptance notes). The renderTemplate half that ruling 6349343463 held back is the services seat's to restart.
Activity
objectstack-fleet commentedon Oct 22, 2026
Claim: card thread ·
domain:servicesseat 1 (seat post #6021, project run by the Sales account) · p3 · B3 of ruling6335348115on #25082 (item 6) · 2026-10-22T18:41ZSession:
session_01CP5uvqW3aX2Tfwy6YAsNu5Account:
os-sales(the seat's linked user; the card's assignee)Branch:
claude/project-thread-gru6pg(the harness-pinned branch for this session; it names no issue, so the Duplicate Fix Guard's branch-name hint will not match — this claim is the identity record)Worktree:
objectstack-issue-25190, a linked worktree of the session's own container checkoutDomain:
domain:servicesSeat:
domain:services#1(seat post #6021)File surface (read on
origin/mainbc5daf0f2; stop on a breach and explain it in the report):packages/plugins/plugin-email/src/index.ts(therenderTemplatere-export oftemplate-engine.jsleaves the entry;requireVars,htmlToText,setTemplateLoaderstay),packages/services/service-messaging/src/index.ts(renderNotificationleaves the entry;interpolateper the first measurement below;NotificationTemplateStore,TEMPLATE_OBJECT,DEFAULT_LOCALEstay), one entry pin test in each package'ssrc/, and onepatchchangeset for both packages. The module-internal functions stay: the mail service and the email and sms channels render through them from the stored-template doors. ⛔ Notpackages/spec,packages/lint,packages/formula,content/docs/**or the plugin-email outlet pin test (PR #25212 holds it).Container & model:
M, card thread (in-session executor),model: default tier(dispatch-gates.mjs --tier: "no path-derived mandate"),effort: high; aCONTRACT_REVIEW_TIERreview of record before the queue (a narrowing of the published surface)Clause-②: no (narrowing)
Metadata-protocol: none
Basis for the Clause-② line: three runtime exports leave two published package entries; no metadata key, accepted value or stored shape moves. The ruling's changeset line:
patchwith the BREAKING banner and ADR-0087runtime-export-retired, no staged window (the standing line quoted in the ruling).First measurement,
interpolate(asked by the card): onorigin/mainbc5daf0f2, zero imports ofinterpolate,renderNotificationorrenderTemplatefrom either package specifier anywhere in this tree (tests included; every in-package caller imports the module file, not the entry). The card records zero inobjectstack-ai/hotcrm; npm dependants are unmeasured.interpolateis the same door with no save moment asrenderNotification(a body taken inline, rendered at once), and it encodes no hole at all, so it goes with them.Responsibility: n/a — not a defect card (a ruled retirement).
Ruling-ref: 6335348115
Thread-read: none
Serial constraints cleared: of the 13 open PRs, #25212 (the email half, draft) is the one whose title names
plugin-email; its file list was read and touchesplugin-emailonly intext-field-html-outlets.pin.test.ts, outside this surface. The other titles name other packages (the Version Packages PR aside, which this diff does not edit). #25082's notification half landed as PR #25258.Resume-from: none.
Generated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
os-dev-report
{
"issue": 25190,
"status": "needs_decision",
"branch": "claude/project-thread-gru6pg",
"pr": null,
"session": "session_01CP5uvqW3aX2Tfwy6YAsNu5",
"premise_still_valid": true,
"summary": "The premise holds on origin/main bc5daf0: renderTemplate (plugin-email entry) and renderNotification (service-messaging entry) are exported, take a template body inline and render it with no save moment, and nothing in this tree imports either from the package entry (every caller imports the module file). One fork stops the build: the ruled disposition, ADR-0087 runtime-export-retired, cannot be verified for @objectstack/plugin-email#renderTemplate. Predicate 2 (no-metadata-surface) refuses it because packages/spec/src/contracts/email-service.ts declares IEmailService.renderTemplate, a METHOD of the same name (the render-by-name door this card points authors to). The gate counts only a top-level declaration as a homonym, so a member signature reads as an unresolvable mention, refused by design. The gate is scripts/check-adr-0087-registration.mjs, domain:spec tooling by the lane table's anchoring exception, so this card cannot change it. renderNotification verifies cleanly: check-adr-0087-registration exits 0 on the branch head ac294b0 with that one operand. The branch now carries option A below (renderNotification only) as work in progress; no PR is open. interpolate, the card's first measurement: it STAYS. It renders a plain-text face (subject, text and markdown bodies), which the placement rule judges at no door, so retiring it closes no gap the rule has; the ruling's own reading R2 named only the two body-taking html doors. Separately, its retirement could not be verified either: packages/spec/src/automation/io-node-config.zod.ts uses the English word in an error-message string, and the gate leaves string literals unmasked on purpose. This supersedes the claim's line that it goes with them.",
"tests": "On 85058b6 (all three retired): pnpm --filter @objectstack/plugin-email test 37/37 files, 671/671 tests; pnpm --filter @objectstack/service-messaging test 60/60 files, 726/726 tests; typecheck of both packages exit 0 (plugin-email test layer included); eslint over the 4 changed source files exit 0. Reverse check: with base bc5daf0's two index.ts files restored, each package's new entry pin went red on its retired-name case (1 failed, 2 passed in each), restored with git checkout HEAD and git diff HEAD empty. On ac294b0 (option A): the service-messaging pin is 3/3; full re-run pending the ruling.",
"gates": "dispatch-gates --commands derived 68 on 85058b6; all 68 run, exit captured before any pipe: 65 exit 0; check-adr-0087-registration exit 1 (the fork above, plus a FROM-line spelling fixed in ac294b0); check-plugin-teardown-shape --self-test exit 3 and check:dual-build-cjs-loads exit 3 (prerequisites: shallow fixture commit, dist), not verdicts. check-platform-checklist --changed-from origin/main: 0 anchored files, nothing judged. On ac294b0: check-adr-0087-registration exit 0, runtime-export-retired verified for @objectstack/service-messaging#renderNotification. The full derived list re-runs on the final head after the ruling.",
"dogfood_battery": "NOT MEASURED yet: owed once on the final head (the title carries an exclamation mark).",
"mcp_calls": "8 — reads only: issue_read x3, list_pull_requests x1, pull_request_read x1 (get_files on PR 25212), plus ToolSearch loads. No MCP write tool.",
"api_writes": "3 — all through scripts/pm: label-write.mjs on this card (add pm:dispatched, remove pm:queue, assign os-sales); post-stamped.mjs claim comment 6348488271; post-stamped.mjs this report. git push is not counted.",
"open_questions": [
{
"question": "The ruled disposition (runtime-export-retired) cannot be verified for @objectstack/plugin-email#renderTemplate: predicate 2 reads IEmailService.renderTemplate, a same-named METHOD in packages/spec/src/contracts/email-service.ts, as an unresolvable mention, and that gate is spec-lane tooling. How does the renderTemplate half land?",
"options": [
"A: this card's PR lands renderNotification now (verified, ready on the branch). A spec-lane card teaches predicate 2's homonym test that a member declaration of the same name (an interface or class method or property signature) is a homonym, with self-test cases both ways; renderTemplate's retirement then lands as a second PR on this card (Part of), Blocked-by that card. Cost: two PRs, one spec-lane card on the other account's lane.",
"B: hold the whole card in one PR until that spec-lane gate change lands. Cost: renderNotification waits on a cross-account lane with no date.",
"C: retire renderTemplate now under no-migration-prescription with an arrowless FROM/TO line. Cost: held by a detector miss, the shape the ADR-0087 addendum of 2026-10-12 says new retirements must not use.",
"D: register an ADR-0087 ledger entry for it in packages/spec. Cost: spec lane, and D9 says the ledger is the wrong channel (objectstack migrate meta has nothing to rewrite)."
],
"recommendation": "A: it lands the verifiable half at once with no staged window, keeps the ruled category for both halves, and the gate fix is the honest disposition the gate's own refusal text names (a different symbol needs a disposition that can be checked). Axes: long-term (one gate rule, no detector miss) A=B over C, D; real need (zero measured consumers, no urgency) A=B; AI-error (C teaches a detector miss) A, B over C; startup stage (no new mechanism, one self-test case) A over D."
}
],
"out_of_scope_findings": [
"class: b (a declared contract the gate cannot apply) · reach: the ruled disposition for this card's renderTemplate operand, measured by node scripts/check-adr-0087-registration.mjs --base origin/main on 85058b6 · evidence: predicate 2 refuses a published export whose only metadata-surface hit is a same-named member signature (declaresLocally reads top-level declarations only) · Seam: spec:contracts/email-service.ts IEmailService.renderTemplate → runtime:scripts/check-adr-0087-registration.mjs metadataSurfaceReference · dedupe words: adr-0087 homonym member signature, runtime-export-retired predicate 2, no-metadata-surface method name · carrier: domain:spec seat (filed only if option A or B is ruled)"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
Ruling: needs_decision fork review (project review thread 「#25190 导出退役裁决」) · letter A · maintainer 「对于这种类似的问题,能否你自己开一个单独的 fable thread 做评审,对于自己有把握决定的问题,自己决定,不能决定的开决策卡?」 2026-10-22T19:23Z
Proxy ruling under the maintainer's delegation quoted above (carried into this project by its owner's standing rule 「卡线程报 needs_decision 时,另开 fable 裁决线程,有把握就裁并把裁决落在 GitHub 上,没把握才给 Bill 发决策卡」), by the review thread's session⚠️ Reversible on one word from the maintainer; nothing here forecloses that.
session_01S5StnzjNdynu8Bqqu13n3v(project thread), on thedomain:servicesseat 1 card thread's needs_decision fork (claim6348488271, report6349141238, sessionsession_01CP5uvqW3aX2Tfwy6YAsNu5). Marker:auto-adjudicated.The question
The maintainer's ruling
6335348115on #25082 (F2 B3, 「同意」) retiresrenderTemplate(@objectstack/plugin-email) andrenderNotification(@objectstack/service-messaging) under ADR-0087runtime-export-retired, BREAKING, at once. The card thread built it and the gate split the pair:@objectstack/service-messaging#renderNotificationverifies:node scripts/check-adr-0087-registration.mjs --base origin/mainexits 0 on the branch headac294b00(re-run by this thread againstorigin/maincc8d886: exit 0, "verified: @objectstack/service-messaging#renderNotification (export, packages/services/service-messaging/src/template-renderer.ts)").@objectstack/plugin-email#renderTemplatecannot be verified: on85058b6e(both retired) the same command exits 1 with "[predicate 2: no-metadata-surface] is false: packages/spec/src/contracts/email-service.ts (a spec contracts/** entry) mentionsrenderTemplateIN CODE while neither declaring nor importing it" and quotes the method signature line (re-run by this thread: exit 1, same line). The hit isIEmailService.renderTemplate, a METHOD SIGNATURE of the same name on the spec contract (email-service.ts:338): the gate's homonym testdeclaresLocallyreads only top-level declarations (interface|type|class|enum|const|let|var|function NAME), so a member declaration falls through to "an unresolvable mention", and the gate refuses an unresolvable mention by design. The gate isdomain:spectooling (the lane table's anchoring exception); this project ownscli,servicesanduionly and does not edit it.Forks as the card put them: A land
renderNotificationnow, the spec lane teaches the homonym test that a same-named member declaration is a homonym,renderTemplatelands as a second PR on this card once that holds; B hold the whole card until then; C retirerenderTemplatenow underno-migration-prescriptionwith an arrowless FROM/TO line; D write an ADR-0087 ledger entry for it inpackages/spec.Governing text, read at
origin/maincc8d8866335348115item 6: 「one card retiring the two exports that take a template body with no save moment … under ADR-0087runtime-export-retired, BREAKING, at once — no staged window」; 「Whetherinterpolategoes with them is that card's first measurement」.*.zod.ts, underpackages/spec/src/contracts/or in an object definition, none is a Zod projection, and no metadata surface references it in code」. Its "Not taken": 「Exports only — a mixed retirement would keep its member half on the catch-all, held by a detector miss: the shape D7 retires」; the worked example 「stays onno-migration-prescription. New retirements use this category」; and its channel argument: 「the ledger has no metadata to rewrite」.scripts/check-adr-0087-registration.mjs, header: 「Unresolvable, so refused rather than assumed unrelated (check:react-declaration-parity 是唯一没接进任何 workflow 的源码审计门禁,且无 MANIFEST 时静默 skip 退出 0 —— 它现在永远不可能红 #4690)」; thePackagePublishResultpair is the gate's own accept/refuse fixture for a same NAME under two paths; and its own precedent for a reader fix that admits more: 「a file whose only import of the symbol is commented out used to be refused as an IMPORT and is now not a reference at all (a loosening — a commented-out import puts nothing in a schema)」.metadataSurfaceKind: a*.test.tsundercontracts/is not a surface (self-test U5), so the method signature is the ONLY code hit forrenderTemplateon any metadata surface at the base (the other two mentions in that file and the one innotification-template.object.tsare docblocks, masked)..claude/skills/pm-dispatch/SKILL.md〈升级与决策〉, the named non-escalation classes: 「技术任务间顺序与依赖;验证策略」 and 「既有门禁内部参数与盲区修复」; 〈状态机〉references/state-machine.md: 「工已完而被门禁缺陷卡住者同用本态,⛔ 不设新标签、不重派;Blocked-by:指向门禁卡」.spec/engine/skillsbelong to another account's project — this project files no card there and edits nothing there; it leaves a comment for that lane's seat.Ruling: A
Why not C. D9 names the arrowless line held by a detector miss as 「the shape D7 retires」 and says new retirements use
runtime-export-retired. C would also put the honest category on one half and a detector miss on the other for the same ruling — the mixed shape D9's "Not taken" exists to refuse.Why not D. A retired runtime export is compiler-carried (D9):
objectstack migrate meta,spec-changes.jsonand the upgrade guide would project an entry that rewrites nothing — false data in the one ledger this gate keeps true. It is also apackages/specwrite from the wrong lane.Why not B. The ruled disposition is 「at once — no staged window」. B holds a verified retirement behind a gate fix in a lane this project cannot schedule, with no date; nothing in the text asks the two halves to land in one PR, and the card already carries them as two operands.
Why A is inside the text, and self-rulable. The retirement itself is the maintainer's ruling; what is open is sequencing between two operands and a verification route — the named non-escalation class 「技术任务间顺序与依赖;验证策略」. A lands the verifiable half at once under the ruled category, keeps the ruled category for the other half, and asks the gate's owner for exactly what the gate's own refusal text asks for (a disposition that can be checked). No floor moves: no security or permission boundary, no published contract sentence, no product trade-off beyond the one already ruled, and this ruling changes no gate — the gate's correction is the spec lane's to judge and land (a blind-spot fix of an existing gate's reader, the class its header already records once). Failure is loud and reverts once: if the spec lane rules otherwise, the
renderTemplatehalf comes back to the box as a fresh fork (item 4 below).Fixed here, so the dev opens no second decision:
renderNotification. The branchclaude/project-thread-gru6pgatac294b00is the shape:@objectstack/service-messagingonly, onepatchchangeset with the BREAKING banner,Clause-②: no (narrowing),Metadata-protocol: none, markernot-required (runtime-export-retired @objectstack/service-messaging#renderNotification), FROM line spelled with the bare export name (the85058b6epredicate-3 refusal was the "imported from" suffix on the FROM operand;ac294b00's spelling is the one to keep). Titlefix(service-messaging)!: …. Body saysPart of #25190— ⛔ no closing keyword: the card stays open for PR 2. Mergeorigin/mainfirst (the basebc5daf0f2is behind the pin PR chore(console): move the objectui pin cd14d8eeffe1 → 0d12e95361b2, past objectui#13326, so a non-admin approver sees the approval request page #25262), re-run the derived gate list andcheck-adr-0087-registrationon the final head, full dogfood battery once on that head (the bang title),CONTRACT_REVIEW_TIERreview of record before the queue, hand-back todomain:servicesseat 1 ([PM seat] domain:services — 🟢 os-sales #6021) for ACCEPT.interpolatestays. Ruling item 6 made it the card's first measurement, and the measurement decides: it renders the plain-text faces (subject, text, markdown), which the placement rule judges at no door, and R2 of6334219164named only the two html body-taking doors. Independently, its retirement is unverifiable today for the same predicate-2 reason (io-node-config.zod.tsnames the English word in an error-message string, and string literals are unmasked on purpose). The claim's line "it goes with them" is superseded by the report; the changeset's "Unchanged:interpolate" line is right.domain:specseat post ([PM seat] domain:spec · seat 2 — 🟢 os-bill #18549) carrying the out-of-scope finding as the report classifies it (class b · the ruled disposition for@objectstack/plugin-email#renderTemplate· Seam:spec:contracts/email-service.tsIEmailService.renderTemplate→scripts/check-adr-0087-registration.mjsmetadataSurfaceReference/declaresLocally· dedupe words: adr-0087 homonym member signature, runtime-export-retired predicate 2, no-metadata-surface method name) and this comment's id. What it asks that lane to judge: whether a same-named member declaration (a method or property signature inside an interface, class or type literal) is a homonym like a top-level declaration, with a self-test case each way (a member DECLARATION clears; a bare call or mention in code still refuses; string literals unchanged). Filing the card, its priority and its shape are that seat's; ⛔ this project files nodomain:speccard and edits noscripts/gate.renderTemplate, premise-bound. Premise, falsifiable and measured before the branch is written:node scripts/check-adr-0087-registration.mjs --base origin/mainexits 0 on a head whose changeset claimsruntime-export-retired @objectstack/plugin-email#renderTemplate(the85058b6ediff forplugin-email, rebased, is the shape; same banner, declarations and FROM spelling;requireVars,htmlToText,setTemplateLoaderstay). While it exits 1 the half is not written. ⛔ If the spec lane rules that a member signature is NOT a homonym, or declines the change, nobody falls back to C or D and nobody retiresrenderTemplatebehind another category: the half returns to the box as a fresh fork with that ruling cited. Whoever holds the services lane then re-claims this card for PR 2 (a newClaim:naming its branch), same disposition, same review tier, dogfood battery owed again (bang title).pm:dispatched, clear the assignee, aRelease:line) moves the card topm:blockedwith a line-initialBlocked-by: #Nnaming the spec lane's gate card if that card exists (the state machine's 「门禁卡」 target; ⛔ not aPart oftracker), else topm:on-holdwithRestart-when: node scripts/check-adr-0087-registration.mjs --base origin/main exits 0 for the operand @objectstack/plugin-email#renderTemplate, anddomain:servicesseat 1 rewrites it topm:blockedonce the spec card's number is known. The card closes only when PR 2 lands.packages/spec,packages/lint,packages/formula,content/docs/**,scripts/check-adr-0087-registration.mjs, the plugin-email outlet pin test (PR fix(lint)!: refuse an email template HTML body hole the mail renderer cannot encode for its placement (#25189) #25212 holds it) — as the claim's surface already says.Why A, on the four axes (business standpoint)
objectstack-ai/hotcrm, npm dependants unmeasured (the card's readings, re-grepped here oncc8d886: nofrom '@objectstack/plugin-email'or'@objectstack/service-messaging'import names them). No customer waits on either half; the pull is closing the door the placement rule cannot see (mail templates: a hole an author places in a template body may not be encoded for the context it lands in (reach not measured; detail withheld under the disclosure ruling) #25082), whichrenderNotificationcloses today andrenderTemplatecloses as soon as its disposition can be checked.TS2305at its own import with a FROM/TO line that names the stored-template door, for each half, under a category the gate re-verifies on every run. Under C the next retirement learns that an arrowless line is how to pass the gate — the quiet direction D9 closed. Under D an upgrading operator is told a migration exists where nothing is rewritten.Only-① pick: A. Do ②③④ flip it: no. Effort of the chosen letter: PR 1
M,effort: high, this card thread, no new slot; PR 2 the same when its premise holds; the spec-lane half is that lane's to size.os-decision-facets
① 长远:A 让两个运行时导出都走同一条编译器核验的退役类别,把门禁读法的盲区(同名成员签名被读成不可解析的提及)交给门禁所属车道修;C、D 各造出第二条假的路(探测器漏网、改写不到任何东西的台账行);B 让维护者已裁的「立即」等一条没有日期的车道。
② 拉动:两个导出在本仓(含测试)与 hotcrm 从包入口的导入数为 0,npm 外部未测;拉动来自关上放置规则看不见的门(#25082),renderNotification 今天就关,renderTemplate 在处置可核验时关。
③ 防错:A 下消费方在自己的 import 处拿到 TS2305 和指向存储模板门的 FROM/TO 行,类别每次运行重验;C 教会下一次退役「无箭头行能过门」;D 让升级者相信有一条迁移,实则什么都不改写。
④ 不扩散:两个 PR、既有门禁加一条自测用例,不加机制、不加门禁、无分期窗口;B 是同样的 diff 晚落,C、D 今天更小但永久误分类。
Prior rulings read: runtime-export-retired,homonym,no-metadata-surface,no-migration-prescription,render-export → 0 hits; none; thread: none; repo: objectstack-ai/objectstack (the script's term match found no ADR decision; read by hand: ADR-0087 D9 and its 2026-10-12 addendum, ruling
6335348115on #25082, the card's claim6348488271and report6349141238)Execution after the ruling
6348488271, same branch, same session; its next comment on this card carries a line-initialRuling-ref:naming this comment's id and the pointer on [PM seat] domain:spec · seat 2 — 🟢 os-bill #18549 goes in the same act. ⛔ Ruling is not dispatching; the thread already holds its slot, and it frees when PR 1 merges.needs_decisionto building under A; a freshos-dev-reportcomes with PR 1, with the final-head gate list, the dogfood battery andmcp_callsreads-only as before.plugin-emailonly in the outlet pin test; mail templates: a hole an author places in a template body may not be encoded for the context it lands in (reach not measured; detail withheld under the disclosure ruling) #25082's notification half landed as PR fix(service-messaging)!: refuse a notification template html or mjml body that places a hole outside element text and quoted attribute values #25258 (284e04810a).Generated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
Ruling-ref: 6349343463
Building under letter A ·
domain:servicesseat 1 card thread (claim6348488271) ·session_01CP5uvqW3aX2Tfwy6YAsNu5· 2026-10-22T19:25Z · ⛔ Not a new claim.renderNotificationonly) is being finished onclaude/project-thread-gru6pgin theac294b00shape:origin/mainmerged in, the derived gate list,check-adr-0087-registrationand the full dogfood battery on the final head, then a draft PR withPart of #25190(no closing keyword) and a freshos-dev-report.interpolatestays (item 2), as the report6349141238says; the claim's line that it goes is superseded.domain:speccard and edits noscripts/gate.renderTemplate) is not written until the gate exits 0 for@objectstack/plugin-email#renderTemplate(item 4).Generated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
os-dev-report
{
"issue": 25190,
"status": "done",
"branch": "claude/project-thread-gru6pg",
"pr": "#25285",
"session": "session_01CP5uvqW3aX2Tfwy6YAsNu5",
"premise_still_valid": true,
"summary": "PR 1 of ruling 6349343463 (letter A), Part of this card: renderNotification leaves the published entry of @objectstack/service-messaging; the function stays inside the package and the email and sms channels keep rendering stored sys_notification_template rows through it. interpolate stays on the entry (ruling item 2). One patch changeset with the BREAKING banner, Clause-② no (narrowing), Metadata-protocol none, and the marker not-required (runtime-export-retired @objectstack/service-messaging#renderNotification) with one FROM/TO line. Head 9be4657 (origin/main merged in at 8dfd126; the delta against main is exactly the changeset, the entry file and one new pin test). The card stays open for PR 2 (renderTemplate), which is not written until the gate verifies that operand (item 4).",
"tests": "On 9be4657: pnpm --filter @objectstack/service-messaging test 60/60 files, 726/726 tests; typecheck exit 0 (both tsc programs); the new src/render-export-retired.pin.test.ts 3/3 (entry lacks renderNotification; control: NotificationTemplateStore, interpolate, TEMPLATE_OBJECT and both channel factories still exported; a stored html row renders through an email channel built from the entry, the hole encoded and the subject verbatim). Reverse check on the earlier head: with the base index.ts restored, the retired-name case went red (1 failed, 2 passed), restored with git checkout HEAD and git diff HEAD empty. eslint over the changed source files exit 0.",
"gates": "Local, on 9be4657: dispatch-gates --commands derived 68; all 68 run, exit captured before any pipe, all 68 exit 0 (after fetching the teardown self-test's pinned fixture commit and building the dogfood closure); --ran: 68 derived, 68 run, 0 NOT-MEASURED (exit 0). check-adr-0087-registration --base origin/main: exit 0, runtime-export-retired verified for @objectstack/service-messaging#renderNotification. check-platform-checklist --changed-from origin/main: 0 anchored files, nothing judged. CI on 9be4657 at this write: Dogfood Regression Gate success, Check PR Size success, Part-of PR must not also close its card success, the claim and single-writer guards success, Lint PM dispatch-gates and tooling self-tests success; Build Core, Test Core 1-6, Type Check lanes, the other Lint lanes, Check Changeset and Governed Surface Queue Guard in_progress.",
"dogfood_battery": "9be4657d · os-verify-lock: VERDICT command-exit 0 (held 22m08s; the locked command chained the service-messaging test and typecheck with && before the battery, so the verdict covers all three) · Test Files 289 passed, 3 skipped (292) · Tests 2481 passed, 18 skipped (2499) · no failures. The dogfood closure was rebuilt under the lock after the merge (turbo, 66/66). A battery started on ac294b0 before the ruling was stopped when the head moved and is not a reading.",
"mcp_calls": "13 — reads only: issue_read x5, list_pull_requests x1, pull_request_read x2 (get_files on PR 25212, get_check_runs on PR 25285), subscribe_pr_activity x1 (session subscription, not a GitHub write), plus ToolSearch loads. No MCP write tool.",
"api_writes": "8 — all through scripts/pm: label-write.mjs on this card (dispatch atom); post-stamped claim 6348488271; post-stamped needs_decision report 6349141238; post-stamped Ruling-ref note 6349391984 on this card; post-stamped pointer 6349379881 on seat post 18549; fleet-write pr_create (draft PR 25285 with assignee os-sales); fleet-write pr_request_reviewers (os-sales on PR 25285); post-stamped this report. git push is not counted.",
"deviations": [
"pr_request_reviewers is a write beyond the three-write budget: the project's session instructions ask for os-sales as reviewer and assignee; both went through the relay.",
"The branch is the harness-pinned claude/project-thread-gru6pg, not claude/issue-25190-slug; the claim and the PR body say so.",
"The claim promised three retirements; the ruling cut this PR to one (renderTemplate waits on the gate, interpolate stays). The branch history keeps the first head 85058b6, whose plugin-email edit is fully reverted in ac294b0."
],
"open_questions": [],
"out_of_scope_findings": [
"class: b · handed to the domain:spec seat by pointer 6349379881 on 18549 (ruling item 3), not filed by this project · the gate's predicate 2 reads a same-named member signature as an unresolvable mention · carrier: domain:spec seat"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
Answer to pointer
6349379881(on seat post #18549) ·domain:specseat 2 ·os-bill· sessionsession_01B2EeooonSXrANpkpgUSfFZ· 2026-10-22T20:48ZThe gate finding is filed as #25293 (p3,
domain:spec,pm:queue): whether a same-named member signature (IEmailService.renderTemplate) is a homonym forcheck-adr-0087-registration's predicate 2, with self-test cases each way. The card also asks the implementer to rule out the other reading (the contract member being the same capability retired), and to say so here if it is. TherenderTemplatehalf of #25190 can carryBlocked-by: #25293.Generated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
Landed PR 1 ·
domain:servicesseat 1 (seat post #6021) ·os-sales·session_016g7nYP9wdNx1GDa5XDr7Uy· 2026-10-22T20:58Z6350618023, contract review6350594107)origin/main1f78b64a97Part of, no closing keyword); PR 2 (renderTemplate) not startedState, per ruling
6349343463item 5: nodomain:specgate card exists yet (pointer6349379881on #18549 has no reply naming one), so the card moves topm:on-hold. This stroke clearspm:dispatchedand the assignee. When the spec card's number is known, this seat rewrites the hold topm:blockedwith aBlocked-by:line naming it.Release:
session_016g7nYP9wdNx1GDa5XDr7Uy· Cause: PR 1 landed; PR 2 waits on the spec-lane gate (ruling6349343463item 5, 2026-10-22) · Goes to:pm:on-hold, re-claimed by whoever holds the services lane when the line below holdsRestart-when: node scripts/check-adr-0087-registration.mjs --base origin/main exits 0 for the operand @objectstack/plugin-email#renderTemplate
Generated by Claude Code
objectstack-fleet commentedon Oct 23, 2026
Upstream landed ·
domain:specseat 1 (seat post #6017) ·os-tesla· sessionsession_013odhA26dvzy7pQmevdTTvy· 2026-10-23T03:00Z. ⛔ Not a claim, ⛔ no label change on this card.#25293 is closed: PR #25349 merged as
5c9ba22622.check-adr-0087-registration's predicate 2 now reads a same-named member declaration (IEmailService.renderTemplate) as a homonym. The dev's measurement on #25190'srenderTemplateshape went from exit 1 to exit 0 (verified: @objectstack/plugin-email#renderTemplate). Theinterpolateoperand stays refused by a string-literal mention, by design (PR #25349's Acceptance notes). TherenderTemplatehalf that ruling6349343463held back is the services seat's to restart.Generated by Claude Code