Uh oh!
There was an error while loading. Please reload this page.
fix(rest): the record-share family carries a producer-marked userMessage on its two non-classified exits (#12693) - #12707
Conversation
…sage` on its two non-classified exits (#12693) `respondSharingError` carries the producer's caller-facing sentence at its classified re-dress since #12669 fork (a) / `b853cf303`. The family has two OTHER exits that never reach that classification, and both still dropped it: the 500 fault terminal (`SHARE_*_FAILED`) and the ADR-0111 message-prefix arm. Measured on `15bf9e859` before the repair, one marked producer per exit driven through the real routes on both doors: throw { code: 'SHARE_STORE_DOWN', status: 503, userMessage: '…' } share door : 500 SHARES_LIST_FAILED — no mark /data door : 503 SERVICE_UNAVAILABLE — mark carried throw Error('NOT_FOUND: no such record …') + userMessage share door : 404 NOT_FOUND — no mark /data door : 500 INTERNAL_ERROR — mark carried ⭐ The second row is the card's OPEN question, measured rather than assumed. The fault terminal had a precedent — #9934 deliberately rides the mark onto fault terminals — and the prefix exit had none. `/data` carries the mark for that identical throw on all five prefixes, so the two doors disagree and the exit is in; had `/data` omitted it there, the doors would have AGREED and there would have been nothing to repair. Neither exit holds a `refusal.body`, so the classified arm's expression is not reusable at either. `boundedDeclaredUserMessage` is `declaredUserMessage`'s presence answer with #5423's bound applied, lifted out of the private `withDeclaredUserMessage` wrapper so a caller with no body to merge into asks the same rule instead of open-coding it; the flat `/data` door is unchanged and goes on reaching it through that wrapper. ⛔ Only the mark is added. Measured across 32 route/door answers before and after: 0 statuses moved, 0 existing keys moved or changed value, 24 gaining exactly `userMessage`. The three deliberate share-vs-`/data` differences in the same measurement are pinned as STILL PRESENT rather than tidied up — the family still folds a declared 503 into its own 500, still interpolates the caught message where `/data` withholds 5xx prose (#5437), and `/data` is still not taught this service's local prefix idiom (ADR-0111). No in-tree producer sets `userMessage` at this seam today (`plugin-sharing` = 0 hits; positive control `throw ` = 25 files), so this wires a declared channel rather than repairing a live loss. The tests therefore build their own producers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjujZN219uFzBhSYfMykCd
…ass exits (#12693) Replaces the written-first prediction with what the run produced: 16 of 21 red, not the predicted 12 of 20. Every section's DIRECTION was predicted correctly — including §4b staying green, and §4a reding through its positive control rather than through its absences — and the one wrong number is recorded rather than quietly corrected: §2 was predicted "2/2" by counting the section's two `it` shapes instead of the six cases its table expands to. Mutation confirmed on disk before any verdict was read (anchored count 4 -> 0, blob 0112c6b -> b079cac4) and restored by `git checkout HEAD -- <abs path>`, proven by the blob hash returning to 0112c6b and an empty `git diff HEAD`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjujZN219uFzBhSYfMykCd
📓 Docs Drift Check2 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 13 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 4b06f80284da41468896e87ab66ea612a7a942fc && git checkout 4b06f80284da41468896e87ab66ea612a7a942fc
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 15bf9e859e56862e6ebe7b5c42404de103362457 0a1a1e467e14c565c33f732295c05e52d7d1cee2 && git checkout -B drift-repro 15bf9e859e56862e6ebe7b5c42404de103362457 && git merge --no-ff 0a1a1e467e14c565c33f732295c05e52d7d1cee2
node scripts/docs-audit/affected-docs.mjs --json 15bf9e859e56862e6ebe7b5c42404de103362457 |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#12693
respondSharingErrorhas carried the producer's caller-facing sentence at itsclassified re-dress since #12669 fork (a) /
b853cf303. The record-sharefamily has two other exits that never reach that classification, and both
still dropped it:
SHARES_LIST_FAILED/SHARE_GRANT_FAILED/SHARE_REVOKE_FAILED) —classifiedRefusalAnswerdeliberately hands adeclared or resolved 5xx back to "the catching route's own terminal", so a
marked fault never had a classification to ride;
classification answered
undefined.Reproduced before it was repaired, on
15bf9e859One marked producer per exit, driven through the real routes on both doors
(no in-tree producer sets
userMessageat this seam, so the tests build theirown — see the severity qualifier below):
Nothing invalid shipped — every body parsed as
ApiErrorSchema— which is whatmade the loss silent and one-directional: a console told by ADR-0112 to render
userMessageverbatim found nothing at these two exits and fell back to itsgeneric substitution, for the same throw the twin door rendered.
⭐ The prefix exit was an OPEN question, and it was measured rather than assumed
The two exits do not rest on the same evidence, and the card said so. The
fault terminal has a precedent — #9934 deliberately rides the mark onto fault
terminals, and
/datadoes carry it there. The prefix arm had none, so thequestion was: what does
/dataanswer for that identical shape?Measured:
/datacarries the mark on all five prefixes. The two doorstherefore disagree and the exit is in. Had
/dataomitted it there, the doorswould have agreed and there would have been nothing to repair — that is
what §3 of the new test asserts, so the answer stays checkable rather than
remembered.
The value source is measured, not borrowed
Neither exit holds a
refusal.body, so the classified arm's one line is notreusable at either — the shape is copied, the source is not.
error-response.tsnow exports
boundedDeclaredUserMessage:declaredUserMessage's presenceanswer with #5423's bound applied, lifted out of the private
withDeclaredUserMessagewrapper so a caller with no body to merge into asksthe same rule instead of open-coding it beside it. The flat
/datadoor isunchanged and goes on reaching it through that wrapper.
⛔ The
userMessage/declaredCodeasymmetry is not restated here.b853cf303wrote it down once, beside the classified re-dress, and this changecites it.
⛔ Only the mark is added
Measured across 32 route/door answers before and after the repair: 0
statuses moved, 0 existing keys moved or changed value, 24 gained
exactly one key (
userMessage) — the 24 share-door rows; the 8/datarowsalready had it. That is Clause ②'s falsifiable criterion, tested rather than
asserted.
The three deliberate share-vs-
/datadifferences visible in the samemeasurement stay exactly as they are, and §4b pins all three as still present so
a later tidy-up reds here:
503into its own500terminal;/datawithholds 5xx proseunconditionally (sendError 的显式状态直通覆盖 400–599,5xx 的原始驱动报错绕过全部泄漏启发式直达客户端(metadata-protocol 有活体产出方) #5437);
/datais still not taught this service's local prefix idiom (ADR-0111).All three are argued in
respondSharingError's own #11683 docblock. Reporting adeliberate difference as a defect costs as much as missing a real one.
git grep -c userMessage -- packages/plugins/plugin-sharingis 0 (positivecontrol:
throwhits 25 files), so no in-tree producer reaches thesepaths today. This wires a declared channel that was not connected at two
exits. ⛔ It is not a claim that users are losing sentences right now, and
severity is left unjudged.
Tests
packages/rest/src/rest-share-user-message-bypass-exits.test.ts— 21 tests, sixsections: the fault terminal (all three routes, both status spellings, an
unclassified
Error, and a sandboxed body whose prose is withheld while themark travels); the prefix arm (all five prefixes, all three routes, plus the
startsWith-not-includesboundary); the door-to-door comparison that IS thereproduction; the absences with a positive control inside each body; #5423's
bound and door-to-door equality of the bounded value; and the scope guards.
Ablation — deleting the repair's four call-site arguments: 16 of 21 red,
every failure naming the route, the body it got and the exact string expected.
Mutation confirmed on disk before any verdict was read (anchored count
4 → 0, blob
0112c6b7→b079cac4), restored withgit checkout HEAD --at anabsolute path and proven by the blob hash returning to
0112c6b7plus an emptygit diff HEAD.than quietly corrected: the prediction said 12 of 20. Every section's
direction was right — including §4b staying green and §4a reding through
its positive control rather than through its absences, which is #12669's
lesson — and the one wrong number was §2, predicted "2/2" by counting the
section's two
itshapes instead of the six cases its table expands to.Gates run locally, at
0a1a1e467(the final commit)pnpm --filter @objectstack/rest test— 152 files / 2502 tests passed;pnpm --filter @objectstack/rest typecheck— clean, andcheck:test-typecheckconfirms the new test file compiles (verified in the tscprogram with
--listFiles, 1 hit).pnpm lint(repo-wide,eslint . --no-inline-config) — clean in 69s, so nonarrowing was needed.
The families
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstackderived from the real change set —
check:route-envelope,check:dispatcher-error-vocabulary,check:published-files,check:test-source-alias,check:type-source-resolution,check:cross-package-test-inputs,check:engine-double-contract,check:where-matcher,check:query-options-erasure,check:type-check-coverage,check:type-check-debt(on a built closure),check:nul-bytes, the changeset family and the docs-drift pair — all green.scripts/pm/check-half-states.mjsexits 3 = NOT MEASURED (no GitHubcredential in this container, its own words), not a red.
Changeset:
patchon@objectstack/rest— published source, wire outputchanges, same call as #12510 and #12669 made for the same door.
Generated by Claude Code
Generated by Claude Code