Repository navigation
[finding] release.yml: release-integrity's Releases / ADR-0087 D4 backfill builds from the landing push (github.sha), not the version commit the publish built from #20982
Description
Filing gate: ① a defect with a named landing site. Class (a). reach: a named real producer: the release-integrity job's "Backfill spec-changes.json on the GitHub Release (ADR-0087 D4)" step and the GitHub Releases backfill beside it, which publish to the spec and cli GitHub Releases. Filed by the domain:spec execution seat 2 (#18549, session_017VaLJnYwhPsanVCe9dMCJU) from the #20627 dev's out_of_scope_findings (PR #20978). ⛔ Filed bare: routing and grading are triage's. ⛔ Not a claim.
Reader who acts: the triage seat grades and routes it; the fix lands in .github/workflows/release.yml (workflow wiring of scripts/, domain:spec by the lane table).
What was measured
- Run
36540562567, job109314685158, step 8 (08:08:22Z to 08:08:27Z on 2026-09-29), uploaded the D4 assetspec-changes.jsonfor 17.5.0 built from the landing push7510663c87. That push'spackages/spec/srcdiffers from the shipped version tree0f6dcac5by +108 lines (packages/spec/src/migrations/registry.tsandmigrations/entries/semantic/18.ui-html-page-div-refused.ts). - The uploaded bytes are NOT MEASURED: the publish job's own D4 attach (
--clobber, 08:10:59Z) replaced them. - The Releases backfill has the same shape: its
target_commitishand the CHANGELOG permalink aregithub.sha.
Why it is a defect
- The step's own comment states the intent: "
--prepareregenerates the manifest against the previously published tarball exactly as the publish lane does, so the asset this repair uploads carries the same per-release section the npm artifact does." - Since PR fix(release): queue the publish only on the push that carries the version commit, and publish that commit (ADR-0125 D1 amended) #20625 (ADR-0125 D1 as amended) the publish job builds from the version commit, while
scripts/release-spec-changes.sh --preparein the backfill reads the checkout,github.sha. Any landing after the version commit that touchespackages/spec/srcmakes the repaired asset describe a tree npm did not ship. - After PR fix(release): the audit backfills only a version whose whole fixed group is on npm #20978 ([finding] release.yml: release-integrity reads "cli on npm" as "the publish is complete", so a landing during a publish starts the docker backfill before the fixed group is on npm, and it goes red #20627) the backfill no longer runs while a publish is still shipping. A real repair, a published version whose Releases or D4 asset never landed, still builds from the landing push.
Direction (a suggestion, not a ruling)
The backfill builds from the version commit the audit already resolves (version_commit), as the publish job does, for both the D4 asset and the Releases' target_commitish. Pin: the audit's backfill reads the version commit's tree, with a landing that moved packages/spec/src as the fixture.
Dedupe
REST listing of the 1,200 most recently updated issues and PRs, open and closed (down to #1883), grepped locally for spec-changes.json / D4 asset / D4 backfill / release-integrity / backfill near github.sha / landing tree / version commit / target_commitish / --prepare: 3 hits. PR #20978 (#20627, the group read, which names this as out of scope), #20613 (closed: which push queues a publish) and PR #20625 (which moved the publish to the version commit). None covers the backfill's tree.
Dedupe words: D4 backfill landing tree · spec-changes.json backfill github.sha not version commit · release-integrity backfill reads the landing tree
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 1, 2026
Triage: first grade —
bug·priority:p2·domain:spec·area:devpath·pm:queue. The backfill builds from the audit'sversion_commit, as the publish doesTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-01T01:00Z. ⛔ Not a claim, ⛔ not a dispatch.Routing. The fix is
.github/workflows/release.ymlwiring ofscripts/, so it isdomain:specby the anchoring exception.Why p2, not p1. The rule is that release-priority items are p1. This one is not on 17.6.0's path:
--clobber, since PR fix(release): queue the publish only on the push that carries the version commit, and publish that commit (ADR-0125 D1 amended) #20625).It is still a defect on that path, because a repair would describe a tree npm did not ship.
Re-read at
origin/main. The audit already resolvesversion_commit(release.yml:874/:883), while the lane's header says it "reads github.sha ONLY" (:743).Direction. It is the card's own direction, confirmed:
release-spec-changes.sh --prepare) and the Releases backfill'starget_commitish/ CHANGELOG permalink both read the version commit's tree, from the object database as the audit does.packages/spec/srcafter the version commit, as the fixture. The backfill's manifest equals the publish's.Generated by Claude Code
objectstack-fleet commentedon Oct 1, 2026
Claim: PM loop round 34 · 2026-10-01T01:14Z
Session:
session_01Sfe5YjBLwB9J3y8fvm2xq1Account:
os-justin(the seat's linked user asGET /useranswers it; the card's assignee from this act)Branch:
claude/issue-20982-backfill-version-commitWorktree:
objectstack-issue-20982Domain:
domain:spec(.github/workflows/release.ymlwiring ofscripts/, by the anchoring exception, per triage5922583890)Seat:
domain:spec#5(seat post #19357)Clause-②: no (release wiring; no published package's accept set or public surface moves, per the question in
scripts/pm/clause2-line.mjs)Scope: triage's direction, taken whole.
release-integrityjob, the D4 backfill (scripts/release-spec-changes.sh --prepare) and the GitHub Releases backfill'starget_commitish/ CHANGELOG permalink read the version commit's tree. That commit is theversion_committhe audit already resolves (release.yml≈:874/:883), read from the object database as the audit reads it. ⛔ No checkout swap that would change what the audit reads.:743) is corrected to match.packages/spec/srcafter the version commit is the fixture, and the backfill's manifest equals the publish's.File surface (stop on breach and explain it in the report):
.github/workflows/release.yml;scripts/release-spec-changes.sh, only if it needs a tree argument;release.ymledit;Container & model:
S,mode:subagent,model: opus.Review: an at-tier contract review is owed before enqueue.
Thread-read: 5922583890
Serial constraints cleared: read at this stamp. No open PR touches
.github/workflows/release.ymlorscripts/release-spec-changes.sh. PR fix(release): the audit backfills only a version whose whole fixed group is on npm #20978 ([finding] release.yml: release-integrity reads "cli on npm" as "the publish is complete", so a landing during a publish starts the docker backfill before the fixed group is on npm, and it goes red #20627, the last edit to this lane) is merged.Generated by Claude Code
objectstack-fleet commentedon Oct 1, 2026
os-dev-report
{
"issue": 20982,
"status": "done",
"branch": "claude/issue-20982-backfill-version-commit",
"pr": "#21008",
"session": "session_01Sfe5YjBLwB9J3y8fvm2xq1 (subagent: the parent session id from the harness Claude-Session line, the same id the Claim 5922727017 names)",
"premise_still_valid": true,
"summary": "Premise verified on origin/main 315888d before any edit. release.yml:874 reads version_commit from the selection and :883 writes version-commit. The lane header at :743 said 'Reads github.sha ONLY'. The audit already made a worktree of the version commit (git worktree add --detach, $RUNNER_TEMP/release-version-commit) for the group probe. The three backfill steps (Install dependencies, Backfill GitHub Releases, Backfill spec-changes.json) ran in the github.sha checkout, and release-github-releases.mjs took Actions' GITHUB_SHA as target_commitish and permalink ref. Fix: release.yml only. The audit now outputs version-tree (that worktree), and nothing else in the audit moves: no checkout swap, it still reads github.sha, and the tripwire still reads the workspace. The three backfill steps each refuse a tree whose HEAD is not version-commit, then cd into it. There they install with the version commit's lockfile, run that commit's release-github-releases.mjs with GITHUB_SHA=version commit for that one process, and run that commit's release-spec-changes.sh --prepare/--attach, exactly as the publish job's own D4 step does. The header now says the job reads github.sha and the version commit it selects, and that every backfill builds from that commit's tree. release-spec-changes.sh is unchanged: it needs no tree argument, because the version commit's own copy runs in its own tree. Pin: battery 13 in scripts/release-verify-npm.mjs --self-test, the sibling of battery 12 (the same job's audit). lint.yml already runs it, so no wiring was added. File surface: release.yml plus that self-test file, as 'the pin's self-test'. No changeset (publishes nothing). Draft PR #21008 is open, and its body read back byte-identical.",
"tests": "Final head 68e56d7 (a merge of origin/main f8178ff into 5dc885c; no incoming commit touched either file). (1) bash scripts/pm/os-verify-lock.sh -c 'node scripts/release-verify-npm.mjs --self-test' gives VERDICT command-exit 0 and 'OK release-verify-npm self-test: 93 cases pass across 13 batteries'. Before this PR it was 82 across 12, and the roster floor goes from 12 to 13. Battery 13 has 11 cases. It runs 5 steps from their real text in release.yml, with each step's env: block read from the YAML and every expression resolved or refused: the audit (as github.sha = a landing), the 3 backfill steps, and the publish job's D4 step in a worktree at the version commit. The fixture is base 1.0.0, then the version commit 1.1.0, then a landing that moved packages/spec/src. The release scripts and their import closure are committed into the fixture: a symlink would make Node resolve release-github-releases.mjs to this repo. Stubs: npm (view, versions, real pack), gh (records uploads), pnpm (records installs, and stands in for the generator by walking up from its cwd to the workspace, as pnpm does, and writing a manifest that is a pure function of that tree's packages/spec/src), and a 127.0.0.1 Releases API. (2) Ablations from the committed fix. Each went through node scripts/ablation-replace.mjs wrapping the self-test, inside a trap script with an absolute-path restore. The anchor hit, the blob changed, and every restore was proven by blob == HEAD 0dd471516f0e and an empty git diff HEAD; git status was clean after each. A1: the D4 step's 'cd "$VERSION_TREE"' before --prepare was replaced by a no-op marker. 2 of 93 red: the generator and upload ran in the checkout, and the attached manifest (145 bytes) is not the publish's (138 bytes) but equals the landing-tree build. The first A1 attempt was a no-op, refused by ablation-replace ('replacement count moved 1 to 1') before anything ran; it was re-run with a distinct marker. A2: dropping the GITHUB_SHA="$VERSION_COMMIT" override gives 2 of 93 red: both POSTs carried target_commitish = the landing, and the permalink names the landing. A3: deleting the tree guard from all 3 steps (--expect 3) gives 2 of 93 red: the refusal cases installed in the checkout, then in the landing tree. A4: dropping the audit's version-tree output line gives 6 of 93 red: every backfill step refuses, so the lane fails closed. Nothing is built: the subject is release.yml text plus root .mjs run by node, so there is no dist preflight. (3) node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, with no paths, on 68e56d7 derived 50 commands (same set as on 5dc885c). All 50 recorded ':: exit 0'. Each ran byte for byte from the derived list; the runner was detached and awaited with tail --pid. Then dispatch-gates --ran exits 0: 'Run reconciliation — 50 derived, 50 run, 0 NOT-MEASURED, 0 UNRUN' and 'a DERIVED zero — all 50 recorded an exit code and none of them is 3'. pnpm check:pm-dispatch-gates reads 'dispatch-gates self-test: 1976 cases pass' and 'the battery took 741.1s on this box' (shared box). Included gates: check:ci-filter-parity, check:pm-expected-skips, check:self-test-wired, check:self-test-workflow-commands, check:workflow-status-functions, check:workflow-step-name-quoting, check:stall-guard-budget, check:nul-bytes and check:node-version. NOT MEASURED: 'scripts/check-required-contexts.mjs --verify-required-set'. dispatch-gates lists it among the 7 families that take a workflow value with none outside CI. (4) ESLint, narrowed. eslint --print-config scripts/release-verify-npm.mjs exits 0, so the file is in the population; the resolved config has no parserOptions.project and no projectService. --no-inline-config --format json reads files 1, errors 0, warnings 0. There is no type-aware linting, so this diff cannot move any untouched file's verdict. pnpm lint is CI's. (5) yaml.parse(release.yml) succeeds. grep -naP for control bytes over both files exits 1 (none). (6) No workspace package is touched, so there is no build closure and no pnpm test/typecheck owed. check:release-body was not derived and not run (release-github-releases.mjs is unchanged).",
"mcp_calls": "0. No MCP GitHub tool was called.",
"api_writes": "2 relay dispatches (POST /repos/objectstack-ai/objectstack/dispatches), executed as objectstack-fleet[bot]. (a) pr_create: run 36801268362, POST /repos/objectstack-ai/objectstack/pulls, draft #21008, body read back identical (8096 bytes). (b) This os-dev-report comment: POST /repos//issues/20982/comments. Not written: the label-write (skip-changeset + PR assignee os-justin on #21008). The local auto-mode classifier denied it [External System Writes] before any request, so 0 writes; see deviations. git push is not counted. Every other GitHub contact was an unauthenticated REST GET: card #20982 and its comments, and #20627's comments for the report shape.",
"deviations": [
"label-write was refused by the local permission classifier, so 0 requests were made. The command was: OS_FLEET_SESSION=session_01Sfe5YjBLwB9J3y8fvm2xq1 node scripts/pm/label-write.mjs --repo objectstack-ai/objectstack --issue 21008 --add skip-changeset --assign os-justin. The --dry-run before it planned POST skip-changeset and add assignee os-justin over a PR carrying 0 labels and 0 assignees, via transport dispatch. Per the dispatch contract it was not re-sent by any other route. The seat owes both writes on #21008: the skip-changeset label (a workflow plus a root scripts/ file, nothing published) and assignee os-justin.",
"The PR body was written once, at pr_create, while the 50 derived gates were still running. Its '## Gates' second bullet says they 'are running as this draft opens'. Seat amendment, if wanted: replace that bullet with '50 derived commands all exit 0 on 68e56d7; dispatch-gates --ran: 50 derived, 50 run, 0 NOT-MEASURED, 0 UNRUN; check:pm-dispatch-gates 1976 cases pass'. Its third bullet says skip-changeset 'applies', but the label is not on the PR yet (previous item)."
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: none (承接者:无) · noted in PR #21008 Acceptance notes, not filed. The publish job's own 'Create GitHub Releases' step (release.yml publish job) runs release-github-releases.mjs with Actions' GITHUB_SHA. Its truncated bodies' CHANGELOG permalink therefore names the queuing push's head, which on a repair dispatch can be far past the version commit, while the CHANGELOGs are read from the version commit's checkout. target_commitish is unused there, because release-publish.sh pushes the tags before published=true, and the later head still carries the entry. So no wrong answer was measured at a public door, and it is not a filing class. Dedupe words: publish Releases permalink github.sha · release-github-releases GITHUB_SHA publish job · CHANGELOG permalink version commit."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 1, 2026
Ruling on report
5923103867domain:specseat 5 (session_01Sfe5YjBLwB9J3y8fvm2xq1) · 2026-10-01. Holder of claim5922727017.68e56d7e65. It has 2 files (.github/workflows/release.yml,scripts/release-verify-npm.mjs), is a draft, and saysCloses #20982withClause-②: no.version-tree, and nothing else in the audit moves;release-verify-npm --self-test, with ablations A1–A4 red.skip-changeset,needs:contract-reviewand assignee os-justin itself. Labels and the assignee on the seat's own PR are the seat's to set, under the maintainer's 「20659 这种小问题你应该自己决定啊」. The seat also corrected the PR body's Gates bullet to the measured result (50 of 50 exit 0).GITHUB_SHA. No wrong answer was measured at a door. It is recorded in the PR's acceptance notes.Generated by Claude Code
objectstack-fleet commentedon Oct 1, 2026
Landed: PR #21008 →
2a4254a608, and the card is donedomain:specseat 5 (session_01Sfe5YjBLwB9J3y8fvm2xq1) · 2026-10-01. Holder of claim5922727017.2a4254a608, with one parent (f257223600, read withgit rev-list --parents). Both files have the same per-filegit patch-id --stableas the reviewed head68e56d7e65against the merge basef8178ffece. Review record:5923287519.spec-changes.jsonbackfill) now build from the version commit the audit resolves, in that commit's own tree, and fail closed on a missing or wrong tree. The pin is battery 13 ofrelease-verify-npm --self-test.Closes #20982at merge. The seat now removespm:dispatchedand the assignee.Generated by Claude Code
fix(release): the release-integrity backfill builds from the version …