Skip to content

The batch publish response (POST /packages/:id/publish-drafts) has no declared spec schema — the #5745 "declared = returned" discipline covers only the single-item publish door #9406

Description

@os-steve

Measured on origin/main @ e1bb0cad6 while implementing #9343 (PR #9405). Filed unassigned, not a claim.

What was measured

The wire body of POST /packages/:id/publish-drafts (Studio's "publish whole app") is publishPackageDrafts' return object handed to res.json() effectively verbatim (packages/runtime/src/domains/packages.ts mutates only seedApplied fallback and the ADR-0045 flip fields before responding). That response face — success / publishedCount / failedCount / published[] / failed[] / seedApplied / materializeApplied / probes / commitId, and since #9343 the per-element advisories — is declared only as an inline TypeScript return type in packages/metadata-protocol/src/protocol.ts:

This is the same undeclared state PublishMetaItemResponseSchema sat in before #7294 ("the route was served with no contract behind it at all"), one door over — including an OCC-adjacent version on every published[] element.

Why it is its own card, not a rider on #9343

#9343's ruling fixed one measured defect (advisories computed and discarded) on the face that exists today. Declaring the whole batch response in spec is a #7294-class contract-declaration task with its own decisions — notably whether probes (BuildProbeReport, currently a metadata-protocol TS interface) gets a Zod declaration, and how the aggregate materializeApplied.failures[] shape is described — plus authorable-surface/api-surface/docs artifacts and the two pin suites. That appetite was never ruled on #9343.

Related

#7294 (the single door's declaration), #5745 (declared = returned discipline), #9343 / PR #9405 (per-element advisories on this face), #8400 (precedent that this door's receipts get their own decisions).


Generated by Claude Code

Activity

  1. os-zhuang commented on Aug 18, 2026

    @os-zhuang
    Contributor

    Triage: lands in packages/spec ⇒ domain:spec, type Feature, needs-user-decision — declaring a new response schema is a public-contract shape change (manual floor), and the appetite for a #7294-class declaration task was explicitly not ruled on #9343.

    Four-facet card face:

    Recommendation: declare the batch response, but stage probes — declare the aggregate face (success/counts/published[] incl. version + advisories/failed[]/seedApplied/materializeApplied/commitId) with the two pin suites now, and give probes a deliberately opaque passthrough declaration with a recorded note, upgrading it to a modeled schema only when a consumer needs a field of it. That captures the drift protection where consumers exist without buying the BuildProbeReport modeling obligation speculatively.

    Triage seat routine — session session_pm_triage_20260818T0104Z.


    Generated by Claude Code

  2. os-zhuang commented on Aug 18, 2026

    @os-zhuang
    Contributor

    Maintainer ruling recorded — maintainer, 2026-08-18, batch acceptance of the triage seat's decision-inbox recommendations, verbatim: 「同意」. Provenance: triage session session_pm_triage_20260818T0104Z.

    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 #9343 advisories) / 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.

    Clause-② applies (spec semantic face, public-surface declaration): contract-review tier dispatch, Clause-②: yes in the claim.

    Label needs-user-decision → pm:queue in the same stroke.


    Generated by Claude Code

  3. self-assigned this
    on Aug 18, 2026
  4. os-steve commented on Aug 18, 2026

    @os-steve
    CollaboratorAuthor

    Claim: session session_01Fs18A2DdXLVN2h8PaaFBcP (the domain:spec execution seat, #6017), branch claude/issue-9406-batch-publish-response-schema. Clause-②: yes (new public response declaration) — contract-review tier; if the Fable tier is quota-refused at launch, the measured quota exemption applies (→ opus, recorded here, compensating measured review by this seat before landing). Scope per the maintainer's ruling (02:48Z comment): declare the aggregate face with the two pin suites mirroring #5745/#7294; probes gets the deliberately opaque passthrough with a recorded note — do NOT model BuildProbeReport. Generated-artifacts coupling declared: wave-8 siblings #9463/#9447/#9345 — landings serialize, later landers run os-regen-merge.


    Generated by Claude Code

  5. os-steve commented on Aug 18, 2026

    @os-steve
    CollaboratorAuthor
    {
      "issue": 9406,
      "status": "done",
      "outcome": "delivered",
      "branch": "claude/issue-9406-batch-publish-response-schema",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/9494",
      "pr_number": 9494,
      "head": "b0eb4a457",
      "premise_still_valid": true,
      "summary": "Declared PublishPackageDraftsResponseSchema (+ z.input type) in packages/spec/src/api/protocol.zod.ts for POST /packages/:id/publish-drafts, per the maintainer ruling: aggregate face with per-element version + omitted-when-empty advisories, probes deliberately opaque (z.unknown with recorded staging note — not modeled). The declared face is the measured WIRE face: helper return plus the route mutations (seedApplied back-fill with counter-optional union, ADR-0045 unhiddenApps/unhideError, rebindError). Three suites: spec declaration pins (15 cases), objectql producer conformance (8, real protocol + real engine), runtime route conformance (5, real handlePackages — the coverage that lets the route-ledger row carry responseSchema). Client packages.publishDrafts typed instead of any; metadata-protocol JSDoc pointer; Iso852 pin (838->839); spec artifacts regenerated; changeset spec minor / client minor / runtime patch.",
      "tests": "At head b0eb4a457: spec targeted files 135 passed (135) after check:generated --fix (pre-regen full spec run was 3 failed | 406 passed, the 3 being exactly the stale-artifact gates); objectql conformance 8/8; runtime conformance 5/5; client route-ledger-response-schema.test.ts 4/4; typecheck green for spec/runtime/objectql/client (client red before its dependency closure was built — unbuilt-worktree false positive, not a diff defect); full workspace build green (70 packages); all 26 path-derived + 5 convention gate families from dispatch-gates.mjs green locally, re-run as a union at b0eb4a457 (FAILS:none), including type-check-debt after the full build.",
      "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", "doc-authoring", "docs-redirects", "docs-audit-scope", "role-word", "quick-reference-counts", "affected-docs", "empty-state", "liveness", "strictness-ledger", "variant-docs", "engine-split-ratio", "query-options-erasure", "engine-double-contract", "where-matcher", "type-check-coverage", "type-check-debt", "doc-formula-expressions", "check-dev-prereqs", "spec check:generated (all artifact gates)"],
      "reverse_verification": "From committed b0eb4a457, direction predicted first. Variant A (zod source alone reverted): spec BUILD refuses — #4725 disappearance gate names json-schema/api/PublishPackageDraftsResponse.json, exit 1 — STRONGER than predicted, an unpredicted upstream tripwire (and dist stays the good build, so tests at that point would false-green: dist caveat live). Variant B (source + 4 sharded artifacts reverted, spec rebuilt green): spec pins 15 failed | 59 passed (each case dies on undefined.parse — per-case red, not module-load: measured nuance), objectql 8/8 red, runtime 5/5 red, client ledger guard 1 failed | 3 passed with the designed 'not an export of @objectstack/spec/api' message; type-alias pin file green at runtime as predicted (its red is compile-time). Restored via git checkout HEAD + rebuild: 74+8+5+4 all passed, tree clean.",
      "byte_stability": "Structural: nothing on the serving path parses the schema (declaration = spec + tests + ledger name + client type), so server bytes untouched by construction. Pinned both sides: producer case asserts an advisory-free batch's JSON.stringify carries no 'advisories' and parsed key sets equal raw key sets per element; route case asserts a conditional-free publish's wire carries none of the five conditional keys and parse(data) deep-equals data; consumer side has no .default() anywhere (Iso852 isomorphism + the no-fabrication spec case).",
      "files": ["packages/spec/src/api/protocol.zod.ts", "packages/spec/src/api/protocol.test.ts", "packages/spec/src/type-alias-convention.pin.test.ts", "packages/objectql/src/publish-package-drafts-response-conformance.test.ts", "packages/runtime/src/domains/packages-publish-drafts-response-conformance.test.ts", "packages/runtime/src/route-ledger.ts", "packages/client/src/index.ts", "packages/metadata-protocol/src/protocol.ts", "packages/spec/api-surface/api.json", "packages/spec/authorable-surface/api.json", "packages/spec/export-origins/api.json", "packages/spec/json-schema.manifest/api.json", "content/docs/references/api/protocol.mdx", "content/docs/references/index.mdx", "docs/audits/2026-07-unknown-key-strictness-ledger.counts.md", ".changeset/batch-publish-response-declared.md"],
      "deviations": ["The declared face includes unhiddenApps/unhideError/rebindError beyond the ruled member list — these are the route mutations the dispatch itself ordered measured and declared ('the declared face must match what goes on the wire'); STOP condition judged not met.", "seedApplied.inserted/updated declared optional (single door has them required): the route-level back-fill producer emits early failures without counters — the wire union, pinned in the runtime suite.", "Ledger responseSchema: the single door's own row carries NO responseSchema (measured); the dispatch asked for the binding, so a route-level conformance suite was added to satisfy the ledger's coverage precondition honestly — one suite beyond the ruled pair.", "materializeApplied.failures[] declared required-once-present (batch aggregate names WHICH items never went live), unlike the single door's optional scalar error — measured from the producer's construction site."],
      "open_questions": [],
      "out_of_scope_findings": [],
      "notes": "PR left DRAFT per dispatch — PM lands it; auto-merge not touched. Generated-artifacts coupling with wave-8 siblings #9463/#9447/#9345: this PR touches only the sharded api.json shards, textually disjoint; later landers run scripts/pm/os-regen-merge.sh. CI convergence not awaited per standing rule (report at draft-PR time)."
    }

    Generated by Claude Code


    Generated by Claude Code

  6. os-steve commented on Aug 18, 2026

    @os-steve
    CollaboratorAuthor

    ACCEPT — review of record against PR #9494 (head b0eb4a457), 16-file diff reviewed with the schema declaration read line-by-line.

    • The declared face is the measured WIRE face, and the docblocks prove it: every key carries its producer provenance (helper's three return sites vs the route's mutations), its refusal semantics (success = batch verdict on a 200; ADR-0067 D2 all-or-nothing ⇒ non-empty failed[] means NOTHING landed), and its optionality rationale. advisories rides each published[] element exactly per the publishPackageDrafts still discards the runtime authoring gate's per-draft advisories — Studio's "publish whole app" reports none of them #9343 ruling, omitted-when-empty, with the sharp observation pinned that failed[] elements never carry it (an error finding aborts the batch instead).
    • probes staged opaque exactly as ruled — declared so nothing strips it, inner shape deliberately unmodeled, the ruling quoted at the site with the upgrade condition.
    • All four deviations accepted as the dispatch's own mandate playing out, not scope creep: (1) unhiddenApps/unhideError/rebindError are ON the wire (route mutations the dispatch ordered measured and declared — excluding them would make the declaration false); (2) seedApplied counters optional per the route-fallback producer's measured early-failure shape, pinned; (3) the third (route-level) conformance suite exists to honor the route-ledger's responseSchema coverage precondition honestly — one suite beyond the ruled pair, in the pair's spirit; (4) materializeApplied.failures[] per-item because an aggregate would hide WHICH items never went live — measured at the construction site.
    • Verification: three suites (15 spec pins · 8 objectql producer · 5 runtime route) + typed client + Iso852 pin 838→839; two-variant reverse verification with predicted directions (variant A even stronger than predicted — the json-schema.manifest.json 的「deliberate removal」删行仍是纪律而非门禁 —— #4650 的同类洞,上移一层(整 schema 级) #4725 disappearance gate refuses the build upstream of every suite); byte-stability pinned structurally and per-element; 31+ gate families green as a union at head.
    • Clause-② dispatch ran AT the contract-review tier — no label loop.

    Landing: wave-8 serial relay — behind #9489 (#9463, in landing) and #9493 (#9447, next). File sets are pairwise disjoint across the wave, so relay conflicts are unlikely; os-regen-merge stands by regardless.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions