You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Each was a body that did not match the schema declaring it, and each survived a green suite for the same reason: every test asserted the emitted body against a hand-written literal. Comparing output to a literal proves the code does what the test author believed. It cannot prove the code does what the contract declares. Nothing had ever put the emitted value and the declared schema in the same assertion.
The fourth was found by the conformance suite #3870 added, on its first run. That suite is the pattern this issue is about — the question is how far it should be pushed, and how.
Measurements
I claimed in #3870 that the route → declaring-schema mapping "exists today and is unused". That is wrong at repo scale and I want it corrected in the record before anyone plans against it. It is true only of plugin-rest-api.zod.ts's own 25-route table.
Metric
Value
Mounted routes across the 5 route ledgers
237
…of which disposition: 'sdk' (the surface worth gating)
215
Route blocks in plugin-rest-api.zod.ts carrying responseSchema
25 (≈10% of mounted)
Ledger entries carrying a schema reference
0
Existing envelope-conformance files
4 (storage error+success, i18n error+success)
The ledgers record route → disposition → SDK method. They carry no schema reference. So for ~90% of the mounted surface the problem is not "emitted ≠ declared" — it is that nothing is declared to check against.
Spot check: the dispatcher's automation domain serves /flows, /actions, /connectors, /_status; the automation category declares /trigger and /actions. Most served routes have no response schema at all.
Second cost axis: thin vs deep emit sites
Of the dispatcher domains' 86 success emit sites:
32 inline literals — the body is built at the emit site; drivable with a mock service, cheap.
54 forwarded (deps.success(result)) — the body comes from objectql/drivers; needs a booted stack.
Per domain: i18n 5/0, data 0/6, meta 8/14, packages 4/13, automation 11/8.
Note i18n was the cheapest family in the repo — and it still took a PR.
Staged plan
Stage
Scope
Size
Character
A
Conformance for families that already have declarations and thin emit sites: discovery (2), analytics (3), automation's declared routes (3), notification (8)
ride the existing packages/qa/dogfood integration suite rather than new harnesses
needs design
C
Author response schemas for the ~190 routes that have none
largest
contract design, not test work
D
The actual ratchet: a route declaring responseSchema with no conformance coverage fails CI
small
only meaningful after A/B
Risks
Violation rate unknown, and the one sample is bad. i18n yielded four defects across five routes. Even a 20% rate across Stage A implies ~30 breaking wire changes.
Blast radius stops being nil. The four fixes were safe because objectui does not call those endpoints. data and metadata are not like that — they are the hot paths objectui and the SDK actually use. One wrong call there breaks the console.
Turning the ratchet on early reds the build — ~190 undeclared routes would trip it. Stage D must come last.
Recommendation
Do not open this as a repo-wide sweep. Work Stage A family by family, one PR each, ordered by thin emit sites × has declarations × low consumer risk. By that ordering the next family is discovery or notification, explicitly not data/meta.
Do not schedule Stage C at all. A route's response schema is a product decision about what that endpoint promises. Mass-producing ~190 of them would create a batch of declarations nobody validated — which is precisely how the four defects above came to exist. Let schemas get authored when a route is touched for other reasons.
Smallest useful first step
Add an optional responseSchema field to the route-ledger entry type. It turns "what does this route declare" into queryable data, is the prerequisite for Stage D, and can be filled in incrementally alongside Stage A — no need to populate it in one pass, and it creates no unvalidated declarations.
Not covered by existing gates
check:liveness does not overlap: it is registry-rooted over authorable metadata types, not API request/response schemas. That is exactly the blind spot all four defects lived in.
Tracking issue for the generalizable part of #3676 / #3833 / #3847 / #3870.
What those four shared
Each was a body that did not match the schema declaring it, and each survived a green suite for the same reason: every test asserted the emitted body against a hand-written literal. Comparing output to a literal proves the code does what the test author believed. It cannot prove the code does what the contract declares. Nothing had ever put the emitted value and the declared schema in the same assertion.
namespace/keysfilters no server read{}for every providerlabelsentries emitted as bare strings under a schema declaring{ label, help?, options? }/i18n/localesemitted a barestring[]under a schema declaring descriptors; the two serving surfaces disagreedThe fourth was found by the conformance suite #3870 added, on its first run. That suite is the pattern this issue is about — the question is how far it should be pushed, and how.
Measurements
I claimed in #3870 that the route → declaring-schema mapping "exists today and is unused". That is wrong at repo scale and I want it corrected in the record before anyone plans against it. It is true only of
plugin-rest-api.zod.ts's own 25-route table.disposition: 'sdk'(the surface worth gating)plugin-rest-api.zod.tscarryingresponseSchemaThe ledgers record route → disposition → SDK method. They carry no schema reference. So for ~90% of the mounted surface the problem is not "emitted ≠ declared" — it is that nothing is declared to check against.
Spot check: the dispatcher's automation domain serves
/flows,/actions,/connectors,/_status; the automation category declares/triggerand/actions. Most served routes have no response schema at all.Second cost axis: thin vs deep emit sites
Of the dispatcher domains' 86 success emit sites:
deps.success(result)) — the body comes from objectql/drivers; needs a booted stack.Per domain:
i18n5/0,data0/6,meta8/14,packages4/13,automation11/8.Note
i18nwas the cheapest family in the repo — and it still took a PR.Staged plan
packages/qa/dogfoodintegration suite rather than new harnessesresponseSchemawith no conformance coverage fails CIRisks
Violation rate unknown, and the one sample is bad. i18n yielded four defects across five routes. Even a 20% rate across Stage A implies ~30 breaking wire changes.
Each fix is a judgement call, not a mechanical substitution. This is the most under-estimated part.
GetTranslationsRequestdeclaresnamespace/keysthat no server reads — declared ≠ enforced #3676 concluded delete the declaration (the capability could not be delivered and nobody wanted it);GetFieldLabelsResponsedeclares rich label entries; both surfaces emit bare strings #3847 concluded change the implementation to satisfy it (it could be delivered, the data was already loaded, and it was the more useful surface). Opposite directions, decided by consumer sweeps and by where the data lives. That cannot be batched.Blast radius stops being nil. The four fixes were safe because objectui does not call those endpoints.
dataandmetadataare not like that — they are the hot paths objectui and the SDK actually use. One wrong call there breaks the console.Turning the ratchet on early reds the build — ~190 undeclared routes would trip it. Stage D must come last.
Recommendation
Do not open this as a repo-wide sweep. Work Stage A family by family, one PR each, ordered by thin emit sites × has declarations × low consumer risk. By that ordering the next family is discovery or notification, explicitly not data/meta.
Do not schedule Stage C at all. A route's response schema is a product decision about what that endpoint promises. Mass-producing ~190 of them would create a batch of declarations nobody validated — which is precisely how the four defects above came to exist. Let schemas get authored when a route is touched for other reasons.
Smallest useful first step
Add an optional
responseSchemafield to the route-ledger entry type. It turns "what does this route declare" into queryable data, is the prerequisite for Stage D, and can be filled in incrementally alongside Stage A — no need to populate it in one pass, and it creates no unvalidated declarations.Not covered by existing gates
check:livenessdoes not overlap: it is registry-rooted over authorable metadata types, not API request/response schemas. That is exactly the blind spot all four defects lived in.