Uh oh!
There was an error while loading. Please reload this page.
fix(rest): /analytics/dataset/query carries a producer-marked userMessage on its three hand-built terminals (#12710) - #12731
Merged
Conversation
…e on its three hand-built terminals The route builds its error envelopes by hand and shares no exit with the /data door, so #9934's producer-marked `userMessage` — applied there once at the exit through `withDeclaredUserMessage`, branch-agnostically — was applied at none of them. Scope is by ARM: ① (declared 4xx passthrough), ③a (declared 5xx relay) and ③b (generic 500) all dropped it; ①b already carried it, because its body comes from `resolveErrorResponse`, and it is deliberately untouched. The value is `boundedDeclaredUserMessage` (#12693), resolved once for the whole catch. Exactly one optional key is added, only when the producer marked one; no existing key moves or changes value at any arm. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjujZN219uFzBhSYfMykCd
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)), so this run has no opinion about the docs. 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): |
os-litant
marked this pull request as ready for review
August 27, 2026 14:40
This was referenced Aug 27, 2026
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Aug 27, 2026
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#12710
POST /api/v1/analytics/dataset/query(and its environment-scoped twin) builds itserror envelopes by hand and shares no exit with the
/datadoor, so #9934'sproducer-marked
userMessage— which that door applies once at its exit,branch-agnostically, through
withDeclaredUserMessage— was applied at none of them.A producer's caller-facing sentence reached the client on
POST /data/:objectandvanished here for the identical throw.
Verified at
09123bf25.The card (and the claim's ruling 2) reports this door's classified arm as already
correct and scopes the fix to the two 5xx terminals. Measured on
4af6c4419, thatholds for one of its two classified arms and not for the other. Four terminals live
in that catch; three dropped the mark:
packages/rest/src/rest-server.ts@4af6c4419):9169-9170{ code, message }classifiedRefusalAnswerre-dress:9213-9219{ ...refusalFields, message }:9346-9348declaredServerFaultAnswer's body, sent verbatim500 ANALYTICS_QUERY_FAILED:9359{ code, error }①b is the arm the card measured — it does carry the mark, because its body comes
from
resolveErrorResponse, whose arms ride it already. ① is a different arm: it isread first, it serves every producer that declares
code+ a 4xxstatus(theINVALID_FILTER/CUBE_NOT_FOUND/INVALID_FIELD/DATASET_INVALIDfamilies thisdoor's own comments enumerate), and it re-shapes the envelope by hand without the mark.
Driven through the real route, against the flat
/datadoor for the identical throw:① is repaired in this PR, named here because it is outside the card's stated scope.
It is the same defect class on the same door, the same mechanical repair with its
correct form already pinned by the
/datadoor and by ①b, in a file no other open PRholds, under the same gate families — so it adds no verification surface. Leaving it
would have shipped a PR whose own prose asserted a falsehood, and queued a fourth card
in this chain. ⭐ If the reviewer prefers the card's stated scope, the ① hunk and its
§1/§4 pins are the separable half — say so and they come out into their own card.
The census that chose the layer (ruling 1)
declaredServerFaultAnswerhas exactly 2 consumers inpackages/rest/src:classifyDataError(error-response.ts:1009), whose only caller is the exportedmapDataError=withDeclaredUserMessage(error, classifyDataError(…))rest-server.ts:9346)So this is the "some already have it" case, and the reason is visible rather than
unknown: the mark is a door-exit rule, not an arm rule.#9934 deliberately applies
it once per door, outside classification; the shared body-builder carries none of its
own because its
/dataconsumer's wrapper already does. Repairing the shared functionwould therefore be a no-op duplicate for consumer 1, would still miss ① and ③b (neither
calls it), and would split a one-per-door rule across two layers. The repair is at the
call site, resolved once for the whole catch — which is also what lets one expression
serve all three arms.
The repair
boundedDeclaredUserMessage(exported by #12693) —declaredUserMessage's presenceanswer with #5423's bound applied — asked of the raw thrown error, since none of the
three arms holds a classification to ride on. ⛔ ①b is deliberately untouched: a second
application there would be one rule applied twice. Confirmed for these arms rather than
assumed (ruling 4): all three hold the raw
error, not arefusal.body, so the valuesource is the record-share family's, not #12669's.
No export is added. Re-measured (ruling 4):
boundedDeclaredUserMessageoccurs 0times in
packages/rest/src/index.ts(positive control: 17exportlines) — samenumbers the claim reports.
Clause ② criterion — falsified, and it holds
Criterion: no existing key moves or changes value. Exactly one optional key is
added, only when the producer marked one. Pinned per arm as an explicit key-order
assertion for an unmarked producer (§5): ① stays
['code','message'], ③a stays['error','code'], ③b stays['code','error'], each with its exact body. A blank,empty or non-string mark is not a declaration and invents no key. Because the layer
chosen is the call site, the criterion has only one door to hold on —
/datais nottouched by this diff at all.
Anti-vacuity
4af6c4419(branch base): the pin ran 10 failed /3 passed, §4's
/datahalf green as the positive control while the analytics halfreported
the analytics door dropped the producer's userMessage: {"error":"Internal server error","code":"READ_SCOPE_COMPILE_FAILED"}....markExtraspreads deleted): 12 failed / 5 passed,ABLATION_VITEST_EXIT=1. The 5 survivors are §5 — the absence assertions, which anunrepaired door also satisfies, exactly as predicted. What reds is the positive
control: every assertion that a marked producer's sentence reaches the body.
...markExtra3 → 0, and blobd23f5c009…→47fdb04b5….git checkout HEAD --naming thefile's ABSOLUTE path (never a bare double-dash form, which restores from the polluted
index),
trap … EXIT INT TERM, restored blobd23f5c009…equal to theHEADblob,git diff HEADempty. The fix was committed first, so the restore pointwas real.
Verification (all at
09123bf25, each through the shared verify lock)pnpm --filter @objectstack/rest test— 153 files / 2539 tests passed,VERDICT command-exit 0.pnpm --filter @objectstack/rest typecheck— green; its test half printscheck:test-typecheck: OK — @objectstack/rest's test layer compiles under packages/rest/tsconfig.test.json; 9 file(s) / 20 error(s) held in test-typecheck-debt.json (shrink-only)— unchanged. The new pin is in thatprogram (
--listFiles: 1 hit; positive control, an existing sibling test: 1 hit) andcontributes 0 of the 20 held errors.
pnpm lint(repo-wideeslint . --no-inline-config) —VERDICT command-exit 0.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(re-derived from the committed change set, not from a hand-written list): 24
path-derived + 6 convention-triggered, all exit 0, including
check:route-envelope,check:dispatcher-error-vocabulary,check:nul-bytes,check:cross-package-test-inputs,check:engine-double-contract,check:where-matcher,check:type-check-coverage,check:published-files.scripts/pm/check-half-states.mjsexits 3 — its owntext says "it is no reading at all" (the trigger-file index needs GitHub API access
this seat does not have). And
check:type-check-debtrefuses on an unbuiltclosure — "measuring now would not fail, it would silently measure a DIFFERENT
WORLD" — which is a refusal, not a red. Its ratchet for this package is the
check:test-typecheckreading above, measured green with the new file inside theprogram. CI runs both properly.
Reachability — stated plainly, ⛔ not inherited from #12693
Producer census for this door, run here with positive controls: zero in-repo
producers can reach these terminals with a mark. No file under
packages/services/**contains
userMessagein any form (positive controls inservice-analytics/src: 102code:hits, 278statushits), and that service dispatches no sandbox hook (positivecontrol: 1013
awaithits), so the QuickJS side-channel — the other in-repo carrier —does not reach it either. Repo-wide,
userMessagelives only in the channel packagesthemselves (
spec,types,rest,runtime,client) plus oneqa/dogfoodfile.So this wires up a declared channel that was not connected at these three arms, not
a report that anyone is being harmed today. The published contract already promises the
field on this door's envelope (
content/docs/references/api/analytics.mdx,ApiError.userMessage); the intended producer is an out-of-repo one — an app author'sanalytics datasource or strategy.
⛔ #12693's
plugin-sharingproducer count of 0 is the share family's number and wasnot reused; the 0 above is this door's own, measured with its own controls.
Generated by Claude Code