Found while implementing #14552 (PR #15962), which gave defineStack's cross-reference refusal an ADR-0112 envelope. The sibling refusals raised by the same function, a few lines apart, still carry none.
What was measured
packages/spec/src/stack.zod.ts, inside defineStack. After #14552 lands, one of the seven refusal sites carries code / status; the other six are new Error(message) with both undefined:
| Raiser |
Header text |
Envelope |
Zod parse (formatZodError) |
defineStack validation failed |
none |
validateKnownCapabilities |
defineStack capability validation failed |
none |
validateCrossReferences |
defineStack cross-reference validation failed |
STACK_CROSS_REFERENCE_INVALID / 422 (#14552) |
validateNamespacePrefix |
defineStack namespace-prefix validation failed |
none |
validateSingleApp |
defineStack single-app validation failed |
none |
validateHierarchyScopeCapability |
defineStack hierarchy-scope capability validation failed |
none |
validateTriggerCapability |
defineStack trigger capability validation failed |
none |
composeStacks in the same file carries several more bare refusals (action-key collisions, the object-source internal error).
Why it matters
Same argument ADR-0112 makes for every refusal, and the same one #14552, #14367 and #14474 were filed on: code / status are the machine-readable half. os validate and os build surface all seven of these to authors and to AI authoring agents, and six of them remain separable only by prose. The inconsistency is now the sharper problem — a consumer that learns to branch on error.code from the cross-reference refusal gets undefined from its six neighbours, which reads as "not a validation refusal" rather than "this one has no code yet".
Is the surface published?
Measured against the built artifacts rather than assumed:
packages/spec/dist/**/*.d.ts — absent. The refusals are thrown values, not declared types; no type surface changes.
packages/spec/dist/index.js, dist/index.mjs — present. The message strings ship in the published bundles, so this is published runtime behaviour.
content/docs/** — absent. No documentation page publishes these messages or a code for them.
So: published as runtime behaviour, with no type surface and no doc surface to reconcile.
Suggested shape (not a decision)
Follow the settled precedent chain — #14367, #14474, #14552 — one error class per rule family, status: 422, message text byte-for-byte unchanged, no ADR-0112 ledger registration (no wire door raises them), plus a classification row per code in packages/runtime/src/dispatcher-error-vocabulary.ts (door: 'none', verdict: 'boot-refusal'), which pnpm check:dispatcher-error-vocabulary requires and enforces in both directions.
⚠️ One decision this needs from a spec seat before code: whether the six get one shared code (e.g. a single STACK_VALIDATION_* family) or one code each. #14552 took one-code-per-raise-site because its raiser aggregates several item classes into a single throw; these six are separate raisers with separate headers, so per-raiser codes are available in a way they were not there. That choice is a vocabulary decision, not a mechanical port.
Not release-gating. Filed unassigned for triage.
Generated by Claude Code
Found while implementing #14552 (PR #15962), which gave
defineStack's cross-reference refusal an ADR-0112 envelope. The sibling refusals raised by the same function, a few lines apart, still carry none.What was measured
packages/spec/src/stack.zod.ts, insidedefineStack. After #14552 lands, one of the seven refusal sites carriescode/status; the other six arenew Error(message)with bothundefined:formatZodError)defineStack validation failedvalidateKnownCapabilitiesdefineStack capability validation failedvalidateCrossReferencesdefineStack cross-reference validation failedSTACK_CROSS_REFERENCE_INVALID/ 422 (#14552)validateNamespacePrefixdefineStack namespace-prefix validation failedvalidateSingleAppdefineStack single-app validation failedvalidateHierarchyScopeCapabilitydefineStack hierarchy-scope capability validation failedvalidateTriggerCapabilitydefineStack trigger capability validation failedcomposeStacksin the same file carries several more bare refusals (action-key collisions, the object-source internal error).Why it matters
Same argument ADR-0112 makes for every refusal, and the same one #14552, #14367 and #14474 were filed on:
code/statusare the machine-readable half.os validateandos buildsurface all seven of these to authors and to AI authoring agents, and six of them remain separable only by prose. The inconsistency is now the sharper problem — a consumer that learns to branch onerror.codefrom the cross-reference refusal getsundefinedfrom its six neighbours, which reads as "not a validation refusal" rather than "this one has no code yet".Is the surface published?
Measured against the built artifacts rather than assumed:
packages/spec/dist/**/*.d.ts— absent. The refusals are thrown values, not declared types; no type surface changes.packages/spec/dist/index.js,dist/index.mjs— present. The message strings ship in the published bundles, so this is published runtime behaviour.content/docs/**— absent. No documentation page publishes these messages or a code for them.So: published as runtime behaviour, with no type surface and no doc surface to reconcile.
Suggested shape (not a decision)
Follow the settled precedent chain — #14367, #14474, #14552 — one error class per rule family,
status: 422, message text byte-for-byte unchanged, no ADR-0112 ledger registration (no wire door raises them), plus a classification row per code inpackages/runtime/src/dispatcher-error-vocabulary.ts(door: 'none',verdict: 'boot-refusal'), whichpnpm check:dispatcher-error-vocabularyrequires and enforces in both directions.STACK_VALIDATION_*family) or one code each. #14552 took one-code-per-raise-site because its raiser aggregates several item classes into a single throw; these six are separate raisers with separate headers, so per-raiser codes are available in a way they were not there. That choice is a vocabulary decision, not a mechanical port.Not release-gating. Filed unassigned for triage.
Generated by Claude Code