fix(spec): widen AnalyticsResultResponseSchema and TriggerFlowResponseSchema data to producer-contract parity - #13184
Conversation
…eSchema data to producer-contract parity Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
…sponse-schema-contract-parity
…surface baseline Tooling output of the schema widening, not hand-written: - `gen:docs` re-renders the analytics/automation-api reference pages with the newly declared members and the now-importable `AnalyticsResultResponse`. - `gen:strictness-ledger` moves the `api/` unknown-key site count 444 -> 448. - `--update-import-baseline` discharges the shrink-only ratchet entry `api/AnalyticsResultResponse — no type export`: the gap the card names is closed, and a stale line would stay available to excuse the next one.
📓 Docs Drift CheckThis PR changes 2 package(s): 7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 129 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 f03f418d3c51e758e6af51ed0a973d9dabf3933d && git checkout f03f418d3c51e758e6af51ed0a973d9dabf3933d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin dc729f3585830b8c71550245e6d883afbfcea94e 9ae99605fc02392cf3989cfa57826fc7e2cdc984 && git checkout -B drift-repro dc729f3585830b8c71550245e6d883afbfcea94e && git merge --no-ff 9ae99605fc02392cf3989cfa57826fc7e2cdc984
node scripts/docs-audit/affected-docs.mjs --json dc729f3585830b8c71550245e6d883afbfcea94e
|
|
Attribution + a platform reading, because the body cannot carry the first one right now. Measured on this PR, 2026-08-29, three writes each read back from the rendered page and from the API:
Consequence for anyone editing this PR body later: the session URL is written into the body prose (under the opening paragraph) as the durable copy, because the footer form cannot survive an edit. Do not try to re-paste the footer into the body — it will be removed again. Generated by Claude Code |
|
Standing down on this failure — it is not this PR's (domain:spec seat,
Landing posture is unchanged and independent of this: the PR is DRAFT and parked for contract review (see the parking note on #13078). Green here hands the reviewer a clean PR; it does not authorize this seat to land it. Generated by Claude Code |
Fixes #13078
Two
packages/specroute response schemas declared adatathat was a strict subset of thecontract their route relays. This brings each one up to parity with its producer’s declared
return — the ruled route, not the projection-docblock alternative.
Continuation of a dispatch that terminated mid-work on a host-side quota 429; the predecessor’s
commit is carried forward rather than restarted. Session:
https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
Why parity, not a projection note
The #6442 precedent is recorded in
analytics.zod.tsitself, on the sibling schema it was ruledfor: when the TS contract and the runtime already agree and the schema is the lone outlier, the
schema moves. Both missing member sets here are served in reality, so a deliberate-projection
note would have documented a refusal of reads the wire really carries — the same thing the #6442
route was rejected for.
What changed
AnalyticsResultResponseSchema.datanow declares everythingAnalyticsResult(
contracts/analytics-service.ts) declares —fields[].label/format/currency/percentScale(the ADR-0053 renderer chains) andtotals(the ADR-0021 marginal-aggregatechannel).
POST /analytics/queryendsdeps.success(await analyticsService.query(body, ctx)),so that contract IS the body under
data.TriggerFlowResponseSchema.datanow declares everythingAutomationResult(
contracts/automation-service.ts) declares — the paused third state of the #9378 / #9510 triggercontract (
status/runId/screen), the closedcodeclassification, the friendly terminalmessages and the run
summary. Both trigger routes enddeps.success(result)with that samecontract.
The screen payload’s two nested shapes are declared module-local (
screenFieldSpecShape,screenSpecShape) rather than exported:ScreenSpec/ScreenFieldSpecon the contract arealready THE names for those shapes, so a second exported name would be the permanent synonym
ADR-0122 D3 forbids and a new dual-source export. This follows the
cubeMetaMemberShapetreatmentalready in
analytics.zod.ts.Zero wire change. This is an accept-set widening: every payload that parsed before still
parses. What changes is that the served keys these schemas used to silently strip — a paused run’s
runIdandscreen, a measure’slabel— now survive a parse.The export gap the card names
AnalyticsResultResponsehad no exported type at all, so a consumer could not name the route’sresponse even once the schema said the right thing. It is now exported exactly as every sibling in
the file is (
z.inputplus aParsedz.infer), and the shrink-only import-surface ratchet entryapi/AnalyticsResultResponse — no type exportis discharged by tooling(
build-docs.ts --update-import-baseline), because a stale line stays available to excuse the nextmissing export.
protocol.zod.tskeeps its module-localz.inferalias deliberately, and it is NOT the export gap:that alias sits in a block of four (
AnalyticsQueryRequest,AnalyticsResultResponse,GetAnalyticsMetaRequest,GetAnalyticsMetaResponse) that type theAnalyticsProtocolinterfacewith post-parse shapes, and the first of them coexists with an exported
AnalyticsQueryRequestin
analytics.zod.tsalready. Rewiring it would change that interface’s declared types from parsedto input — a different change, out of this card’s scope.
Pins, both directions
AnalyticsResultMatchesContractinspec/api/analytics.test.tsandTriggerFlowDataMatchesContractinspec/api/automation-api.zod.test.tsbind each schema’sdatamember to its contract with atype-identity assert. Narrow either side alone and it goes red.
including the paused-flow
status/runId/screentriple, an object-form screen pause, therenderer-chain field metadata and the grand-total
totalsentry.percentScale/
status/codeoutside its closed vocabulary is rejected, and so is ascreenmissing itsrequired
nodeId.totalstototalsMUTANTinthe schema (mutation confirmed on disk by anchor grep counts 1 to 0 and 0 to 1, plus a changed
blob hash) turns
check:test-typecheckred atsrc/api/analytics.test.tswith 4 type errors,the parity pin. Restored by
git checkout HEAD -- PATH, proven by the restored blob hash matchingthe HEAD blob exactly and
git diff HEADempty.The #12104 client bindings are NOT retargeted
client.analytics.queryandclient.automation.triggerstill return the producer contracts(
BaseResponseintersected with the contract). Only the docblocks and the measurement comments arerefreshed, because they asserted a defect that no longer exists. Two
@ts-expect-errorpins inreturn-type-precision.test.tshad to be re-judged rather than re-spelled: their suppressions wouldnow be UNUSED (TS2578) precisely because the gap they pinned is closed, so they are replaced by the
stronger equality — the
datamember ofTriggerFlowResponseequalsAutomationResult. The pinfile still passes:
Test Files 2 passed (2) · Tests 9 passed (9).Changeset
@objectstack/specminor — an additive widening of published response schemas plus two newexported types is a feature-level surface addition, not a fix to behaviour; nothing narrows, so it
is not major.
@objectstack/clientpatch — comments and docblocks only.check:changeset-no-majorand
check:empty-changesetboth green.Verification
All gate results below were measured on
9ae99605f, the branch head, with exit codes capturedbefore any pipe. Gate family derived by
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstackwith no hand-fed paths: 39 matched families, plus the convention-triggered ones for the
adds-or-edits-a-test-file kind.
pnpm exec turbo run build --concurrency=2over./packages/*and./packages/*/*—Tasks: 70 successful, 70 total.pnpm --filter @objectstack/spec exec vitest runon the two spec pin files —Test Files 2 passed (2) · Tests 68 passed (68).pnpm --filter @objectstack/client exec vitest runon the two client pin files —Test Files 2 passed (2) · Tests 9 passed (9).typecheckandcheck:test-typecheckgreen for both packages. The pin files are proven insidethe checked program:
tsc -p tsconfig.test.json --listFilesnames both, and none of the 263frozen TEST_DEBT errors are in files this PR touches.
pnpm --filter @objectstack/spec check:generated— 14 of 14 artifacts current afterregeneration; the regenerated artifacts (reference docs, strictness ledger
api/444 to 448,import-surface baseline) are in the diff as their own commit.
pnpm check:type-check-debt—31 ledger entr(ies) re-measured in 243.4s, 1570 raw tsc error(s) total, none above its recorded number. surplus: none.pnpm lint(repo-wideeslint . --no-inline-config) — exit 0, so no narrowing was needed.workspace and were re-run green after the full build (
check:dual-build-cjs-loads,check:skill-examples,check-dev-prereqs.mjs). Two are not measurable in this container and arerecorded as NOT MEASURED rather than as passes:
scripts/pm/check-half-states.mjs(exit 3, noGitHub credential — a PM-board sweep unrelated to this diff) and
scripts/check-test-completeness.mjs(a CI-only invocation that needs a turbo test log argument).
Landing posture
Implementation tier was
opusunder the recorded quota-exhaustion exemption, withneeds:contract-reviewas the compensating control.Clause-② applies on both legs, so this PR stays draft and is parked for the review chain — not
marked ready, not auto-merged, not enqueued.