From 45ee8b3faf309f1d671f6f8009bc003635bc73f8 Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Tue, 29 Sep 2026 18:30:09 +0200 Subject: [PATCH 1/6] Resume committed pm-linear release metadata after a failed publish Detect an untagged package version left by a merged release commit and retry that exact tag across release days. Refuse unrelated manual bumps and inconsistent tag/version pairs instead of minting another release. Exercise the real Decide release step in a disposable Git fixture for fresh changes, repeated retries, later source commits, and unsafe manual bumps. Release check: 219/219 tests, docstrings 49/49, audit 0; measured source coverage 99.29% lines, 96.68% branches, 100% functions. pm-linear-yyr9. --- .agents/pm/history/pm-linear-yyr9.jsonl | 4 ++ .agents/pm/issues/pm-linear-yyr9.toon | 16 ++++- .github/workflows/release.yml | 40 +++++++++++ test/release-decision.test.ts | 91 +++++++++++++++++++++++++ 4 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 test/release-decision.test.ts diff --git a/.agents/pm/history/pm-linear-yyr9.jsonl b/.agents/pm/history/pm-linear-yyr9.jsonl index 8a47b64..ed5e817 100644 --- a/.agents/pm/history/pm-linear-yyr9.jsonl +++ b/.agents/pm/history/pm-linear-yyr9.jsonl @@ -1,2 +1,6 @@ {"ts":"2026-08-09T13:46:47.559Z","author":"claude","author_source":"asserted","agent_harness":"claude-code","agent_instance":"6c62aab177c939482aa0465b","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":{"value":"1","source":"environment"},"topic":null},"op":"create","patch":[{"op":"add","path":"/metadata/id","value":"pm-linear-yyr9"},{"op":"add","path":"/metadata/title","value":"A release that merges but fails to publish is abandoned by the next day's run"},{"op":"add","path":"/metadata/description","value":"Decide release computes the version from today's date and the existing tags, and the tag is pushed only after npm publish succeeds. If the protected release PR merges and publication then fails, main carries release metadata for a version that was never tagged or published. The next day's run sees the old latest tag, computes a new version from the new date, and commits it over the merged metadata, so the earlier version is silently skipped on npm. Needs a guard in Decide release that detects a committed-but-untagged version and resumes it rather than computing a new one."},{"op":"add","path":"/metadata/type","value":"Issue"},{"op":"add","path":"/metadata/status","value":"open"},{"op":"add","path":"/metadata/priority","value":2},{"op":"add","path":"/metadata/tags","value":[]},{"op":"add","path":"/metadata/created_at","value":"2026-08-09T13:46:47.559Z"},{"op":"add","path":"/metadata/updated_at","value":"2026-08-09T13:46:47.559Z"},{"op":"add","path":"/metadata/author","value":"claude"}],"before_hash":"3cc22dff72be7b14824654a7a64ea62b04799939b2fee54c1b5f52ca60bf6df0","after_hash":"8027125e7ae6bb5418a6cf8969083e195fdf2b9f24122e694789c958c0f047f0","message":""} {"ts":"2026-08-28T13:22:15.617Z","author":"claude","op":"history_repair","patch":[],"before_hash":"8027125e7ae6bb5418a6cf8969083e195fdf2b9f24122e694789c958c0f047f0","after_hash":"8027125e7ae6bb5418a6cf8969083e195fdf2b9f24122e694789c958c0f047f0","message":"history-repair re-anchored 0 entries.","context":{"provenance_normalization":{"changed":true,"events_changed":1,"observations_removed":1,"invalid_values":[{"harness":"claude-code","dimension":"role","kind":"single_digit","count":1}]}}} +{"hash_algorithm":"sha256","ts":"2026-09-29T16:20:23.508Z","author":"codex","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6-sol","agent_model_source":"probe","agent_instance":"3b5e62ebb1f8ac82bae80594","agent_provenance":{"model":{"value":"gpt-6-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-09-29T16:20:23.508Z"},{"op":"add","path":"/metadata/assignee","value":"codex"},{"op":"add","path":"/metadata/claim_principal","value":"codex"}],"before_hash":"8027125e7ae6bb5418a6cf8969083e195fdf2b9f24122e694789c958c0f047f0","after_hash":"c671d9aea17bdf81b22569e1b8130d263c2cf8f48a30d6a7cef5f0bcf9968d70","item_hash_version":3,"message":"Implement and verify resume of an untagged committed release version","context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"69b270443de42deb3d7cef72f515c7c1effcfcbf9813b1eec693272cd41b9d5b"} +{"hash_algorithm":"sha256","ts":"2026-09-29T16:20:23.557Z","author":"codex","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6-sol","agent_model_source":"probe","agent_instance":"3b5e62ebb1f8ac82bae80594","agent_provenance":{"model":{"value":"gpt-6-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-09-29T16:20:23.557Z"},{"op":"replace","path":"/metadata/status","value":"in_progress"}],"before_hash":"c671d9aea17bdf81b22569e1b8130d263c2cf8f48a30d6a7cef5f0bcf9968d70","after_hash":"df26f2f77cbddfe8552e12ee76fa68b19878f504a44f7105208f3e7f739bb376","item_hash_version":3,"message":"Implement and verify resume of an untagged committed release version","context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"a92e0dcd98c4be48f6f43537df3189d504f013023fc2d2271d585b3713236c64"} +{"hash_algorithm":"sha256","ts":"2026-09-29T16:24:31.334Z","author":"codex","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6-sol","agent_model_source":"probe","agent_instance":"3b5e62ebb1f8ac82bae80594","agent_provenance":{"model":{"value":"gpt-6-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-09-29T16:24:31.334Z"},{"op":"add","path":"/metadata/acceptance_criteria","value":"A failed publish after a merged release resumes the committed package version and release tag on later runs; Ordinary unreleased source changes receive a new date-based version and an unrelated manual version bump fails closed; A disposable Git fixture executes the actual workflow decision for both paths; The package release gate and exact-head CI and review gates pass before merge"},{"op":"add","path":"/metadata/risk","value":"high"},{"op":"add","path":"/metadata/files","value":[{"path":".github/workflows/release.yml","scope":"project","note":"release decision and retry"},{"path":"test/release-decision.test.ts","scope":"project","note":"disposable Git acceptance"}]},{"op":"add","path":"/metadata/tests","value":[{"command":"node --test test/release-decision.test.ts","path":"test/release-decision.test.ts","scope":"project","provenance":{"author":"codex","created_at":"2026-09-29T16:24:31.307Z","source_kind":"local_mutation","source_ref":"fix/resume-untagged-linear-release-2026-09-29"}},{"command":"npm run release:check","path":"package.json","scope":"project","provenance":{"author":"codex","created_at":"2026-09-29T16:24:31.307Z","source_kind":"local_mutation","source_ref":"fix/resume-untagged-linear-release-2026-09-29"}}]}],"before_hash":"df26f2f77cbddfe8552e12ee76fa68b19878f504a44f7105208f3e7f739bb376","after_hash":"937e9836d22ff661e5201fdc1457bb80caa67e516bcbb03f9372ed7fd590a5f5","item_hash_version":3,"message":"Define retry safety and executable acceptance before PR","context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"8680df722a3eae724e4d00f4700617fe3b29eaa48a141809dfc27e5779cf4e10"} +{"hash_algorithm":"sha256","ts":"2026-09-29T16:29:55.004Z","author":"codex","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6-sol","agent_model_source":"probe","agent_instance":"3b5e62ebb1f8ac82bae80594","agent_provenance":{"model":{"value":"gpt-6-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"note_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-09-29T16:29:55.004Z"},{"op":"add","path":"/metadata/notes","value":[{"created_at":"2026-09-29T16:29:55.004Z","author":"codex","text":"2026-09-29 candidate: the disposable Git fixture failed on the previous workflow (new date tag instead of committed v2000.01.02), then passed after the decision resumed that tag. It also verifies a fresh feature gets a new version, later source commits preserve the pending version, and an unrelated manual version bump is refused. npm run release:check passed 219/219 tests, docstring 49/49 declarations across 8 files, audit 0 production vulnerabilities, and attestation gate 1/1 publish path; coverage measured 99.29% lines, 96.68% branches, 100% functions, below the requested 100% floor. pm health --strict-exit fails on two pre-existing merge receipts for pm-linear-unfa and pm-linear-4yle; pm merge reconcile --dry-run reports no_receipt_set_proves_snapshot for both, so neither was forced. Rollback: revert the workflow decision change before any release run; no registry or hosted state was changed by this candidate."}]}],"before_hash":"937e9836d22ff661e5201fdc1457bb80caa67e516bcbb03f9372ed7fd590a5f5","after_hash":"85da9e6e4b909e6552dc46604946985c4c51df64dad9166e3c53030a40a40e17","item_hash_version":3,"message":"Record red-green fixture, exact gate evidence, and rollback","context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"e9caf785e89a30f1f0927d2c978605a0c9151a267dd3d081a815d183d76dca21"} diff --git a/.agents/pm/issues/pm-linear-yyr9.toon b/.agents/pm/issues/pm-linear-yyr9.toon index 67cddde..f29e0b9 100644 --- a/.agents/pm/issues/pm-linear-yyr9.toon +++ b/.agents/pm/issues/pm-linear-yyr9.toon @@ -2,10 +2,22 @@ id: pm-linear-yyr9 title: A release that merges but fails to publish is abandoned by the next day's run description: "Decide release computes the version from today's date and the existing tags, and the tag is pushed only after npm publish succeeds. If the protected release PR merges and publication then fails, main carries release metadata for a version that was never tagged or published. The next day's run sees the old latest tag, computes a new version from the new date, and commits it over the merged metadata, so the earlier version is silently skipped on npm. Needs a guard in Decide release that detects a committed-but-untagged version and resumes it rather than computing a new one." type: Issue -status: open +status: in_progress priority: 2 tags: [] created_at: "2026-08-09T13:46:47.559Z" -updated_at: "2026-08-09T13:46:47.559Z" +updated_at: "2026-09-29T16:29:55.004Z" +assignee: codex +claim_principal: codex author: claude +acceptance_criteria: A failed publish after a merged release resumes the committed package version and release tag on later runs; Ordinary unreleased source changes receive a new date-based version and an unrelated manual version bump fails closed; A disposable Git fixture executes the actual workflow decision for both paths; The package release gate and exact-head CI and review gates pass before merge +risk: high +notes[1]{created_at,author,text}: + "2026-09-29T16:29:55.004Z",codex,"2026-09-29 candidate: the disposable Git fixture failed on the previous workflow (new date tag instead of committed v2000.01.02), then passed after the decision resumed that tag. It also verifies a fresh feature gets a new version, later source commits preserve the pending version, and an unrelated manual version bump is refused. npm run release:check passed 219/219 tests, docstring 49/49 declarations across 8 files, audit 0 production vulnerabilities, and attestation gate 1/1 publish path; coverage measured 99.29% lines, 96.68% branches, 100% functions, below the requested 100% floor. pm health --strict-exit fails on two pre-existing merge receipts for pm-linear-unfa and pm-linear-4yle; pm merge reconcile --dry-run reports no_receipt_set_proves_snapshot for both, so neither was forced. Rollback: revert the workflow decision change before any release run; no registry or hosted state was changed by this candidate." +files[2]{path,scope,note}: + .github/workflows/release.yml,project,release decision and retry + test/release-decision.test.ts,project,disposable Git acceptance +tests[2]{command,path,scope,provenance{author,created_at,source_kind,source_ref}}: + node --test test/release-decision.test.ts,test/release-decision.test.ts,project,codex,"2026-09-29T16:24:31.307Z",local_mutation,fix/resume-untagged-linear-release-2026-09-29 + "npm run release:check",package.json,project,codex,"2026-09-29T16:24:31.307Z",local_mutation,fix/resume-untagged-linear-release-2026-09-29 body: "" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f4ac73c..30d0081 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -86,6 +86,46 @@ jobs: exit 0 fi + # A protected release PR can merge before npm accepts the artifact. + # The next run must complete that committed version, even after the + # release date changes. Only a package.json last changed by the + # release commit is eligible; an unrelated manual bump fails closed. + if [[ -n "$latest_tag" ]]; then + current_version="$(node -p "require('./package.json').version")" + tagged_version="$(git show "${latest_tag}:package.json" | node -p "JSON.parse(require('node:fs').readFileSync(0, 'utf8')).version")" + if [[ "$current_version" != "$tagged_version" ]]; then + release_subject="$(git log -1 --format=%s -- package.json)" + if ! printf '%s\n' "$release_subject" | grep -Eq '^Release pm-linear v[0-9]{4}\.[0-9]{2}\.[0-9]{2}(-[0-9]+)?$'; then + echo "::error::package.json is ahead of ${latest_tag} without a recognized release commit. Resolve its version before releasing." + exit 1 + fi + tag="${release_subject##* }" + version_core="${tag#v}" + year="${version_core%%.*}" + version_tail="${version_core#*.}" + month="${version_tail%%.*}" + day_and_suffix="${version_tail#*.}" + day="${day_and_suffix%%-*}" + suffix="${day_and_suffix#"$day"}" + npm_version="$((10#$year)).$((10#$month)).$((10#$day))${suffix}" + if [[ "$npm_version" != "$current_version" ]]; then + echo "::error::Release commit ${tag} disagrees with package.json version ${current_version}." + exit 1 + fi + if git show-ref --verify --quiet "refs/tags/${tag}"; then + echo "::error::Release tag ${tag} already exists but ${latest_tag} was selected as latest. Resolve tag ordering before releasing." + exit 1 + fi + echo "should_release=true" >> "$GITHUB_OUTPUT" + echo "latest_tag=$latest_tag" >> "$GITHUB_OUTPUT" + echo "base_sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" + echo "tag=$tag" >> "$GITHUB_OUTPUT" + echo "npm_version=$npm_version" >> "$GITHUB_OUTPUT" + echo "Resuming committed but untagged release ${tag}." >> "$GITHUB_STEP_SUMMARY" + exit 0 + fi + fi + release_date="$(TZ="$RELEASE_TIMEZONE" date +%Y.%m.%d)" base_tag="v${release_date}" max_suffix=-1 diff --git a/test/release-decision.test.ts b/test/release-decision.test.ts new file mode 100644 index 0000000..1e33f5a --- /dev/null +++ b/test/release-decision.test.ts @@ -0,0 +1,91 @@ +import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join, resolve } from "node:path"; +import test from "node:test"; + +/** Exercise the actual workflow decision in a disposable Git project. */ +test("a merged but untagged release keeps its committed version on retry", () => { + const root = mkdtempSync(join(tmpdir(), "pm-linear-release-decision-")); + const project = join(root, "project"); + const remote = join(root, "remote.git"); + const workflow = readFileSync(resolve(import.meta.dirname, "../.github/workflows/release.yml"), "utf8"); + const block = / - name: Decide release\n[\s\S]*? run: \|\n([\s\S]*?)(?=\n - name:)/.exec(workflow)?.[1]; + assert.ok(block, "the release decision must be present"); + const script = block.split("\n").map((line) => line.startsWith(" ") ? line.slice(10) : line).join("\n"); + + /** Run a command against the fixture and fail with its diagnostic. */ + function run(command: string, args: string[], cwd = project): string { + const result = spawnSync(command, args, { cwd, encoding: "utf8" }); + assert.equal(result.status, 0, `${command} ${args.join(" ")}: ${result.stderr}`); + return result.stdout.trim(); + } + + /** Capture the workflow's machine-readable decision without publishing anything. */ + function decide(): Record { + const output = join(root, "output"); + const summary = join(root, "summary"); + writeFileSync(output, ""); + writeFileSync(summary, ""); + const result = spawnSync("bash", ["-c", script], { + cwd: project, + encoding: "utf8", + env: { ...process.env, GITHUB_OUTPUT: output, GITHUB_STEP_SUMMARY: summary, RELEASE_TIMEZONE: "Europe/Vienna" }, + }); + assert.equal(result.status, 0, `release decision failed: ${result.stderr}`); + return Object.fromEntries(readFileSync(output, "utf8").trim().split("\n").map((line) => { + const separator = line.indexOf("="); + return [line.slice(0, separator), line.slice(separator + 1)]; + })); + } + + try { + run("git", ["init", "-b", "main", project], root); + run("git", ["config", "user.email", "fixture@example.invalid"]); + run("git", ["config", "user.name", "Release fixture"]); + writeFileSync(join(project, "package.json"), '{"name":"pm-linear","version":"2000.1.1"}\n'); + writeFileSync(join(project, "source.txt"), "first\n"); + run("git", ["add", "."]); + run("git", ["commit", "-m", "Initial release"]); + run("git", ["tag", "v2000.01.01"]); + run("git", ["init", "--bare", remote], root); + run("git", ["remote", "add", "origin", remote]); + run("git", ["push", "origin", "main", "--tags"]); + + writeFileSync(join(project, "source.txt"), "second\n"); + run("git", ["add", "source.txt"]); + run("git", ["commit", "-m", "Add a feature"]); + const fresh = decide(); + assert.equal(fresh.should_release, "true"); + assert.notEqual(fresh.npm_version, "2000.1.1", "new work should get a new version"); + + writeFileSync(join(project, "package.json"), '{"name":"pm-linear","version":"2000.1.2"}\n'); + run("git", ["add", "package.json"]); + run("git", ["commit", "-m", "Release pm-linear v2000.01.02"]); + const retry = decide(); + assert.equal(retry.should_release, "true"); + assert.equal(retry.tag, "v2000.01.02"); + assert.equal(retry.npm_version, "2000.1.2"); + assert.equal(retry.base_sha, run("git", ["rev-parse", "HEAD"])); + + writeFileSync(join(project, "source.txt"), "third\n"); + run("git", ["add", "source.txt"]); + run("git", ["commit", "-m", "Document the failed release"]); + const laterRetry = decide(); + assert.equal(laterRetry.tag, "v2000.01.02", "later source commits must not mint another version"); + + writeFileSync(join(project, "package.json"), '{"name":"pm-linear","version":"2000.1.3"}\n'); + run("git", ["add", "package.json"]); + run("git", ["commit", "-m", "Manually change the package version"]); + const refused = spawnSync("bash", ["-c", script], { + cwd: project, + encoding: "utf8", + env: { ...process.env, GITHUB_OUTPUT: join(root, "output"), GITHUB_STEP_SUMMARY: join(root, "summary"), RELEASE_TIMEZONE: "Europe/Vienna" }, + }); + assert.notEqual(refused.status, 0, "an unrelated version change needs an explicit resolution"); + assert.match(refused.stdout, /without a recognized release commit/); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); From 570ca96d8e7d801c773e861cb5894e4bba0e4906 Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Tue, 29 Sep 2026 18:31:23 +0200 Subject: [PATCH 2/6] Link pm-linear release retry PR from its owner item Record PR #126 and the immutable implementation commit in pm-linear-yyr9 so the review and release gates remain attached to the authoritative tracker. --- .agents/pm/history/pm-linear-yyr9.jsonl | 1 + .agents/pm/issues/pm-linear-yyr9.toon | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.agents/pm/history/pm-linear-yyr9.jsonl b/.agents/pm/history/pm-linear-yyr9.jsonl index ed5e817..086bd18 100644 --- a/.agents/pm/history/pm-linear-yyr9.jsonl +++ b/.agents/pm/history/pm-linear-yyr9.jsonl @@ -4,3 +4,4 @@ {"hash_algorithm":"sha256","ts":"2026-09-29T16:20:23.557Z","author":"codex","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6-sol","agent_model_source":"probe","agent_instance":"3b5e62ebb1f8ac82bae80594","agent_provenance":{"model":{"value":"gpt-6-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-09-29T16:20:23.557Z"},{"op":"replace","path":"/metadata/status","value":"in_progress"}],"before_hash":"c671d9aea17bdf81b22569e1b8130d263c2cf8f48a30d6a7cef5f0bcf9968d70","after_hash":"df26f2f77cbddfe8552e12ee76fa68b19878f504a44f7105208f3e7f739bb376","item_hash_version":3,"message":"Implement and verify resume of an untagged committed release version","context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"a92e0dcd98c4be48f6f43537df3189d504f013023fc2d2271d585b3713236c64"} {"hash_algorithm":"sha256","ts":"2026-09-29T16:24:31.334Z","author":"codex","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6-sol","agent_model_source":"probe","agent_instance":"3b5e62ebb1f8ac82bae80594","agent_provenance":{"model":{"value":"gpt-6-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-09-29T16:24:31.334Z"},{"op":"add","path":"/metadata/acceptance_criteria","value":"A failed publish after a merged release resumes the committed package version and release tag on later runs; Ordinary unreleased source changes receive a new date-based version and an unrelated manual version bump fails closed; A disposable Git fixture executes the actual workflow decision for both paths; The package release gate and exact-head CI and review gates pass before merge"},{"op":"add","path":"/metadata/risk","value":"high"},{"op":"add","path":"/metadata/files","value":[{"path":".github/workflows/release.yml","scope":"project","note":"release decision and retry"},{"path":"test/release-decision.test.ts","scope":"project","note":"disposable Git acceptance"}]},{"op":"add","path":"/metadata/tests","value":[{"command":"node --test test/release-decision.test.ts","path":"test/release-decision.test.ts","scope":"project","provenance":{"author":"codex","created_at":"2026-09-29T16:24:31.307Z","source_kind":"local_mutation","source_ref":"fix/resume-untagged-linear-release-2026-09-29"}},{"command":"npm run release:check","path":"package.json","scope":"project","provenance":{"author":"codex","created_at":"2026-09-29T16:24:31.307Z","source_kind":"local_mutation","source_ref":"fix/resume-untagged-linear-release-2026-09-29"}}]}],"before_hash":"df26f2f77cbddfe8552e12ee76fa68b19878f504a44f7105208f3e7f739bb376","after_hash":"937e9836d22ff661e5201fdc1457bb80caa67e516bcbb03f9372ed7fd590a5f5","item_hash_version":3,"message":"Define retry safety and executable acceptance before PR","context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"8680df722a3eae724e4d00f4700617fe3b29eaa48a141809dfc27e5779cf4e10"} {"hash_algorithm":"sha256","ts":"2026-09-29T16:29:55.004Z","author":"codex","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6-sol","agent_model_source":"probe","agent_instance":"3b5e62ebb1f8ac82bae80594","agent_provenance":{"model":{"value":"gpt-6-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"note_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-09-29T16:29:55.004Z"},{"op":"add","path":"/metadata/notes","value":[{"created_at":"2026-09-29T16:29:55.004Z","author":"codex","text":"2026-09-29 candidate: the disposable Git fixture failed on the previous workflow (new date tag instead of committed v2000.01.02), then passed after the decision resumed that tag. It also verifies a fresh feature gets a new version, later source commits preserve the pending version, and an unrelated manual version bump is refused. npm run release:check passed 219/219 tests, docstring 49/49 declarations across 8 files, audit 0 production vulnerabilities, and attestation gate 1/1 publish path; coverage measured 99.29% lines, 96.68% branches, 100% functions, below the requested 100% floor. pm health --strict-exit fails on two pre-existing merge receipts for pm-linear-unfa and pm-linear-4yle; pm merge reconcile --dry-run reports no_receipt_set_proves_snapshot for both, so neither was forced. Rollback: revert the workflow decision change before any release run; no registry or hosted state was changed by this candidate."}]}],"before_hash":"937e9836d22ff661e5201fdc1457bb80caa67e516bcbb03f9372ed7fd590a5f5","after_hash":"85da9e6e4b909e6552dc46604946985c4c51df64dad9166e3c53030a40a40e17","item_hash_version":3,"message":"Record red-green fixture, exact gate evidence, and rollback","context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"e9caf785e89a30f1f0927d2c978605a0c9151a267dd3d081a815d183d76dca21"} +{"hash_algorithm":"sha256","ts":"2026-09-29T16:31:15.876Z","author":"codex","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6-sol","agent_model_source":"probe","agent_instance":"3b5e62ebb1f8ac82bae80594","agent_provenance":{"model":{"value":"gpt-6-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"note_add","patch":[{"op":"add","path":"/metadata/notes/1","value":{"created_at":"2026-09-29T16:31:15.876Z","author":"codex","text":"Implementation PR: https://github.com/unbraind/pm-linear/pull/126 . Candidate code commit: https://github.com/unbraind/pm-linear/commit/45ee8b3faf309f1d671f6f8009bc003635bc73f8 . Awaiting exact-head CI and configured bot review; leave unmerged while coverage and local strict-health blockers remain."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-29T16:31:15.876Z"}],"before_hash":"85da9e6e4b909e6552dc46604946985c4c51df64dad9166e3c53030a40a40e17","after_hash":"3a7f0234c7585e43b50728f2bb70d36ef2f6449fddc8485014f5d428a4257861","item_hash_version":3,"message":"Link implementation PR and its gate state","context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"0d811e7b9025bdb40f19e72d1c7a66a2efbafa6a016bec0a197c31fe53c5f878"} diff --git a/.agents/pm/issues/pm-linear-yyr9.toon b/.agents/pm/issues/pm-linear-yyr9.toon index f29e0b9..febe1c5 100644 --- a/.agents/pm/issues/pm-linear-yyr9.toon +++ b/.agents/pm/issues/pm-linear-yyr9.toon @@ -6,14 +6,15 @@ status: in_progress priority: 2 tags: [] created_at: "2026-08-09T13:46:47.559Z" -updated_at: "2026-09-29T16:29:55.004Z" +updated_at: "2026-09-29T16:31:15.876Z" assignee: codex claim_principal: codex author: claude acceptance_criteria: A failed publish after a merged release resumes the committed package version and release tag on later runs; Ordinary unreleased source changes receive a new date-based version and an unrelated manual version bump fails closed; A disposable Git fixture executes the actual workflow decision for both paths; The package release gate and exact-head CI and review gates pass before merge risk: high -notes[1]{created_at,author,text}: +notes[2]{created_at,author,text}: "2026-09-29T16:29:55.004Z",codex,"2026-09-29 candidate: the disposable Git fixture failed on the previous workflow (new date tag instead of committed v2000.01.02), then passed after the decision resumed that tag. It also verifies a fresh feature gets a new version, later source commits preserve the pending version, and an unrelated manual version bump is refused. npm run release:check passed 219/219 tests, docstring 49/49 declarations across 8 files, audit 0 production vulnerabilities, and attestation gate 1/1 publish path; coverage measured 99.29% lines, 96.68% branches, 100% functions, below the requested 100% floor. pm health --strict-exit fails on two pre-existing merge receipts for pm-linear-unfa and pm-linear-4yle; pm merge reconcile --dry-run reports no_receipt_set_proves_snapshot for both, so neither was forced. Rollback: revert the workflow decision change before any release run; no registry or hosted state was changed by this candidate." + "2026-09-29T16:31:15.876Z",codex,"Implementation PR: https://github.com/unbraind/pm-linear/pull/126 . Candidate code commit: https://github.com/unbraind/pm-linear/commit/45ee8b3faf309f1d671f6f8009bc003635bc73f8 . Awaiting exact-head CI and configured bot review; leave unmerged while coverage and local strict-health blockers remain." files[2]{path,scope,note}: .github/workflows/release.yml,project,release decision and retry test/release-decision.test.ts,project,disposable Git acceptance From 4f71467426170c709240c2380e1ca35e943eaca9 Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Tue, 29 Sep 2026 18:43:42 +0200 Subject: [PATCH 3/6] Refuse release retry after main advances and select tag by ancestry Keep the pending version only when its release commit is the checked-out head, preventing a tag for source newer than an already accepted npm artifact. Select the nearest reachable first-parent release tag instead of trusting tagger timestamps. Extend the disposable Git fixture for newer-source refusal, occupied tag, inconsistent version, and skewed tag dates. Reconcile Greptile and CodeRabbit findings on pm-linear-yyr9. Release check: 219/219 tests; coverage remains 99.29% lines, 96.68% branches, 100% functions. --- .agents/pm/history/pm-linear-yyr9.jsonl | 2 + .agents/pm/issues/pm-linear-yyr9.toon | 7 ++-- .github/workflows/release.yml | 14 +++++-- test/release-decision.test.ts | 50 ++++++++++++++++++++++--- 4 files changed, 61 insertions(+), 12 deletions(-) diff --git a/.agents/pm/history/pm-linear-yyr9.jsonl b/.agents/pm/history/pm-linear-yyr9.jsonl index 086bd18..a0f39f0 100644 --- a/.agents/pm/history/pm-linear-yyr9.jsonl +++ b/.agents/pm/history/pm-linear-yyr9.jsonl @@ -5,3 +5,5 @@ {"hash_algorithm":"sha256","ts":"2026-09-29T16:24:31.334Z","author":"codex","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6-sol","agent_model_source":"probe","agent_instance":"3b5e62ebb1f8ac82bae80594","agent_provenance":{"model":{"value":"gpt-6-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-09-29T16:24:31.334Z"},{"op":"add","path":"/metadata/acceptance_criteria","value":"A failed publish after a merged release resumes the committed package version and release tag on later runs; Ordinary unreleased source changes receive a new date-based version and an unrelated manual version bump fails closed; A disposable Git fixture executes the actual workflow decision for both paths; The package release gate and exact-head CI and review gates pass before merge"},{"op":"add","path":"/metadata/risk","value":"high"},{"op":"add","path":"/metadata/files","value":[{"path":".github/workflows/release.yml","scope":"project","note":"release decision and retry"},{"path":"test/release-decision.test.ts","scope":"project","note":"disposable Git acceptance"}]},{"op":"add","path":"/metadata/tests","value":[{"command":"node --test test/release-decision.test.ts","path":"test/release-decision.test.ts","scope":"project","provenance":{"author":"codex","created_at":"2026-09-29T16:24:31.307Z","source_kind":"local_mutation","source_ref":"fix/resume-untagged-linear-release-2026-09-29"}},{"command":"npm run release:check","path":"package.json","scope":"project","provenance":{"author":"codex","created_at":"2026-09-29T16:24:31.307Z","source_kind":"local_mutation","source_ref":"fix/resume-untagged-linear-release-2026-09-29"}}]}],"before_hash":"df26f2f77cbddfe8552e12ee76fa68b19878f504a44f7105208f3e7f739bb376","after_hash":"937e9836d22ff661e5201fdc1457bb80caa67e516bcbb03f9372ed7fd590a5f5","item_hash_version":3,"message":"Define retry safety and executable acceptance before PR","context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"8680df722a3eae724e4d00f4700617fe3b29eaa48a141809dfc27e5779cf4e10"} {"hash_algorithm":"sha256","ts":"2026-09-29T16:29:55.004Z","author":"codex","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6-sol","agent_model_source":"probe","agent_instance":"3b5e62ebb1f8ac82bae80594","agent_provenance":{"model":{"value":"gpt-6-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"note_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-09-29T16:29:55.004Z"},{"op":"add","path":"/metadata/notes","value":[{"created_at":"2026-09-29T16:29:55.004Z","author":"codex","text":"2026-09-29 candidate: the disposable Git fixture failed on the previous workflow (new date tag instead of committed v2000.01.02), then passed after the decision resumed that tag. It also verifies a fresh feature gets a new version, later source commits preserve the pending version, and an unrelated manual version bump is refused. npm run release:check passed 219/219 tests, docstring 49/49 declarations across 8 files, audit 0 production vulnerabilities, and attestation gate 1/1 publish path; coverage measured 99.29% lines, 96.68% branches, 100% functions, below the requested 100% floor. pm health --strict-exit fails on two pre-existing merge receipts for pm-linear-unfa and pm-linear-4yle; pm merge reconcile --dry-run reports no_receipt_set_proves_snapshot for both, so neither was forced. Rollback: revert the workflow decision change before any release run; no registry or hosted state was changed by this candidate."}]}],"before_hash":"937e9836d22ff661e5201fdc1457bb80caa67e516bcbb03f9372ed7fd590a5f5","after_hash":"85da9e6e4b909e6552dc46604946985c4c51df64dad9166e3c53030a40a40e17","item_hash_version":3,"message":"Record red-green fixture, exact gate evidence, and rollback","context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"e9caf785e89a30f1f0927d2c978605a0c9151a267dd3d081a815d183d76dca21"} {"hash_algorithm":"sha256","ts":"2026-09-29T16:31:15.876Z","author":"codex","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6-sol","agent_model_source":"probe","agent_instance":"3b5e62ebb1f8ac82bae80594","agent_provenance":{"model":{"value":"gpt-6-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"note_add","patch":[{"op":"add","path":"/metadata/notes/1","value":{"created_at":"2026-09-29T16:31:15.876Z","author":"codex","text":"Implementation PR: https://github.com/unbraind/pm-linear/pull/126 . Candidate code commit: https://github.com/unbraind/pm-linear/commit/45ee8b3faf309f1d671f6f8009bc003635bc73f8 . Awaiting exact-head CI and configured bot review; leave unmerged while coverage and local strict-health blockers remain."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-29T16:31:15.876Z"}],"before_hash":"85da9e6e4b909e6552dc46604946985c4c51df64dad9166e3c53030a40a40e17","after_hash":"3a7f0234c7585e43b50728f2bb70d36ef2f6449fddc8485014f5d428a4257861","item_hash_version":3,"message":"Link implementation PR and its gate state","context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"0d811e7b9025bdb40f19e72d1c7a66a2efbafa6a016bec0a197c31fe53c5f878"} +{"hash_algorithm":"sha256","ts":"2026-09-29T16:40:29.871Z","author":"codex","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6-sol","agent_model_source":"probe","agent_instance":"3b5e62ebb1f8ac82bae80594","agent_provenance":{"model":{"value":"gpt-6-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/acceptance_criteria","value":"A failed publish after a merged release resumes the committed package version and release tag on later runs; Ordinary unreleased source changes receive a new date-based version and an unrelated manual version bump fails closed; A disposable Git fixture executes the actual workflow decision for both paths; The package release gate and exact-head CI and review gates pass before merge; If main has advanced past the release commit, the workflow refuses to publish or tag that pending version from a different source revision"},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-29T16:40:29.871Z"}],"before_hash":"3a7f0234c7585e43b50728f2bb70d36ef2f6449fddc8485014f5d428a4257861","after_hash":"7532caa48ccad11df7fba1dd172a9515e0a9a6c65db2e191f02a1b2a0d470db3","item_hash_version":3,"message":"Add immutable-source retry safety criterion from review","context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"50555a858a42c9afa3449d450a80aafc14768c0e47816184da5c0b503502b152"} +{"hash_algorithm":"sha256","ts":"2026-09-29T16:40:30.460Z","author":"codex","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6-sol","agent_model_source":"probe","agent_instance":"3b5e62ebb1f8ac82bae80594","agent_provenance":{"model":{"value":"gpt-6-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"note_add","patch":[{"op":"add","path":"/metadata/notes/2","value":{"created_at":"2026-09-29T16:40:30.460Z","author":"codex","text":"Review reconciliation 2026-09-29: Greptile correctly found that a retry after later source commits could tag the newer HEAD for an already-published version. The candidate now refuses that state before emitting release outputs; automatic recovery across a newer main head requires a separate provenance-safe design. Greptile also identified two missing refusal cases; the disposable Git fixture now covers a conflicting tag on unrelated history and a release-subject/package-version mismatch. CodeRabbit correctly found tagger-clock order could select an older release; the workflow now selects the nearest reachable mainline release tag and a skewed-tagger-date fixture exercises it. None of these findings are treated as resolved until exact-head gates and re-review complete."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-29T16:40:30.460Z"}],"before_hash":"7532caa48ccad11df7fba1dd172a9515e0a9a6c65db2e191f02a1b2a0d470db3","after_hash":"f45d73bbe4fe1ecceadbdf8c25cbc536771a2ff844651db653c3930f8ec70f0f","item_hash_version":3,"message":"Record review findings, fixes, and remaining recovery limit","context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"6cf3ef2137bd8e6864ee09071d08ddf4397d6ff3f4ea7918d180c562ec91939c"} diff --git a/.agents/pm/issues/pm-linear-yyr9.toon b/.agents/pm/issues/pm-linear-yyr9.toon index febe1c5..677a9d6 100644 --- a/.agents/pm/issues/pm-linear-yyr9.toon +++ b/.agents/pm/issues/pm-linear-yyr9.toon @@ -6,15 +6,16 @@ status: in_progress priority: 2 tags: [] created_at: "2026-08-09T13:46:47.559Z" -updated_at: "2026-09-29T16:31:15.876Z" +updated_at: "2026-09-29T16:40:30.460Z" assignee: codex claim_principal: codex author: claude -acceptance_criteria: A failed publish after a merged release resumes the committed package version and release tag on later runs; Ordinary unreleased source changes receive a new date-based version and an unrelated manual version bump fails closed; A disposable Git fixture executes the actual workflow decision for both paths; The package release gate and exact-head CI and review gates pass before merge +acceptance_criteria: "A failed publish after a merged release resumes the committed package version and release tag on later runs; Ordinary unreleased source changes receive a new date-based version and an unrelated manual version bump fails closed; A disposable Git fixture executes the actual workflow decision for both paths; The package release gate and exact-head CI and review gates pass before merge; If main has advanced past the release commit, the workflow refuses to publish or tag that pending version from a different source revision" risk: high -notes[2]{created_at,author,text}: +notes[3]{created_at,author,text}: "2026-09-29T16:29:55.004Z",codex,"2026-09-29 candidate: the disposable Git fixture failed on the previous workflow (new date tag instead of committed v2000.01.02), then passed after the decision resumed that tag. It also verifies a fresh feature gets a new version, later source commits preserve the pending version, and an unrelated manual version bump is refused. npm run release:check passed 219/219 tests, docstring 49/49 declarations across 8 files, audit 0 production vulnerabilities, and attestation gate 1/1 publish path; coverage measured 99.29% lines, 96.68% branches, 100% functions, below the requested 100% floor. pm health --strict-exit fails on two pre-existing merge receipts for pm-linear-unfa and pm-linear-4yle; pm merge reconcile --dry-run reports no_receipt_set_proves_snapshot for both, so neither was forced. Rollback: revert the workflow decision change before any release run; no registry or hosted state was changed by this candidate." "2026-09-29T16:31:15.876Z",codex,"Implementation PR: https://github.com/unbraind/pm-linear/pull/126 . Candidate code commit: https://github.com/unbraind/pm-linear/commit/45ee8b3faf309f1d671f6f8009bc003635bc73f8 . Awaiting exact-head CI and configured bot review; leave unmerged while coverage and local strict-health blockers remain." + "2026-09-29T16:40:30.460Z",codex,"Review reconciliation 2026-09-29: Greptile correctly found that a retry after later source commits could tag the newer HEAD for an already-published version. The candidate now refuses that state before emitting release outputs; automatic recovery across a newer main head requires a separate provenance-safe design. Greptile also identified two missing refusal cases; the disposable Git fixture now covers a conflicting tag on unrelated history and a release-subject/package-version mismatch. CodeRabbit correctly found tagger-clock order could select an older release; the workflow now selects the nearest reachable mainline release tag and a skewed-tagger-date fixture exercises it. None of these findings are treated as resolved until exact-head gates and re-review complete." files[2]{path,scope,note}: .github/workflows/release.yml,project,release decision and retry test/release-decision.test.ts,project,disposable Git acceptance diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 30d0081..7032dd9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -78,7 +78,9 @@ jobs: run: | set -euo pipefail git fetch --force --tags - latest_tag="$(git tag --sort=-creatordate | head -n 1 || true)" + # Creator dates can be out of order. Release ancestry, not tagger + # clocks, determines which version this main commit follows. + latest_tag="$(git describe --first-parent --tags --match 'v[0-9]*' --abbrev=0 HEAD 2>/dev/null || true)" if [[ -n "$latest_tag" ]] && git diff --quiet "$latest_tag"..HEAD -- .; then echo "should_release=false" >> "$GITHUB_OUTPUT" echo "latest_tag=$latest_tag" >> "$GITHUB_OUTPUT" @@ -88,12 +90,14 @@ jobs: # A protected release PR can merge before npm accepts the artifact. # The next run must complete that committed version, even after the - # release date changes. Only a package.json last changed by the - # release commit is eligible; an unrelated manual bump fails closed. + # release date changes. The release commit must still be HEAD: a + # later commit could otherwise receive a tag for bytes npm already + # accepted from the earlier one. That case needs separate recovery. if [[ -n "$latest_tag" ]]; then current_version="$(node -p "require('./package.json').version")" tagged_version="$(git show "${latest_tag}:package.json" | node -p "JSON.parse(require('node:fs').readFileSync(0, 'utf8')).version")" if [[ "$current_version" != "$tagged_version" ]]; then + release_commit="$(git log -1 --format=%H -- package.json)" release_subject="$(git log -1 --format=%s -- package.json)" if ! printf '%s\n' "$release_subject" | grep -Eq '^Release pm-linear v[0-9]{4}\.[0-9]{2}\.[0-9]{2}(-[0-9]+)?$'; then echo "::error::package.json is ahead of ${latest_tag} without a recognized release commit. Resolve its version before releasing." @@ -112,6 +116,10 @@ jobs: echo "::error::Release commit ${tag} disagrees with package.json version ${current_version}." exit 1 fi + if [[ "$release_commit" != "$(git rev-parse HEAD)" ]]; then + echo "::error::main advanced past release commit ${release_commit}. Refusing to tag or publish newer source as ${tag}; recover the original release transaction before scheduling another." + exit 1 + fi if git show-ref --verify --quiet "refs/tags/${tag}"; then echo "::error::Release tag ${tag} already exists but ${latest_tag} was selected as latest. Resolve tag ordering before releasing." exit 1 diff --git a/test/release-decision.test.ts b/test/release-decision.test.ts index 1e33f5a..96ac1e6 100644 --- a/test/release-decision.test.ts +++ b/test/release-decision.test.ts @@ -40,6 +40,20 @@ test("a merged but untagged release keeps its committed version on retry", () => })); } + /** Assert the decision stops before writing release outputs on an unsafe state. */ + function refuse(message: RegExp): void { + const output = join(root, "output"); + writeFileSync(output, ""); + const result = spawnSync("bash", ["-c", script], { + cwd: project, + encoding: "utf8", + env: { ...process.env, GITHUB_OUTPUT: output, GITHUB_STEP_SUMMARY: join(root, "summary"), RELEASE_TIMEZONE: "Europe/Vienna" }, + }); + assert.notEqual(result.status, 0, "an unsafe release state must fail closed"); + assert.match(result.stdout, message); + assert.equal(readFileSync(output, "utf8"), "", "no release outputs may escape a refused decision"); + } + try { run("git", ["init", "-b", "main", project], root); run("git", ["config", "user.email", "fixture@example.invalid"]); @@ -69,22 +83,46 @@ test("a merged but untagged release keeps its committed version on retry", () => assert.equal(retry.npm_version, "2000.1.2"); assert.equal(retry.base_sha, run("git", ["rev-parse", "HEAD"])); + const unrelatedCommit = run("git", ["commit-tree", run("git", ["rev-parse", "HEAD^{tree}"]), "-m", "Unrelated release"]); + const conflictingTag = spawnSync("git", ["tag", "-a", "v2000.01.02", "-m", "Conflicting release tag", unrelatedCommit], { + cwd: project, + encoding: "utf8", + env: { ...process.env, GIT_COMMITTER_DATE: "2000-01-02T00:00:00Z" }, + }); + assert.equal(conflictingTag.status, 0, conflictingTag.stderr); + refuse(/already exists/); + run("git", ["tag", "-d", "v2000.01.02"]); + writeFileSync(join(project, "source.txt"), "third\n"); run("git", ["add", "source.txt"]); run("git", ["commit", "-m", "Document the failed release"]); - const laterRetry = decide(); - assert.equal(laterRetry.tag, "v2000.01.02", "later source commits must not mint another version"); + refuse(/main advanced past release commit/); writeFileSync(join(project, "package.json"), '{"name":"pm-linear","version":"2000.1.3"}\n'); run("git", ["add", "package.json"]); + run("git", ["commit", "-m", "Release pm-linear v2000.01.02"]); + refuse(/disagrees with package.json version/); + + writeFileSync(join(project, "package.json"), '{"name":"pm-linear","version":"2000.1.4"}\n'); + run("git", ["add", "package.json"]); run("git", ["commit", "-m", "Manually change the package version"]); - const refused = spawnSync("bash", ["-c", script], { + refuse(/without a recognized release commit/); + + const initialCommit = run("git", ["rev-list", "--max-parents=0", "HEAD"]); + const futureDatedOldTag = spawnSync("git", ["tag", "-a", "v1999.01.01", "-m", "Old release with a future tagger date", initialCommit], { cwd: project, encoding: "utf8", - env: { ...process.env, GITHUB_OUTPUT: join(root, "output"), GITHUB_STEP_SUMMARY: join(root, "summary"), RELEASE_TIMEZONE: "Europe/Vienna" }, + env: { ...process.env, GIT_COMMITTER_DATE: "2099-01-01T00:00:00Z" }, }); - assert.notEqual(refused.status, 0, "an unrelated version change needs an explicit resolution"); - assert.match(refused.stdout, /without a recognized release commit/); + assert.equal(futureDatedOldTag.status, 0, futureDatedOldTag.stderr); + run("git", ["tag", "v2000.01.04"]); + writeFileSync(join(project, "source.txt"), "fourth\n"); + run("git", ["add", "source.txt"]); + run("git", ["commit", "-m", "Add another feature"]); + assert.equal(run("git", ["tag", "--sort=-creatordate"]).split("\n")[0], "v1999.01.01"); + const afterClockSkew = decide(); + assert.equal(afterClockSkew.latest_tag, "v2000.01.04", "choose the nearest reachable release tag, not the newest tagger clock"); + assert.equal(afterClockSkew.should_release, "true"); } finally { rmSync(root, { recursive: true, force: true }); } From 5beea42b63492f92bdfbabe265f468cdf942dcdc Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Tue, 29 Sep 2026 18:50:31 +0200 Subject: [PATCH 4/6] Mark pm-linear release retry candidate blocked after review Record exact-head Greptile and CI evidence, the free review limits, coverage shortfall, and unprovable local merge receipts. Release the active claim while PR #126 remains unmerged and no version is published. --- .agents/pm/history/pm-linear-yyr9.jsonl | 3 +++ .agents/pm/issues/pm-linear-yyr9.toon | 10 +++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.agents/pm/history/pm-linear-yyr9.jsonl b/.agents/pm/history/pm-linear-yyr9.jsonl index a0f39f0..46a25fa 100644 --- a/.agents/pm/history/pm-linear-yyr9.jsonl +++ b/.agents/pm/history/pm-linear-yyr9.jsonl @@ -7,3 +7,6 @@ {"hash_algorithm":"sha256","ts":"2026-09-29T16:31:15.876Z","author":"codex","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6-sol","agent_model_source":"probe","agent_instance":"3b5e62ebb1f8ac82bae80594","agent_provenance":{"model":{"value":"gpt-6-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"note_add","patch":[{"op":"add","path":"/metadata/notes/1","value":{"created_at":"2026-09-29T16:31:15.876Z","author":"codex","text":"Implementation PR: https://github.com/unbraind/pm-linear/pull/126 . Candidate code commit: https://github.com/unbraind/pm-linear/commit/45ee8b3faf309f1d671f6f8009bc003635bc73f8 . Awaiting exact-head CI and configured bot review; leave unmerged while coverage and local strict-health blockers remain."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-29T16:31:15.876Z"}],"before_hash":"85da9e6e4b909e6552dc46604946985c4c51df64dad9166e3c53030a40a40e17","after_hash":"3a7f0234c7585e43b50728f2bb70d36ef2f6449fddc8485014f5d428a4257861","item_hash_version":3,"message":"Link implementation PR and its gate state","context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"0d811e7b9025bdb40f19e72d1c7a66a2efbafa6a016bec0a197c31fe53c5f878"} {"hash_algorithm":"sha256","ts":"2026-09-29T16:40:29.871Z","author":"codex","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6-sol","agent_model_source":"probe","agent_instance":"3b5e62ebb1f8ac82bae80594","agent_provenance":{"model":{"value":"gpt-6-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/acceptance_criteria","value":"A failed publish after a merged release resumes the committed package version and release tag on later runs; Ordinary unreleased source changes receive a new date-based version and an unrelated manual version bump fails closed; A disposable Git fixture executes the actual workflow decision for both paths; The package release gate and exact-head CI and review gates pass before merge; If main has advanced past the release commit, the workflow refuses to publish or tag that pending version from a different source revision"},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-29T16:40:29.871Z"}],"before_hash":"3a7f0234c7585e43b50728f2bb70d36ef2f6449fddc8485014f5d428a4257861","after_hash":"7532caa48ccad11df7fba1dd172a9515e0a9a6c65db2e191f02a1b2a0d470db3","item_hash_version":3,"message":"Add immutable-source retry safety criterion from review","context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"50555a858a42c9afa3449d450a80aafc14768c0e47816184da5c0b503502b152"} {"hash_algorithm":"sha256","ts":"2026-09-29T16:40:30.460Z","author":"codex","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6-sol","agent_model_source":"probe","agent_instance":"3b5e62ebb1f8ac82bae80594","agent_provenance":{"model":{"value":"gpt-6-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"note_add","patch":[{"op":"add","path":"/metadata/notes/2","value":{"created_at":"2026-09-29T16:40:30.460Z","author":"codex","text":"Review reconciliation 2026-09-29: Greptile correctly found that a retry after later source commits could tag the newer HEAD for an already-published version. The candidate now refuses that state before emitting release outputs; automatic recovery across a newer main head requires a separate provenance-safe design. Greptile also identified two missing refusal cases; the disposable Git fixture now covers a conflicting tag on unrelated history and a release-subject/package-version mismatch. CodeRabbit correctly found tagger-clock order could select an older release; the workflow now selects the nearest reachable mainline release tag and a skewed-tagger-date fixture exercises it. None of these findings are treated as resolved until exact-head gates and re-review complete."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-29T16:40:30.460Z"}],"before_hash":"7532caa48ccad11df7fba1dd172a9515e0a9a6c65db2e191f02a1b2a0d470db3","after_hash":"f45d73bbe4fe1ecceadbdf8c25cbc536771a2ff844651db653c3930f8ec70f0f","item_hash_version":3,"message":"Record review findings, fixes, and remaining recovery limit","context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"6cf3ef2137bd8e6864ee09071d08ddf4397d6ff3f4ea7918d180c562ec91939c"} +{"hash_algorithm":"sha256","ts":"2026-09-29T16:50:20.378Z","author":"codex","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6-sol","agent_model_source":"probe","agent_instance":"3b5e62ebb1f8ac82bae80594","agent_provenance":{"model":{"value":"gpt-6-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"note_add","patch":[{"op":"add","path":"/metadata/notes/3","value":{"created_at":"2026-09-29T16:50:20.378Z","author":"codex","text":"Final review state for 4f71467426170c709240c2380e1ca35e943eaca9: Node 22 and 26 CI, CodeQL, local release:check 219/219, and Greptile re-review passed; Greptile explicitly marked both earlier findings addressed. CodeRabbit acknowledged the tag-selection fix but its requested full review was rate-limited for 47 minutes, and Sourcery reported an exhausted free weekly review budget. Neither is counted as substantive final-head approval. Measured source coverage remains 99.29% lines, 96.68% branches, 100% functions, with no separate statement metric. Local strict pm health still fails on two unprovable pre-existing merge receipts. PR #126 remains unmerged and no release or deployment occurred."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-29T16:50:20.378Z"}],"before_hash":"f45d73bbe4fe1ecceadbdf8c25cbc536771a2ff844651db653c3930f8ec70f0f","after_hash":"5d92f6832a1676d5c2c27980baaff094abd94b264790985e7a353e27536bffb2","item_hash_version":3,"message":"Record exact reviewed head and unresolved gates","context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"8ebc693cf24689a9cb7a5b138433019f4091f1801114df608f4255ddd7d627e8"} +{"hash_algorithm":"sha256","ts":"2026-09-29T16:50:20.889Z","author":"codex","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6-sol","agent_model_source":"probe","agent_instance":"3b5e62ebb1f8ac82bae80594","agent_provenance":{"model":{"value":"gpt-6-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-09-29T16:50:20.889Z"},{"op":"replace","path":"/metadata/status","value":"blocked"},{"op":"add","path":"/metadata/blocked_reason","value":"Unmerged PR #126 awaits 100% coverage, resolution of two unprovable local merge receipts, and substantive configured final-head reviews; safe automatic recovery after newer main commits remains open."}],"before_hash":"5d92f6832a1676d5c2c27980baaff094abd94b264790985e7a353e27536bffb2","after_hash":"64afa58cef89c31d9832335f43506ef330798a83516543183df419cffff03c7b","item_hash_version":3,"message":"Move candidate to blocked while publication gates remain unmet","context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"b244a76bbd648c32393850a4bb37de591979714cf7a33b88995319e3226682aa"} +{"hash_algorithm":"sha256","ts":"2026-09-29T16:50:21.404Z","author":"codex","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6-sol","agent_model_source":"probe","agent_instance":"3b5e62ebb1f8ac82bae80594","agent_provenance":{"model":{"value":"gpt-6-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"release","patch":[{"op":"remove","path":"/metadata/claim_principal"},{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-29T16:50:21.404Z"}],"before_hash":"64afa58cef89c31d9832335f43506ef330798a83516543183df419cffff03c7b","after_hash":"1f344a2ecdb4e7da04f42a4abfe59dfa4c051e4438aa92c3e7cfa19492b427e1","item_hash_version":3,"message":"Validation round ended; no active claim while gates are blocked","context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"ba2f8e98e857472e3bd7aab502865b8ef270bce434507e469cb1dd568ab9eef9"} diff --git a/.agents/pm/issues/pm-linear-yyr9.toon b/.agents/pm/issues/pm-linear-yyr9.toon index 677a9d6..10109b3 100644 --- a/.agents/pm/issues/pm-linear-yyr9.toon +++ b/.agents/pm/issues/pm-linear-yyr9.toon @@ -2,20 +2,20 @@ id: pm-linear-yyr9 title: A release that merges but fails to publish is abandoned by the next day's run description: "Decide release computes the version from today's date and the existing tags, and the tag is pushed only after npm publish succeeds. If the protected release PR merges and publication then fails, main carries release metadata for a version that was never tagged or published. The next day's run sees the old latest tag, computes a new version from the new date, and commits it over the merged metadata, so the earlier version is silently skipped on npm. Needs a guard in Decide release that detects a committed-but-untagged version and resumes it rather than computing a new one." type: Issue -status: in_progress +status: blocked priority: 2 tags: [] created_at: "2026-08-09T13:46:47.559Z" -updated_at: "2026-09-29T16:40:30.460Z" -assignee: codex -claim_principal: codex +updated_at: "2026-09-29T16:50:21.404Z" author: claude acceptance_criteria: "A failed publish after a merged release resumes the committed package version and release tag on later runs; Ordinary unreleased source changes receive a new date-based version and an unrelated manual version bump fails closed; A disposable Git fixture executes the actual workflow decision for both paths; The package release gate and exact-head CI and review gates pass before merge; If main has advanced past the release commit, the workflow refuses to publish or tag that pending version from a different source revision" risk: high -notes[3]{created_at,author,text}: +blocked_reason: "Unmerged PR #126 awaits 100% coverage, resolution of two unprovable local merge receipts, and substantive configured final-head reviews; safe automatic recovery after newer main commits remains open." +notes[4]{created_at,author,text}: "2026-09-29T16:29:55.004Z",codex,"2026-09-29 candidate: the disposable Git fixture failed on the previous workflow (new date tag instead of committed v2000.01.02), then passed after the decision resumed that tag. It also verifies a fresh feature gets a new version, later source commits preserve the pending version, and an unrelated manual version bump is refused. npm run release:check passed 219/219 tests, docstring 49/49 declarations across 8 files, audit 0 production vulnerabilities, and attestation gate 1/1 publish path; coverage measured 99.29% lines, 96.68% branches, 100% functions, below the requested 100% floor. pm health --strict-exit fails on two pre-existing merge receipts for pm-linear-unfa and pm-linear-4yle; pm merge reconcile --dry-run reports no_receipt_set_proves_snapshot for both, so neither was forced. Rollback: revert the workflow decision change before any release run; no registry or hosted state was changed by this candidate." "2026-09-29T16:31:15.876Z",codex,"Implementation PR: https://github.com/unbraind/pm-linear/pull/126 . Candidate code commit: https://github.com/unbraind/pm-linear/commit/45ee8b3faf309f1d671f6f8009bc003635bc73f8 . Awaiting exact-head CI and configured bot review; leave unmerged while coverage and local strict-health blockers remain." "2026-09-29T16:40:30.460Z",codex,"Review reconciliation 2026-09-29: Greptile correctly found that a retry after later source commits could tag the newer HEAD for an already-published version. The candidate now refuses that state before emitting release outputs; automatic recovery across a newer main head requires a separate provenance-safe design. Greptile also identified two missing refusal cases; the disposable Git fixture now covers a conflicting tag on unrelated history and a release-subject/package-version mismatch. CodeRabbit correctly found tagger-clock order could select an older release; the workflow now selects the nearest reachable mainline release tag and a skewed-tagger-date fixture exercises it. None of these findings are treated as resolved until exact-head gates and re-review complete." + "2026-09-29T16:50:20.378Z",codex,"Final review state for 4f71467426170c709240c2380e1ca35e943eaca9: Node 22 and 26 CI, CodeQL, local release:check 219/219, and Greptile re-review passed; Greptile explicitly marked both earlier findings addressed. CodeRabbit acknowledged the tag-selection fix but its requested full review was rate-limited for 47 minutes, and Sourcery reported an exhausted free weekly review budget. Neither is counted as substantive final-head approval. Measured source coverage remains 99.29% lines, 96.68% branches, 100% functions, with no separate statement metric. Local strict pm health still fails on two unprovable pre-existing merge receipts. PR #126 remains unmerged and no release or deployment occurred." files[2]{path,scope,note}: .github/workflows/release.yml,project,release decision and retry test/release-decision.test.ts,project,disposable Git acceptance From 1b684c985cda1187627887fbbe305b2c9cf5bcf3 Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Sat, 3 Oct 2026 00:25:28 +0200 Subject: [PATCH 5/6] docs(linear): record full gate and remaining merge prerequisites --- .agents/pm/history/pm-linear-yyr9.jsonl | 9 +++++ .agents/pm/issues/pm-linear-yyr9.toon | 46 +++++++++++++++++++++---- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/.agents/pm/history/pm-linear-yyr9.jsonl b/.agents/pm/history/pm-linear-yyr9.jsonl index 46a25fa..b6c25a9 100644 --- a/.agents/pm/history/pm-linear-yyr9.jsonl +++ b/.agents/pm/history/pm-linear-yyr9.jsonl @@ -10,3 +10,12 @@ {"hash_algorithm":"sha256","ts":"2026-09-29T16:50:20.378Z","author":"codex","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6-sol","agent_model_source":"probe","agent_instance":"3b5e62ebb1f8ac82bae80594","agent_provenance":{"model":{"value":"gpt-6-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"note_add","patch":[{"op":"add","path":"/metadata/notes/3","value":{"created_at":"2026-09-29T16:50:20.378Z","author":"codex","text":"Final review state for 4f71467426170c709240c2380e1ca35e943eaca9: Node 22 and 26 CI, CodeQL, local release:check 219/219, and Greptile re-review passed; Greptile explicitly marked both earlier findings addressed. CodeRabbit acknowledged the tag-selection fix but its requested full review was rate-limited for 47 minutes, and Sourcery reported an exhausted free weekly review budget. Neither is counted as substantive final-head approval. Measured source coverage remains 99.29% lines, 96.68% branches, 100% functions, with no separate statement metric. Local strict pm health still fails on two unprovable pre-existing merge receipts. PR #126 remains unmerged and no release or deployment occurred."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-29T16:50:20.378Z"}],"before_hash":"f45d73bbe4fe1ecceadbdf8c25cbc536771a2ff844651db653c3930f8ec70f0f","after_hash":"5d92f6832a1676d5c2c27980baaff094abd94b264790985e7a353e27536bffb2","item_hash_version":3,"message":"Record exact reviewed head and unresolved gates","context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"8ebc693cf24689a9cb7a5b138433019f4091f1801114df608f4255ddd7d627e8"} {"hash_algorithm":"sha256","ts":"2026-09-29T16:50:20.889Z","author":"codex","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6-sol","agent_model_source":"probe","agent_instance":"3b5e62ebb1f8ac82bae80594","agent_provenance":{"model":{"value":"gpt-6-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-09-29T16:50:20.889Z"},{"op":"replace","path":"/metadata/status","value":"blocked"},{"op":"add","path":"/metadata/blocked_reason","value":"Unmerged PR #126 awaits 100% coverage, resolution of two unprovable local merge receipts, and substantive configured final-head reviews; safe automatic recovery after newer main commits remains open."}],"before_hash":"5d92f6832a1676d5c2c27980baaff094abd94b264790985e7a353e27536bffb2","after_hash":"64afa58cef89c31d9832335f43506ef330798a83516543183df419cffff03c7b","item_hash_version":3,"message":"Move candidate to blocked while publication gates remain unmet","context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"b244a76bbd648c32393850a4bb37de591979714cf7a33b88995319e3226682aa"} {"hash_algorithm":"sha256","ts":"2026-09-29T16:50:21.404Z","author":"codex","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6-sol","agent_model_source":"probe","agent_instance":"3b5e62ebb1f8ac82bae80594","agent_provenance":{"model":{"value":"gpt-6-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"release","patch":[{"op":"remove","path":"/metadata/claim_principal"},{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-29T16:50:21.404Z"}],"before_hash":"64afa58cef89c31d9832335f43506ef330798a83516543183df419cffff03c7b","after_hash":"1f344a2ecdb4e7da04f42a4abfe59dfa4c051e4438aa92c3e7cfa19492b427e1","item_hash_version":3,"message":"Validation round ended; no active claim while gates are blocked","context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"ba2f8e98e857472e3bd7aab502865b8ef270bce434507e469cb1dd568ab9eef9"} +{"hash_algorithm":"sha256","ts":"2026-10-02T22:10:02.752Z","author":"codex-sol","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"b2389d19e26c4d93974a8087","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-02T22:10:02.752Z"},{"op":"add","path":"/metadata/assignee","value":"codex-sol"},{"op":"add","path":"/metadata/claim_principal","value":"codex-sol"}],"before_hash":"1f344a2ecdb4e7da04f42a4abfe59dfa4c051e4438aa92c3e7cfa19492b427e1","after_hash":"f4ec5ba72dfc3b7ca9699c1797a19c45254b1400de99c5c6be6a940af8e22a29","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"11d9960b181c15add12226644be9a425b8eb4e8415d499ebe56a6deabad65187"} +{"hash_algorithm":"sha256","ts":"2026-10-02T22:10:04.678Z","author":"codex-sol","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"b2389d19e26c4d93974a8087","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-02T22:10:04.678Z"},{"op":"replace","path":"/metadata/status","value":"in_progress"}],"before_hash":"f4ec5ba72dfc3b7ca9699c1797a19c45254b1400de99c5c6be6a940af8e22a29","after_hash":"48835ca43580a4cb4ea943ac25304e0315035fcf8a201783f3ab375c9cac089f","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"d44ca031e997b3fdd7d4dc7666c355cb11bf38c9a4468feed2f212a62f3fbcfa"} +{"hash_algorithm":"sha256","ts":"2026-10-02T22:10:06.066Z","author":"codex-sol","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"b2389d19e26c4d93974a8087","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-02T22:10:06.066Z"},{"op":"add","path":"/metadata/comments","value":[{"created_at":"2026-10-02T22:10:06.066Z","author":"codex-sol","text":"Restart by codex-sol: preserved remote PR #126 and all existing bot replies/reactions were inspected. Branch is already current with origin/main. No new actionable unresolved inline finding is present. Retain documented coverage/health/publication blockers; rerun unchanged full CI gate with the host-wide flock. No merge, publish or item closure."}]}],"before_hash":"48835ca43580a4cb4ea943ac25304e0315035fcf8a201783f3ab375c9cac089f","after_hash":"26b893ab6c858ca38cefcd583cbb7ec7c60980be6935c065a86761b423ecee1f","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"039e13e8e41393421723bb05da6fa78e2f9a06d88725cb89a957bf7bd0f2cfff"} +{"hash_algorithm":"sha256","ts":"2026-10-02T22:10:07.600Z","author":"codex-sol","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"b2389d19e26c4d93974a8087","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"files_add","patch":[{"op":"remove","path":"/metadata/files/1/note"},{"op":"replace","path":"/metadata/files/1/path","value":"package.json"},{"op":"add","path":"/metadata/files/2","value":{"path":"test/release-decision.test.ts","scope":"project","note":"disposable Git acceptance"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-02T22:10:07.600Z"}],"before_hash":"26b893ab6c858ca38cefcd583cbb7ec7c60980be6935c065a86761b423ecee1f","after_hash":"839fb4bf10cee1d5a4090c54f99531c952ef991fe7dc36860253cd1fda504176","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"487aa6bb5780dd100f8441b46764811bb48f4a53cdf41e06b318cc8f42770767"} +{"hash_algorithm":"sha256","ts":"2026-10-02T22:10:08.649Z","author":"codex-sol","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"b2389d19e26c4d93974a8087","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"docs_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-02T22:10:08.649Z"},{"op":"add","path":"/metadata/docs","value":[{"path":"README.md","scope":"project"}]}],"before_hash":"839fb4bf10cee1d5a4090c54f99531c952ef991fe7dc36860253cd1fda504176","after_hash":"f24f9d70ef26929610ecd6ebca595781c9acf5bacd0b0c4b6e7e89fc24a5944e","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"d11f46c16a56ddea6321a18c04515418aa31cd9f86097e8d26e61242506e8398"} +{"hash_algorithm":"sha256","ts":"2026-10-02T22:10:09.313Z","author":"codex-sol","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"b2389d19e26c4d93974a8087","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"tests_add","patch":[{"op":"add","path":"/metadata/tests/2","value":{"command":"node --test test/release-decision.test.ts","scope":"project","provenance":{"author":"codex-sol","created_at":"2026-10-02T22:10:09.276Z","source_kind":"local_mutation","source_ref":"land-restart/pm-linear-126"}}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-02T22:10:09.313Z"}],"before_hash":"f24f9d70ef26929610ecd6ebca595781c9acf5bacd0b0c4b6e7e89fc24a5944e","after_hash":"984b27d32021fa0aa95894e337cfc2a7427e52fd2db787585c510d0d81757e83","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"68079cc75e551755748f0e4a94ef95d234066af9dbb49e724e7057a979c1e8c6"} +{"hash_algorithm":"sha256","ts":"2026-10-02T22:25:25.986Z","author":"codex-sol","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"b2389d19e26c4d93974a8087","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/1","value":{"created_at":"2026-10-02T22:25:25.986Z","author":"codex-sol","text":"Restart: locked npm ci and release:check pass 219/219 tests, zero skips; configured index.ts coverage is 99.29% lines, 96.68% branches, 100% functions, with no separate statements dimension. PM-linked release-decision fixture tests pass, including later-head refusal, version mismatch, occupied tag, and nearest first-parent tag with skewed dates. Current worktree strict health with merge drivers passes; this fresh worktree lacks earlier clone-local merge receipts, so it does not reconcile the previously recorded pm-linear-unfa/4yle evidence. Coverage pm-linear-xw01 and substantive exact-head review remain blockers. No merge, publish, or close; release claim for orchestrator."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-02T22:25:25.986Z"}],"before_hash":"984b27d32021fa0aa95894e337cfc2a7427e52fd2db787585c510d0d81757e83","after_hash":"a2a082f17691567a947acbc0dfcbd1973c3137e7580a19b0c8f73bfb5075d774","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"b3bceffff71815858c935570d197e1a5ee44ad329d115e73b7b89331dc8c8982"} +{"hash_algorithm":"sha256","ts":"2026-10-02T22:25:27.148Z","author":"codex-sol","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"b2389d19e26c4d93974a8087","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-02T22:25:27.148Z"},{"op":"replace","path":"/metadata/status","value":"blocked"}],"before_hash":"a2a082f17691567a947acbc0dfcbd1973c3137e7580a19b0c8f73bfb5075d774","after_hash":"ff7755e77c0a43f1fd5c4066b56e305e1ad33101c1fe50cccbf769c6519d6ede","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"56fa2c412c66bef16630794c9af7c31056823c2ccf3c41446a47c3d50fc7d80c"} +{"hash_algorithm":"sha256","ts":"2026-10-02T22:25:28.099Z","author":"codex-sol","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"b2389d19e26c4d93974a8087","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"release","patch":[{"op":"remove","path":"/metadata/claim_principal"},{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-02T22:25:28.099Z"}],"before_hash":"ff7755e77c0a43f1fd5c4066b56e305e1ad33101c1fe50cccbf769c6519d6ede","after_hash":"9cc43f2fedf0e3ad1fd0f09c8f631aeaa969983b9215743e7871f89d58f81faa","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"d9090d36b9f60ca88135ec1ec543b04ab0eb5118252246edee5d2758e747d70a"} diff --git a/.agents/pm/issues/pm-linear-yyr9.toon b/.agents/pm/issues/pm-linear-yyr9.toon index 10109b3..fa6d6de 100644 --- a/.agents/pm/issues/pm-linear-yyr9.toon +++ b/.agents/pm/issues/pm-linear-yyr9.toon @@ -6,20 +6,52 @@ status: blocked priority: 2 tags: [] created_at: "2026-08-09T13:46:47.559Z" -updated_at: "2026-09-29T16:50:21.404Z" +updated_at: "2026-10-02T22:25:28.099Z" author: claude acceptance_criteria: "A failed publish after a merged release resumes the committed package version and release tag on later runs; Ordinary unreleased source changes receive a new date-based version and an unrelated manual version bump fails closed; A disposable Git fixture executes the actual workflow decision for both paths; The package release gate and exact-head CI and review gates pass before merge; If main has advanced past the release commit, the workflow refuses to publish or tag that pending version from a different source revision" risk: high blocked_reason: "Unmerged PR #126 awaits 100% coverage, resolution of two unprovable local merge receipts, and substantive configured final-head reviews; safe automatic recovery after newer main commits remains open." +comments[2]{created_at,author,text}: + "2026-10-02T22:10:06.066Z",codex-sol,"Restart by codex-sol: preserved remote PR #126 and all existing bot replies/reactions were inspected. Branch is already current with origin/main. No new actionable unresolved inline finding is present. Retain documented coverage/health/publication blockers; rerun unchanged full CI gate with the host-wide flock. No merge, publish or item closure." + "2026-10-02T22:25:25.986Z",codex-sol,"Restart: locked npm ci and release:check pass 219/219 tests, zero skips; configured index.ts coverage is 99.29% lines, 96.68% branches, 100% functions, with no separate statements dimension. PM-linked release-decision fixture tests pass, including later-head refusal, version mismatch, occupied tag, and nearest first-parent tag with skewed dates. Current worktree strict health with merge drivers passes; this fresh worktree lacks earlier clone-local merge receipts, so it does not reconcile the previously recorded pm-linear-unfa/4yle evidence. Coverage pm-linear-xw01 and substantive exact-head review remain blockers. No merge, publish, or close; release claim for orchestrator." notes[4]{created_at,author,text}: "2026-09-29T16:29:55.004Z",codex,"2026-09-29 candidate: the disposable Git fixture failed on the previous workflow (new date tag instead of committed v2000.01.02), then passed after the decision resumed that tag. It also verifies a fresh feature gets a new version, later source commits preserve the pending version, and an unrelated manual version bump is refused. npm run release:check passed 219/219 tests, docstring 49/49 declarations across 8 files, audit 0 production vulnerabilities, and attestation gate 1/1 publish path; coverage measured 99.29% lines, 96.68% branches, 100% functions, below the requested 100% floor. pm health --strict-exit fails on two pre-existing merge receipts for pm-linear-unfa and pm-linear-4yle; pm merge reconcile --dry-run reports no_receipt_set_proves_snapshot for both, so neither was forced. Rollback: revert the workflow decision change before any release run; no registry or hosted state was changed by this candidate." "2026-09-29T16:31:15.876Z",codex,"Implementation PR: https://github.com/unbraind/pm-linear/pull/126 . Candidate code commit: https://github.com/unbraind/pm-linear/commit/45ee8b3faf309f1d671f6f8009bc003635bc73f8 . Awaiting exact-head CI and configured bot review; leave unmerged while coverage and local strict-health blockers remain." "2026-09-29T16:40:30.460Z",codex,"Review reconciliation 2026-09-29: Greptile correctly found that a retry after later source commits could tag the newer HEAD for an already-published version. The candidate now refuses that state before emitting release outputs; automatic recovery across a newer main head requires a separate provenance-safe design. Greptile also identified two missing refusal cases; the disposable Git fixture now covers a conflicting tag on unrelated history and a release-subject/package-version mismatch. CodeRabbit correctly found tagger-clock order could select an older release; the workflow now selects the nearest reachable mainline release tag and a skewed-tagger-date fixture exercises it. None of these findings are treated as resolved until exact-head gates and re-review complete." "2026-09-29T16:50:20.378Z",codex,"Final review state for 4f71467426170c709240c2380e1ca35e943eaca9: Node 22 and 26 CI, CodeQL, local release:check 219/219, and Greptile re-review passed; Greptile explicitly marked both earlier findings addressed. CodeRabbit acknowledged the tag-selection fix but its requested full review was rate-limited for 47 minutes, and Sourcery reported an exhausted free weekly review budget. Neither is counted as substantive final-head approval. Measured source coverage remains 99.29% lines, 96.68% branches, 100% functions, with no separate statement metric. Local strict pm health still fails on two unprovable pre-existing merge receipts. PR #126 remains unmerged and no release or deployment occurred." -files[2]{path,scope,note}: - .github/workflows/release.yml,project,release decision and retry - test/release-decision.test.ts,project,disposable Git acceptance -tests[2]{command,path,scope,provenance{author,created_at,source_kind,source_ref}}: - node --test test/release-decision.test.ts,test/release-decision.test.ts,project,codex,"2026-09-29T16:24:31.307Z",local_mutation,fix/resume-untagged-linear-release-2026-09-29 - "npm run release:check",package.json,project,codex,"2026-09-29T16:24:31.307Z",local_mutation,fix/resume-untagged-linear-release-2026-09-29 +files[3]: + - path: .github/workflows/release.yml + scope: project + note: release decision and retry + - path: package.json + scope: project + - path: test/release-decision.test.ts + scope: project + note: disposable Git acceptance +tests[3]: + - command: node --test test/release-decision.test.ts + path: test/release-decision.test.ts + scope: project + provenance: + author: codex + created_at: "2026-09-29T16:24:31.307Z" + source_kind: local_mutation + source_ref: fix/resume-untagged-linear-release-2026-09-29 + - command: "npm run release:check" + path: package.json + scope: project + provenance: + author: codex + created_at: "2026-09-29T16:24:31.307Z" + source_kind: local_mutation + source_ref: fix/resume-untagged-linear-release-2026-09-29 + - command: node --test test/release-decision.test.ts + scope: project + provenance: + author: codex-sol + created_at: "2026-10-02T22:10:09.276Z" + source_kind: local_mutation + source_ref: land-restart/pm-linear-126 +docs[1]{path,scope}: + README.md,project body: "" From a5d03a940e011f191d8e6d4559abcdc2ea2afcf2 Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Sat, 3 Oct 2026 00:59:52 +0200 Subject: [PATCH 6/6] fix(linear): match release repository literally and test annotated retry --- .agents/pm/history/pm-linear-yyr9.jsonl | 6 ++++++ .agents/pm/issues/pm-linear-yyr9.toon | 6 ++++-- .github/workflows/release.yml | 5 +++-- test/release-decision.test.ts | 27 ++++++++++++++++++++----- 4 files changed, 35 insertions(+), 9 deletions(-) diff --git a/.agents/pm/history/pm-linear-yyr9.jsonl b/.agents/pm/history/pm-linear-yyr9.jsonl index b6c25a9..f81709e 100644 --- a/.agents/pm/history/pm-linear-yyr9.jsonl +++ b/.agents/pm/history/pm-linear-yyr9.jsonl @@ -19,3 +19,9 @@ {"hash_algorithm":"sha256","ts":"2026-10-02T22:25:25.986Z","author":"codex-sol","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"b2389d19e26c4d93974a8087","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/1","value":{"created_at":"2026-10-02T22:25:25.986Z","author":"codex-sol","text":"Restart: locked npm ci and release:check pass 219/219 tests, zero skips; configured index.ts coverage is 99.29% lines, 96.68% branches, 100% functions, with no separate statements dimension. PM-linked release-decision fixture tests pass, including later-head refusal, version mismatch, occupied tag, and nearest first-parent tag with skewed dates. Current worktree strict health with merge drivers passes; this fresh worktree lacks earlier clone-local merge receipts, so it does not reconcile the previously recorded pm-linear-unfa/4yle evidence. Coverage pm-linear-xw01 and substantive exact-head review remain blockers. No merge, publish, or close; release claim for orchestrator."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-02T22:25:25.986Z"}],"before_hash":"984b27d32021fa0aa95894e337cfc2a7427e52fd2db787585c510d0d81757e83","after_hash":"a2a082f17691567a947acbc0dfcbd1973c3137e7580a19b0c8f73bfb5075d774","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"b3bceffff71815858c935570d197e1a5ee44ad329d115e73b7b89331dc8c8982"} {"hash_algorithm":"sha256","ts":"2026-10-02T22:25:27.148Z","author":"codex-sol","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"b2389d19e26c4d93974a8087","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-02T22:25:27.148Z"},{"op":"replace","path":"/metadata/status","value":"blocked"}],"before_hash":"a2a082f17691567a947acbc0dfcbd1973c3137e7580a19b0c8f73bfb5075d774","after_hash":"ff7755e77c0a43f1fd5c4066b56e305e1ad33101c1fe50cccbf769c6519d6ede","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"56fa2c412c66bef16630794c9af7c31056823c2ccf3c41446a47c3d50fc7d80c"} {"hash_algorithm":"sha256","ts":"2026-10-02T22:25:28.099Z","author":"codex-sol","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"b2389d19e26c4d93974a8087","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"release","patch":[{"op":"remove","path":"/metadata/claim_principal"},{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-02T22:25:28.099Z"}],"before_hash":"ff7755e77c0a43f1fd5c4066b56e305e1ad33101c1fe50cccbf769c6519d6ede","after_hash":"9cc43f2fedf0e3ad1fd0f09c8f631aeaa969983b9215743e7871f89d58f81faa","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"d9090d36b9f60ca88135ec1ec543b04ab0eb5118252246edee5d2758e747d70a"} +{"hash_algorithm":"sha256","ts":"2026-10-02T22:41:24.400Z","author":"codex-sol","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"b2389d19e26c4d93974a8087","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-02T22:41:24.400Z"},{"op":"add","path":"/metadata/assignee","value":"codex-sol"},{"op":"add","path":"/metadata/claim_principal","value":"codex-sol"}],"before_hash":"9cc43f2fedf0e3ad1fd0f09c8f631aeaa969983b9215743e7871f89d58f81faa","after_hash":"4a9378b1f72586938b69187952958c2f3721f2ea335d08ec50d80f7156777e19","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"03b5e50cddaeb15827db24f7cc8e39e6df57eb8d20b8c19b7ef68c4169d9cba3"} +{"hash_algorithm":"sha256","ts":"2026-10-02T22:41:25.069Z","author":"codex-sol","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"b2389d19e26c4d93974a8087","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-02T22:41:25.069Z"},{"op":"replace","path":"/metadata/status","value":"in_progress"}],"before_hash":"4a9378b1f72586938b69187952958c2f3721f2ea335d08ec50d80f7156777e19","after_hash":"14d2e806499340d32ffb21673e85cad452874c617b56cdbb078c79304a997ce5","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"fb858ebe3f8e3417638f831cc08113d365002176ee76e6c35c2ae3ebcf2eb689"} +{"hash_algorithm":"sha256","ts":"2026-10-02T22:41:25.559Z","author":"codex-sol","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"b2389d19e26c4d93974a8087","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/2","value":{"created_at":"2026-10-02T22:41:25.559Z","author":"codex-sol","text":"New exact-head feedback: CodeRabbit 4170348351 / Cubic 4170382743 request mainline annotated-tag coverage (valid); Cubic 4170382735 finds hardcoded repository name inconsistent with the release writer (valid). Add red fixtures and fix literal repository-prefix matching. Cubic 4170382715 asks to search older release commits after package.json advances; refuse because same-HEAD-only recovery is deliberate, and add a dependency-after-release refusal regression before outputs."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-02T22:41:25.559Z"}],"before_hash":"14d2e806499340d32ffb21673e85cad452874c617b56cdbb078c79304a997ce5","after_hash":"648074fa780d35d5dd717c98b9cb65fe40a9df7dd5a588e3d695b807a3f89582","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"77045e6d5e5390179d3e046b2bd36bef39b5027133fa66fe82c42b5b3d991b76"} +{"hash_algorithm":"sha256","ts":"2026-10-02T22:59:50.273Z","author":"codex-sol","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"b2389d19e26c4d93974a8087","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/3","value":{"created_at":"2026-10-02T22:59:50.273Z","author":"codex-sol","text":"Review fixes: actual annotated mainline tag is selected as latest_tag; renamed repository containing a literal period resumes while the mismatched wildcard-like name refuses; dependency-followup package edit refuses before outputs. Annotated and renamed fixtures failed before their changes, then real workflow test and PM-linked command passed. Full locked release:check passes 219/219 with zero skips, measured index.ts 99.29 lines / 96.68 branches / 100 functions. Refuse scanning older release commits after main advances: this workflow requires original release HEAD and either failure message remains safely closed. Prior clone-local health receipts remain unreconciled; pm-linear-xw01 coverage remains open."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-02T22:59:50.273Z"}],"before_hash":"648074fa780d35d5dd717c98b9cb65fe40a9df7dd5a588e3d695b807a3f89582","after_hash":"14e980b006a50c63a91ac9b5b73c907a9f1feab168eec8a1b42c7eb9fddb9033","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"b11e2f7ee55d943407854aff54fcc5aa6a2c0b441d764d4a58c009b42b73b324"} +{"hash_algorithm":"sha256","ts":"2026-10-02T22:59:50.893Z","author":"codex-sol","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"b2389d19e26c4d93974a8087","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-10-02T22:59:50.893Z"},{"op":"replace","path":"/metadata/status","value":"blocked"}],"before_hash":"14e980b006a50c63a91ac9b5b73c907a9f1feab168eec8a1b42c7eb9fddb9033","after_hash":"ca2009c47784d8e6ad2c7d04b8989127f398608934382453e03ce3733f5aab9b","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"a68624bed9ca37635d6f2d193c0babc18957f20420e0c241dba26a8246c10025"} +{"hash_algorithm":"sha256","ts":"2026-10-02T22:59:51.369Z","author":"codex-sol","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"b2389d19e26c4d93974a8087","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"release","patch":[{"op":"remove","path":"/metadata/claim_principal"},{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-10-02T22:59:51.369Z"}],"before_hash":"ca2009c47784d8e6ad2c7d04b8989127f398608934382453e03ce3733f5aab9b","after_hash":"36ceaa443087a8e7c1bf0f7f3ce7f0ccc6c97147c222d92d5bdd3152962587cf","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"47a8d60b77b19b9b02c5fad8da0ee73a2e1c6479364598c3ab593ab3c3c5f47f"} diff --git a/.agents/pm/issues/pm-linear-yyr9.toon b/.agents/pm/issues/pm-linear-yyr9.toon index fa6d6de..9e94f4d 100644 --- a/.agents/pm/issues/pm-linear-yyr9.toon +++ b/.agents/pm/issues/pm-linear-yyr9.toon @@ -6,14 +6,16 @@ status: blocked priority: 2 tags: [] created_at: "2026-08-09T13:46:47.559Z" -updated_at: "2026-10-02T22:25:28.099Z" +updated_at: "2026-10-02T22:59:51.369Z" author: claude acceptance_criteria: "A failed publish after a merged release resumes the committed package version and release tag on later runs; Ordinary unreleased source changes receive a new date-based version and an unrelated manual version bump fails closed; A disposable Git fixture executes the actual workflow decision for both paths; The package release gate and exact-head CI and review gates pass before merge; If main has advanced past the release commit, the workflow refuses to publish or tag that pending version from a different source revision" risk: high blocked_reason: "Unmerged PR #126 awaits 100% coverage, resolution of two unprovable local merge receipts, and substantive configured final-head reviews; safe automatic recovery after newer main commits remains open." -comments[2]{created_at,author,text}: +comments[4]{created_at,author,text}: "2026-10-02T22:10:06.066Z",codex-sol,"Restart by codex-sol: preserved remote PR #126 and all existing bot replies/reactions were inspected. Branch is already current with origin/main. No new actionable unresolved inline finding is present. Retain documented coverage/health/publication blockers; rerun unchanged full CI gate with the host-wide flock. No merge, publish or item closure." "2026-10-02T22:25:25.986Z",codex-sol,"Restart: locked npm ci and release:check pass 219/219 tests, zero skips; configured index.ts coverage is 99.29% lines, 96.68% branches, 100% functions, with no separate statements dimension. PM-linked release-decision fixture tests pass, including later-head refusal, version mismatch, occupied tag, and nearest first-parent tag with skewed dates. Current worktree strict health with merge drivers passes; this fresh worktree lacks earlier clone-local merge receipts, so it does not reconcile the previously recorded pm-linear-unfa/4yle evidence. Coverage pm-linear-xw01 and substantive exact-head review remain blockers. No merge, publish, or close; release claim for orchestrator." + "2026-10-02T22:41:25.559Z",codex-sol,"New exact-head feedback: CodeRabbit 4170348351 / Cubic 4170382743 request mainline annotated-tag coverage (valid); Cubic 4170382735 finds hardcoded repository name inconsistent with the release writer (valid). Add red fixtures and fix literal repository-prefix matching. Cubic 4170382715 asks to search older release commits after package.json advances; refuse because same-HEAD-only recovery is deliberate, and add a dependency-after-release refusal regression before outputs." + "2026-10-02T22:59:50.273Z",codex-sol,"Review fixes: actual annotated mainline tag is selected as latest_tag; renamed repository containing a literal period resumes while the mismatched wildcard-like name refuses; dependency-followup package edit refuses before outputs. Annotated and renamed fixtures failed before their changes, then real workflow test and PM-linked command passed. Full locked release:check passes 219/219 with zero skips, measured index.ts 99.29 lines / 96.68 branches / 100 functions. Refuse scanning older release commits after main advances: this workflow requires original release HEAD and either failure message remains safely closed. Prior clone-local health receipts remain unreconciled; pm-linear-xw01 coverage remains open." notes[4]{created_at,author,text}: "2026-09-29T16:29:55.004Z",codex,"2026-09-29 candidate: the disposable Git fixture failed on the previous workflow (new date tag instead of committed v2000.01.02), then passed after the decision resumed that tag. It also verifies a fresh feature gets a new version, later source commits preserve the pending version, and an unrelated manual version bump is refused. npm run release:check passed 219/219 tests, docstring 49/49 declarations across 8 files, audit 0 production vulnerabilities, and attestation gate 1/1 publish path; coverage measured 99.29% lines, 96.68% branches, 100% functions, below the requested 100% floor. pm health --strict-exit fails on two pre-existing merge receipts for pm-linear-unfa and pm-linear-4yle; pm merge reconcile --dry-run reports no_receipt_set_proves_snapshot for both, so neither was forced. Rollback: revert the workflow decision change before any release run; no registry or hosted state was changed by this candidate." "2026-09-29T16:31:15.876Z",codex,"Implementation PR: https://github.com/unbraind/pm-linear/pull/126 . Candidate code commit: https://github.com/unbraind/pm-linear/commit/45ee8b3faf309f1d671f6f8009bc003635bc73f8 . Awaiting exact-head CI and configured bot review; leave unmerged while coverage and local strict-health blockers remain." diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7032dd9..33a44d9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -99,11 +99,12 @@ jobs: if [[ "$current_version" != "$tagged_version" ]]; then release_commit="$(git log -1 --format=%H -- package.json)" release_subject="$(git log -1 --format=%s -- package.json)" - if ! printf '%s\n' "$release_subject" | grep -Eq '^Release pm-linear v[0-9]{4}\.[0-9]{2}\.[0-9]{2}(-[0-9]+)?$'; then + release_prefix="Release ${GITHUB_REPOSITORY##*/} " + tag="${release_subject#"$release_prefix"}" + if [[ "$release_subject" != "$release_prefix"* ]] || ! printf '%s\n' "$tag" | grep -Eq '^v[0-9]{4}\.[0-9]{2}\.[0-9]{2}(-[0-9]+)?$'; then echo "::error::package.json is ahead of ${latest_tag} without a recognized release commit. Resolve its version before releasing." exit 1 fi - tag="${release_subject##* }" version_core="${tag#v}" year="${version_core%%.*}" version_tail="${version_core#*.}" diff --git a/test/release-decision.test.ts b/test/release-decision.test.ts index 96ac1e6..6589c11 100644 --- a/test/release-decision.test.ts +++ b/test/release-decision.test.ts @@ -23,7 +23,7 @@ test("a merged but untagged release keeps its committed version on retry", () => } /** Capture the workflow's machine-readable decision without publishing anything. */ - function decide(): Record { + function decide(repositoryName = "pm-linear"): Record { const output = join(root, "output"); const summary = join(root, "summary"); writeFileSync(output, ""); @@ -31,7 +31,7 @@ test("a merged but untagged release keeps its committed version on retry", () => const result = spawnSync("bash", ["-c", script], { cwd: project, encoding: "utf8", - env: { ...process.env, GITHUB_OUTPUT: output, GITHUB_STEP_SUMMARY: summary, RELEASE_TIMEZONE: "Europe/Vienna" }, + env: { ...process.env, GITHUB_OUTPUT: output, GITHUB_STEP_SUMMARY: summary, RELEASE_TIMEZONE: "Europe/Vienna", GITHUB_REPOSITORY: `unbraind/${repositoryName}` }, }); assert.equal(result.status, 0, `release decision failed: ${result.stderr}`); return Object.fromEntries(readFileSync(output, "utf8").trim().split("\n").map((line) => { @@ -41,13 +41,13 @@ test("a merged but untagged release keeps its committed version on retry", () => } /** Assert the decision stops before writing release outputs on an unsafe state. */ - function refuse(message: RegExp): void { + function refuse(message: RegExp, repositoryName = "pm-linear"): void { const output = join(root, "output"); writeFileSync(output, ""); const result = spawnSync("bash", ["-c", script], { cwd: project, encoding: "utf8", - env: { ...process.env, GITHUB_OUTPUT: output, GITHUB_STEP_SUMMARY: join(root, "summary"), RELEASE_TIMEZONE: "Europe/Vienna" }, + env: { ...process.env, GITHUB_OUTPUT: output, GITHUB_STEP_SUMMARY: join(root, "summary"), RELEASE_TIMEZONE: "Europe/Vienna", GITHUB_REPOSITORY: `unbraind/${repositoryName}` }, }); assert.notEqual(result.status, 0, "an unsafe release state must fail closed"); assert.match(result.stdout, message); @@ -62,7 +62,8 @@ test("a merged but untagged release keeps its committed version on retry", () => writeFileSync(join(project, "source.txt"), "first\n"); run("git", ["add", "."]); run("git", ["commit", "-m", "Initial release"]); - run("git", ["tag", "v2000.01.01"]); + run("git", ["tag", "-a", "v2000.01.01", "-m", "Initial production-style release"]); + assert.equal(run("git", ["cat-file", "-t", "v2000.01.01"]), "tag", "production release tags are annotated"); run("git", ["init", "--bare", remote], root); run("git", ["remote", "add", "origin", remote]); run("git", ["push", "origin", "main", "--tags"]); @@ -80,9 +81,25 @@ test("a merged but untagged release keeps its committed version on retry", () => const retry = decide(); assert.equal(retry.should_release, "true"); assert.equal(retry.tag, "v2000.01.02"); + assert.equal(retry.latest_tag, "v2000.01.01", "the retry selects the annotated mainline tag"); assert.equal(retry.npm_version, "2000.1.2"); assert.equal(retry.base_sha, run("git", ["rev-parse", "HEAD"])); + const releaseHead = run("git", ["rev-parse", "HEAD"]); + writeFileSync(join(project, "package.json"), '{"name":"pm-linear","version":"2000.1.2","dependencies":{"fixture":"1.0.0"}}\n'); + run("git", ["add", "package.json"]); + run("git", ["commit", "-m", "Update dependencies after failed publication"]); + refuse(/without a recognized release commit/); + run("git", ["reset", "--hard", releaseHead]); + + run("git", ["commit", "--amend", "-m", "Release pm-linear.fork v2000.01.02"]); + const renamedRetry = decide("pm-linear.fork"); + assert.equal(renamedRetry.latest_tag, "v2000.01.01"); + assert.equal(renamedRetry.tag, "v2000.01.02"); + assert.equal(renamedRetry.npm_version, "2000.1.2"); + refuse(/without a recognized release commit/, "pm-linearXfork"); + run("git", ["reset", "--hard", releaseHead]); + const unrelatedCommit = run("git", ["commit-tree", run("git", ["rev-parse", "HEAD^{tree}"]), "-m", "Unrelated release"]); const conflictingTag = spawnSync("git", ["tag", "-a", "v2000.01.02", "-m", "Conflicting release tag", unrelatedCommit], { cwd: project,