Skip to content

feat(spec): declare the batch publish response schema — probes staged opaque (#9406) - #9494

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-9406-batch-publish-response-schema
Aug 18, 2026
Merged

feat(spec): declare the batch publish response schema — probes staged opaque (#9406)#9494
os-steve merged 1 commit into
mainfrom
claude/issue-9406-batch-publish-response-schema

Conversation

@os-steve

Copy link
Copy Markdown
Collaborator

Fixes#9406

Ruling (maintainer, 2026-08-18, batch acceptance — verbatim: 「同意」)

Ruled: declare the batch publish response in packages/spec, with probes staged. Concretely:

  1. Declare the aggregate face now — success / publishedCount / failedCount / published[] (including the per-element version and the publishPackageDrafts still discards the runtime authoring gate's per-draft advisories — Studio's "publish whole app" reports none of them #9343advisories) / failed[] / seedApplied / materializeApplied / commitId — with the two pin suites mirroring the single door's [#5563 附带裁决] SaveMetaItemResponseSchema 补齐实现实际返回的字段(version / seq / state / projectionApplied) #5745/[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 pair (declaration pins + producer-side conformance).
  2. probes (BuildProbeReport) gets a deliberately opaque passthrough declaration with a recorded note at the site; it upgrades to a modeled Zod schema only when a consumer needs a field of it. Do not model it speculatively.

What landed

PublishPackageDraftsResponseSchema + PublishPackageDraftsResponse in packages/spec/src/api/protocol.zod.ts, declaring the full data payload of POST /api/v1/packages/:id/publish-drafts (the dispatcher wraps it in the { success, data } envelope; the schema names the payload, the same convention the ledger records for DiscoverySchema).

  • probes is deliberately opaquez.unknown().optional() with the recorded staging note at the site (and in the .describe(), the docs page, the metadata-protocol JSDoc, and the client JSDoc). Not modeled, per the ruling.
  • Route ledger: the POST /packages/:id/publish-drafts row now carries responseSchema: 'PublishPackageDraftsResponseSchema' — fillable because a route-level conformance suite drives the real handler (the ledger's own rule: the field is forbidden without coverage of what the route answers).
  • Typed client: packages.publishDrafts resolves Promise of PublishPackageDraftsResponse instead of any — the same move [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 made for meta.publishItem.
  • Producer JSDoc pointer: publishPackageDrafts in metadata-protocol now names the declared face and the suites a return-type change must update.
  • Changeset: @objectstack/spec minor, @objectstack/client minor, @objectstack/runtime patch. Additive declaration of an existing wire face; check:adr-0087-registration green (no retirement, no conversion entry needed).
  • Type-alias convention pin Iso852 (isomorphism measured: no .default()/.transform() anywhere — the no-default fact is also the consumer half of byte-stability), count 838 → 839.

Measured wire-face receipts

Ruled member list, measured against the producer pair on origin/main (ad217b1):

memberproducer evidencedeclared as
successall three return sites; happy path sets failed.length === 0 && published.length > 0required boolean
publishedCount / failedCountall three return sitesrequired ints
published[]{ type, name, version, advisories? } — advisories omitted-when-empty (#9343 / PR #9405)required array; advisories optional, RuntimeAuthoringIssueSchema by reference
failed[]{ type, name, error, code? } — pre-flight codes and BATCH_ABORTEDrequired array; code optional string
seedAppliedTWO producers: in-batch applySeedBodies (counters always) and the route back-fill applyPublishedSeeds + its catch (early failures WITHOUT counters)optional; inserted/updated optional — the union is the wire face
materializeAppliedhappy-path aggregate { success, inserted, updated, failures[] }optional; failures[] required per-item (deliberately not the single door's scalar error)
probesattached when published.length is positive, BuildProbeReport TS interfaceopaquez.unknown(), staged
commitIdattached when a commit was recordedoptional string
unhiddenApps / unhideError / rebindErrorROUTE mutations (packages/runtime/src/domains/packages.ts): ADR-0045 flip receipts (#5242/#8516) and announce receipt (#8516)optional — on the wire, so declared (dispatch item 1: "the declared face must match what goes on the wire")

No extra or missing keys vs. the ruled list were found — the three route-attached keys are the "route's response mutations" the dispatch itself named, so the STOP condition was not met.

The two pin suites (plus the route half)

  1. Declaration pins ([#5563 附带裁决] SaveMetaItemResponseSchema 补齐实现实际返回的字段(version / seq / state / projectionApplied) #5745 style) — packages/spec/src/api/protocol.test.ts: two new describe blocks (15 cases — exactly the 15 that go red in reverse verification): round-trip without stripping, required five, int counters, per-element version required, refusal face with BATCH_ABORTED, seedApplied counter-union, materializeApplied failures[] required-once-present, probes-opaque (today's shape AND an arbitrary future shape both pass), route receipts, all-conditionals-optional, no-fabrication, and the publishPackageDrafts still discards the runtime authoring gate's per-draft advisories — Studio's "publish whole app" reports none of them #9343 per-element advisories block (carried/optional/one-dialect-by-reference/non-array-refused).
  2. Producer conformance ([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 style) — packages/objectql/src/publish-package-drafts-response-conformance.test.ts: drives the REAL publishPackageDrafts against a REAL ObjectQL engine (8 cases): plain batch, required keys, probes carried opaque by reference, seed batch counters, materializer per-item failures, advisories on their own element only, byte-stability, and the ADR-0067 D2 refusal face parsing unstripped.
  3. Route conformancepackages/runtime/src/domains/packages-publish-drafts-response-conformance.test.ts: drives the real handlePackages (protocol doubled, flip/announce/back-fill/assembly shipping code — the publish-drafts has two more undeclared-driver-text fields on the same 200 body: unhideError and rebindError #8516 harness split) (5 cases): envelope/payload convention + unhiddenApps, mid-flip split report, rebindError, the route back-fill's no-counter seedApplied arm, byte-stability across the route.

Verification (all from head b0eb4a457)

  • pnpm --filter @objectstack/spec build then full spec vitest: 3 failed | 406 passed pre-regeneration (the 3 = exactly the stale-artifact gates), then check:generated --fix regenerated api-surface / export-origins / gen:docs / gen:strictness-ledger (plus gen:schema's sharded authorable-surface/api.json, json-schema.manifest/api.json — expected artifacts, not reverted), after which the 5 targeted spec files: 135 passed (135).
  • objectql producer conformance: 8 passed (8). runtime route conformance: 5 passed (5). client route-ledger-response-schema.test.ts: 4 passed (4) (the new ledger name resolves).
  • typecheck green for @objectstack/spec, @objectstack/runtime, @objectstack/objectql, @objectstack/client (client after building its dependency closure — the initial red was the unbuilt-worktree false positive AGENTS.md §9 documents, not a diff defect).
  • Gate families from node scripts/pm/dispatch-gates.mjs over the changed paths (26 matched + 5 convention): all run locally and green — changeset gates (adr-0087-registration, changeset-no-major, empty-changeset, objectui-changeset, changeset-gate-self-tests), nul-bytes, cross-package-test-inputs, merge-driver, type-source-resolution, route-envelope, spec-parsed-alias, filter-alias-parity, durability-log-level, docs family (doc-authoring, docs-redirects, docs-audit-scope, role-word, quick-reference-counts, affected-docs), spec-liveness family (empty-state, liveness, strictness-ledger, variant-docs), engine-split-ratio, query-options-erasure, engine-double-contract, where-matcher, type-check-coverage, type-check-debt (after full workspace build), doc-formula-expressions, check-dev-prereqs.

Reverse verification (from committed state b0eb4a457; direction predicted before running)

Variant A — declaration source alone reverted (git restore --source=origin/main -- packages/spec/src/api/protocol.zod.ts), then rebuild:

Variant B — faithful pre-declaration state (source + the four sharded spec artifacts reverted to origin/main), spec REBUILT green, then the suites:

  • PREDICTED red → MEASURED red: spec declaration pins 15 failed | 59 passed (every new case; the import resolves to undefined so each case dies on undefined.parse — per-case red, not module-load, a measured nuance vs. the predicted load failure); objectql conformance 8 failed (8); runtime conformance 5 failed (5); client route-ledger-response-schema.test.ts1 failed | 3 passed, with exactly the designed message: responseSchema 'PublishPackageDraftsResponseSchema' is not an export of @objectstack/spec/api.
  • PREDICTED green-both-ways → MEASURED green: the type-alias pin file at RUNTIME (its count reads source text; its isomorphism claim is compile-time only, so its red lands in typecheck, not vitest).

Restored (git checkout HEAD -- ..., rebuild): 74 + 8 + 5 + 4 all passed at b0eb4a457; working tree clean.

Byte-stability proof

Nothing on the serving path parses this schema — the declaration is spec + tests + ledger name + client type, so the server's bytes are untouched by construction. Pinned so it stays true: producer-side (JSON.stringify(raw) of an advisory-free batch contains no advisories; parsed key sets equal raw key sets, per element too) and route-side (a conditional-free publish's wire carries none of the five conditional keys and parse(data) deep-equals data); consumer-side, the schema carries no .default() (pinned by Iso852's isomorphism and the no-fabrication spec case), so a parsing consumer observes exactly the wire.

Coupling

Wave-8 generated-artifacts siblings: #9463 / #9447 / #9345 — this PR rewrites sharded api.json artifacts only (api-surface/, authorable-surface/, export-origins/, json-schema.manifest/), textually disjoint from theirs by design; PM serializes landings, later landers run scripts/pm/os-regen-merge.sh.


Generated by Claude Code

… opaque (#9406)
PublishPackageDraftsResponseSchema declares the full wire payload of
POST /packages/:id/publish-drafts (the #5745/#7294 declared-=-returned
discipline on the batch door), including the REST door's response
mutations (seedApplied back-fill, ADR-0045 unhiddenApps/unhideError,
rebindError). probes is deliberately opaque per the #9406 ruling.
Pin suites: spec declaration pins, objectql producer conformance,
runtime route conformance; route ledger names the schema; client
packages.publishDrafts resolves the declared type instead of any.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fs18A2DdXLVN2h8PaaFBcP
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 4 package(s): @objectstack/client, @objectstack/metadata-protocol, @objectstack/runtime, @objectstack/spec, touching 16 documentable anchor(s).

3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/client-sdk.mdx(via getReferences (sdk), meta.getReferences (sdk), meta.publishItem (sdk), publishItem (sdk))
  • content/docs/concepts/metadata-lifecycle.mdx(via ObjectStackProtocolImplementation (symbol))
  • content/docs/kernel/contracts/metadata-service.mdx(via /meta/:type/:name/publish (route))

2 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v16.mdx(via ObjectStackProtocolImplementation (symbol))
  • content/docs/releases/v17.mdx(via ObjectStackProtocolImplementation (symbol))

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.

What this run could not see
  • 4 changed file(s) yielded no anchor (packages/spec/api-surface/api.json, packages/spec/authorable-surface/api.json, packages/spec/export-origins/api.json, …) — pages documenting those are invisible to this run
  • 2 name(s) were too generic to anchor anything (single lowercase words)

Coarse fallback — 120 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json origin/mainpackageMentionDocs.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. 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-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Aug 18, 2026
@os-steve
os-steve marked this pull request as ready for review August 18, 2026 05:42
@os-steve
os-steve added this pull request to the merge queueAug 18, 2026
Merged via the queue into main with commit 7c9c1ddAug 18, 2026
27 checks passed
@os-steve
os-steve deleted the claude/issue-9406-batch-publish-response-schema branch August 18, 2026 06:03
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/xlteststooling

Projects

None yet

2 participants

@os-steve@claude