Skip to content

fix(rest): the record-share family carries a producer-marked userMessage (#12669 fork (a)) - #12691

Merged
os-litant merged 1 commit into
mainfrom
claude/issue-12669-share-user-message
Aug 27, 2026
Merged

fix(rest): the record-share family carries a producer-marked userMessage (#12669 fork (a))#12691
os-litant merged 1 commit into
mainfrom
claude/issue-12669-share-user-message

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Part of #12669fork (a) only. The card's fork (b) (issuesApiError.details) is a shape decision on a contract field and is deliberately not shipped here, so this PR does not close the card.

The defect, reproduced before it was repaired

respondSharingError (packages/rest/src/rest-server.ts, inside registerSharingEndpoints) asks classifiedRefusalAnswer — the flat /data door's own classification, which attaches a producer's caller-facing sentence in withDeclaredUserMessage (error-response.ts, #9934) — and re-dressed only status, code, the message and, since #12510, declaredCode into the nested ADR-0112 D5 envelope. The author's own remedy text was dropped one line below the local that carried it.

Measured on 07e646565 before the change, the card's producer driven through the real routes on both doors:

throw { code: 'CLOSE_PERIOD_LOCKED', status: 409,
userMessage: 'Ask finance to reopen the period.' }
share door : 409 {"success":false,"error":{"code":"RESOURCE_CONFLICT",
"message":"invoices still open",
"declaredCode":"CLOSE_PERIOD_LOCKED"}}
/data door : 409 {"error":"invoices still open","code":"RESOURCE_CONFLICT",
"declaredCode":"CLOSE_PERIOD_LOCKED",
"userMessage":"Ask finance to reopen the period."}

Nothing invalid shipped — every body parsed as ApiErrorSchema — which is what made the loss silent and one-directional: a console told by ADR-0112 to render userMessage verbatim found nothing at this door and fell back to its generic substitution, for the same throw the twin door rendered.

⛔ The two channels are NOT symmetric, and the code now says so once

Written down beside the re-dress, because the next reader will assume they are:

Clause ② criterion — tested, not asserted

The dispatching seat ruled clause ② inapplicable on a falsifiable criterion: no existing key moves or changes value. Measured over a 14-shape census driven through both doors before and after the change (the "before" leg is the ablated tree, so it is the same harness on both sides), 28 wire answers:

  • 7 lines changed, each by appending exactly one optional key at the end of the nested error object;
  • 21 lines byte-identical — all 14 /data answers, and the 7 share-door answers whose producer marked nothing;
  • no key moved position, and no existing key changed value.

The criterion was not falsified.

Tests — packages/rest/src/rest-share-user-message.test.ts (21 tests)

Six sections, driving all three routes (GET/POST /api/v1/data/:object/:id/shares, DELETE …/shares/:shareId) through the real handlers with a seam-reached witness on each: the sentence on the wire; absence for the shapes declaredUserMessage rejects; door-to-door parity with /data; what must not move (the 500 terminal, the ADR-0111 prefix idiom, and fork (b) reaching no key); the #5423 bound arriving already applied; and a four-corner matrix holding userMessage and declaredCode apart mechanically.

Every zero assertion carries a positive control in the same test body — the discipline the card itself modelled by reverse-checking its own expected-zero grep with sendEnvelopeError. That is not decoration: the ablation predicted §2 GREEN and measured it RED, because it is the control that reds, and a §2 that had gone green would have been the warning that its zeroes proved nothing.

Ablation (deleting the repair's read and its spread): 15 of 21 red. Mutation confirmed on disk by anchored counts (repair anchors 1→0, pre-change call 0→1) and a changed blob hash before any verdict was read; restored with git checkout HEAD -- on the absolute path under a trap … EXIT INT TERM, proven by an equal blob hash and an empty git diff HEAD — never by an exit code. Every failure names itself: the route, both doors' bodies, and the exact expected string.

Verification — union re-run on the final commit fecc97035

Every run through scripts/pm/os-verify-lock.sh; exit codes captured before any pipe.

runverdict
pnpm --filter @objectstack/rest exec vitest runTest Files 151 passed (151) · Tests 2481 passed (2481)
pnpm --filter @objectstack/rest typecheckcheck:test-typecheck: OK — 9 files / 20 errors held in test-typecheck-debt.json, none in the new file (--listFiles puts it in the program; 0 of the 20 are its)
pnpm lint (repo-wide eslint . --no-inline-config)exit 0 — full farm, not narrowed
check:route-envelope✓ Route-envelope conformance — 10 route module(s) audited: 7 conformant, 2 ratcheted, 1 exempt
check:dispatcher-error-vocabulary · check:nul-bytes · check:engine-double-contract · check:where-matcher · check:query-options-erasure · check:objectql-double-limit · check:slot-lookup · check:type-check-coverage · check:cross-package-test-inputsall exit 0
check:type-check-debt (--re-measure, workspace closure built first)OK — 31 ledger entr(ies) re-measured, 1687 raw tsc error(s), none above its recorded number
check:published-files · check:authz-resolver · check:page-declaration-shape · check:type-source-resolution · check:test-source-alias · check:empty-changeset · check:changeset-gate-self-tests · check:objectui-changeset · check-adr-0087-registration · check-changeset-no-major · check-comment-mask-adoption · check-plugin-teardown-shape · check-ci-filter-parityall exit 0
scripts/pm/check-half-states.mjsexit 3 = NOT MEASURED, not red — the script says so itself: "no issue was listed, no predicate ran … it is no reading at all" (this seat has no GitHub token)

Gate families re-derived from the real change set with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack after the last edit.

Changeset: patch on @objectstack/rest. Re-derived rather than copied — published source, wire output changes, additively, on one optional key an existing schema already declares.

Generated by Claude Code


Generated by Claude Code

…sage` (#12669 fork (a))
`respondSharingError` asks `classifiedRefusalAnswer` — the flat `/data` door's own
classification, which attaches a producer's caller-facing sentence in
`withDeclaredUserMessage` (#9934) — and re-dressed only `status`, `code`, the message
and, since #12510, `declaredCode` into the nested ADR-0112 D5 envelope. The author's
own remedy text was dropped one line below the local that carried it.
Measured on `07e646565` before the repair, one producer through both real routes:
throw { code: 'CLOSE_PERIOD_LOCKED', status: 409,
userMessage: 'Ask finance to reopen the period.' }
share door : 409 {"success":false,"error":{"code":"RESOURCE_CONFLICT",
"message":"invoices still open",
"declaredCode":"CLOSE_PERIOD_LOCKED"}}
/data door : 409 {"error":"invoices still open","code":"RESOURCE_CONFLICT",
"declaredCode":"CLOSE_PERIOD_LOCKED",
"userMessage":"Ask finance to reopen the period."}
Nothing invalid shipped — every body parsed as `ApiErrorSchema` — which is what made
the loss silent and one-directional: a console told by ADR-0112 to render
`userMessage` verbatim found nothing here and fell back to its generic substitution,
for the same throw the twin door rendered.
⛔ The two channels are NOT symmetric, and the repair writes that down once beside
the re-dress rather than restating #12510's derivation. `declaredCode` is read from
the classification because presence there MEANS demotion, an invariant a caller would
otherwise re-derive. `userMessage` has none left: `declaredUserMessage` already
decided presence and `truncateClientMessage` already applied #5423's bound, so the
classification's own field is carried straight through. The population is also wider
— a REGISTERED code demotes nothing and carries no `declaredCode`, and still carries
its author's sentence.
Additive and shape-preserving, measured rather than asserted: over a 13-shape census
driven through both doors, the only difference between the pre- and post-change wire
answers is one optional key appended to the nested `error` object on the two rows
that declared one. Unmarked, empty, whitespace-only and non-string marks still carry
no key; the ADR-0111 prefix idiom and this family's own `SHARE_*_FAILED` 500 terminal
are untouched.
Fork (b) — mapping the flat dialect's top-level `issues` onto the nested envelope's
`ApiError.details` — is a shape decision on a contract field and is deliberately not
shipped here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjujZN219uFzBhSYfMykCd
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️1 changed file(s) yielded no anchor (packages/rest/src/rest-server.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/rest/src/rest-server.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 — 13 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 07e64656593afb7c1915f5c97ea2e1587ef3b003packageMentionDocs.

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

Labels

documentationImprovements or additions to documentationsize/lteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-litant@claude