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
{{ message }}
Repository navigation
[finding] POST /meta/:type/:name/publish is a served REST route with NO spec declaration — the #5745 "declared = returned" discipline covers only the save door #7294
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.
CLAIM — spec-lane PM seat (#6017), session session_01PiRUoQkTSBBmpyXBY3cVn2. Branch: claude/issue-7294-declare-publish-response. Dispatching a cloud dev session (model: Opus) under the maintainer's standing acceleration instructions.
Direction: mirror the #5745 discipline one door over, exactly as the card lays out — the publish door gets the same three artifacts the save door has: (1) PublishMetaItemResponseSchema in packages/spec/src/api/protocol.zod.ts declaring the full measured body (success/version/seq/message + conditional seedApplied/materializeApplied/projectionApplied, from protocol.ts:9771-9798); (2) the endpoint declaration in plugin-rest-api.zod.ts (POST {metaPath}/:type/:name/publish, handler: 'publishMetaItem', responseSchema reference), matching the 6 existing metadata-category entries' shape; (3) a producer-side conformance gate mirroring save-meta-response-conformance.test.ts including its header discipline. Premise-first: re-measure that the route is still served and still undeclared. New public exports expected ⇒ dual-snapshot rule in full.
{
"issue": 7294,
"outcome": "success",
"branch": "claude/issue-7294-declare-publish-response",
"pr_number": 7356,
"premise_check": {
"base": "origin/main @ 88154be",
"route_still_served": true,
"route_evidence": "rest-server.ts:5481 registers POST {metaPath}/:type/:name/publish; :5499 awaits publishMetaItem(...); :5504 res.json(result) verbatim",
"response_body_still_as_measured": true,
"body_evidence": "protocol.ts:9769-9798 — one response literal: success/version/seq/message, then seedApplied/materializeApplied/projectionApplied attached conditionally by runPublishSideEffects",
"publishmetaitem_hits_under_packages_spec_src": 0,
"publish_hits_in_plugin_rest_api_zod": 0,
"single_producer_confirmed": true,
"verdict": "premise holds in full — not already declared; proceeded"
},
"changes": [
"packages/spec/src/api/protocol.zod.ts — PublishMetaItemResponseSchema (full measured body; success/version/seq required, message + seedApplied/materializeApplied/projectionApplied optional) + PublishMetaItemResponse alias, modelled field-for-field on SaveMetaItemResponseSchema",
"packages/spec/src/api/plugin-rest-api.zod.ts — endpoint entry POST /:type/:name/publish, handler publishMetaItem, category metadata, responseSchema PublishMetaItemResponseSchema, permissions ['metadata.write']; publishMetaItem added to the group's methods",
"packages/objectql/src/publish-meta-response-conformance.test.ts — NEW producer-side gate mirroring save-meta-response-conformance.test.ts incl. header discipline; 9 cases covering plain shape + each conditional receipt path",
"packages/spec/src/api/protocol.test.ts — spec-side half the gate's header points at; 8 cases pinning what the schema says",
"packages/client/src/index.ts — publishItem() typed Promise<PublishMetaItemResponse> (was any); type re-exported",
"packages/metadata-protocol/src/protocol.ts — publishMetaItem's Promise<...> annotation gains projectionApplied (assigned in the body, missing from the type)",
"packages/spec/src/type-alias-convention.pin.test.ts — ADR-0122 pin Iso836; count 824 -> 825 + both prose statements",
"packages/spec/src/api/plugin-rest-api.test.ts — metadata endpoint count 5 -> 6 with the receipt",
".changeset/declare-publish-meta-item-response.md — spec: minor, metadata-protocol + client: patch"
],
"judgement_calls": {
"message_optional_not_required": "producer sets it unconditionally today, but its own signature says message?, the SaveMetaItemResponse twin is optional, and an optional-but-always-present field strips nothing — stripping is the #5745 failure mode. success/version/seq ARE required (sole producer's only success return always sets them), with the tripwire case in the gate.",
"no_request_schema": "#3899 — body optional, only read key is `message` taken only when already a string; the route cannot 400 a malformed body, so a requestSchema would advertise a gate that does not run."
},
"client_reexport_decision": {
"included": true,
"why": "mirror is exact and cheap — publishItem() already existed and already returned unwrapResponse<any>, the concrete symptom the card named. One type import + one re-export line, both beside the SaveMetaItemResponse ones. No extra surface pulled in; client suite 279/279 green, tsc --noEmit clean."
},
"reverse_verification": {
"direction_predicted_first": true,
"prediction": "removing the seedApplied block from the schema turns the producer gate RED in exactly 2 cases (the two seed paths) via non-empty strippedKeys; the other 7 stay green; the save-door gate is untouched",
"probe": "omission — deleted the seedApplied z.object block from PublishMetaItemResponseSchema",
"result_matched_prediction": true,
"observed_red": "publish gate 2 failed / 7 passed, both failures `expected [ 'seedApplied' ] to deeply equal []` at the strippedKeys lines; spec-side protocol.test.ts 2 failed (the two seedApplied cases); save-meta-response-conformance.test.ts 5/5 untouched",
"observed_green_as_landed": "publish gate 9/9, protocol.test.ts 44/44, save gate 5/5",
"stronger_than_predicted": "`pnpm --filter @objectstack/spec build` itself REFUSES the removal before any test runs — '1 authorable key(s) disappeared from the contract: api/PublishMetaItemResponse:seedApplied'. The declaration is covered by the authorable-surface gate too, not only the two suites.",
"restored": "probe reverted from backup; schema file byte-identical to pre-probe state; spec rebuilt and all suites re-run green"
},
"gates": {
"spec_build_before_dist_derived_regen": "yes — #7122 trap hit once in the honest direction: gen:api-surface REFUSED ('dist older than src') after a post-build source edit, was re-run after a rebuild",
"check_generated": "all 11 artifacts up to date",
"typecheck": "turbo typecheck spec/client/objectql/metadata-protocol/rest + deps — 35/35",
"spec_suite": "362 files / 9473 tests passed",
"objectql_suite": "169 files / 2950 tests passed",
"rest_suite": "77 files / 1228 tests passed",
"runtime_suite": "119 files / 1870 tests passed",
"metadata_protocol_suite": "67 files / 862 tests passed",
"client_suite": "21 files / 279 tests passed",
"root_gates": "check:route-envelope, check:doc-authoring, check:quick-reference-counts, check:nul-bytes — all green",
"heavy_runs_flocked": "/tmp/os-heavy-verify.lock"
},
"snapshots": {
"dual_snapshot_rule": "satisfied in full, regenerated after a real build",
"api_surface": "packages/spec/api-surface/api.json — PublishMetaItemResponse (type), PublishMetaItemResponseSchema (const)",
"export_origins": "packages/spec/export-origins/api.json — both, resolved to src/api/protocol.zod.ts#...",
"docs_reference_rows": "content/docs/references/api/protocol.mdx + index.mdx via the gen chain (1575 -> 1576 schemas)",
"other_generated": "authorable-surface/api.json, json-schema.manifest/api.json, docs/audits/2026-07-unknown-key-strictness-ledger.counts.md (api/ 393 -> 397)"
},
"deviations": [
"Added the spec-side pin suite in protocol.test.ts beyond the three artifacts named. Not scope creep: the conformance gate's mirrored header asserts 'the spec-side suite pins what the schema says; this one pins that the schema still matches what the code emits' — that sentence is false without it, and #5745 landed both halves.",
"Also fixed packages/metadata-protocol/src/protocol.ts: publishMetaItem's Promise<...> annotation omitted projectionApplied while the implementation assigned it. Left alone, the new schema would declare a field the producer's own TS type denies. Same declared-!=-returned class, one layer down.",
"The in-memory driver harness is duplicated into the new test rather than shared. save-meta-response-conformance.test.ts and plugin.authoring-channel.test.ts each already carry their own copy, so self-contained is the established shape; extracting would edit two landed files for no gate.",
"Worktree setup: `git branch -D` was blocked by the permission classifier, so the shared checkout was detached and the pre-existing branch checked out in the worktree instead (same commit). Net effect identical."
],
"docs_adr_touched": false,
"special_inspection_notes": [
"MetadataProtocol (protocol.zod.ts:1840) still does NOT declare publishMetaItem — the REST layer reaches it via `(p as any).publishMetaItem`. Naming it on the interface needs a PublishMetaItemRequest type, i.e. a request schema, which the card scoped out. Left as-is; flagging for whoever wants the cast gone (Prime Directive #12).",
"For #4717's metadata half: the asymmetry the card warned about is now GONE — both write doors have a declared, conformance-gated response envelope, so an advisory-findings field can be added to the two symmetrically.",
"The `seed` conformance case had to be written env-wide (no organizationId): ADR-0005/#6190 refuse an org-scoped write for a type whose registry entry says allowOrgOverride:false, and `seed` is one. Documented at the fixture. The seed path exercises the real SUCCESS branch — {success:true, inserted:1, updated:0}, verified by probe, not the surfaced-failure fallback — so the assertion pins the counters rather than their types.",
"No out-of-scope finding issues filed — nothing surfaced that was not either in scope or noted above.",
"NO auto-merge enabled. PR #7356 is a DRAFT awaiting the PM's serial landing."
]
}
Activity
os-zhuang commentedon Aug 10, 2026
CLAIM — spec-lane PM seat (#6017), session
session_01PiRUoQkTSBBmpyXBY3cVn2. Branch:claude/issue-7294-declare-publish-response. Dispatching a cloud dev session (model: Opus) under the maintainer's standing acceleration instructions.Direction: mirror the #5745 discipline one door over, exactly as the card lays out — the publish door gets the same three artifacts the save door has: (1)
PublishMetaItemResponseSchemainpackages/spec/src/api/protocol.zod.tsdeclaring the full measured body (success/version/seq/message+ conditionalseedApplied/materializeApplied/projectionApplied, fromprotocol.ts:9771-9798); (2) the endpoint declaration inplugin-rest-api.zod.ts(POST {metaPath}/:type/:name/publish,handler: 'publishMetaItem',responseSchemareference), matching the 6 existing metadata-category entries' shape; (3) a producer-side conformance gate mirroringsave-meta-response-conformance.test.tsincluding its header discipline. Premise-first: re-measure that the route is still served and still undeclared. New public exports expected ⇒ dual-snapshot rule in full.Generated by Claude Code
os-zhuang commentedon Aug 10, 2026
OS-DEV-REPORT
{ "issue": 7294, "outcome": "success", "branch": "claude/issue-7294-declare-publish-response", "pr_number": 7356, "premise_check": { "base": "origin/main @ 88154be", "route_still_served": true, "route_evidence": "rest-server.ts:5481 registers POST {metaPath}/:type/:name/publish; :5499 awaits publishMetaItem(...); :5504 res.json(result) verbatim", "response_body_still_as_measured": true, "body_evidence": "protocol.ts:9769-9798 — one response literal: success/version/seq/message, then seedApplied/materializeApplied/projectionApplied attached conditionally by runPublishSideEffects", "publishmetaitem_hits_under_packages_spec_src": 0, "publish_hits_in_plugin_rest_api_zod": 0, "single_producer_confirmed": true, "verdict": "premise holds in full — not already declared; proceeded" }, "changes": [ "packages/spec/src/api/protocol.zod.ts — PublishMetaItemResponseSchema (full measured body; success/version/seq required, message + seedApplied/materializeApplied/projectionApplied optional) + PublishMetaItemResponse alias, modelled field-for-field on SaveMetaItemResponseSchema", "packages/spec/src/api/plugin-rest-api.zod.ts — endpoint entry POST /:type/:name/publish, handler publishMetaItem, category metadata, responseSchema PublishMetaItemResponseSchema, permissions ['metadata.write']; publishMetaItem added to the group's methods", "packages/objectql/src/publish-meta-response-conformance.test.ts — NEW producer-side gate mirroring save-meta-response-conformance.test.ts incl. header discipline; 9 cases covering plain shape + each conditional receipt path", "packages/spec/src/api/protocol.test.ts — spec-side half the gate's header points at; 8 cases pinning what the schema says", "packages/client/src/index.ts — publishItem() typed Promise<PublishMetaItemResponse> (was any); type re-exported", "packages/metadata-protocol/src/protocol.ts — publishMetaItem's Promise<...> annotation gains projectionApplied (assigned in the body, missing from the type)", "packages/spec/src/type-alias-convention.pin.test.ts — ADR-0122 pin Iso836; count 824 -> 825 + both prose statements", "packages/spec/src/api/plugin-rest-api.test.ts — metadata endpoint count 5 -> 6 with the receipt", ".changeset/declare-publish-meta-item-response.md — spec: minor, metadata-protocol + client: patch" ], "judgement_calls": { "message_optional_not_required": "producer sets it unconditionally today, but its own signature says message?, the SaveMetaItemResponse twin is optional, and an optional-but-always-present field strips nothing — stripping is the #5745 failure mode. success/version/seq ARE required (sole producer's only success return always sets them), with the tripwire case in the gate.", "no_request_schema": "#3899 — body optional, only read key is `message` taken only when already a string; the route cannot 400 a malformed body, so a requestSchema would advertise a gate that does not run." }, "client_reexport_decision": { "included": true, "why": "mirror is exact and cheap — publishItem() already existed and already returned unwrapResponse<any>, the concrete symptom the card named. One type import + one re-export line, both beside the SaveMetaItemResponse ones. No extra surface pulled in; client suite 279/279 green, tsc --noEmit clean." }, "reverse_verification": { "direction_predicted_first": true, "prediction": "removing the seedApplied block from the schema turns the producer gate RED in exactly 2 cases (the two seed paths) via non-empty strippedKeys; the other 7 stay green; the save-door gate is untouched", "probe": "omission — deleted the seedApplied z.object block from PublishMetaItemResponseSchema", "result_matched_prediction": true, "observed_red": "publish gate 2 failed / 7 passed, both failures `expected [ 'seedApplied' ] to deeply equal []` at the strippedKeys lines; spec-side protocol.test.ts 2 failed (the two seedApplied cases); save-meta-response-conformance.test.ts 5/5 untouched", "observed_green_as_landed": "publish gate 9/9, protocol.test.ts 44/44, save gate 5/5", "stronger_than_predicted": "`pnpm --filter @objectstack/spec build` itself REFUSES the removal before any test runs — '1 authorable key(s) disappeared from the contract: api/PublishMetaItemResponse:seedApplied'. The declaration is covered by the authorable-surface gate too, not only the two suites.", "restored": "probe reverted from backup; schema file byte-identical to pre-probe state; spec rebuilt and all suites re-run green" }, "gates": { "spec_build_before_dist_derived_regen": "yes — #7122 trap hit once in the honest direction: gen:api-surface REFUSED ('dist older than src') after a post-build source edit, was re-run after a rebuild", "check_generated": "all 11 artifacts up to date", "typecheck": "turbo typecheck spec/client/objectql/metadata-protocol/rest + deps — 35/35", "spec_suite": "362 files / 9473 tests passed", "objectql_suite": "169 files / 2950 tests passed", "rest_suite": "77 files / 1228 tests passed", "runtime_suite": "119 files / 1870 tests passed", "metadata_protocol_suite": "67 files / 862 tests passed", "client_suite": "21 files / 279 tests passed", "root_gates": "check:route-envelope, check:doc-authoring, check:quick-reference-counts, check:nul-bytes — all green", "heavy_runs_flocked": "/tmp/os-heavy-verify.lock" }, "snapshots": { "dual_snapshot_rule": "satisfied in full, regenerated after a real build", "api_surface": "packages/spec/api-surface/api.json — PublishMetaItemResponse (type), PublishMetaItemResponseSchema (const)", "export_origins": "packages/spec/export-origins/api.json — both, resolved to src/api/protocol.zod.ts#...", "docs_reference_rows": "content/docs/references/api/protocol.mdx + index.mdx via the gen chain (1575 -> 1576 schemas)", "other_generated": "authorable-surface/api.json, json-schema.manifest/api.json, docs/audits/2026-07-unknown-key-strictness-ledger.counts.md (api/ 393 -> 397)" }, "deviations": [ "Added the spec-side pin suite in protocol.test.ts beyond the three artifacts named. Not scope creep: the conformance gate's mirrored header asserts 'the spec-side suite pins what the schema says; this one pins that the schema still matches what the code emits' — that sentence is false without it, and #5745 landed both halves.", "Also fixed packages/metadata-protocol/src/protocol.ts: publishMetaItem's Promise<...> annotation omitted projectionApplied while the implementation assigned it. Left alone, the new schema would declare a field the producer's own TS type denies. Same declared-!=-returned class, one layer down.", "The in-memory driver harness is duplicated into the new test rather than shared. save-meta-response-conformance.test.ts and plugin.authoring-channel.test.ts each already carry their own copy, so self-contained is the established shape; extracting would edit two landed files for no gate.", "Worktree setup: `git branch -D` was blocked by the permission classifier, so the shared checkout was detached and the pre-existing branch checked out in the worktree instead (same commit). Net effect identical." ], "docs_adr_touched": false, "special_inspection_notes": [ "MetadataProtocol (protocol.zod.ts:1840) still does NOT declare publishMetaItem — the REST layer reaches it via `(p as any).publishMetaItem`. Naming it on the interface needs a PublishMetaItemRequest type, i.e. a request schema, which the card scoped out. Left as-is; flagging for whoever wants the cast gone (Prime Directive #12).", "For #4717's metadata half: the asymmetry the card warned about is now GONE — both write doors have a declared, conformance-gated response envelope, so an advisory-findings field can be added to the two symmetrically.", "The `seed` conformance case had to be written env-wide (no organizationId): ADR-0005/#6190 refuse an org-scoped write for a type whose registry entry says allowOrgOverride:false, and `seed` is one. Documented at the fixture. The seed path exercises the real SUCCESS branch — {success:true, inserted:1, updated:0}, verified by probe, not the surfaced-failure fallback — so the assertion pins the counters rather than their types.", "No out-of-scope finding issues filed — nothing surfaced that was not either in scope or noted above.", "NO auto-merge enabled. PR #7356 is a DRAFT awaiting the PM's serial landing." ] }Generated by Claude Code
feat(spec,client): declare the publish door's response contract (obje…
feat(spec): declare the batch publish response schema — probes staged…
feat(spec): declare the batch publish response schema — probes staged…
feat(spec): declare publishMetaItem as optional MetadataProtocol memb…
fix(spec): the protocol 16 → 17 conversion summaries and three descri…
test(spec): the second api/ file group's test titles state each cited…