[RESCUED — evidence complete] feat(metadata-protocol,spec): declare outcome discriminant on publishPackageDrafts response (#10462) - #10635
Conversation
…shPackageDrafts response (#10462) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019yDEhPBC3tcGkW9bkce1HM
📓 Docs Drift CheckThis PR changes 2 package(s): ⛔ 1 release-owned page(s) name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 127 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 708a36ffd00ee5c0adc60fb41173ca297d933235 && git checkout 708a36ffd00ee5c0adc60fb41173ca297d933235
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 5f2e54cc66330cbc53a17f6e3746acdfcdc14704 a94e656ee83f347cc8a09578fc18f91f29e2b9b5 && git checkout -B drift-repro 5f2e54cc66330cbc53a17f6e3746acdfcdc14704 && git merge --no-ff a94e656ee83f347cc8a09578fc18f91f29e2b9b5
node scripts/docs-audit/affected-docs.mjs --json 5f2e54cc66330cbc53a17f6e3746acdfcdc14704
|
… discriminant check:generated proved exactly one artifact stale (content/docs/references/**, from the new .describe() text) and --fix regenerated only it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019yDEhPBC3tcGkW9bkce1HM
…key (#10462) Found by running the full spec suite — the rescued commit updated three fixtures in this file for the now-required key but missed the advisories block's shared 'base' (2 cases failed with ZodError invalid_value on path ['outcome']). The fixture is a successful publish; 'published' is the invariant-consistent value. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019yDEhPBC3tcGkW9bkce1HM
…blish-noop-discriminant
os-regen-merge.sh step 4: the merge took main's side of api.json and protocol.mdx; regenerating from the merged sources restores exactly the outcome rows (api.json +1, protocol.mdx +2/-1). check:generated 14/14 green; main added no api.json entries since merge-base, so no sibling rows were at stake. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019yDEhPBC3tcGkW9bkce1HM
outcome discriminant on publishPackageDrafts response (#10462)outcome discriminant on publishPackageDrafts response (#10462)
…blish-noop-discriminant
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 32489707012 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Fixes #10462
⛔ Draft by policy — the PM flips it ready on acceptance. Clause-② surface (
packages/spec/src/api/protocol.zod.ts), reviewed at the contract-review tier.Provenance
The implementation commit
124579ab80is the SIGKILLed dev's own work, rescued and pushed unmodified (PR history above records the rescue). This dispatch (sessionsession_019yDEhPBC3tcGkW9bkce1HM) produced the evidence the dev died before running, plus exactly one completion measured against the full spec suite (below). The implementation was not redone and not redesigned.The dead dev's staged ablation (exact reversal of the three non-test files, tests untouched) was used deliberately as the defect-control leg, then restored:
git restore --source=HEAD --staged --worktree, proven on disk bygit diff --exit-code(0), emptygit status --porcelain, andgit hash-objectbyte-identity of all three files to the HEAD blobs. It was never committed.What the change is
outcome: 'published' | 'refused' | 'nothing_to_publish'becomes a required key on thepublishPackageDraftsresponse, declared inPublishPackageDraftsResponseSchemaand set at all three producer return sites. The no-op exit additionally logs oneinfoline, so it is no longer traceless.successkeeps its exact pre-change value on every exit. Producer invariants, pinned both directions:outcome === 'refused'⟺failed.length > 0;outcome === 'nothing_to_publish'⟺published.length === 0 && failed.length === 0;success === (outcome === 'published'). The card's original wording ("every non-success return carries at least onefailed[]entry") is false today — the no-op is its counterexample — so the pins state the honest invariants instead (per the PM ruling on the card).Evidence — four controls, each leg rebuilt and dist-verified
Ablation validity: the objectql suites resolve
@objectstack/metadata-protocoland@objectstack/specthrough packageexportsintodist/(both areKNOWN_UNALIASED_TEST_IMPORTSledger entries), so every mutated and restored leg rebuilt the touched package(s) and proved the state ofdist/withscripts/ablation-dist-preflight.mjsbefore its run was read. The metadata-protocol pin file imports./protocol.jsrelative (src-resolved), and was additionally covered by the same rebuilds.1. Defect control — before-state on
origin/main(red). The staged ablation put the three non-test files byte-identical toorigin/main(git hash-object=origin/mainblob ids,git diff origin/main --empty); spec + metadata-protocol rebuilt from those sources; preflight--absentconfirmednothing_to_publishgone from both dists (22 and 189 built files). Result: in the entire 225-file objectql suite, the only red file is this branch's conformance suite —Test Files 1 failed | 224 passed (225),Tests 5 failed | 3981 passed (3986). The defect-control case fails at exactly the missing discriminant, after its side-by-side identity assertions PASS (no-op and refusal identical onsuccess/counts/published):AssertionError: expected undefined to be 'nothing_to_publish'atpublish-package-drafts-response-conformance.test.ts:433. The new metadata-protocol pin file: 4/4 red, each at itsoutcomeassert. Predicted before the run: 5 red + 6 green in the conformance file (the preserved-behaviour control among the greens, by construction) — observed exactly.2. Preserved-behaviour control — falsified by mutation (the leg the dev died inside). Mutation: third return site only,
success: failed.length === 0 && published.length > 0→success: failed.length === 0(the "a no-op is not a failure" refactor this card deliberately does NOT make); markerMUT_10462_LEGBpreflight-proven present in dist. Predicted red: the control's no-op leg, invariant (iii)'s no-op leg, the defect control'ssuccessassert, and the shipped pin the fence names. Predicted green (non-effects, named in advance): all 8 remaining conformance cases (happy paths agree under the mutation; the refusal exercises an untouched return site), and all 4 metadata-protocol pins (none readssuccessat the third return site). Observed exactly: conformance3 failed | 8 passed, the control red withsuccess: truereceived vsfalseexpected on the strict no-op shape; shipped pin file1 failed | 16 passed—returns publishedCount 0 / success false for an empty packagered; metadata-protocol 4/4 green. Restore proven on disk, rebuild + preflight--absent, re-run: 28/28 green.3. Trace control. Baseline green (4/4). Mutation: the no-op
console.infoblock deleted; preflight--absentproved the line string gone from all 22 built files. Observed:1 failed | 3 passed— onlyTRACE CONTROL: the no-op logs one info line…red (expected undefined to be defined); the specificity case (line absent on published/refused exits) stays green, as predicted; objectql conformance 11/11 green (predicted non-effect — no objectql case reads console output). Restore proven, rebuild, preflight line present, 4/4 green.4. The pins, honest form, green. The three biconditionals are asserted as boolean equality (both directions at once) on all three response classes in the objectql conformance suite (11/11) and at the mocked-seam sites objectql cannot stage (Phase-1 unwind, zero-draft machinery edge) in the metadata-protocol pin file (4/4).
Gap found in the rescued work, fixed
Running the full spec suite (which the dev never reached) found 2 red: the #9343 advisories block in
packages/spec/src/api/protocol.test.tsbuilds its ownbasefixture, which lacked the now-required key —ZodError: invalid_value, path: ['outcome']on both cases. The dev updated three sibling fixtures in the same file and missed this one. Fixed in86ba1c68acby addingoutcome: 'published'(the fixture is a successful publish; invariant-consistent). File re-run: 104/104.Changeset, regeneration, merge
3ad7597383: minor for@objectstack/spec+@objectstack/metadata-protocol; states the invariants and the custom-producer obligation. Not declared-breaking;check:adr-0087-registrationandcheck:changeset-no-majorgreen.check:generatedproved exactly one artifact stale (content/docs/references/**, from the new.describe());--fixregenerated only it.origin/mainviascripts/pm/os-regen-merge.sh(merge committed before regeneration, per the driver's deferral contract); regeneration on the merged base restored exactly theoutcomerows the merge had taken from main's side (api.json+1,protocol.mdx+2/−1);check:generated14/14 green; main added noapi.jsonentries since merge-base, so no sibling rows were at stake.Verification at head
9d05bb038b(all runs post-merge, on this exact tree)1742 passed | 10 skipped; spec11107 passed (416 files); objectql3986 passed (225 files); runtime2678 passed (179 files); client314 passed (23 files).tsc --noEmit, plus spec's scripts/test-typecheck legs — debt ledgers unchanged). metadata-protocol declares notypecheckscript (it is a measured DEBT-ledger package); its coverage ridescheck:type-check-coverage/check:type-check-debt, both green.node scripts/pm/dispatch-gates.mjs(no path args, three-dot, re-derived after the merge — it added four gates main landed today): all 38 gates exit 0, pluscheck:type-check-debt --re-measuregreen ("none above its recorded number"; it reports a 12-error surplus BELOW ceiling in one entry from a sibling's landing — issue 6376's standing pattern, not this card's to lower).Fence intact
The no-op still answers
success: false. Control 2's mutation was precisely the forbidden flip, applied temporarily to prove the controls catch it — it went red on the shipped pin and on this branch's controls, and was restored with on-disk proof.📌 #10524 stays serialized behind this card and is not addressed here.
Generated by Claude Code