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.
## Root cause (CI flake, run 29785505667 attempt 1, PR #944)
The bats test `adding only the temp-signal half to a file with an
EXISTING rename still trips` failed once on ubuntu with the ratchet
printing a clean `PASS … grandfathered=0` where the fixture
deterministically trips. Diagnosed by analysis per the test-isolation
discipline (not repro-chasing):
- bats errexit proves every fixture `git commit` succeeded; the fixture
repo is fully isolated.
- `grandfathered=0` in the PASS line fingerprints the run as in-fixture
(real repo = 4).
- Zero stderr in the captured output rules out every loud failure
handler.
What remains: **three silent paths where a scan helper (grep/awk/git)
dying under 4-way-parallel runner load reads as "no finding"** — the
gate then certifies an unchecked diff:
1. `file_trips`: strip/grep failure returned 1 ("does not trip") → file
silently skipped.
2. Detector loop read from `< <(ratchet_changed_files | sort)` — a
collector rc 2 inside process substitution is discarded, certifying an
EMPTY change set (the exact fail-open the ratchet-lib header warns
about).
3. `ratchet_added_hunk_matches`: helper death propagated as arbitrary
nonzero, swallowed by `|| continue`.
## Fix
- `file_trips` is tri-state (0 trips / 1 no / 2 refuse-to-certify,
loud); signal greps read full input (no `-q` early-exit) so pipefail can
never surface a SIGPIPE'd printf as a phantom failure on large files.
- Changed files are collected into a variable under pipefail with an
explicit `exit 2` handler.
- `ratchet_added_hunk_matches` maps fallback-grep/awk death to loud rc 2
(0/1 unchanged; existing `|| continue` consumers skip on 2 exactly as
before, now loudly).
- `compute_grandfather_set` aborts `--regenerate` on helper failure —
never a truncated grandfather list.
## Pinned
Dying-awk PATH-shim tests at gate and lib level: helper death is rc 2
"refusing to certify", never PASS. Same fail-closed class as the
pawl-refuted empty-stream certify (2026-07-10).
## Validation
- 58/58 in `check-atomic-write-ratchet.bats` + `ratchet-lib.bats` on
fresh origin/main
- All six ratchet-lib consumer gate suites green
- `shellcheck -S warning` clean; real-repo gate PASS (grandfathered=4);
`--regenerate` byte-identical
- 65 repetitions of the formerly-flaky test green
Residual: the external kill source is unprovable from one occurrence; a
recurrence now exits 2 naming the failed helper + file instead of
printing PASS.
Registers the fully-built-but-never-wired eval module into the CLI spine
and repairs its one real instrument bug. Three commits, RPI-shaped: wire
+ fix, drift guards + docs, fixture counts.
## What
- **`ao eval` joins the spine** (cmd/ao/eval_composition.go):
deterministic suite runs, run-record
compare/baseline/scorecard/coverage, locked Tasks, holdout scenarios,
suite A/B verdicts with power-derived n-required, and the holdout-safe
Outcomes projection. This is the operating contract's *Learn* seat — a
read/measure consumer of evidence. It reports numbers and owns no retry,
scheduling, promotion, or lifecycle authority. The retired Aliases/Bench
seats stay nil and their subcommands are omitted.
- **Instrument bug fixed**: both scenario-ab arms were corpus-denied at
runtime — the with-gold treatment was only a prompt injection through
the retired `ao lookup`, so the A/B silently guaranteed a zero delta.
Treatment is now environment-shaped: the with-gold arm runs with the
corpus readable, the control arm stays sandbox-confined (the fail-closed
empty-deny guard is untouched), and judges are always corpus-denied.
- **Schema drift guards**: a run record persisted by the production
writer must validate against eval-run.v1.schema.json (and the minimal
suite against eval-suite.v1.schema.json) — closing the same silent-fork
class the verdict.v2 golden corpus closed.
- **Hermetic test builds hardened**: keep the real HOME's Go caches
(TestMain isolation was forcing module re-downloads) and skip VCS
stamping.
- docs/architecture/go-cli.md gains the eval section; MIGRATION.md notes
`ao eval`'s return; COMMANDS.md, cli-surface, and surface counts
regenerated; new leaves carry honest allowlist reasons; new cmd/ao L2
tests drive eval run/compare/help through the full production wiring.
## Evidence
- Full cli suite 2,801 green; race green on cmd/ao + the eval island;
vet clean
- Full-tier gate: 65/66 on the penultimate commit; the single failure
(surface counts) is the final fixture commit, re-verified PASS
standalone — no other check reads those fixtures
- Fresh independent validation (Codex, frozen subject 86022a3c6, clean
tree at start and end): **OVERALL PASS**, 6/6 — including a live
end-to-end `ao eval run` producing a passing run record
- Rubric-relocation decision honored: true holdout rubrics live in the
external measurement register; the repo keeps only leak-guarded
projections
Fixes the three gremlins observed 2026-07-18 (bead
age-gate-scripts-worktree-gitdir-p62wo):
1. **Gate scripts broke under hook env from linked worktrees** (#919
regression): new scripts/lib/repo-root.sh (GIT_*-scrubbed resolution,
BASH_SOURCE anchor, worktree-correct) swept across 32 scripts. Proof:
this PR's own push ran the pre-push gates from a linked worktree and
passed.
2. **core.bare=true recurrence**: mechanism proven (leaked GIT_DIR +
no-arg git init --bare rewrites the SHARED config); writer traced with
high confidence to a live Gas City city process (concrete suspect:
gascity cmd_rig_test.go runGitInTest, unscrubbed env + no-arg init
--bare — fork-side fix filed separately). Defense: new
always.git-config-hygiene gate fails fast on core.bare/test identities
with the repair command inline (--self-test proves fail-closed).
3. **Test/test@test.com identity leak**: writer convicted — our own
pre-push chain leaked GIT_DIR into go test, whose helpers wrote the
shared config via git -C (mechanism proven). Cut at both ends:
validate-go-fast scrubs env for children; every test git-helper set-site
scrubs; check-test-isolation gains a WARN-ratchet rule (baseline 3 →
FAIL at 0).
Gates: shellcheck rc=0 across 36 scripts; go build/vet clean;
655+379+149 tests; hygiene self-test; both originally-failing validators
pass under the repro env.
## Summary
Closes the findings from the v3.3.0 release-viability audit of main.
**Surface truth (High):** `ao constraint`, `ao goals trace`, `ao session
memory`, and `ao skills edit` are production tombstones but
docs/cli-surface.md documented them as `public-tested`/`covered` (the
test binary keeps constraint live, and the generated doc reflected the
test build). Added `deprecated` allowlist rows, dropped the stale
constraint-subcommand rows, regrouped constraint with its tombstone
peers in the default spine, regenerated both surface projections, and
added `TestCathedralCutTombstonesSurvivePruning` pinning the invariant
that every Cathedral Cut verb keeps an executable tombstone.
**Installer hardening (Medium):** `install-bd.sh` now fails closed when
no published checksum exists for a release asset (explicit `--no-verify`
overrides), and the curl|bash path verifies the fetched
`installer-common.sh` against a pinned SHA-256 before sourcing it
(drift-guarded by a new install-bd.bats test).
**Docs (Medium/Low):** UPGRADING.md no longer links the MIGRATION-3.0.md
file the Cathedral Cut deleted; CHANGELOG 3.3.0 `### Removed` now names
`yield`, `constraint`, and the 3.2 `verify` front door and links the
MIGRATION.md map.
**Gate scripts (Low):** flywheel/provenance gate scripts build `ao` into
`mktemp -d` scratch dirs instead of fixed world-known /tmp paths
(symlink/TOCTOU on shared hosts).
**Bonus (surfaced by the push gate):** `scripts/lib/preamble.sh`
mis-resolved REPO_ROOT under git hook env (GIT_DIR without
GIT_WORK_TREE), which made `go.cli-architecture` refuse every
worktree-origin push touching `cli/cmd/ao/**`. Fixed by scrubbing the
hook-injected discovery env around the rev-parse.
## Validation
- `go build && go vet && go test ./...`: 5,085 tests green,
golangci-lint clean
- `ao gate check --fast`: 27/27 pass, including under simulated hook env
- bats install-bd suite 6/6 incl. new pin drift test; shellcheck clean
on all touched scripts
- markdownlint clean; doc-release gate passes; tombstone exit codes
verified on the built binary
Retire the 3.x plugin curl/PowerShell installers as tombstones, delete the
plugin-cache helpers, and rewire CI/doctor/conformance onto the canonical
checkout + ao skills link path. Keep install-bd and the opt-in operator scripts.
Co-authored-by: Cursor <cursoragent@cursor.com>
'ao pawl --help' listed the warm standing-service verbs as flat peers of review,
pushing users toward operator machinery (NTM, projects_base) they cannot run.
Cobra groups now split the surface: review under 'Use the membrane (the front
door — needs no NTM, no setup)', up/down/reap/health/doctor/smoke/route/metrics
under 'Operate the standing warm service (operator-only; requires the NTM swarm
substrate)'. Grouping/labeling only — every verb keeps its name and behavior.
docs/contracts/pawls.md carries the same operator-only note.
Fix swept in: scripts/lib/sku_extract.py's cobra help parser only recognized
'*Commands:' section titles, so EVERY grouped command's subcommands were
invisible to the SKU catalog ('ao goals' was already affected; this change made
'ao pawl' the second victim and surfaced the bug). The parser now enters any
non-reserved column-0 title block and requires the 2+-space name/short gap, so
grouped subcommands resolve precisely (registry.json reflects the corrected
projection for ao goals too). Locked by TestPawlHelpPresentsReviewAsFrontDoor.
scripts/lib/ratchet.sh: the one implementation of the grandfather/baseline
ratchet mechanics 7 gates hand-roll today — per-consumer parse modes
(raw|cr-strip|strip|trailing-comment), scope-aware changed-file collection
incl. upstream, fail-closed growth guard (comm -13 + intersection authority,
RATCHET_GROWTH_GUARD=off escape for migrated gates), parameterized stale
predicates, generic regenerate (header-fn + LC_ALL=C sorted body), added-hunk
guard with ENVIRON-passed ERE.
tests/scripts/lib/ratchet-difftest.bash: byte-parity differential harness
(stdout, stderr, exit code separately) for the migration slices.
tests/scripts/ratchet-lib.bats: 24 cases — real pinned-shape round-trips per
parse mode, growth rejection, self-allowlist refusal, stale by set-diff and
by predicate, regenerate golden, added-hunk guard, harness smoke.
cli/internal/gates/checks/ratchet_routing_test.go: self-extending FM3 closure —
every check script sourcing the lib must route lib edits via its Match globs.
Pre-mortem: .agents/council/2026-07-10-pre-mortem-ratchet-lib.md (3 rounds).
Lib is FROZEN for the rest of the arc per FM6.
Claude-Session: https://claude.ai/code/session_01RDWPYn4ZS2SyBkcBvAM18u
Two guards for the 2026-07-09 wrong-tree/evidence-loss incident:
wckn: pawl_tip_coherence refuses (exit 3, fix-and-re-run) when the review
target commit cites bead ids sharing the reviewed bead's prefix and none is
the bead or a dotted relative - a verdict can no longer bind a tree that
belongs to a different bead. Positive-mismatch only; PAWL_ALLOW_TIP_MISMATCH=1
opts out. 7 bats.
np1e: evidence transcripts and membrane catches now root at the CANONICAL
checkout (git-common-dir parent) via pawl_evidence_dir/pawl_canonical_root,
so reviews run in disposable land worktrees no longer write artifacts that
die with 'git worktree remove', and ledger evidence_path never dangles.
5 bats. Both libs registered in validate-embedded-sync + embedded copies synced.
Claude-Session: https://claude.ai/code/session_01BkgdP88SiccCSyYdi8GHNa
Dual-support increment 3 (the user-facing completion). AgentOps ships to users
who mostly track with bd; operating-loop skills hardcoded `br`, breaking them.
Repoint bead CRUD (ready/update/close/create/dep/show/list, non-jq) and the
children-extraction pattern to the tracker-agnostic `ao beads exec` (bd-or-br)
across skills/**/SKILL.md + references. In THIS repo (br) there is no behavior
change; the win is bd users.
- children pattern `br show <epic> --json | jq '…parent-child….id'` →
`ao beads exec children <epic>` (post-mortem closure-integrity-audit + the
latent invalid `br children` in rpi phase-data-contracts).
- probe blocks re-gated `command -v br` → `command -v ao` (bd users have ao).
- parity_only codex twins refreshed via codex-sync + hashes; bespoke twins are
hand-maintained (codex-sync skips them by design) and left for a deliberate
follow-up.
- jq-shape `br … --json | jq '<shape>'` sites left on br (increment 4 will
normalize the --json shape across trackers).
- rpi/SKILL.md trimmed 280→249 lines (meta-tier ≤250): receipt JSON folded into
the existing phase-data-contracts.md, rpi_state block compacted, redundant
Reference-Documents entries dropped.
- snippet validators (ao_snippet_resolve.py lib + validate-skill-body-refs.sh)
taught that `ao beads exec` is a flag-forwarding passthrough, so tracker flags
after it are not validated against ao's help.
Kills the operator-error class that fired ~4x on 2026-07-07, each costing a wasted
review round: after the pawl auto-binds a #trivial provenance commit on the feat, a
'git commit --amend' folds the fix INTO the bind — HEAD becomes a #trivial commit
carrying code, the reviewer's trivial-waiver walk steps PAST it to review the STALE
original feat, and the verdict binds the wrong tree (surfacing only as an opaque
'hidden non-trivial commit' gate failure at push).
scripts/lib/pawl-amend-guard.sh (new, sourceable, unit-tested): pawl_amend_guard
returns 2 when HEAD is a trailing-#trivial commit changing non-provenance files,
printing the diagnosis + the rebuild-don't-amend recovery. Wired EARLY into
pawl-review.sh (scope=head, before the review — where it saves the round) and as a
land-time backstop in pawl-land.sh; embedded pawl bundle + Makefile + embed validator
updated. Safe: provenance-only #trivial binds, plain feats, and mid-subject #trivial
mentions all pass; opt out PAWL_NO_AMEND_GUARD=1. bats 5/5; guard proven to fire on
the trap and stay silent on legitimate binds; race suite green.
THE bottleneck fix. On 2026-07-07, closing skill-doc changes took 4-7 model-review
rounds each because ~90% of the defects were DETERMINISTIC (format/link/twin/
gate-hint) — but the expensive cross-family verdict was requested BEFORE the local
deterministic checks were green, so the membrane re-discovered them serially, each
round a full re-review + re-land.
scripts/lib/pawl-preflight.sh (new, sourceable, unit-tested): pawl_preflight <scope>
<repo> runs the reviewed repo's deterministic battery before the reviewer and fails
fast (exit 3, same fix-and-re-run disposition as the --smoke/REFUTED path) on a
CONFIRMED-red battery — WITHOUT spending a reviewer round (0 tokens). Command:
PAWL_PREFLIGHT_CMD if set (operator owns exit semantics), else the default
'ao gate check --fast --scope head' (ao resolved AO_BIN -> repo cli/bin/ao -> PATH,
mirroring check-pawl-pre-push.sh). Wired into pawl-review.sh after the HEAD snapshot,
before packet assembly + the meter clock.
SAFETY — pure ACCELERATOR, never a false blocker: a nonzero that cannot be CONFIRMED
as a real gate run (RCE trust-guard, missing/stale ao, build error — distinguished by
the absence of the gate's 'checks —' summary marker) is SKIP-and-proceed, not RED.
Worst case it no-ops and behavior is byte-identical to today (the pre-push gate still
backstops). Disabled for PAWL_UNTRUSTED_REPO=1 (never run a repo's own battery over an
untrusted checkout) and scope != head (no committed HEAD). Opt out: PAWL_NO_PREFLIGHT=1.
Converts the inert prose lesson 'run the battery before the verdict' into MECHANISM
(the skills audit proved prose lessons are inert). bats 9/9; embedded pawl bundle +
Makefile sync + embed-sync validator updated; Go embed test green.
Post-refutation fixes (cross-family pawl caught two defects in the first cut):
- DEFECT 1 (fail-open): the agy/local-mlx genuine-run marker 'VERDICT:' appears in the
packet itself, so a run that merely cat/echoed the packet classified GENUINE and the
last-verdict parser could extract a CONFIRMED from echoed diff-context lines. Fixed:
sentinel-wrapped agy packets (random-nonce boundary lines) + a marker-independent
packet-line containment check (reviewer_packet_echoed) classify packet echo as ECHO.
- DEFECT 2 (wrong family certified): the verdict write hardcoded --refuter codex:...;
REVIEWER=agy now certifies the canonical 'gemini' family (roster: agy|google->gemini),
codex stays byte-identical, local-mlx gets a deliberately off-roster label so an
opted-in eval verdict can never pass the prod roster check.
Advisory-first rollout, deliberately: shell.preamble-ratchet seeds Blocking:false
for ONE clean cycle on main, then flips Blocking — the same staged pattern the
epic already landed for docs.cli-snippets (egwt.4) and docs.demoted-claims
(egwt.6), recorded in the RepairHint. The backing check itself is fail-closed
(exit 1) on every violation class:
- new/changed top-level scripts/*.sh must source scripts/lib/preamble.sh or
carry a non-empty '# preamble-exempt: <reason>' (empty reason rejected)
- grandfather authority = base-ref ∩ working snapshot, so appending an entry in
the same diff grants nothing (both halves fail independently)
- the snapshot is shrink-only vs. its base-ref version; only the initial
snapshot commit may introduce it
- scripts/lib/** exempt as a class (sourced libs, not entrypoints)
Also folds with_tmpdir (trap-EXIT mktemp cleanup, ~79 re-rolls) and require_cmd
(command -v + install-hint die, ~62 re-rolls) into the preamble; 18 preamble
bats + 10 ratchet bats.
Heavy legacy RPI chains with no measured uplift. Add a new `experimental` tier
and demote the three. The tier value is registered at ALL FOUR enum sites — the
authoritative schemas/skill-frontmatter.v1.schema.json (enforced by the blocking
skill.manifests gate), validate-skill-schema.sh, sku_catalog.py, and
generate-registry.sh — plus a Tier Values row in SKILL-TIERS.md. Frontmatter
metadata.tier and the SKILL-TIERS row cell flipped in lockstep for all three.
acfs is demoted, NOT cut: its cut is external (~/acfs) and a deferred operator
one-way-door decision (noted in the disposition ledger). Codex twins refreshed
by regen (not dropped — .18 owns twin cuts). registry drift, manifests schema,
sku-catalog, sync-counts all green; go build + internal tests pass.
Salvaged the uncommitted work from wt-ag-ez7y6 and completed the 4->2 beads
skill consolidation the salvaged test asserts:
- Fold the "Issue-Lifecycle Discipline" doctrine (live-reads-authoritative,
scoped-closure-proof, parent-reconciliation, narrow-the-umbrella-issue,
normalize-stale-queue) from the retired `beads` umbrella into beads-br.
- Retire skills/beads --into beads-br via `ao skills retire` (trees removed,
ledger row moved to historical: merged-into beads-br).
- Re-target every retired-skill reference: crank/implement/plan/post-mortem/
refactor frontmatter+deps, SKILL-TIERS rows, docs/SKILLS.md, GLOSSARY link,
sku_catalog LOOP_MOVES[2], image manifests (counts adjusted).
- Codex twin: mirror the doctrine into skills-codex/beads-br, prune `beads` from
the codex catalog/manifest, regen hashes (parity gate age-yxl).
- Add cli/cmd/ao/beads_consolidation_test.go (L2 structural invariants). Uses a
leak-proof repo-root finder (skills/+docs/contracts/) because the shared
findRepoRoot keys on .agents/, which the cmd/ao suite leaks into the pkg dir.
Fixed an ao-skills-retire bug surfaced here: the historical row was appended at
the end of the `workflows:` section (between historical: and dispositions:)
instead of inside historical:. Corrected manually; full fix tracked separately.
Bounded-context: BC3-Loop
Evidence: cli/cmd/ao/beads_consolidation_test.go
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
S0 foundation slice (v4 additive, no-rename — quorum-ratified). Adds the
artifact-classification fields to every active `- skill:` row + workflows entry
and introduces BC6 Orchestration, without renaming the ledger file, the
`- skill:` row key, or the `domain: "BCn Name"` string format.
- skill-dispositions.yaml: backfill kind/runtime_targets/parity_policy/
capability_class/path/aliases/supersedes on all 71 active rows + 4 workflows;
re-bin the 6 orchestration skills (ntm/swarm/agent-mail/using-atm/
vibing-with-ntm/continuity-loop) into BC6.
- bounded-contexts.yaml: add BC6 Orchestration + OrchestrationPort.
- sku_catalog.py: BC6 enum + cli-command-per-BC carve-out (require >=1 active
artifact per BC; cli coverage only where a BC owns commands).
- generate-registry.sh: emit the new fields (+ derived runtime_reach) and a
first-class `workflows` surface.
- generate-skill-domain-map.sh / check-bounded-contexts-drift.sh: BC-count
sites 5->6; both registry docs carry BC6.
- new gate validate-skill-disposition-schema.sh (+ bats) rejects unknown
kind/capability_class and missing fields, naming the offending row; wired into
regen-all, pre-push-gate, and the Go gate registry.
Renamed-consumer parsers (resolve-skill-path.sh, skills_retire.go, heal.sh) and
the `- skill:` fixtures are byte-untouched.
Closes-scenario: ag-4akl8#backfilled-fields-preserve-the-skill-count
Bounded-context: BC4-Factory
Evidence: docs/contracts/skill-dispositions.yaml
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Shared scripts/lib/resolve-skill-path.sh routes skills/<slug>/ and
skills-codex/<slug>/ paths through the historical: section of
docs/contracts/skill-dispositions.yaml: merged-into rewrites the slug
segment to the target, cut skips visibly (stderr warn), no row / unknown
slug / missing ledger degrade to identity. Exact-slug awk parse (plan does
not match plan-foundry). The 4 validators that hardcoded skill paths
(rpi-contract, lifecycle-guards, cold-start, pre-push-gate) route their
fixed entries through it so folds/cuts auto-retarget instead of needing
hand-repointing. Identity-preserving on the current repo (no historical row
matches any routed active skill): 3 standalone validators byte-identical
before/after; 16 bats incl. 4 validator-integration cases.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
## What
Extends `registry.json` into a **generated SKU capability catalog**
(schema_version 2) — the flagship 3.0 inventory feature. It is a 4th
DERIVED projection (alongside DDD-vocab / Hex-frontmatter /
Gherkin-acceptance), a JOIN of existing sources — **never
hand-authored** (repo bans hand-edited inventory maps). Oracle-validated
design: `.agents/discovery/2026-05-24-capability-inventory-and-gaps.md`.
## Catalog
214 SKU entries: **81 skills + 66 cli-commands + 64 gates + 3
reference-impls**. Each entry: `sku` (stable id), `name`, `type`,
`bounded_context`, `hex_role`, `tier`, `purpose`, `status` (active |
deprecated | planned | alias-of:<sku>), `disposition`, `consumes`,
`produces`, `drives_commands`, `driven_by_skills`.
`registry.json` schema v2 is a strict **superset** of v1 — existing
`summary`/`surfaces`/`cadence_recommendations` consumers (the Go `ao
registry` command) keep working; new
`capabilities`/`capability_summary`/`cli_top_level_commands` blocks are
added.
## The join key — `drives_commands`
Derived from each skill **BODY** (SKILL.md + `references/*`), resolved
against the live cobra tree (`scripts/lib/sku_extract.py`). Stale refs
that resolve to nothing (e.g. the removed `ao schedule`) never become
edges. `driven_by_skills` is the cmd→skill reverse. This closes oracle
gap #1 (no skill↔command linkage anywhere).
## New gate — `validate-sku-catalog-drift`
Three required checks: **(a) drift** (regenerate + diff), **(b) linkage
integrity** (every `drives_commands` edge resolves to a real `ao`
command — closes gaps #1/#2/#3), **(c) coverage** (every BC + every
operating-loop move has an active skill; every BC has a cli-command).
Wired into `validate.yml` + the verdict aggregator.
## Stale refs fixed
`ao schedule` in `skills/crank` + `skills/rpi` best-practices and
`agentopsd` in `skills/swarm` shared-checkout-discipline → reframed to
the hookless / out-of-session (cron-driven `ao rpi`/`ao evolve` via Gas
City) model. Disjoint from PR #524 (which fixed the hooks/inject refs);
rebased on it — `validate-skill-cli-snippets.sh` is now 0.
## Retired
The bogus "163 cli_commands" count (Go-file count, oracle gap #6) → **66
real top-level cobra nodes** from the live tree.
## Gates (all green locally)
registry-check · validate-sku-catalog-drift (3/3) ·
validate-context-map-drift · skill-domain-map-golden ·
bounded-contexts-drift · codex-parity-drift · regen-codex-hashes --check
· contract-compatibility (sku-catalog.md catalogued) · doc-release ·
validate-skill-cli-snippets (0) · skill-frontmatter · heal --strict ·
markdownlint (0) · shellcheck (0 errors) · mkdocs --strict · `go test
./cmd/ao` (9207) · new bats suite (5/5).
Closes-scenario: ag-cbm#sku-catalog
Bounded-context: BC1-Corpus
Evidence: .agents/discovery/2026-05-24-capability-inventory-and-gaps.md