Skip to content

Predict the release commit from staged-blob truth - #17

Merged
DocksDocks merged 3 commits into
mainfrom
fix/release-dry-run-truth
Aug 21, 2026
Merged

Predict the release commit from staged-blob truth#17
DocksDocks merged 3 commits into
mainfrom
fix/release-dry-run-truth

Conversation

@DocksDocks

Copy link
Copy Markdown
Owner

What & why

release.mjs --dry-run lied twice about a re-cut. The tail printed a git commit line unconditionally, while the executed path detects an empty cached diff and tags existing HEAD; and the manifest preview compared canonical bytes against canonical bytes, so its none — formatting drift! label named an unreachable state while a noncanonical manifest at the same version really did stage a diff and commit.

Both sides now decide the question the executed path decides — would git add stage anything — through git hash-object --path <file> --stdin against HEAD:<path>. That applies the same clean filters git add applies and never passes -w, so no object is written. The dry run also consults ensureCleanTree, reports the refusal the real release raises, and suppresses the landing prediction on a dirty tree. The executed re-cut sentence moved behind a shared constant, byte for byte.

Test-gap this exposed

The mutation matrix proving each behaviour load-bearing found two mutations the suite could not catch: the fake-adapter release contracts and the dirty-tree comparison were reachable only from --dry-run-release-safety, a flag nothing schedules. Both now run in the default suite path, the adapter's two fail-loud git predicates are asserted against real git with hermetic fixtures, and the shim passes through only the three exact read-only argument vectors while refusing -w, index, and ref writes.

Verification

node scripts/ci.mjs exits 0. All twenty-one acceptance rows pass, including the flag-gated safety scenario re-run from a clean clone of these bytes. Details in the record.

Closes #16

The dry-run tail promised a commit unconditionally while the executed path
detects an empty cached diff and tags existing HEAD instead, so a re-cut
preview named an action the release would not take. The manifest preview was
worse than mislabelled: it compared canonical bytes against canonical bytes,
so its formatting-drift label named an unreachable state while a noncanonical
manifest at the same version really did stage a diff and commit.

Both now decide the one question the executed path decides - would `git add`
stage anything - through `git hash-object --path <file> --stdin` against
`HEAD:<path>`, which applies the same clean filters and writes no object. The
dry run consults `ensureCleanTree`, reports the refusal the real release
raises, and predicts no landing on a dirty tree; the executed re-cut sentence
moves behind a shared constant byte for byte, so preview and execution cannot
drift.

The mutation matrix that proved these behaviours load-bearing also exposed the
reason the old reporting survived: the fake-adapter release contracts and the
dirty-tree comparison were reachable only from a flag-gated scenario that
nothing schedules. Both now run in the default suite, and the adapter's two
fail-loud predicates are asserted against real git. The shim passes through
only the three exact read-only argument vectors and refuses `-w`, index, and
ref writes.
Round-1 review found the safety scenario could certify a false negative. The
shim passes nested `node` argv through with the real PATH, because the scenario
exists to run a real plugin gate, and an object write is the one mutation such
a child could make that neither status nor refs reveal. Restricting the argv
would make the harness brittle without closing the hole, so the before/after
snapshot now counts objects: injecting one `hash-object -w` into that exact
branch fails the scenario at the comparison, 2845 objects to 2846, while the
real gate leaves the count byte-identical.

The real-git fixtures inherited ambient Git configuration, so a developer's
`commit.gpgSign` or `core.hooksPath` could fail or alter fixtures the test calls
hermetic. Fixture spawns now null out global and system config and disable
signing, hooks, and prompts, without ever mutating `process.env`; a hostile
injected config failed the suite before this change and passes after it. The
fixture whose purpose is a required-but-broken clean filter keeps its
repository-local filter.

The dirty-tree negative list stopped at add, commit, push, and tag, so moving
the workflow or release forecast above the blocked return would still have
passed. One list now covers every line the unblocked tail can print.
The dry-run truth work added real-git fixtures, and two of them let the
developer's environment decide the result.

`releaseTagExists` called `git ls-remote` directly, so the default suite path
reached the network: `node scripts/ci.mjs` failed with "cannot reach origin to
check whether plan-lifecycle--v0.6.0 is already released" whenever origin was
unreachable. The consult is now the sixteenth closed-set operation,
`tagPublished`, keeping the local-tag-then-origin order and the refusal text
byte for byte, with the fixture stubbing it false. The same-version consult and
the already-released refusal are now asserted instead of inherited.

`git help config` documents that `GIT_CONFIG_KEY_<n>` pairs override every
configuration file, a fixture's own `--local` settings included, so pointing
`GIT_CONFIG_GLOBAL` at devNull was not isolation. The adapter spawns git with
this process's environment and must keep doing so, because in production it has
to predict what the operator's `git add` would stage; the boundary is therefore
the test process, which now drops the pairs around the adapter contracts and
restores every prior value. The shim matrix carries the same zero pair count.
@DocksDocks
DocksDocks merged commit dcb28ab into main Aug 21, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Report the release re-cut truth in the dry-run preview

1 participant