Migrate SYSTEM and AGENTS-{WORKFLOW,CI,CODEX,RUNTIME} into docs owners,
retarget consumers, rewrite the split gate, and craft-densify AGENTS,
README, PRODUCT, and GOALS under the documentation authority graph.
Co-authored-by: Cursor <cursoragent@cursor.com>
## Why
Every PR since #906 has run red on the same 5 checks (correctness
ubuntu+windows, go-gate-shadow, security, summary) — #906 and #907 both
merged with identical red sets. A permanently-red backstop is worse than
none: a real regression is invisible in the noise. This PR fixes every
class and makes green the expected state again.
## What (three commits)
**Wave 1 — the four red check classes** (each verified by running the CI
job's own command):
- **correctness/bats (138 CI-only failures, ~6 env mismatches)**: pawl
verdict-edge fail-closed fires without a trusted `ao` on PATH →
`PAWL_EDGE_FAIL_OPEN=1` on the bats step (the 3 fail-closed regression
suites opt back out); gtimeout probe crashing setup() on Linux; gawk \\"
bracket-expression warning corrupting jq assertions; ripgrep missing on
runners; teardown/skip-path exit codes; taxonomy fixture missing the
now-required observations field.
- **go-gate-shadow (8 full-gate failures → 0)**: #906 landed without a
heal/regen pass — pre/post-mortem pointer skills missing
`hexagonal_role`; codex twins/hashes drift; shrink-only grandfather
lists; evolve SKILL.md referencing 3 deleted scripts;
@covered-by/allowlist conflicts; 3 go.lint findings.
- **security (BLOCKED_HIGH → PASS)**: 3 false positives suppressed
per-finding with justification (gosec G122 on the dev-time arch checker;
2 semgrep mis-traces).
- **correctness/windows**: tests set HOME but Go os.UserHomeDir() reads
USERPROFILE on Windows → cross-platform setHome helper.
**Wave 2 — 18 residual bats failures across 13 files**, each first
proven identical on pristine origin/main (differential run) before
fixing. Content drift (README golden-path literal, discovery references,
bootstrap↔install-bd wiring, pawl-review reachability via council, rpi
helper-rung tokens), validator regressions (validate-codex-rpi-contract
resolver routing, em-loop labels, goal-design fixture), and stale tests
updated only where provably behind a deliberate change (catalog
schema_version pin 1→2, SHA-pinned action regex, retired perf scenario,
drained dangler pin 2→0, tagged-ao test build).
**Commit 3**: the `ao skills unlink` train that landed on main mid-work
re-reddened security with the same semgrep value-struct FP — suppressed
like its skills_link sibling.
## Verification (rebased onto current main tip)
- `ao gate check --full`: **92/93 pass, 0 fail** (1 expected AP7 skip)
- `security-gate.sh --mode quick`: **PASS**
- `go vet` clean; **11,878 Go tests green** (149 packages)
- All 13 wave-2 bats suites **0 not-ok**; CI-sim (no-ao PATH) bats clean
on wave-1 suites
- Windows fix verified via GOOS=windows build+vet (needs the
windows-latest job for final confirmation)
**Acceptance: this PR's own validate.yml run going green.**
Bead: age-htrqp
---------
Co-authored-by: boshu <241868352+boshu2@users.noreply.github.com>
The integration net (FU1 capstone) over the five fresh-install staleness
classes fixed by sibling beads. Consumes the product the way a new user
receives it — clean HOME, fresh non-agentops git project, ao binary, offline
Codex bundle — and exercises first-run so the class cannot regrow:
- scripts/fresh-install-conformance.sh: build/acquire ao (tree default,
--release-tarball mode for the published binary, loud SKIP offline), install
the Codex bundle offline (AGENTOPS_BUNDLE_ROOT), then assert against the REAL
binary output: every quick-start-advertised `ao` command resolves against the
capability contract (extends #907's cobra-tree guard to the integration
level), every /skill mention exists in the installed bundle or repo skills/,
pristine-install `ao doctor` exits 0 with no warnings and names no
repo-relative script fix, and installer skill counts agree
(disk==meta==package_count==printed, the 66-vs-62 class). PASS/FAIL per
assertion + summary table; nonzero exit on any FAIL.
- .github/workflows/fresh-install-conformance.yml: tree-conformance
(ubuntu+macos) on push-to-main paths-filtered + nightly + dispatch;
release-conformance against the published asset on release/dispatch.
- tests/scripts/fresh-install-conformance.bats: proves the harness actually
catches a missing advertised command and a repo-relative doctor fix, passes
on a consistent binary, and loud-SKIPs offline — all offline via a fake ao.
Adopts scripts/lib/preamble.sh; relaxes set -e for record-and-continue.
shellcheck -S warning clean.
Ban CLAIMS of proven/automatic knowledge compounding (unproven — ADR-0004,
ADR-0011) and hookless-3.0-violating 'session hooks' language (docs/3.0.md,
ADR-0009) from user-facing CLI strings + seed/template assets. The claims kept
regrowing (#907, FU4 diet commit) because nothing gated them.
- docs/contracts/forbidden-claims.yaml: seeded lexicon (regex + rationale +
allowed_contexts). Tight patterns require an automatic/every-session qualifier
so the conditional escape-velocity model and the 'flywheel' subsystem name are
NOT banned; the honest 'whether the corpus compounds is unproven' never matches.
- scripts/check-honest-voice.sh: scans cli/cmd + cli/internal Go (excl. *_test.go)
+ seed/template assets; honest-voice:allow suppression; exit 1 naming
phrase+file+line+rationale. Registered contract.honest-voice in the Go gate
registry (Fast|Full, blocking) routed on cli/**, the lexicon, and self-ref.
- cli/cmd/ao/seed.go: sweep — rewrote the stale 'ao init --hooks / session hooks /
knowledge compounds automatically' next-steps + decision-record template to
honest hookless phrasing.
- tests/scripts/check-honest-voice.bats: proves fail on a seeded phrase, pass on
the current tree, suppression, test-file exclusion, and the flywheel/conditional
false-positive guard.
Installers now prove their claims before printing success: the installed
skill directory count, the manifest count `ao doctor` reads, and the
recorded metadata skill_count must all agree, the config-enable entry
must be present, and a sentinel skill must be readable. On any mismatch
the installer exits nonzero naming the delta instead of declaring
victory over a broken state.
Root cause of the audit's "install metadata says 66 skills but manifest
says 62": install-codex-plugin.sh counted on-disk SKILL.md directories
(66) and wrote that into install metadata, while `ao doctor`
(ReadCodexManifestSkillCount) reads the manifest's package_count and
falls back to len(skills[]) = 62 (implementation rows only) when
package_count is absent/stale. The two numbers came from independent
sources with nothing reconciling them. The new self-test computes the
count doctor will use (package_count, else len(skills[])) and refuses to
report success unless disk == manifest == metadata — closing the gap at
the true source rather than papering over it.
- install-codex-plugin.sh: manifest_doctor_count() mirrors doctor;
selftest_codex_plugin() enforces the three-way count identity + config
+ sentinel; success line -> "Verify it worked: restart Codex, /plan".
- install-opencode.sh: AGENTOPS_BUNDLE_ROOT local-source mode (offline);
self-test on linked-vs-source count + plugin link + sentinel.
- install.sh: AGENTOPS_BUNDLE_ROOT local-source mode; orchestrator
self-test tail; fix empty-array expansion under set -u on bash 3.2
(reachable now that local-source mode runs this path offline).
- install-agy.sh: self-test on plugin registration + sentinel skill.
- tests/scripts/installer-selftest.bats: offline coverage incl. the
seeded 66-vs-62 stale-manifest fixture that must fail, and a legacy
no-package_count-but-consistent manifest that must pass (doctor parity).
.ps1 parity (install-codex.ps1/install-ao.ps1) deferred: same latent
divergence exists but no pwsh on the dev host to verify; shipping
untested Windows installer code would undercut the first-run-trust goal.
Remove the vestigial in-repo homebrew-tap/Formula/agentops.rb (frozen at
version 2.31.0 + license MIT while the repo is 3.2.0 / Apache-2.0); the live
tap is generated by GoReleaser into boshu2/homebrew-agentops. Replace the tap
README with a pointer that documents this and forbids re-adding a formula.
Make the .goreleaser.yml archive files: list explicit (README.md, LICENSE,
CHANGELOG.md) so the packed README always matches the tagged commit rather than
relying on GoReleaser defaults.
Add scripts/check-release-parity.sh: asserts (a) tarball README sha256 == repo
README, (b) any in-repo formula license == LICENSE (Apache-2.0), (c) any in-repo
formula version == release tag. Wired into .github/workflows/release.yml as a
post-build gate over each dist/ tarball. Fixture-based bats coverage in
tests/scripts/check-release-parity.bats (red on stale README / wrong license /
stale version, green on parity).
F4 stamped the embedded pawl bundle with a deterministic content hash; this surfaces it in
`ao pawl doctor` so the landed!=installed class (an installed ao silently running OLD pawl scripts —
it bit this very session) is diagnosable.
THE DESIGN IS THE LESSON. Two cross-family codex refutes killed the obvious implementation:
round 1 — I hashed any repo containing scripts/pawl-*.sh and reported STALE. A stranger repo with
same-named files would be falsely failed.
round 2 — I gated that on "identity markers" (the verdict schema + pawls contract). Codex refuted
again, correctly: FILE PRESENCE IS NOT IDENTITY. Every marker is attacker-controlled and
spoofable, so no marker list can ever make it safe.
The real fix is not a better marker list — it is a TRUST BOUNDARY. A staleness FAILURE must never be
asserted against a repo the binary does not trust. So:
- trusted dogfood path (untrustedRoot == "", i.e. the ao binary lives inside the checkout — the ONE
trust decision the whole pawl surface is built on): the LIVE scripts run in place, so there is
nothing to compare and nothing to warn about;
- untrusted/installed path: surface the stamp INFORMATIONALLY (so the operator can diagnose
landed!=installed themselves and rebuild), and assert NO verdict about a repo we cannot trust.
This is the honest feature, not a weaker one: the diagnostic signal is preserved while doctor cannot
be weaponized into failing on an untrusted tree.
Tests: TestPawlBundleStaleness — dogfood surfaces the stamp and never says STALE; an untrusted repo
is never reported STALE *whatever it contains*, including a repo that SPOOFS every identity marker a
naive check might have used.
Claude-Session: https://claude.ai/code/session_01RDWPYn4ZS2SyBkcBvAM18u
THE ADVISORY WINDOW DID EXACTLY ITS JOB, and the answer is NOT the one the bead expected.
Measured over the 261 local verdicts whose head_sha is a live commit: 26 PASS, **199 FLOOR-fail
(76%)**, 36 fail for unrelated (stale/missing) reasons. The FLOOR failures include verdicts from
unambiguously SUBSTANTIVE reviews — codex caught 3 real defects in this session and those verdicts
still fail the floor.
ROOT CAUSE: pawl_evidence_floor demands a `file:line` finding OR a reviewed-scope attestation. But a
CONFIRMED review has, by definition, NO DEFECT TO CITE — a clean review legitimately reads "no
blocking defects". The floor CONFLATES "no defects found" with "no review performed", so every clean
CONFIRM from codex/agy fails it. Right idea, wrong signal.
CONSEQUENCE: FLOOR_ENFORCE_AFTER=2026-07-16 was FIVE DAYS AWAY and would have started HOLDing ~76%
of real reviews on a CALENDAR TRIGGER with nobody watching — silently breaking the membrane.
THIS COMMIT DEFUSES THE CLOCK ONLY (the minimal, safe, time-critical fix): the date is pushed to a
deliberately distant 2027-01-01 so no calendar event can auto-enforce a floor with a measured 76%
false-positive rate. The floor still MEASURES and WARNs (unchanged), and PAWL_FLOOR_ENFORCE=1 still
forces enforcement for tests/opt-in operators — enforcement is now an EXPLICIT, evidence-gated
decision instead of a silent clock event.
FIXING THE SIGNAL stays on .10: a clean CONFIRM must be provable-substantive WITHOUT a defect
citation, which means changing the REVIEWER PROMPT + verdict writer (emit an explicit reviewed-scope
attestation every time), then re-measuring, then flipping deliberately.
Tests: the floor suite is green (it pins behavior via the env override, clock-independent) + a new
regression pinning that the date cannot auto-fire in the near term.
Claude-Session: https://claude.ai/code/session_01RDWPYn4ZS2SyBkcBvAM18u
F13 item 2. cod_dead/agy_dead treated an unreadable `pane_current_command` as ALIVE, so a pane that
was actually GONE was only caught minutes later by the engage deadline. Two shapes now route into
the new bounded _pane_gone re-probe:
- an EMPTY read; and
- a FAILED read — a VANISHED pane makes display-message exit NON-ZERO, and the old
`cmd="$(...)" || return 1` short-circuited straight to ALIVE, so the very case this bead targets
never reached the re-probe at all (caught by a cross-family codex refute; test 4 pins it).
_pane_gone declares death only when BOTH hold: the second read is also unreadable AND the pane
produced NO scrollback change across the probe (a producing pane is alive whatever the read says). A
real foreground command on the re-probe keeps it alive. The conservative bias is preserved for the
genuinely uncertain case; an actually-gone pane now hits the existing respawn path in seconds.
PAWL_PANE_REPROBE_SLEEP tunes the pause (default 2s).
Tests: pawl-pane-reprobe.bats (4): DEAD on two unreadable reads + no activity; ALIVE on a real
re-probe command; ALIVE while still producing output; and the codex-refuted FAILING-first-read case
now correctly reporting DEAD.
Claude-Session: https://claude.ai/code/session_01RDWPYn4ZS2SyBkcBvAM18u
landed!=installed is a recurring class (an installed ao carries whatever pawl scripts were
embedded at build time, silently running old logic — it bit this very session). F4 adds the
MECHANISM: `make sync-hooks` writes a DETERMINISTIC content-hash stamp (sha256 of the three
embedded review scripts) to cli/embedded/pawl/BUNDLE_STAMP — it changes ONLY when they change, in
the SAME sync-hooks run (no always-dirty). `ao pawl doctor` gains a bundle row: the installed-binary
path shows the embedded stamp, the in-checkout dogfood path says live scripts run in place. The row
deliberately does NOT read $ROOT/scripts (on the embedded/stranger path $ROOT is the UNTRUSTED
caller repo; the full race suite's security guard TestEmbeddedPawlBundleCarriesVerdictWriterSibling
correctly caught a first attempt that did). A go:embed drift test (TestEmbeddedBundleStampMatchesScripts)
catches an embedded-script edit that forgot to regenerate the stamp.
Scoped: the trust-aware live-vs-installed STALE comparison (Go doctor layer) + the review-time
staleness warning split to .19 — they need the Go trust split to safely read a checkout.
Claude-Session: https://claude.ai/code/session_01RDWPYn4ZS2SyBkcBvAM18u