Skip to content

feat(spec,client): declare the publish door's response contract (#7294) - #7356

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-7294-declare-publish-response
Aug 10, 2026
Merged

feat(spec,client): declare the publish door's response contract (#7294)#7356
os-zhuang merged 1 commit into
mainfrom
claude/issue-7294-declare-publish-response

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Carries the #5745 "declared = returned" discipline one door over: the publish door gets the three artifacts the save door already has.

Premise re-measured on fresh origin/main (88154be)

Every claim on the card re-checked before writing a line — all three still hold:

Claim Re-measurement Verdict
The route is served rest-server.ts:5481 registers POST {metaPath}/:type/:name/publish; :5499 awaits publishMetaItem(...) and :5504 hands the result to res.json() verbatim ✅ still served
The body is as measured protocol.ts:9769-9798 — one response literal: success / version / seq / message, then seedApplied / materializeApplied / projectionApplied attached conditionally by runPublishSideEffects ✅ unchanged
Undeclared in spec grep -rn "PublishMetaItem" packages/spec/src/zero hits; grep -n publish packages/spec/src/api/plugin-rest-api.zod.tszero hits ✅ still undeclared
Single producer publishMetaItem is implemented once (ObjectStackProtocolImplementation); every other hit is a caller or a comment ✅ — which is what licenses required below

So version on this door sat in exactly the state version on the save door sat in before #5745 — the ADR-0008 OCC token, the value a caller echoes back as If-Match to get a 409 instead of a lost update, on a public wire surface with nothing declaring it.

Changes

File What
packages/spec/src/api/protocol.zod.ts PublishMetaItemResponseSchema — the full measured body, modelled on SaveMetaItemResponseSchema (style, describe() density, OCC framing). success / version / seq required; message + the three receipts optional. Plus the PublishMetaItemResponse alias.
packages/spec/src/api/plugin-rest-api.zod.ts The endpoint entry — POST /:type/:name/publish, handler: 'publishMetaItem', category: 'metadata', responseSchema: 'PublishMetaItemResponseSchema', permissions: ['metadata.write'], field-for-field with the five siblings. publishMetaItem added to the group's methods.
packages/objectql/src/publish-meta-response-conformance.test.ts New — the producer-side gate, mirroring save-meta-response-conformance.test.ts including its header discipline. 9 cases.
packages/spec/src/api/protocol.test.ts The spec-side half the conformance 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 — it was assigned in the body and missing from the type.
packages/spec/src/type-alias-convention.pin.test.ts ADR-0122 pin Iso836 (isomorphic: no .default()/.transform()/.catch()/.pipe() anywhere in the tree), count 824 → 825 + both prose statements.
packages/spec/src/api/plugin-rest-api.test.ts Endpoint count 5 → 6, with the receipt.

Two judgement calls, stated

message is optional, not required — even though the producer sets it unconditionally today. Its own signature declares message?, the SaveMetaItemResponse twin declares it optional, and the failure #5745 exists to prevent is stripping: an over-tight required would make a real response fail its own contract, while an optional field that is always present strips nothing. success / version / seq are required — the sole producer's only success return always sets them, and the gate below carries the tripwire for that decision.

No requestSchema (#3899): the body is optional and its only read key is message, taken only when it is already a string and ignored otherwise. The route cannot 400 a malformed body, so declaring a request schema would advertise a gate that does not run.

packages/client re-export: included

The mirror is exact and cheap. publishItem() already existed and already returned unwrapResponse<any> — the concrete symptom the issue named ("PublishMetaItemResponse cannot be named at the type level the way SaveMetaItemResponse can"). Typing it pulls in no new surface: one type import, one re-export line, both beside the SaveMetaItemResponse ones. Client suite green (279/279); tsc --noEmit clean.

Conformance-gate probe — direction predicted first

Prediction, stated before running: delete the seedApplied block from the schema and the producer gate must go RED in exactly 2 cases (the two seed paths) via non-empty strippedKeys, the other 7 stay green, and the save-door gate is untouched.

Result — matched exactly:

Landed seedApplied removed from schema
publish-meta-response-conformance.test.ts ✅ 9/9 2 failed — both AssertionError: expected [ 'seedApplied' ] to deeply equal [], at the two strippedKeys(raw) lines; other 7 green
protocol.test.ts (spec-side) ✅ 44/44 2 failed — the two seedApplied cases
save-meta-response-conformance.test.ts ✅ 5/5 ✅ 5/5 — untouched, as predicted

One result stronger than predicted: pnpm --filter @objectstack/spec build itself refuses the removal before a test can run —

❌ 1 authorable key(s) disappeared from the contract: api/PublishMetaItemResponse:seedApplied

so the declaration is now covered by the authorable-surface gate too, not only by the two suites. (The probe was completed against a tsup-only build to reach the test layer, then reverted and rebuilt; the schema file is byte-identical to its pre-probe state.)

Gates

Gate Result
check:generated (all 11 artifacts) ✅ all up to date
pnpm --filter @objectstack/spec build ✅ (run before every dist-derived regen — #7122 stale-dist trap; gen:api-surface refused once mid-run for exactly that reason and was re-run after a rebuild)
turbo typecheck (spec, client, objectql, metadata-protocol, rest + deps) ✅ 35/35
spec suite ✅ 362 files / 9473 tests
objectql suite ✅ 169 files / 2950 tests
rest suite ✅ 77 files / 1228 tests
runtime suite ✅ 119 files / 1870 tests
metadata-protocol suite ✅ 67 files / 862 tests
client suite ✅ 21 files / 279 tests
check:route-envelope, check:doc-authoring, check:quick-reference-counts, check:nul-bytes

Dual-snapshot rule, in full — the new public exports land in both, regenerated after a real build: packages/spec/api-surface/api.json (PublishMetaItemResponse (type), PublishMetaItemResponseSchema (const)) and packages/spec/export-origins/api.json (both, resolved to src/api/protocol.zod.ts). Docs reference rows came through the gen chain (content/docs/references/api/protocol.mdx + index.mdx, 1575 → 1576 schemas), along with authorable-surface/api.json, json-schema.manifest/api.json and the strictness-ledger counts.

One non-major changeset (spec: minor, metadata-protocol + client: patch). No content/docs/releases/ or docs/adr/** touched.

Closes #7294


Generated by Claude Code

`POST /api/v1/meta/:type/:name/publish` is served by `@objectstack/rest`
(`rest-server.ts` registers it and hands `publishMetaItem`'s return straight to
`res.json()`), and had no declaration behind it: `PublishMetaItem` appeared
nowhere under `packages/spec/src/`, and the route was absent from
`plugin-rest-api.zod.ts`'s metadata table. So `version` on the publish response
sat in exactly the state `version` on the save response sat in before #5745 —
the ADR-0008 OCC token, echoed back as `If-Match` to get a 409 instead of a
lost update, on a public wire surface with nothing declaring it.

This carries the #5745 "declared = returned" discipline one door over, with the
same three artifacts:

- `PublishMetaItemResponseSchema` declares the FULL measured body: `success` /
  `version` / `seq` required, `message` plus the three conditional side-effect
  receipts (`seedApplied` / `materializeApplied` / `projectionApplied`)
  optional. Measured from the producer, not assumed — its single response
  literal always sets the first three and attaches each receipt only when the
  matching side effect ran, so an absent receipt means "did not run", never
  "failed".
- The endpoint declaration, matching the five sibling metadata entries. No
  `requestSchema`: the body's only read key is `message`, taken only when
  already a string, so the route cannot 400 a malformed body and declaring one
  would advertise a gate that does not run (#3899).
- `packages/objectql/src/publish-meta-response-conformance.test.ts` — the
  producer-side gate mirroring the save door's, driving a real
  `publishMetaItem` against a real ObjectQL engine through the schema across
  the plain shape and every receipt path.

Also:
- `client.metadata.publishItem()` is typed `Promise<PublishMetaItemResponse>`
  and the type re-exported, matching `saveItem` / `SaveMetaItemResponse`. It
  resolved to `any` before, for want of a declaration to point at.
- `publishMetaItem`'s own `Promise<...>` annotation omitted `projectionApplied`
  while the body assigned it — the same declared-≠-returned gap one layer down.

No behavior change: nothing about the response body moved.

Closes #7294

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DuV91PKtmJ8UFUtDjqXy5h
@vercel

vercel Bot commented Aug 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 10, 2026 7:54am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/client, @objectstack/metadata-protocol, @objectstack/spec.

109 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via packages/client, @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/client, @objectstack/spec)
  • content/docs/api/data-flow.mdx (via @objectstack/client)
  • content/docs/api/environment-routing.mdx (via @objectstack/client, @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/client, @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/metadata-protocol, packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/tenancy-modes.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/client, @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx (via @objectstack/client, @objectstack/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/client, packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/metadata-protocol, @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authentication.mdx (via @objectstack/client)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/permissions/system-context.mdx (via packages/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/client, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/metadata-protocol, @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/realtime-protocol.mdx (via @objectstack/client)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/apps.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/field-grouping-and-order.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

7 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/implementation-status.mdx (via @objectstack/client, @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/client, @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/client, @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/metadata-protocol, @objectstack/spec)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Aug 10, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review August 10, 2026 08:59
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 10, 2026
Merged via the queue into main with commit b3363e9 Aug 10, 2026
27 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-7294-declare-publish-response branch August 10, 2026 09:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

2 participants