Observation-class finding, measured while scoping #4717 on origin/main @ ea8e8490f. Nothing a user hits today — the route works and returns a sensible body. Filing plainly and letting triage grade it.
What was measured
publishMetaItem is served over REST:
packages/rest/src/rest-server.ts:5468 registers POST {metaPath}/:type/:name/publish, and :5488 awaits publishMetaItem(...) and hands the result to res.json().
- Its success return is built at
packages/metadata-protocol/src/protocol.ts:9771-9798 and carries success / version / seq / message, plus the conditional seedApplied / materializeApplied / projectionApplied.
But the string PublishMetaItem appears nowhere under packages/spec/src/:
grep -rn "PublishMetaItem" packages/spec/src/ # zero hits
And the endpoint itself is not in the REST declaration — packages/spec/src/api/plugin-rest-api.zod.ts declares 6 endpoints with category: 'metadata', and none of them has handler: 'publishMetaItem' or a /publish path (grep -n publish on that file: zero hits).
Why this is the same class as #5745, one door over
#5745 closed exactly this gap for the sibling write door, and its own prose states the discipline it was establishing. SaveMetaItemResponseSchema (packages/spec/src/api/protocol.zod.ts:456) now declares the FULL save body, the endpoint declares responseSchema: 'SaveMetaItemResponseSchema' (plugin-rest-api.zod.ts:824), and a producer-side conformance gate keeps the two in step — packages/objectql/src/save-meta-response-conformance.test.ts, whose header reads:
a future field added to the response, or an existing one dropped, turns this red instead of silently vanishing at parse.
The publish door has none of those three: no declaration, no responseSchema reference, no conformance gate. So version on the publish response is in the same undeclared state version on the save response was in before #5745 — and it is the same ADR-0008 OCC token, with the same "echo it back as If-Match" job.
Why it is observation-class rather than a defect
The route answers correctly and the body is well-formed; no caller is broken today. The cost is the one #5745 named: a public wire surface with no contract behind it, so a later field addition or removal is invisible instead of gated, and PublishMetaItemResponse cannot be named at the type level the way SaveMetaItemResponse can (packages/client/src/index.ts:18 imports and re-exports the latter).
Adjacent, and the reason this surfaced
#4717's metadata half puts the runtime authoring gate's advisory findings on the saveMetaItem response. The gate runs on both doors — protocol.ts:9133 for a direct active save, and protocol.ts:9869 for the draft-to-active promotion inside publishMetaItem (deliberately, per #4463 D1, so ?mode=draft then /publish is not a bypass). So whoever designs the advisory field will face two response envelopes, one declared and conformance-gated, one not declared at all. That asymmetry is worth knowing before the shape is chosen, not after.
Filed unassigned per Prime Directive #10. No pm:queue — observation class.
Observation-class finding, measured while scoping #4717 on
origin/main@ea8e8490f. Nothing a user hits today — the route works and returns a sensible body. Filing plainly and letting triage grade it.What was measured
publishMetaItemis served over REST:packages/rest/src/rest-server.ts:5468registersPOST {metaPath}/:type/:name/publish, and:5488awaitspublishMetaItem(...)and hands the result tores.json().packages/metadata-protocol/src/protocol.ts:9771-9798and carriessuccess/version/seq/message, plus the conditionalseedApplied/materializeApplied/projectionApplied.But the string
PublishMetaItemappears nowhere underpackages/spec/src/:And the endpoint itself is not in the REST declaration —
packages/spec/src/api/plugin-rest-api.zod.tsdeclares 6 endpoints withcategory: 'metadata', and none of them hashandler: 'publishMetaItem'or a/publishpath (grep -n publishon that file: zero hits).Why this is the same class as #5745, one door over
#5745 closed exactly this gap for the sibling write door, and its own prose states the discipline it was establishing.
SaveMetaItemResponseSchema(packages/spec/src/api/protocol.zod.ts:456) now declares the FULL save body, the endpoint declaresresponseSchema: 'SaveMetaItemResponseSchema'(plugin-rest-api.zod.ts:824), and a producer-side conformance gate keeps the two in step —packages/objectql/src/save-meta-response-conformance.test.ts, whose header reads:The publish door has none of those three: no declaration, no
responseSchemareference, no conformance gate. Soversionon the publish response is in the same undeclared stateversionon the save response was in before #5745 — and it is the same ADR-0008 OCC token, with the same "echo it back asIf-Match" job.Why it is observation-class rather than a defect
The route answers correctly and the body is well-formed; no caller is broken today. The cost is the one #5745 named: a public wire surface with no contract behind it, so a later field addition or removal is invisible instead of gated, and
PublishMetaItemResponsecannot be named at the type level the waySaveMetaItemResponsecan (packages/client/src/index.ts:18imports and re-exports the latter).Adjacent, and the reason this surfaced
#4717's metadata half puts the runtime authoring gate's advisory findings on the
saveMetaItemresponse. The gate runs on both doors —protocol.ts:9133for a direct active save, andprotocol.ts:9869for the draft-to-active promotion insidepublishMetaItem(deliberately, per #4463 D1, so?mode=draftthen/publishis not a bypass). So whoever designs the advisory field will face two response envelopes, one declared and conformance-gated, one not declared at all. That asymmetry is worth knowing before the shape is chosen, not after.Filed unassigned per Prime Directive #10. No
pm:queue— observation class.