Uh oh!
There was an error while loading. Please reload this page.
feat(spec): declare the batch publish response schema — probes staged opaque (#9406) - #9494
Conversation
… 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
📓 Docs Drift CheckThis PR changes 4 package(s): 3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 120 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop):
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#9406
Ruling (maintainer, 2026-08-18, batch acceptance — verbatim: 「同意」)
What landed
PublishPackageDraftsResponseSchema+PublishPackageDraftsResponseinpackages/spec/src/api/protocol.zod.ts, declaring the fulldatapayload ofPOST /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 forDiscoverySchema).probesis deliberately opaque —z.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.POST /packages/:id/publish-draftsrow now carriesresponseSchema: '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).packages.publishDraftsresolvesPromiseofPublishPackageDraftsResponseinstead ofany— the same move [finding]POST /meta/:type/:name/publishis a served REST route with NO spec declaration — the #5745 "declared = returned" discipline covers only the save door #7294 made formeta.publishItem.publishPackageDraftsinmetadata-protocolnow names the declared face and the suites a return-type change must update.@objectstack/specminor,@objectstack/clientminor,@objectstack/runtimepatch. Additive declaration of an existing wire face;check:adr-0087-registrationgreen (no retirement, no conversion entry needed).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):successfailed.length === 0 && published.length > 0publishedCount/failedCountpublished[]{ type, name, version, advisories? }— advisories omitted-when-empty (#9343 / PR #9405)advisoriesoptional,RuntimeAuthoringIssueSchemaby referencefailed[]{ type, name, error, code? }— pre-flight codes andBATCH_ABORTEDcodeoptional stringseedAppliedapplySeedBodies(counters always) and the route back-fillapplyPublishedSeeds+ its catch (early failures WITHOUT counters)inserted/updatedoptional — the union is the wire facematerializeApplied{ success, inserted, updated, failures[] }failures[]required per-item (deliberately not the single door's scalarerror)probespublished.lengthis positive,BuildProbeReportTS interfacez.unknown(), stagedcommitIdunhiddenApps/unhideError/rebindErrorpackages/runtime/src/domains/packages.ts): ADR-0045 flip receipts (#5242/#8516) and announce receipt (#8516)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)
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-elementversionrequired, refusal face withBATCH_ABORTED, seedApplied counter-union, materializeAppliedfailures[]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).POST /meta/:type/:name/publishis 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 REALpublishPackageDraftsagainst 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.packages/runtime/src/domains/packages-publish-drafts-response-conformance.test.ts: drives the realhandlePackages(protocol doubled, flip/announce/back-fill/assembly shipping code — thepublish-draftshas two more undeclared-driver-text fields on the same 200 body:unhideErrorandrebindError#8516 harness split) (5 cases): envelope/payload convention +unhiddenApps, mid-flip split report,rebindError, the route back-fill's no-counterseedAppliedarm, byte-stability across the route.Verification (all from head
b0eb4a457)pnpm --filter @objectstack/spec buildthen full spec vitest: 3 failed | 406 passed pre-regeneration (the 3 = exactly the stale-artifact gates), thencheck:generated --fixregeneratedapi-surface/export-origins/gen:docs/gen:strictness-ledger(plusgen:schema's shardedauthorable-surface/api.json,json-schema.manifest/api.json— expected artifacts, not reverted), after which the 5 targeted spec files: 135 passed (135).route-ledger-response-schema.test.ts: 4 passed (4) (the new ledger name resolves).typecheckgreen 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).node scripts/pm/dispatch-gates.mjsover 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:json-schema/api/PublishPackageDraftsResponse.jsonlisted in the manifest but not emitted, exit 1 before any test can run. An unpredicted first tripwire, upstream of every suite. (Consequence worth recording: the dist stays the last good build, so running the test suites at this point would have shown false green — the dist caveat, live.)Variant B — faithful pre-declaration state (source + the four sharded spec artifacts reverted to
origin/main), spec REBUILT green, then the suites:undefinedso each case dies onundefined.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); clientroute-ledger-response-schema.test.ts1 failed | 3 passed, with exactly the designed message:responseSchema 'PublishPackageDraftsResponseSchema' is not an export of @objectstack/spec/api.typecheck, not vitest).Restored (
git checkout HEAD -- ..., rebuild): 74 + 8 + 5 + 4 all passed atb0eb4a457; 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 noadvisories; 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 andparse(data)deep-equalsdata); consumer-side, the schema carries no.default()(pinned byIso852'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.jsonartifacts only (api-surface/,authorable-surface/,export-origins/,json-schema.manifest/), textually disjoint from theirs by design; PM serializes landings, later landers runscripts/pm/os-regen-merge.sh.Generated by Claude Code