Repository navigation
docs(spec): the ResumeFailureReport docblock stops inviting the parse that strips its code - #18585
Merged
Merged
Conversation
… that strips its code `ResumeFailureReport.code` is required, and the type's own docblock invited a caller to parse the member with `ResumeFailureDetailsSchema`. That schema declares the three shared members and not `code`, and it is a plain non-strict `z.object`, so the invited path strips the code silently: the parse succeeds, raises no `unrecognized_keys` issue and logs nothing, and the caller is left holding a failure report with no failure class — the one member the same docblock calls indispensable. Prose only. `ResumeFailureDetailsSchema` is correct where it is used (the resume door's `400 FLOW_FAILED` details, where the code rides on the error envelope beside it), and declaring `code` on it would widen a published accept surface and break the "declared ONCE" identity the contract pin asserts. Both halves are pinned in `contracts/resume-failure-report.pin.test.ts`: the silence of the strip, and the docblock carrying the warning instead of the invitation. Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3 Co-authored-by: Claude <noreply@anthropic.com>
Contributor
📓 Docs Drift Check
What this run could not see
Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
This was referenced Sep 17, 2026
os-bill
marked this pull request as ready for review
September 17, 2026 05:02
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #17929
Clause-②: no
What changed
The
ResumeFailureReportdocblock inpackages/spec/src/contracts/approval-service.tsstops inviting a caller to parse that member withResumeFailureDetailsSchema, and says loudly what that path actually does. Prose and its pin only — no schema shape moves.The docblock made two true statements in one paragraph:
ResumeFailureDetailsSchemareads the same three facts it reads off that door";codeis the one member a success envelope cannot leave to its envelope, because on a success answer nothing else names the failure class.Each is true alone. Together they route a reader into losing exactly the member the second one calls indispensable — and the loss is the quiet kind.
The legs, measured on this tree
Taken first-hand against
5ed7ad9df8, not carried over from the card:codeis required —packages/spec/src/contracts/approval-service.ts:617,code: ErrorCode;. The card's line number, unmoved.The docblock did invite the parse — verbatim as it stood at
:573-576:The schema does not declare
code—packages/spec/src/api/automation-api.zod.ts:440:lazySchemaaround a plainz.objectwithrunId/status/repairable, no.strict()anywhere on it.The strip is SILENT — measured, not inferred from the schema shape:
ResumeFailureDetailsSchema.safeParse(report)answerssuccess: truewitherror: undefined, and the returned object has nocodekey. No refusal, nounrecognized_keysissue, nothing logged. That silence is the whole defect; a loud refusal would be a lesser card.The fork, decided on the merits
Two repairs existed and they are not equivalent: fix the prose, or declare
codeonResumeFailureDetailsSchema. The prose is the defective artefact, for three independent reasons:400 FLOW_FAILEDerror.details, and on that door the registered code rides on theerrorenvelope the details sit inside. Declaringcodeon the details would put a second spelling of the failure class on that same answer — the duplication the [Decision] Must a resume failure reach the CALLER in a shape it can act on? — the family question triage reserved, now that its instance cards have each measured their own half and stopped at the same contract #16472 family ruling avoided by declaring the structure once.contracts/resume-failure-report.pin.test.tsasserts a type-level identity: the report minus itscodeISResumeFailureDetails. Addingcodeto the details breaks that identity, so the "declared ONCE" claim would have to be re-litigated, not merely extended.Clause-②would becomeyes) on a schema whose prose is already before the maintainer on another card. See the serial note below.So the repair is the sentence, plus the mechanism that keeps the sentence honest.
Tests
packages/spec/src/contracts/resume-failure-report.pin.test.tsgains one case pinning both halves — the silence, and the prose that now warns about it:safeParseof a full report succeeds, raises no issue at all, and yields nocode;Prose is unassertable except by reading it, so the contract source is read — the pattern this file already uses for the absence rule.
Reverse verification, two legs, each from the committed state, each proved on disk before it was run:
5ed7ad9df8text (invitation back: 1,SILENTLY STRIPS: 0)1 failed, 6 passed—the docblock warns that the schema strips the codeResumeFailureDetailsSchemaswitched toz.strictObject(z.object: 0,z.strictObject: 1)parsing a full report SUCCEEDS -- the strip does not refuse: expected false to be trueBoth restored with
git checkout HEAD -- path, each verified by blob hash against the HEAD blob (95f4ee4d…/c2abbdd6…) and by an emptygit diff HEAD; the restored tree runs the pin green again (7 passed) andgit status --porcelainis empty. Neither mutation ships.Gates
Derived with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsoff the merge base, reconciled with--ran:pnpm --filter @objectstack/spec build→ 0;typecheck→ 0 (tsc --noEmit+check:scripts-typecheck+check:test-typecheck, which compiles the pin file);vitest run --project local→ 0, 483 files / 13776 tests passed.pnpm lint(repo-wideeslint . --no-inline-config) → 0. No narrowing was needed, so no narrowing has to be justified.PREREQUISITE NOT MET(exit 3, which is not a finding):check:doc-formula-expressions,check:dual-build-cjs-loads,check:lean-entry-closure,check:type-check-debt. All four read built output of packages outside this diff and need a whole-repo build; declared to CI.pnpm check:cross-package-test-inputsexits 1 on a tree wherepackages/spechas been built, and its finding namespackages/cli/test/init-created-files-summary.e2e.test.tsdescendingpackages/spec/dist/— no path of this diff. Already filed as [finding] check:cross-package-test-inputs passes in CI and fails on a built tree — its verdict is a function of gitignored build state #18353 / [finding]check:cross-package-test-inputsanswers 1 or 0 depending on whetherpackages/spechas been BUILT — the author who follows AGENTS.md is the only one who sees the red, and CI never does #18440; not re-filed here.Declared deviation — file surface
The dispatch's declared surface was the docblock, the schema only if the repair required it, and a changeset. The repair required no schema edit, and none was made. It did take one file beyond the declaration:
packages/spec/src/contracts/resume-failure-report.pin.test.ts, the pin that exists for this exact contract and already reads this exact file's prose for the absence rule. Same directory, same card, same gate family, no new verification surface, and no in-flight branch touches it (checked against every remoteclaude/issue-*head whose name names this area). Called out here so the deviation is visible rather than inferred.Acceptance notes
packages/runtime/src/domains/automation.ts,packages/client/src/index.ts:5491). Every one of those is about the resume door, where the schema is the right reader and the code is on the envelope — so none of them carries this defect. Read and left alone.packages/specshipsdist, and the repaired prose really does ship: bothdist/contracts/index.d.tsanddist/contracts/index.d.mtscarry the new sentence after a build, with a positive control (a sentence already in that docblock) hitting the same two files. Hence apatchchangeset rather thanskip-changeset.Serial note — #17541
#17541concernsResumeFailureDetailsSchema.repairable's.describe()and sits in the decision box, unassigned and with no PR. This PR changes no shape and no.describe()on that schema — it writes no bytes underpackages/spec/src/api/at all. Nothing here pre-empts that direction — it is out of scope here and stays open.Generated by Claude Code