Skip to content

docs(spec): the ResumeFailureReport docblock stops inviting the parse that strips its code - #18585

Merged
os-bill merged 1 commit into
mainfrom
claude/issue-17929-resume-failure-code-docblock
Sep 17, 2026
Merged

os-bill merged 1 commit into
mainfrom
claude/issue-17929-resume-failure-code-docblock

Conversation

@os-bill

@os-bill os-bill commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator

Fixes #17929

Clause-②: no

What changed

The ResumeFailureReport docblock in packages/spec/src/contracts/approval-service.ts stops inviting a caller to parse that member with ResumeFailureDetailsSchema, 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:

  • a caller "that parses this member with ResumeFailureDetailsSchema reads the same three facts it reads off that door";
  • code is 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:

  1. code is required — packages/spec/src/contracts/approval-service.ts:617, code: ErrorCode;. The card's line number, unmoved.

  2. The docblock did invite the parse — verbatim as it stood at :573-576:

    They are inherited here, never re-spelled, so the two cannot drift, and a caller that parses this member with ResumeFailureDetailsSchema reads the same three facts it reads off that door.

  3. The schema does not declare code — packages/spec/src/api/automation-api.zod.ts:440: lazySchema around a plain z.object with runId / status / repairable, no .strict() anywhere on it.

  4. The strip is SILENT — measured, not inferred from the schema shape: ResumeFailureDetailsSchema.safeParse(report) answers success: true with error: undefined, and the returned object has no code key. No refusal, no unrecognized_keys issue, 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 code on ResumeFailureDetailsSchema. The prose is the defective artefact, for three independent reasons:

So the repair is the sentence, plus the mechanism that keeps the sentence honest.

Tests

packages/spec/src/contracts/resume-failure-report.pin.test.ts gains one case pinning both halves — the silence, and the prose that now warns about it:

  • safeParse of a full report succeeds, raises no issue at all, and yields no code;
  • the docblock carries the warning and names what the schema must not be used for;
  • the sentence that caused this card is gone (the regression guard proper);
  • anti-vacuity: the docblock still makes the claim the warning is about.

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:

leg mutation (landing proved by grep count) pin verdict
A — restore the defect docblock reverted to its 5ed7ad9df8 text (invitation back: 1, SILENTLY STRIPS: 0) exit 1, 1 failed, 6 passed — the docblock warns that the schema strips the code
B — kill the silence ResumeFailureDetailsSchema switched to z.strictObject (z.object: 0, z.strictObject: 1) exit 1, three cases red — parsing a full report SUCCEEDS -- the strip does not refuse: expected false to be true

Both restored with git checkout HEAD -- path, each verified by blob hash against the HEAD blob (95f4ee4d… / c2abbdd6…) and by an empty git diff HEAD; the restored tree runs the pin green again (7 passed) and git status --porcelain is empty. Neither mutation ships.

Gates

Derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands off the merge base, reconciled with --ran:

✓ dispatch-gates --ran: 83 derived famil(ies) accounted for — 79 run, 4 NOT-MEASURED (4 DERIVED from a recorded exit 3).
  83 derived, 79 run, 4 NOT-MEASURED, 0 UNRUN

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 remote claude/issue-* head whose name names this area). Called out here so the deviation is visible rather than inferred.

Acceptance notes

  • Noted, not filed: the same schema name is cited in four other docblocks (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/spec ships dist, and the repaired prose really does ship: both dist/contracts/index.d.ts and dist/contracts/index.d.mts carry the new sentence after a build, with a positive control (a sentence already in that docblock) hitting the same two files. Hence a patch changeset rather than skip-changeset.

Serial note — #17541

#17541 concerns ResumeFailureDetailsSchema.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 under packages/spec/src/api/ at all. Nothing here pre-empts that direction — it is out of scope here and stays open.


Generated by Claude Code

… 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>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 1 changed file(s) yielded no anchor (packages/spec/src/contracts/approval-service.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/src/contracts/approval-service.ts) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 5ed7ad9df84a319a9842ffceb97c030406a508a3 → packageMentionDocs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s tests tooling

Projects

None yet

2 participants