Repairs the two fresh-Sol ADR-0016 review findings. Turns the RED witnesses
recorded in the previous commit green without touching the governed regex, the
growth guard, the allowlist, or scripts/.skill-python-grandfather.
1. check-skill-python-ratchet.sh no longer swallows the collector contract.
scripts/lib/ratchet.sh gives every chosen collection command an explicit
"|| return 2" because the gate runs in a shell with no ambient strict mode. The
caller then discarded exactly that: ratchet_changed_files_status ... 2>/dev/null
|| true turned a dead Git read into an EMPTY changed set, the governed loop ran
zero times, and the gate printed its green banner over a diff nobody read.
Measured before this change, with a diff-tree shim over a tree carrying a real
unpinned violation: "PASS: skill-python ratchet" / exit 0. The rc is now
preserved and the gate exits 2, with stderr no longer sent to /dev/null so the
library's own reason survives.
2. head scope now HAS the first-parent semantics it declared.
git diff-tree -m --first-parent does not mean "diff only against parent 1":
-m splits a merge into one diff block PER PARENT and emits them all, while
--first-parent constrains history traversal rather than which parent blocks
print. The parent-2 block lists every path already settled on the first-parent
line, so a merge commit re-attributed long-landed files to itself. Sol measured
this on the live integration lineage: at 88c2692f2 the old form emitted 14
governed rows, 12 from the second-parent block, none introduced by that merge.
Head collection becomes one owner, ratchet__head_diff, doing an explicit
two-tree HEAD^1..HEAD diff, with the --root empty-tree diff kept for root
commits (HEAD^1 does not resolve there, and a root commit's contents are exactly
what it introduced). An unreadable parent list fails closed at rc 2 rather than
returning an empty set. The helper replaces all SIX open-coded copies of the old
command -- head scope plus the upstream-without-upstream and auto fallbacks, in
both the name-only and name-status collectors -- so the same defect cannot
survive in a path nobody looked at. diff-tree is retained (not git diff) to
preserve the existing rename-detection posture; only ref selection changes.
The lib header no longer claims -m --first-parent means first-parent.
Verification:
- tests/scripts/check-skill-python-ratchet.bats 15/15 (N7, N8 were RED)
- tests/scripts/ratchet-lib.bats 41/41 (merge case was RED)
- all six other ratchet-lib consumer suites: unchanged from base
- go test ./internal/gates/... ok (negative-witness closure)
- shellcheck -S warning on both changed scripts: clean
- git diff --check: clean
- live gate sweep over all eight consumers, run identically at the unmodified
base 0ba51a5f6 and here: byte-identical results. The one pre-existing
failure (check-atomic-write-ratchet stale-entry rc=1, and its bats case 13)
reproduces at base with real history and is NOT caused by this change.
Refs: /tmp/agentops-adr0016-go-promotion-review-sol.md (REQUEST_CHANGES,
"Two ratchet corrections are still required"); ADR-0016 section 3.
Two fixes from the 2026-07-25 skill-overhaul progress review, both independent
of the unlanded tranche fleet.
1. skill.python-ratchet — the gate ADR-0016 said it already had. Section 3
fixed "Python never ships in skills" on 2026-07-18 and called its own
violation "a gate failure, not a style nit", while zero checks enforced it.
Now a blocking shrink-only ratchet over skills/*/scripts/**/*.py: the 24
current files are pinned, new ones hard-fail, a promoted file must be
pruned, and the growth guard rejects a change that allowlists itself. The
surviving count prints on every run. ADR-0016 records the enforcing check by
name and the skills/*/tests/** carve-out with its rationale, so the
exemption cannot decay into the same inert prose.
2. The RPI/Validate acceptance-digest disagreement. run_once.py digested a
canonical-JSON re-serialization of the parsed intent mapping, validate.py
digested the raw intent bytes, and run_once.py hard-compared the two, so the
composed contract could not succeed. Both suites were green because RPI's
test mocked Validate with RPI's own digest function. RPI stops being a
second digest authority: it carries the digest Plan declares over the bytes
Plan snapshotted and cross-checks Validate's independently re-derived value.
3. The cathedral-cut probe, the same blind spot found again. It set
intent_bytes = canonical_bytes(resolved_intent) — precisely the one input
where the two digests coincide — so like the unit test it was built around
the coincidence and could not observe the bug.
Test shape is liveness-first: every gate negative is a seeded witness the gate
must be shown to FAIL on (12/12 bats), and the digest fix landed against a RED
witness that reproduces the real production error.