Skip to content

[RESCUED — evidence complete] feat(metadata-protocol,spec): declare outcome discriminant on publishPackageDrafts response (#10462) - #10635

Merged
os-elon merged 7 commits into
mainfrom
claude/issue-10462-publish-noop-discriminant
Aug 21, 2026
Merged

[RESCUED — evidence complete] feat(metadata-protocol,spec): declare outcome discriminant on publishPackageDrafts response (#10462)#10635
os-elon merged 7 commits into
mainfrom
claude/issue-10462-publish-noop-discriminant

Conversation

@os-elon

@os-elon os-elon commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

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 124579ab80 is the SIGKILLed dev's own work, rescued and pushed unmodified (PR history above records the rescue). This dispatch (session session_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 by git diff --exit-code (0), empty git status --porcelain, and git hash-object byte-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 the publishPackageDrafts response, declared in PublishPackageDraftsResponseSchema and set at all three producer return sites. The no-op exit additionally logs one info line, so it is no longer traceless. success keeps 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 one failed[] 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-protocol and @objectstack/spec through package exports into dist/ (both are KNOWN_UNALIASED_TEST_IMPORTS ledger entries), so every mutated and restored leg rebuilt the touched package(s) and proved the state of dist/ with scripts/ablation-dist-preflight.mjs before its run was read. The metadata-protocol pin file imports ./protocol.js relative (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 to origin/main (git hash-object = origin/main blob ids, git diff origin/main -- empty); spec + metadata-protocol rebuilt from those sources; preflight --absent confirmed nothing_to_publish gone 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 suiteTest 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 on success/counts/published): AssertionError: expected undefined to be 'nothing_to_publish' at publish-package-drafts-response-conformance.test.ts:433. The new metadata-protocol pin file: 4/4 red, each at its outcome assert. 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 > 0success: failed.length === 0 (the "a no-op is not a failure" refactor this card deliberately does NOT make); marker MUT_10462_LEGB preflight-proven present in dist. Predicted red: the control's no-op leg, invariant (iii)'s no-op leg, the defect control's success assert, 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 reads success at the third return site). Observed exactly: conformance 3 failed | 8 passed, the control red with success: true received vs false expected on the strict no-op shape; shipped pin file 1 failed | 16 passedreturns publishedCount 0 / success false for an empty package red; 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.info block deleted; preflight --absent proved the line string gone from all 22 built files. Observed: 1 failed | 3 passed — only TRACE 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.ts builds its own base fixture, 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 in 86ba1c68ac by adding outcome: 'published' (the fixture is a successful publish; invariant-consistent). File re-run: 104/104.

Changeset, regeneration, merge

  • Changeset 3ad7597383: minor for @objectstack/spec + @objectstack/metadata-protocol; states the invariants and the custom-producer obligation. Not declared-breaking; check:adr-0087-registration and check:changeset-no-major green.
  • check:generated proved exactly one artifact stale (content/docs/references/**, from the new .describe()); --fix regenerated only it.
  • Merge of origin/main via scripts/pm/os-regen-merge.sh (merge committed before regeneration, per the driver's deferral contract); regeneration on the merged base restored exactly the outcome rows the merge had taken from main's side (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.

Verification at head 9d05bb038b (all runs post-merge, on this exact tree)

  • Tests: metadata-protocol 1742 passed | 10 skipped; spec 11107 passed (416 files); objectql 3986 passed (225 files); runtime 2678 passed (179 files); client 314 passed (23 files).
  • Typecheck: objectql, runtime, client, spec all green (tsc --noEmit, plus spec's scripts/test-typecheck legs — debt ledgers unchanged). metadata-protocol declares no typecheck script (it is a measured DEBT-ledger package); its coverage rides check:type-check-coverage/check:type-check-debt, both green.
  • Gate union derived by 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, plus check:type-check-debt --re-measure green ("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

…shPackageDrafts response (#10462)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019yDEhPBC3tcGkW9bkce1HM
@github-actions

github-actions Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/metadata-protocol, @objectstack/spec, touching 3 documentable anchor(s).

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

  • content/docs/releases/v17.mdx (via publishPackageDrafts (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
  • 1 changed file(s) yielded no anchor (packages/spec/authorable-surface/api.json) — pages documenting those are invisible to this run
  • the SDK route bridge reached 45 of 221 client-bound route-ledger rows — the other 176 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run: node scripts/docs-audit/affected-docs.mjs --bridge-coverage

Coarse fallback — 127 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 5f2e54cc66330cbc53a17f6e3746acdfcdc14704packageMentionDocs.

Which tree this was computed on

This run read content/docs from 708a36ffd00ee5c0adc60fb41173ca297d933235 — the merge of head a94e656ee83f347cc8a09578fc18f91f29e2b9b5 into base 5f2e54cc66330cbc53a17f6e3746acdfcdc14704, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

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 5f2e54cc66330cbc53a17f6e3746acdfcdc14704 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

… 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
@github-actions github-actions Bot added documentation Improvements or additions to documentation tooling labels Aug 21, 2026
claude added 4 commits August 21, 2026 11:06
…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
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
@os-elon os-elon changed the title [RESCUED — evidence incomplete] feat(metadata-protocol,spec): declare outcome discriminant on publishPackageDrafts response (#10462) [RESCUED — evidence complete] feat(metadata-protocol,spec): declare outcome discriminant on publishPackageDrafts response (#10462) Aug 21, 2026
@os-elon
os-elon marked this pull request as ready for review August 21, 2026 12:08
@os-elon
os-elon enabled auto-merge August 21, 2026 12:09
@os-elon
os-elon added this pull request to the merge queue Aug 21, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 32489707012 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Console Pin Gate — 失败步骤: Build the Console SPA at the pinned objectui SHA

    ✗ Build failed in 5.89s
    

↳ 失败原因 是判读的关键:超时Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

跨 PR 相同签名(24h,按失败测试文件聚合):

  • ⚠️ 本次没有可用的聚合签名(日志里没有能解析出测试文件名的 FAIL 行)—— 这不是「没有同签名的其他 PR」,是这一轮没测到。跨 PR 聚合本次不可用,请手工比对其他 PR 的同类评论。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 7 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

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

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

publishPackageDrafts answers success:false for a publish that refused nothing, and leaves no trace of that exit

2 participants