Filed by the domain:services execution PM seat on hitting this lane's hard red line while landing #15556 (PR #17908). ⛔ This lane holds zero packages/spec — both remedies below are in packages/spec/src/api/error-code-ledger.zod.ts, so this routes to the domain:spec seat.
⛔ Filed unlabelled: domain:* and priority grading are triage's.
The contradiction, measured
Two spec-side declarations disagree, and nothing reconciles them:
(a) The contract makes the code REQUIRED and names it. packages/spec/src/contracts/approval-service.ts:609-617:
export interface ResumeFailureReport extends ResumeFailureDetails {
/**
* The registered error code the failure is told under (ADR-0112 ledger):
* `RESUME_FAILED` for a run that could not be advanced, …
* Required here, on purpose: a success answer has no envelope `code` to fall back on.
*/
code: ErrorCode;
}
⇒ An implementer filling ApprovalDecisionResult.resumeFailure or ApprovalRecallResult.resumeFailure must stamp a code, and for the stranded-resume case the docblock prescribes RESUME_FAILED.
(b) The provenance ledger owns that code elsewhere. packages/spec/src/api/error-code-ledger.zod.ts — RESUME_FAILED sits at :287, inside the '@objectstack/rest' owner block that opens at :191. The '@objectstack/plugin-approvals' block (:1062) does not list it.
(c) check:error-code-provenance then refuses the implementation. Its rule, from the script's own header (packages/spec/scripts/check-error-code-provenance.ts):
every stamp site of a REGISTERED code in packages/** non-test source must be listed under the stamping package's own owner key, or carry a recorded PROVENANCE_WAIVERS entry naming the owner key that deliberately holds the row instead ("the door, not the producer, names the wire vocabulary" — the FLOW_DISABLED / UPDATE_ID_MISMATCH class).
Measured on PR #17908, head 109c9ce13
| reading |
result |
CI Lint & Repo Gates step #103 "Error-code provenance guard" |
failure (the only failing step of 174) |
| the stamp site |
packages/plugins/plugin-approvals/src/approval-service.ts:3370 — code: 'RESUME_FAILED', |
| is it new? |
yes — a + line in this PR's diff |
| such stamps at the merge base |
0 |
does plugin-approvals own the row? |
no |
⚠️ Honest bound on this reading. The step failure and the stamp-site facts are measured. The causal link between them is derived from the guard's stated rule, ⛔ not from the guard's own output: driving pnpm --filter @objectstack/spec check:error-code-provenance in a fresh worktree answered tsx: not found / node_modules missing, which is NOT MEASURED, not a verdict. Whoever takes this should run the guard with a real install and read its refusal text before choosing a remedy.
⭐ Why this is a spec-side defect and not an implementer's mistake
The family ruling #16472 (director seat, decision batch #76, maintainer 「同意」) required that a resume failure told to the caller be told in a machine-readable shape including a registered error code. Spec then landed the carrier — ResumeFailureReport, the resumeFailure members on both approval carriers, ResumeFailureDetailsSchema, and the pin test — but no provenance row or waiver was added for the package that would fill it. So the declaration shipped in a state where satisfying it trips a sibling gate. The first implementer to fill the slot is simply the first to find out.
Shape of a fix — ⛔ not prescribed, it is the spec seat's call
Two routes exist, and the guard names both:
⛔ Neither remedy may be taken by dodging the gate — e.g. hoisting the literal into a constant so the textual scan stops matching. The guard's header states its scan is textual precisely because the failure mode is a string literal in a few syntactic positions; evading it changes nothing and removes the reading.
⚠️ This blocks more than one card
Refs
#15556 / PR #17908 (where it surfaced) · #15970 (the sibling that will hit it) · #16472 (the family ruling, batch #76) · ADR-0112 D3 / #13353 (the provenance gate's own charter)
Filed by the domain:services execution seat · seat post #6021 · R3
Generated by Claude Code
Filed by the
domain:servicesexecution PM seat on hitting this lane's hard red line while landing #15556 (PR #17908). ⛔ This lane holds zeropackages/spec— both remedies below are inpackages/spec/src/api/error-code-ledger.zod.ts, so this routes to thedomain:specseat.⛔ Filed unlabelled:
domain:*and priority grading are triage's.The contradiction, measured
Two spec-side declarations disagree, and nothing reconciles them:
(a) The contract makes the code REQUIRED and names it.
packages/spec/src/contracts/approval-service.ts:609-617:⇒ An implementer filling
ApprovalDecisionResult.resumeFailureorApprovalRecallResult.resumeFailuremust stamp a code, and for the stranded-resume case the docblock prescribesRESUME_FAILED.(b) The provenance ledger owns that code elsewhere.
packages/spec/src/api/error-code-ledger.zod.ts—RESUME_FAILEDsits at:287, inside the'@objectstack/rest'owner block that opens at:191. The'@objectstack/plugin-approvals'block (:1062) does not list it.(c)
check:error-code-provenancethen refuses the implementation. Its rule, from the script's own header (packages/spec/scripts/check-error-code-provenance.ts):Measured on PR #17908, head
109c9ce13Lint & Repo Gatesstep #103 "Error-code provenance guard"packages/plugins/plugin-approvals/src/approval-service.ts:3370—code: 'RESUME_FAILED',+line in this PR's diffplugin-approvalsown the row?pnpm --filter @objectstack/spec check:error-code-provenancein a fresh worktree answeredtsx: not found/node_modules missing, which is NOT MEASURED, not a verdict. Whoever takes this should run the guard with a real install and read its refusal text before choosing a remedy.⭐ Why this is a spec-side defect and not an implementer's mistake
The family ruling #16472 (director seat, decision batch #76, maintainer 「同意」) required that a resume failure told to the caller be told in a machine-readable shape including a registered error code. Spec then landed the carrier —
ResumeFailureReport, theresumeFailuremembers on both approval carriers,ResumeFailureDetailsSchema, and the pin test — but no provenance row or waiver was added for the package that would fill it. So the declaration shipped in a state where satisfying it trips a sibling gate. The first implementer to fill the slot is simply the first to find out.Shape of a fix — ⛔ not prescribed, it is the spec seat's call
Two routes exist, and the guard names both:
RESUME_FAILEDunder'@objectstack/plugin-approvals'(and, when approvals: arecallwhose resume strands reports it as an ordinary non-failure — norepairablediscriminator, where the identical strand throughdecidecarries one #15970 lands, the same site set).PROVENANCE_WAIVERSentry naming'@objectstack/rest'as the owner that deliberately holds the row.FLOW_DISABLED/UPDATE_ID_MISMATCHas the precedent — which reads like the matching shape here, since the REST approvals door is what publishes this vocabulary. Stated as the apparent precedent, ⛔ not as a recommendation.⛔ Neither remedy may be taken by dodging the gate — e.g. hoisting the literal into a constant so the textual scan stops matching. The guard's header states its scan is textual precisely because the failure mode is a string literal in a few syntactic positions; evading it changes nothing and removes the reading.
bubbleToParentfailure is swallowed, so an approval decision answers 200resumed: truewhile the run behind it is stranded — #13807's three-outcome shape, one level up #15556 / PR fix(automation): tell approval decide() callers when a subflow parent strands #17908 — blocked on this now; the PR is otherwise complete and carries a PASSed in-seat contract review.recallwhose resume strands reports it as an ordinary non-failure — norepairablediscriminator, where the identical strand throughdecidecarries one #15970 (recall's door, same file, same family ruling) — will hit this identically:ApprovalRecallResult.resumeFailurehas the same requiredcode. ⇒ Fixing this once unblocks both. Recorded so the second card does not re-derive it.Refs
#15556 / PR #17908 (where it surfaced) · #15970 (the sibling that will hit it) · #16472 (the family ruling, batch #76) · ADR-0112 D3 / #13353 (the provenance gate's own charter)
Filed by the
domain:servicesexecution seat · seat post #6021 · R3Generated by Claude Code