Raised by the domain:spec execution PM seat (session session_01T6HeZvT9wdSJD1ZxJb5Eno, seat post #6017), surfaced by the at-tier contract review of PR #16342 (card #15963, verdict adopted verbatim at 5560140069, its advisory A4).
⛔ This is escalated, not decided. A repo-wide reading of clause ② is not an execution seat's rule to write. Two landed dispositions now disagree for the same class of change, and the next card of this shape will re-litigate it unless one sentence settles it.
The two dispositions
|
#14552 → PR #15962 (landed 2026-09-05) |
#15963 → PR #16342 (at-tier PASS, awaiting landing) |
| change |
defineStack's cross-reference refusal gains code: 'STACK_CROSS_REFERENCE_INVALID' / status: 422 |
the six sibling defineStack refusals gain one STACK_* code each |
| clause ② |
no |
yes |
| tier |
default |
CONTRACT_REVIEW_TIER |
registered in ERROR_CODE_LEDGER / StandardErrorCode |
no |
no |
classification row in dispatcher-error-vocabulary.ts |
yes |
yes |
Same file, same function, same mechanism, seven raise sites — one of them declared no, six declared yes.
The argument for no — #14552's explicit correction, 5553235312
That seat corrected its own dispatch brief with a measurement rather than an opinion:
| code |
hits in StandardErrorCode + ERROR_CODE_LEDGER |
OBJECT_OWNERSHIP_CONFLICT (#14367) |
0 |
NAMESPACE_CONFLICT (#14474) |
0 |
STACK_CROSS_REFERENCE_INVALID (#14552) |
0 |
VALIDATION_ERROR — positive control |
2 |
PERMISSION_DENIED — positive control |
2 |
and named the mechanism at dispatcher-error-vocabulary.ts:642:
the body carries declaredCode: NAMESPACE_CONFLICT beside code: VALIDATION_ERROR. The wire code stays a catalog member; the specific refusal rides alongside it. So the published face is not widened.
Grounded in the maintainer's 2026-08-28 ruling: 「运行时权限/安全行为变更不是条款② …… 条款②只指已发布契约面」.
The argument for yes — the at-tier reviewer of #16342
Measured on a fresh dist: the six STACK_* spellings ship in packages/spec/dist/index.js and index.mjs; nothing reaches .d.ts (0, with defineStack at 17 files as the lit control); the classes are not exported; check:api-surface unchanged. Then:
once shipped the six spellings cannot be renamed without breaking that consumer. Identifiers a downstream pins are contract whether or not a catalog lists them … My probe confirms that mechanism still holds here (resolveThrownHttpError → code: VALIDATION_ERROR, declaredCode: STACK_*). But that argument is about the wire vocabulary at a door, and these refusals have no door (door: 'none'; zero call sites under runtime/src + rest/src, positive control 23 example files) — the thrown value is the boundary, so #14552's reasoning does not transfer.
⭐ The question, stated once
Is "the published contract face" the wire vocabulary, or the thrown value's own fields?
Both readings are honest and each has a landed precedent.
Options
A — the published face is the WIRE vocabulary. An unregistered code on a thrown value is Clause-②: no regardless of door. #15963 is re-declared no (its at-tier review is then belt-and-braces, not owed). Cheap, matches two of three precedents, and keeps CONTRACT_REVIEW_TIER capacity for schema/accept-set changes.
⚠️ Cost: a downstream that branches on e.code from a boot refusal has no protection at all — the codes can be renamed by any later card and nothing would flag it.
B — the published face includes a THROWN value's code/status when they ship in dist. Clause-②: yes for this class. #15963 stands as landed-to-be; #14552's no is recorded as an under-tiering, not re-opened (it merged; the codes it added are now de facto contract either way).
⚠️ Cost: more cards routed to a tier that was quota-exhausted for part of today, for changes whose accept set does not move.
C — split on the door, which is what the two dispositions accidentally encode. door: 'none' (nothing catches it; the thrown value is the boundary) ⇒ yes. A code reachable at an HTTP door, where the demotion to declaredCode runs ⇒ no.
⚠️ Cost: the rule now needs a per-code door reading before the tier can be chosen — but dispatcher-error-vocabulary.ts already requires exactly that reading for its classification row, so the cost may be near zero.
⚠️ And it would make both landed dispositions correct, which is suspicious in a way worth saying out loud: a rule that retroactively blesses everything already done is the one most likely to have been fitted to the data.
四棱分析
实际业务需求 — 服务的是真实场景吗?是,而且可测:六个 STACK_* 码实测进了 dist/index.js 与 index.mjs,而卡自陈的消费方是 os validate / os build / host boot 与 AI 作者。⚠️ 但同样实测:今天树内 .code 消费者为 0(packages/cli/src 实测),所以拉动是预期的而非既有的 —— 这是本卡最诚实的一条读数,也是选项 A 最有力的支点。
项目长远合理性(权重 ≥50%) — 长远看,有一条规则比规则本身选哪一边更要紧:今天同一个函数的七个出口分成 1 + 6 两种申报,下一张同型卡会再吵一遍,而每次重吵都要花一次 CONTRACT_REVIEW_TIER 或一次误判。contract-first 的取向偏 B/C —— 「声明即强制」意味着一个会随包发布、消费方能钉住的标识符就是契约面;⛔ 但把「面」定义得过宽,会让条款②失去分辨力,那也是长远代价。
防 AI 写代码犯错 — 这一轴指向 B 或 C。一个 AI 作者写 catch (e) { switch (e.code) },拿到六个邻居的 undefined 会读成「这不是校验拒绝」而不是「这条还没有码」—— 那正是 #15963 卡自己记录的缺陷形状。选项 A 让这些码可以被任何后续卡静默改名,而没有任何门禁会响;宽容正是 AI 批量犯错被掩盖的温床。
创业阶段不扩散需求 — 这一轴指向 A。CONTRACT_REVIEW_TIER 是稀缺的(今日一度配额耗尽,#16342/#16347 都为此排过队),把不移动接受集的改动送进达档复核,是把稀缺档位花在低风险面上。⚠️ 反向读法同样成立:选项 C 恰好不扩散 —— 它只把 door: 'none' 这一小类送进达档,而那正是没有任何其他保护的一类。
推荐:C,以长远读数领起。 它是唯一让规则跟着机制走而不是跟着先例走的选项:declaredCode 的降级确实保护了有门的码(#14552 的测量是对的),而无门的码没有任何东西保护(#15963 复核的测量也是对的)。两条测量都真,选项 C 是唯一同时容纳它们的读法。
⚠️ 并且我把它最大的弱点写在上面而不是藏起来:C 让两个已落地的处置都变成对的,而「回溯性地祝福一切既成事实」正是最可能被数据拟合出来的规则形状。若维护者认为那个巧合太可疑,A 是更保守的落点,代价是那六个码此后无人守护。
⛔ 推荐是输入,永不是放行。
Governing text
.claude/skills/pm-dispatch/references/contract-review.md(载体纪律与降档保险丝)· scripts/pm/dispatch-gates.mjs 的 SUSPECT_TIER_GLOBS 档头(clause ② 由卡的内容判,路径只是暗示)· 维护者 2026-08-28 「条款②只指已发布契约面」· ADR-0112(code / status 是每个拒绝的机读半边)。
Freshness
Both cited comments re-read in this stroke: #14552 5553235312 (2026-09-05T16:37:01Z, last comment on that card is the 2026-09-06T01:58Z sweep) and #15963 5560140069 (2026-09-06T15:1xZ) plus my own correction to it 5560154218. ⛔ Nothing here is quoted from memory.
Generated by Claude Code
Raised by the
domain:specexecution PM seat (sessionsession_01T6HeZvT9wdSJD1ZxJb5Eno, seat post #6017), surfaced by the at-tier contract review of PR #16342 (card #15963, verdict adopted verbatim at5560140069, its advisory A4).⛔ This is escalated, not decided. A repo-wide reading of clause ② is not an execution seat's rule to write. Two landed dispositions now disagree for the same class of change, and the next card of this shape will re-litigate it unless one sentence settles it.
The two dispositions
defineStack's cross-reference refusal gainscode: 'STACK_CROSS_REFERENCE_INVALID'/status: 422defineStackrefusals gain oneSTACK_*code eachnoyesCONTRACT_REVIEW_TIERERROR_CODE_LEDGER/StandardErrorCodedispatcher-error-vocabulary.tsSame file, same function, same mechanism, seven raise sites — one of them declared
no, six declaredyes.The argument for
no— #14552's explicit correction,5553235312That seat corrected its own dispatch brief with a measurement rather than an opinion:
StandardErrorCode+ERROR_CODE_LEDGEROBJECT_OWNERSHIP_CONFLICT(#14367)NAMESPACE_CONFLICT(#14474)STACK_CROSS_REFERENCE_INVALID(#14552)VALIDATION_ERROR— positive controlPERMISSION_DENIED— positive controland named the mechanism at
dispatcher-error-vocabulary.ts:642:Grounded in the maintainer's 2026-08-28 ruling: 「运行时权限/安全行为变更不是条款② …… 条款②只指已发布契约面」.
The argument for
yes— the at-tier reviewer of #16342Measured on a fresh
dist: the sixSTACK_*spellings ship inpackages/spec/dist/index.jsandindex.mjs; nothing reaches.d.ts(0, withdefineStackat 17 files as the lit control); the classes are not exported;check:api-surfaceunchanged. Then:⭐ The question, stated once
Is "the published contract face" the wire vocabulary, or the thrown value's own fields?
declaredCodebehind a catalog member. ⇒ finding(spec):defineStack's cross-reference refusals are bareErrors — no ADR-0112code/status— so five REFUSED item classes in the ADR-0130 matrix are distinguishable only by message text #14552 was right, finding(spec): six of defineStack's seven refusals are still bare Errors — no ADR-0112 code/status — leaving one envelope among neighbours that have none #15963 is over-tiered.codethat ships indistand that a consumer writescatch (e) { switch (e.code) }against is contract the moment it ships, door or no door. ⇒ finding(spec): six of defineStack's seven refusals are still bare Errors — no ADR-0112 code/status — leaving one envelope among neighbours that have none #15963 is right, finding(spec):defineStack's cross-reference refusals are bareErrors — no ADR-0112code/status— so five REFUSED item classes in the ADR-0130 matrix are distinguishable only by message text #14552 was under-tiered.Both readings are honest and each has a landed precedent.
Options
A — the published face is the WIRE vocabulary. An unregistered code on a thrown value is
⚠️ Cost: a downstream that branches on
Clause-②: noregardless of door. #15963 is re-declaredno(its at-tier review is then belt-and-braces, not owed). Cheap, matches two of three precedents, and keepsCONTRACT_REVIEW_TIERcapacity for schema/accept-set changes.e.codefrom a boot refusal has no protection at all — the codes can be renamed by any later card and nothing would flag it.B — the published face includes a THROWN value's
⚠️ Cost: more cards routed to a tier that was quota-exhausted for part of today, for changes whose accept set does not move.
code/statuswhen they ship indist.Clause-②: yesfor this class. #15963 stands as landed-to-be; #14552'snois recorded as an under-tiering, not re-opened (it merged; the codes it added are now de facto contract either way).C — split on the door, which is what the two dispositions accidentally encode.
⚠️ Cost: the rule now needs a per-code door reading before the tier can be chosen — but
⚠️ And it would make both landed dispositions correct, which is suspicious in a way worth saying out loud: a rule that retroactively blesses everything already done is the one most likely to have been fitted to the data.
door: 'none'(nothing catches it; the thrown value is the boundary) ⇒yes. A code reachable at an HTTP door, where the demotion todeclaredCoderuns ⇒no.dispatcher-error-vocabulary.tsalready requires exactly that reading for its classification row, so the cost may be near zero.四棱分析
实际业务需求 — 服务的是真实场景吗?是,而且可测:六个⚠️ 但同样实测:今天树内
STACK_*码实测进了dist/index.js与index.mjs,而卡自陈的消费方是os validate/os build/ host boot 与 AI 作者。.code消费者为 0(packages/cli/src实测),所以拉动是预期的而非既有的 —— 这是本卡最诚实的一条读数,也是选项 A 最有力的支点。项目长远合理性(权重 ≥50%) — 长远看,有一条规则比规则本身选哪一边更要紧:今天同一个函数的七个出口分成 1 + 6 两种申报,下一张同型卡会再吵一遍,而每次重吵都要花一次
CONTRACT_REVIEW_TIER或一次误判。contract-first 的取向偏 B/C —— 「声明即强制」意味着一个会随包发布、消费方能钉住的标识符就是契约面;⛔ 但把「面」定义得过宽,会让条款②失去分辨力,那也是长远代价。防 AI 写代码犯错 — 这一轴指向 B 或 C。一个 AI 作者写
catch (e) { switch (e.code) },拿到六个邻居的undefined会读成「这不是校验拒绝」而不是「这条还没有码」—— 那正是 #15963 卡自己记录的缺陷形状。选项 A 让这些码可以被任何后续卡静默改名,而没有任何门禁会响;宽容正是 AI 批量犯错被掩盖的温床。创业阶段不扩散需求 — 这一轴指向 A。⚠️ 反向读法同样成立:选项 C 恰好不扩散 —— 它只把
CONTRACT_REVIEW_TIER是稀缺的(今日一度配额耗尽,#16342/#16347 都为此排过队),把不移动接受集的改动送进达档复核,是把稀缺档位花在低风险面上。door: 'none'这一小类送进达档,而那正是没有任何其他保护的一类。推荐:C,以长远读数领起。 它是唯一让规则跟着机制走而不是跟着先例走的选项:
⚠️ 并且我把它最大的弱点写在上面而不是藏起来:C 让两个已落地的处置都变成对的,而「回溯性地祝福一切既成事实」正是最可能被数据拟合出来的规则形状。若维护者认为那个巧合太可疑,A 是更保守的落点,代价是那六个码此后无人守护。
declaredCode的降级确实保护了有门的码(#14552 的测量是对的),而无门的码没有任何东西保护(#15963 复核的测量也是对的)。两条测量都真,选项 C 是唯一同时容纳它们的读法。⛔ 推荐是输入,永不是放行。
Governing text
.claude/skills/pm-dispatch/references/contract-review.md(载体纪律与降档保险丝)·scripts/pm/dispatch-gates.mjs的SUSPECT_TIER_GLOBS档头(clause ② 由卡的内容判,路径只是暗示)· 维护者 2026-08-28 「条款②只指已发布契约面」· ADR-0112(code/status是每个拒绝的机读半边)。Freshness
Both cited comments re-read in this stroke: #14552
5553235312(2026-09-05T16:37:01Z, last comment on that card is the 2026-09-06T01:58Z sweep) and #159635560140069(2026-09-06T15:1xZ) plus my own correction to it5560154218. ⛔ Nothing here is quoted from memory.Generated by Claude Code