feat(evidence): verification-evidence ledger mechanizes /ship's IRON LAW

New bin/gstack-evidence: a transparent wrapper that records every verification
run as {ts, label, command, cmd_sha256, exit, duration_s, commit, tree, dirty,
wtree, log_path} in ~/.gstack/projects/<slug>/<branch>-evidence.jsonl, plus a
read-only `check` that grades FRESH/STALE/MISSING per label. "Tests passed"
now binds to the exact working-tree content it ran on (bin/gstack-wtree
fingerprint), so evidence recorded on uncommitted code stays FRESH after the
exact tested content is committed — the /ship Step 5 -> Step 16 case — while
an untracked new source file or any content change invalidates it.

Check semantics: every named label's latest record must be green, within
--max-age, matching --expect-cmd's hash when given, and fingerprint-identical
(or diff confined to --allow-paths — mechanizing Step 16's existing "CHANGELOG
edits don't count" carve-out). No --any mode: a green lane can never mask a
red sibling. Any git failure inside check (gc'd tree object, not a repo)
degrades to STALE/MISSING, never an error into the calling skill flow.

Transparency invariant (load-bearing, test-pinned): the child's exit code is
ALWAYS the wrapper's exit code; ledger/log/redact failures are stderr
warnings. Logs are per-run (0600, exclusive-open, 2MB truncation marker,
30-day opportunistic prune) — no more shared /tmp collisions between
concurrent ships. Command strings are redact-scanned before recording (HIGH
credential -> stored redacted). Machine-local by design: neither ledger nor
logs brain-sync.

Wired: ship Step 5 lanes run wrapped (per-lane labels), ship Step 16 and
land-and-deploy 3.5b check the ledger first and cite FRESH evidence instead of
re-running; a failed CHECK never blocks (run live), a failed RUN does.
test/evidence.test.ts: 21 tests incl. the keystone dirty-record -> commit ->
FRESH case.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-08-15 23:24:50 -07:00
co-authored by Claude Fable 5
parent 8e4a4f7c3d
commit 4836f0d1e3
8 changed files with 772 additions and 20 deletions
+18 -5
View File
@@ -1369,16 +1369,29 @@ and tell the user: "I found and fixed a few issues during the review. The fixes
### 3.5b: Test results
**Free tests — run them now:**
**Free tests — cite fresh evidence or run them now:**
Read CLAUDE.md to find the project's test command. If not specified, use `bun test`.
Run the test command and capture the exit code and output.
Check the evidence ledger first:
```bash
bun test 2>&1 | tail -10
~/.claude/skills/gstack/bin/gstack-evidence check --label tests --max-age 24
```
If tests fail: **BLOCKER.** Cannot merge with failing tests.
If it prints FRESH (exit 0), a green run is on record for THIS exact
working-tree content (fingerprint-bound, so a rebase or an identical-content
commit doesn't invalidate it) — cite the evidence line (exit, ts, log path)
instead of re-running.
Otherwise (STALE/MISSING, or you want a live run anyway): read CLAUDE.md to
find the project's test command (default `bun test`) and run it wrapped, so
the fresh result is recorded:
```bash
~/.claude/skills/gstack/bin/gstack-evidence run --label tests -- 'bun test 2>&1'
```
If tests fail: **BLOCKER.** Cannot merge with failing tests. (A failed evidence
CHECK is never a blocker — it just means run live; a failed RUN is.)
**E2E tests — check recent results:**
+18 -5
View File
@@ -465,16 +465,29 @@ and tell the user: "I found and fixed a few issues during the review. The fixes
### 3.5b: Test results
**Free tests — run them now:**
**Free tests — cite fresh evidence or run them now:**
Read CLAUDE.md to find the project's test command. If not specified, use `bun test`.
Run the test command and capture the exit code and output.
Check the evidence ledger first:
```bash
bun test 2>&1 | tail -10
~/.claude/skills/gstack/bin/gstack-evidence check --label tests --max-age 24
```
If tests fail: **BLOCKER.** Cannot merge with failing tests.
If it prints FRESH (exit 0), a green run is on record for THIS exact
working-tree content (fingerprint-bound, so a rebase or an identical-content
commit doesn't invalidate it) — cite the evidence line (exit, ts, log path)
instead of re-running.
Otherwise (STALE/MISSING, or you want a live run anyway): read CLAUDE.md to
find the project's test command (default `bun test`) and run it wrapped, so
the fresh result is recorded:
```bash
~/.claude/skills/gstack/bin/gstack-evidence run --label tests -- 'bun test 2>&1'
```
If tests fail: **BLOCKER.** Cannot merge with failing tests. (A failed evidence
CHECK is never a blocker — it just means run live; a failed RUN is.)
**E2E tests — check recent results:**