Repository navigation
fix(verify): read a refused create's whole answer before calling it a hard failure - #24516
Conversation
…tored cut The CRUD stage cut a refused create's body to 280 characters and then tested that cut for the validation code. The /data door's validation envelope puts the app-authored message before `code`, so a long message pushed `"code":"VALIDATION_FAILED"` past the cut and a fixture gap was reported as a hard `create-failed`. The verdict now reads the whole body (the envelope's top-level `code` when the body is JSON, the original pattern over the full text otherwise); the 280-character cut stays on the stored `detail` only. Claude-Session: https://claude.ai/code/session_01SYEJqDL3NXxrJRBVqaMu9T Co-authored-by: Claude <noreply@anthropic.com>
… verdict Claude-Session: https://claude.ai/code/session_01SYEJqDL3NXxrJRBVqaMu9T Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 24 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 5 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 5 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 3630156e255cf4aa8f74f74b8612d63f0613c064 && git checkout 3630156e255cf4aa8f74f74b8612d63f0613c064
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 913fd6df8a2b3ec82612bf35dc40f4e062c3077f 3bcc4ae59269cbf7ebadc2ca75a008967843a0e1 && git checkout -B drift-repro 913fd6df8a2b3ec82612bf35dc40f4e062c3077f && git merge --no-ff 3bcc4ae59269cbf7ebadc2ca75a008967843a0e1
node scripts/docs-audit/affected-docs.mjs --json 913fd6df8a2b3ec82612bf35dc40f4e062c3077f
|
席位核对 — ACCEPT
Generated by Claude Code |
Fixes #24498
Clause-②: no
Basis: the CRUD stage reads the same answer correctly. No accept set, export or published shape moves.
What changed
packages/verify/src/verify.ts,runCrudVerification, the refused-create branch:isValidationAnswer(text).code, a 400 isneeds-fixtureexactly when that code isVALIDATION_FAILED.code, the stage's original pattern (VALIDATION_FAILED|Validation failed, case-insensitive) is matched against the full text.detailkeeps its 280-character cut (text.slice(0, 280)), taken after the verdict.needs-fixture, and anything else iscreate-failed. No other status value, exit-code rule or verify stage is touched.The shape was measured, not guessed. A real in-process stack (
bootStack, this branch, after the dependency closure was built) answered a create refused by a script rule with400and a 755-character body whose keys are["error","code","fields","object"]. The"code":"VALIDATION_FAILED"key starts at character 324. That is the flat envelopepackages/types/src/data-error-classification.tsbuilds for the validation arm (error= the app's message, thencode,fields,object). It is also the top-levelcodethat the sibling stages already read off this same door (reference-shape.ts,delete-behavior.ts,unique-scope.ts). The test fixture uses that measured key order andfieldsentry shape.Before and after: the stored row for a long validation answer, measured
The probe is a throwaway script, not committed. It boots one real stack with one object whose script rule (CEL
true, events insert and update) refuses every create with a three-sentence message, like hotcrm's forecast rule. It then runsrunCrudVerificationover that app. "Before" importsverify.tsexactly as it is at base6b42b6bc8. "After" imports it at3024fa2b7.verify.tsdetaillength6b42b6bc8create-failedcreateFailed: 1, needsFixture: 03024fa2b7needs-fixturecreateFailed: 0, needsFixture: 1The stored
detailis byte-identical in both rows. It ends... reconcile the three amounts, th, before thecodekey at character 324. Only the verdict moves.The optional hotcrm re-run was not done. The hotcrm checkout has no
node_modules. Runningos verifythere with this branch's build means installing its 18.0.0-next.3 dependency tree and building and wiring@objectstack/clifrom this branch, which is more than linking the built package. The in-process probe above drives the same door and the same stage.Ablation
The ablation restores today's cut-before-classify order with one mutation in
verify.ts. It goes throughnode scripts/ablation-replace.mjs(WRAP mode, its own exit, INT and TERM trap, plus a shell trap that runsgit checkout HEAD --on the absolute path):const text = await created.text();. The mutation made itconst text = (await created.text()).slice(0, 280);.d8fe2c05c833tod9528c3cad95.vitest run src/verify.create-refusal-verdict.test.tsgave 1 failed, 4 passed. The one red is the long-message case,expected status "needs-fixture", received "create-failed". All four controls stayed green.d8fe2c05c833, which equals HEAD.git diff HEADwas empty andgit status --porcelainwas empty.The test imports
./verify.jsfrom source (a relative import, so there is nodist/resolution), so no rebuild leg was owed.Controls
The new test file is
packages/verify/src/verify.create-refusal-verdict.test.ts. It uses a stack double (apiAsonly, the same double patternreference-shape.test.tsuses) and boots nothing. It has 5 cases, and all pass on HEAD:VALIDATION_FAILEDwhose message runs past 280 characters isneeds-fixture, anddetailequals the first 280 characters of the body. The fixture asserts its own precondition: the code key starts after character 280.VALIDATION_FAILEDisneeds-fixture.INTERNAL_ERRORiscreate-failed.INVALID_FIELDiscreate-failed.Validation failed: ...body isneeds-fixture. This exercises the full-text fallback.Local verification (HEAD
3bcc4ae59)pnpm --filter @objectstack/verify exec vitest run --maxWorkers=2(the whole package suite, under the shared lock) gave 44 test files and 376 tests passed,VERDICT command-exit 0.pnpm --filter @objectstack/verify typecheckexited 0. It runstsc --noEmitand thencheck:test-typecheck, which reported "0 file(s) / 0 error(s)".tsc -p tsconfig.test.json --listFileslists the new test file (1 hit). For comparison it lists 44 verify test files and 100packages/core/srcfiles.--no-inline-config) oververify.tsand the new test exited 0, with 0 errors and 0 warnings. The JSON output confirms both files were linted.node scripts/pm/dispatch-gates.mjs --commandsderived 63 commands. All 63 were run with each exit code captured before any pipe, and all exited 0.pnpm check:dual-build-cjs-loadsfirst answeredPREREQUISITE NOT MET(exit 3, unbuilt packages elsewhere in the tree). It was re-run after a fullturbo run build(75 of 76 tasks cached) and exited 0.--ranreconciliation read:63 derived, 63 run, 0 NOT-MEASURED, 0 UNRUN(a derived zero, all 63 with recorded exit codes).pnpm lint(eslint . --no-inline-config), the full union, was run at3bcc4ae59(the final commit) and exited 0 with no findings printed.Acceptance notes
codeis something other thanVALIDATION_FAILED, but whose prose contains "Validation failed", now readscreate-failed. The old pattern over the cut made itneeds-fixture. On the/datacreate door, the only producer that writes that phrase into the flat envelope is theVALIDATION_FAILEDarm itself (data-error-classification.ts). The other producers of the phrase are other doors: plugin-auth's adapter, the import runner, and the CLI's own printers. So no answer the door gives today changes its verdict except the long-message case.clipis applied to stored detail only.Generated by Claude Code