Repository navigation
Commit 5c28cc7
docs(spec): the ResumeFailureReport docblock stops inviting the parse that strips its code (#18585)
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:
- **The schema is correct where it is used.** It is the wire schema of
the automation resume door's `400 FLOW_FAILED` `error.details`, and on
that door the registered code rides on the `error` envelope the details
sit inside. Declaring `code` on the details would put a second spelling
of the failure class on that same answer — the duplication the #16472
family ruling avoided by declaring the structure once.
- **It would contradict a landed pin.**
`contracts/resume-failure-report.pin.test.ts` asserts a type-level
identity: the report minus its `code` IS `ResumeFailureDetails`. Adding
`code` to the details breaks that identity, so the "declared ONCE" claim
would have to be re-litigated, not merely extended.
- **It would widen a published accept surface** (`Clause-②` would become
`yes`) 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.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
```
- `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-wide `eslint . --no-inline-config`) → **0**. No
narrowing was needed, so no narrowing has to be justified.
- **4 NOT MEASURED**, each a `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.
- **One red that is not this PR's**: `pnpm
check:cross-package-test-inputs` exits 1 on a tree where `packages/spec`
has been built, and its finding names
`packages/cli/test/init-created-files-summary.e2e.test.ts` descending
`packages/spec/dist/` — no path of this diff. Already filed as #18353 /
#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 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](https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent 4bd2c60 commit 5c28cc7
3 files changed
Lines changed: 68 additions & 6 deletions
File tree
- .changeset
- packages/spec/src/contracts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
571 | 571 | | |
572 | 572 | | |
573 | 573 | | |
574 | | - | |
575 | | - | |
576 | | - | |
577 | | - | |
578 | | - | |
579 | | - | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
| 587 | + | |
| 588 | + | |
| 589 | + | |
| 590 | + | |
| 591 | + | |
| 592 | + | |
580 | 593 | | |
581 | 594 | | |
582 | 595 | | |
| |||
Lines changed: 36 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
125 | 125 | | |
126 | 126 | | |
127 | 127 | | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
128 | 137 | | |
129 | 138 | | |
130 | 139 | | |
| |||
136 | 145 | | |
137 | 146 | | |
138 | 147 | | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
139 | 175 | | |
140 | 176 | | |
141 | 177 | | |
| |||
0 commit comments