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
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
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:
grep -rn publishedCount packages/spec/src — zero hits; no PublishPackageDrafts* schema exists anywhere under packages/spec/src/.
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.
#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).
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.
Measured business pull: real — Studio's "publish whole app" is a shipped, user-visible surface, and objectui consumes this face today; drift here breaks the console silently.
Startup scope discipline: the surface already exists and ships — declaring it is pinning an obligation we already carry, not adding one. The real scope question is probes (BuildProbeReport): a full Zod model of it is the expensive half.
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.
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.
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.
{
"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)."
}
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.
Activity
os-zhuang commentedon Aug 18, 2026
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:
POST /meta/:type/:name/publishis a served REST route with NO spec declaration — the #5745 "declared = returned" discipline covers only the save door #7294); the batch door — same data, same consumers — is an inline TS return type with zero gates. Declaring it removes an asymmetry that already produced one measured defect (publishPackageDrafts still discards the runtime authoring gate's per-draft advisories — Studio's "publish whole app" reports none of them #9343's discarded advisories could not turn any gate red).POST /meta/:type/:name/publishis a served REST route with NO spec declaration — the #5745 "declared = returned" discipline covers only the save door #7294 closed the single door.probes(BuildProbeReport): a full Zod model of it is the expensive half.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 giveprobesa 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 theBuildProbeReportmodeling obligation speculatively.Triage seat routine — session
session_pm_triage_20260818T0104Z.Generated by Claude Code
os-zhuang commentedon Aug 18, 2026
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, withprobesstaged. Concretely:success/publishedCount/failedCount/published[](including the per-elementversionand 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/publishis 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).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-②: yesin the claim.Label
needs-user-decision→pm:queuein the same stroke.Generated by Claude Code
os-steve commentedon Aug 18, 2026
Claim: session
session_01Fs18A2DdXLVN2h8PaaFBcP(thedomain:specexecution seat, #6017), branchclaude/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;probesgets the deliberately opaque passthrough with a recorded note — do NOT modelBuildProbeReport. Generated-artifacts coupling declared: wave-8 siblings #9463/#9447/#9345 — landings serialize, later landers run os-regen-merge.Generated by Claude Code
feat(spec): declare the batch publish response schema — probes staged…
os-steve commentedon Aug 18, 2026
{ "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
os-steve commentedon Aug 18, 2026
ACCEPT — review of record against PR #9494 (head
b0eb4a457), 16-file diff reviewed with the schema declaration read line-by-line.success= batch verdict on a 200; ADR-0067 D2 all-or-nothing ⇒ non-emptyfailed[]means NOTHING landed), and its optionality rationale.advisoriesrides eachpublished[]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 thatfailed[]elements never carry it (anerrorfinding aborts the batch instead).probesstaged opaque exactly as ruled — declared so nothing strips it, inner shape deliberately unmodeled, the ruling quoted at the site with the upgrade condition.unhiddenApps/unhideError/rebindErrorare ON the wire (route mutations the dispatch ordered measured and declared — excluding them would make the declaration false); (2)seedAppliedcounters 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'sresponseSchemacoverage 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.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
feat(spec): declare the batch publish response schema — probes staged…
test(spec): the second api/ file group's test titles state each cited…