Commit Graph

207 Commits

Author SHA1 Message Date
Bo 2a33a64bb3 chore(ci): CLI surface count regen script + fast pre-flight (ag-lkxx #cli-surface-regen) (#760)
## Summary
- New `scripts/update-cli-surface-counts.sh` with `--check` (default)
and `--fix` modes to synchronize CLI heading counts across the smoke
script and canary matrix JSON
- Wires fast pre-flight count-check into contracts-sync CI (catches
drift in ms before the 180s canary build)
- Adding a new ao CLI command is now: add Go code →
`generate-cli-reference.sh` → `update-cli-surface-counts.sh --fix` →
push

## Test plan
- [x] Script passes in check mode with current counts (75/188/263)
- [x] Script detects simulated drift (exit 1 + clear error message)
- [x] `--fix` mode correctly updates smoke script and matrix JSON
- [x] Fixed files are byte-identical to originals (round-trip verified)
- [x] `bash -n` syntax check passes
- [x] Go build passes in worktree
- [ ] CI contracts-sync passes with new pre-flight step

Closes-scenario: ag-lkxx#cli-surface-regen
Bounded-context: BC5-Runtime
Evidence: scripts/update-cli-surface-counts.sh
2026-06-06 11:23:49 -04:00
Bo 1c2ab1b54e ci: cancel-superseded + parallel bats + union-merge generated files (ag-bdg1) (#800)
## Why
The PR drain became a CI-wait treadmill. A 4-lens council (2026-06-06)
found three non-quorum-sensitive, mutually-reinforcing fixes. (Merge
queue — the obvious answer — is **impossible**: it's org-only and this
is a user-owned repo; strict-up-to-date was relaxed permanently
instead.)

## What
1. **`concurrency` cancel-in-progress (PRs only).** validate.yml had no
concurrency group, so every update-branch/force-push left stale runs
consuming the 20-slot free pool — a self-DoS that produced a 19-deep
queue. Now superseded PR runs cancel; **main/tag pushes never cancel**
(`cancel-in-progress` is false unless pull_request).
2. **4-way parallel bats.** Serial `bats tests/scripts/*.bats` was ~237s
= **55% of the `correctness` critical path**. `--jobs 4
--no-parallelize-within-files` targets ~150s off every run (within-file
order preserved; only independent files run concurrently). Installs GNU
`parallel`.
3. **`.gitattributes merge=union`** on `registry.json`,
`*.agentops-generated.json`, `context-map.md`, `COMMANDS.md` — kills the
O(n²) textual re-conflicts these generated files cause during multi-PR
drains (regen-all restores canonical bytes). `cli/embedded/**` +
`skills-codex/**` stay committed (go:embed / tarball-shipped).

## Risk / trial
Only #2 carries risk (parallel test collisions) — **this PR's own
`correctness` run is the live trial**: if a bats suite collides under
`--jobs 4`, correctness fails here and I drop it. The required-check SET
(`summary`) and its semantics are unchanged, so this is not
quorum-sensitive.

Closes-scenario: ag-bdg1#ci-throughput
Bounded-context: BC5-Runtime
Evidence: .github/workflows/validate.yml
2026-06-06 10:59:53 -04:00
Bo a999bcbe62 fix(embedded): restore emptied standards embeds — main hotfix (ag-arpk follow-up) (#799)
## Main is red — hotfix

`cli/embedded/skills/standards/references/{go,test-pyramid}.md` were
**empty (0 bytes)** on main HEAD; #776's merge truncated them.
`contracts-sync` / `correctness` run the full embedded-sync check on
main-push (changed-files-scoped on PRs, so it slipped through during the
batch drain) → main Validate failed (run on 3df599df, def8fc9b).

## Fix
- Re-synced both embedded files from their sources via the
`cli/Makefile` recipe (`cp ../skills/standards/references/*`).
`scripts/validate-embedded-sync.sh` now reports **All embedded files are
in sync**.
- Corrected that script's stale fix hint: `make sync-embedded` (no such
target) → `make sync-hooks` (the real recipe).

## Evidence
`bash scripts/validate-embedded-sync.sh` → "All embedded files are in
sync."

Bounded-context: BC5-Runtime
Evidence: cli/embedded/skills/standards/references/go.md
2026-06-06 10:51:59 -04:00
dependabot[bot] edb6dd4186 deps(actions): bump actions/setup-node from 4 to 6 (#678)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4
to 6.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/setup-node/releases">actions/setup-node's
releases</a>.</em></p>
<blockquote>
<h2>v6.0.0</h2>
<h2>What's Changed</h2>
<p><strong>Breaking Changes</strong></p>
<ul>
<li>Limit automatic caching to npm, update workflows and documentation
by <a
href="https://github.com/priyagupta108"><code>@​priyagupta108</code></a>
in <a
href="https://redirect.github.com/actions/setup-node/pull/1374">actions/setup-node#1374</a></li>
</ul>
<p><strong>Dependency Upgrades</strong></p>
<ul>
<li>Upgrade ts-jest from 29.1.2 to 29.4.1 and document breaking changes
in v5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/setup-node/pull/1336">#1336</a></li>
<li>Upgrade prettier from 2.8.8 to 3.6.2 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/setup-node/pull/1334">#1334</a></li>
<li>Upgrade actions/publish-action from 0.3.0 to 0.4.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/setup-node/pull/1362">#1362</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-node/compare/v5...v6.0.0">https://github.com/actions/setup-node/compare/v5...v6.0.0</a></p>
<h2>v5.0.0</h2>
<h2>What's Changed</h2>
<h3>Breaking Changes</h3>
<ul>
<li>Enhance caching in setup-node with automatic package manager
detection by <a
href="https://github.com/priya-kinthali"><code>@​priya-kinthali</code></a>
in <a
href="https://redirect.github.com/actions/setup-node/pull/1348">actions/setup-node#1348</a></li>
</ul>
<p>This update, introduces automatic caching when a valid
<code>packageManager</code> field is present in your
<code>package.json</code>. This aims to improve workflow performance and
make dependency management more seamless.
To disable this automatic caching, set <code>package-manager-cache:
false</code></p>
<pre lang="yaml"><code>steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
  with:
    package-manager-cache: false
</code></pre>
<ul>
<li>Upgrade action to use node24 by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/setup-node/pull/1325">actions/setup-node#1325</a></li>
</ul>
<p>Make sure your runner is on version v2.327.1 or later to ensure
compatibility with this release. <a
href="https://github.com/actions/runner/releases/tag/v2.327.1">See
Release Notes</a></p>
<h3>Dependency Upgrades</h3>
<ul>
<li>Upgrade <code>@​octokit/request-error</code> and
<code>@​actions/github</code> by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/setup-node/pull/1227">actions/setup-node#1227</a></li>
<li>Upgrade uuid from 9.0.1 to 11.1.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/setup-node/pull/1273">actions/setup-node#1273</a></li>
<li>Upgrade undici from 5.28.5 to 5.29.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/setup-node/pull/1295">actions/setup-node#1295</a></li>
<li>Upgrade form-data to bring in fix for critical vulnerability by <a
href="https://github.com/gowridurgad"><code>@​gowridurgad</code></a> in
<a
href="https://redirect.github.com/actions/setup-node/pull/1332">actions/setup-node#1332</a></li>
<li>Upgrade actions/checkout from 4 to 5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/setup-node/pull/1345">actions/setup-node#1345</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/priya-kinthali"><code>@​priya-kinthali</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/setup-node/pull/1348">actions/setup-node#1348</a></li>
<li><a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/setup-node/pull/1325">actions/setup-node#1325</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-node/compare/v4...v5.0.0">https://github.com/actions/setup-node/compare/v4...v5.0.0</a></p>
<h2>v4.4.0</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/actions/setup-node/commit/48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e"><code>48b55a0</code></a>
Update Node.js versions in versions.yml and bump package to v6.4.0 (<a
href="https://redirect.github.com/actions/setup-node/issues/1533">#1533</a>)</li>
<li><a
href="https://github.com/actions/setup-node/commit/ab72c7e7eba0eaa11f8cab0f5679243900c2cac9"><code>ab72c7e</code></a>
Upgrade <a href="https://github.com/actions"><code>@​actions</code></a>
dependencies (<a
href="https://redirect.github.com/actions/setup-node/issues/1525">#1525</a>)</li>
<li><a
href="https://github.com/actions/setup-node/commit/53b83947a5a98c8d113130e565377fae1a50d02f"><code>53b8394</code></a>
Bump minimatch from 3.1.2 to 3.1.5 (<a
href="https://redirect.github.com/actions/setup-node/issues/1498">#1498</a>)</li>
<li><a
href="https://github.com/actions/setup-node/commit/54045abd5dcd3b0fee9ca02fa24c57545834c9cc"><code>54045ab</code></a>
Scope test lockfiles by package manager and update cache tests (<a
href="https://redirect.github.com/actions/setup-node/issues/1495">#1495</a>)</li>
<li><a
href="https://github.com/actions/setup-node/commit/c882bffdbd4df51ace6b940023952e8669c9932a"><code>c882bff</code></a>
Replace uuid with crypto.randomUUID() (<a
href="https://redirect.github.com/actions/setup-node/issues/1378">#1378</a>)</li>
<li><a
href="https://github.com/actions/setup-node/commit/774c1d62961e73038a114d59c8847023c003194d"><code>774c1d6</code></a>
feat(node-version-file): support parsing <code>devEngines</code> field
(<a
href="https://redirect.github.com/actions/setup-node/issues/1283">#1283</a>)</li>
<li><a
href="https://github.com/actions/setup-node/commit/efcb663fc60e97218a2b2d6d827f7830f164739e"><code>efcb663</code></a>
fix: remove hardcoded bearer (<a
href="https://redirect.github.com/actions/setup-node/issues/1467">#1467</a>)</li>
<li><a
href="https://github.com/actions/setup-node/commit/d02c89dce7e1ba9ef629ce0680989b3a1cc72edb"><code>d02c89d</code></a>
Fix npm audit issues (<a
href="https://redirect.github.com/actions/setup-node/issues/1491">#1491</a>)</li>
<li><a
href="https://github.com/actions/setup-node/commit/6044e13b5dc448c55e2357c09f80417699197238"><code>6044e13</code></a>
Docs: bump actions/checkout from v5 to v6 (<a
href="https://redirect.github.com/actions/setup-node/issues/1468">#1468</a>)</li>
<li><a
href="https://github.com/actions/setup-node/commit/8e494633d082d609d1e9ff931be32f8a44f1f657"><code>8e49463</code></a>
Fix README typo (<a
href="https://redirect.github.com/actions/setup-node/issues/1226">#1226</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/actions/setup-node/compare/v4...v6">compare
view</a></li>
</ul>
</details>
<br />

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bo <boden.fuller@gmail.com>
2026-06-06 10:17:00 -04:00
Bo baf6ffddd2 ci(validate): run summary check on merge_group for merge-queue support (ag-arpk #merge-group-ci-support) (#797)
## What

Prerequisite for enabling GitHub's **merge queue** on `main`. The
`summary` required check is produced by `validate.yml`, which only
triggered on `push`+`pull_request`. With a merge queue, GitHub validates
queued PRs on a `merge_group` ref — if `summary` doesn't run there,
**the queue hangs forever**.

## Change (safe — no behavior change until the queue is on)

1. Add the `merge_group:` trigger to `validate.yml`.
2. Force a full validation run on `merge_group` (mirror the existing
release-tag path), because the `dorny/paths-filter` step has no PR base
in-queue and would otherwise skip all validation.

`push`/`pull_request` runs are unchanged (release stays false, filter
runs as before). `merge_group` events don't fire until the queue is
enabled — so this is inert until step 2 of the rollout.

## Next step (after this merges)

Enable the `merge_queue` rule on the `main` ruleset → the ~25-PR backlog
batch-drains instead of the strict-serial grind.

Closes-scenario: ag-arpk#merge-group-ci-support
Bounded-context: BC5-Runtime
Evidence: .github/workflows/validate.yml
2026-06-06 09:58:50 -04:00
Bo 015bb52286 ci(ci): wire scan_descriptions --probe as I0-informational receipt lane (ag-iyu4 #probe-i0) (#738)
Wires `scripts/skill-probe-i0.sh` — the deterministic lexical trigger
ranker (`scan_descriptions.py --probe`, ag-7led) — as an
**I0-INFORMATIONAL** step inside the `skill-eval` job.

## What it does
- Runs the probe over every `trigger_probes:` phrase in the corpus,
writes a per-skill JSON receipt to `.agents/ao/skill-eval/<id>.json`,
and uploads it as the `skill-retrieval-probe-receipts` workflow
artifact.
- Asserts byte-stable **determinism** across two runs; a
non-deterministic probe is surfaced as a `::warning::` only.
- **Not a PR check:** `continue-on-error: true` lives INSIDE the
existing `skill-eval` job, so no new GitHub check is created and the
step can never block (or even advisory-fail) a merge. I0 =
informational.
- No-ops cleanly (exit 0, no receipts) until a skill opts in with
`trigger_probes:`.

## Acceptance
- Probe emits the JSON-receipt artifact AND no new PR check appears
(verify absent from `gh pr checks`).
- A deliberately non-deterministic probe input is caught by the
determinism assertion before it could become a gate —
`tests/scripts/skill-probe-i0.bats` (3 specs, all green).

## 2-week stability baseline
Documented in three places (validate.yml comment,
`docs/contracts/ci-jobs.yaml` skill-eval entry,
`skills/skill-builder/SKILL.md` trigger_probes note): keep `--probe`
I0-only until the receipt lane runs green + byte-stable across the
corpus for a 2-week baseline of merges before any blocking-gate
promotion.

Closes-scenario: ag-iyu4#probe-i0
Bounded-context: BC1-Corpus
Evidence: scripts/skill-probe-i0.sh
2026-06-05 01:00:52 -04:00
Bo 2a09d29a0e ci(ci): wire skill-eval as T1 changed-files-scoped CI job + pinned ms install (ag-ktjb #skill-eval) (#731)
Wire the existing `scripts/skill-eval.sh` (#726) as a **T1 (≤5min)
changed-files-scoped** CI job in `.github/workflows/validate.yml`.

## What
- New `skill-eval` job (T1, `timeout-minutes: 5`), gated on
`skills`/`ci` path changes.
- **Setup installs PINNED `ms` v0.1.2** (`git+meta_skill@b0bb6bd541…`)
and **gates on `ms --version` + exact-0.1.2 check BEFORE running**. A
failed install or version mismatch **HARD-FAILS the job (red)** — never
green-skips. This is deliberate: the silent-skip-on-absent-ms
anti-pattern recreates the exact "no skill evaluation" gap this gate
exists to close (the same contract `skill-eval.sh` enforces internally).
- **Changed-files scoping** reuses the repo's existing `eval`-job
`skilldelta` convention (`git diff --name-only` against the PR merge
base), kept to `skills/<id>/**` so `ms` runs **only** for skills the PR
touched (`_fixtures`/`_scaffolding` excluded).
- Reports as its own check in `gh pr checks`: added to `summary.needs`,
the parity-gated `docs/contracts/ci-jobs.yaml` manifest, and the
regenerated `AGENTS-CI.md` table.

## Verification (local)
- `scripts/generate-ci-jobs-table.sh --check` → parity PASS (manifest ↔
summary.needs).
- `tests/scripts/generate-ci-jobs-table.bats`,
`test-ci-policy-parity.sh`, `test-pathfilter-gate-coverage.bats` → all
green.
- `validate.yml` parses as valid YAML; both embedded `run:` scripts are
`shellcheck -S warning` clean.
- `markdownlint AGENTS-CI.md` clean.
- `scripts/skill-eval.sh` smoke against the installed `ms 0.1.2`: bad
fixture → non-zero (names no-secrets/required-metadata), good fixture →
0.

Closes-scenario: ag-ktjb#skill-eval
Bounded-context: BC4-Validation
Evidence: docs/contracts/ci-jobs.yaml
2026-06-05 02:24:12 +00:00
Bo 8095877de8 fix(ci): install PyYAML whenever the skill-gates drift sweep runs (#728)
## Problem
The `skill-gates` job's **drift sweep** (`regen-all.sh --check`) runs on
`skills | go | contracts | docs | shell | ci`, but the **Install python
schema deps** step (which installs PyYAML) only ran on `skills |
contracts | ci`.

A **docs-only PR** (e.g. a README/asset change) therefore triggers the
drift sweep but skips the PyYAML install, so the registry /
skill-domain-map / context-map checks fail with `No module named 'yaml'`
— blocking unrelated PRs.

## Fix
Broaden the install step's `if` to cover every trigger the drift sweep
runs on (adds `go`, `docs`, `shell`). One-line condition change.

Found while shipping a docs-only README video PR (#727) that got blocked
by this.
2026-06-04 22:42:55 +00:00
Bo 092b99dad9 feat(skills): add skill-eval.sh ms-backed quality gate (ag-yzoz #skill-eval) (#726)
## What

Adds `scripts/skill-eval.sh` — a thin gate that runs one skill's
`SKILL.md` through Jeff Emanuel's `ms` (meta_skill) `lint` + `validate`.
The wrapper decides the gate purely on whether a **blocking** `rule_id`
appears (ms's own severity/exit is not trusted):

- Blocking: `no-secrets`, `no-injection`, `safe-paths`,
`required-metadata`, `no-cycle`, `valid-version` → non-zero exit.
- Everything else → annotated only (`::warning::`), never fails.
- `ms` not on PATH (or `MS_BIN` missing) → **loud `::error::` hard-fail
(exit 3)**, never skip-and-pass — a silent skip would recreate the
no-evaluation gap this gate closes.

Planted `skills/_fixtures/{bad,good}-skill/SKILL.md` fixtures +
`tests/scripts/skill-eval.bats` (10 tests) exercise the
bad/good/ms-absent contracts.

## Fixture scoping

`skills/_fixtures/` is non-skill scaffolding (planted fixtures). The
skill-enumerating gates/generators are taught to skip `skills/_*` so the
fixtures never inflate the skill count (stays 82) or fail registry /
schema / wiring / runtime / count gates. Touched enumerators:
`generate-registry`, `validate-skill-schema`, `sync-skill-counts`,
`validate-skill-count`, `generate-skill-catalog`,
`generate-context-map`, `validate-manifests`, `check-wiring-closure`,
`validate-headless-runtime-skills`, `lib/sku_catalog.py`,
`check-alias-collisions`, `run-all`, `test-runtime-opencode-smoke`,
`release-smoke-test`, `smoke-test`, `validate-skill`.

Does **not** touch `validate.yml` — wiring skill-eval.sh into CI is a
separate bead.

Epic: ag-czzf

## Verification

- `bats tests/scripts/skill-eval.bats` → 10/10 pass (ms 0.1.2 on PATH)
- `shellcheck scripts/skill-eval.sh` → clean
- `check-registry-drift`, `validate-skill-schema`, `sync-skill-counts`,
`run-all`, opencode-smoke → all PASS with fixtures present, skill count
= 82 (fixtures excluded)

Closes-scenario: ag-yzoz#skill-eval
Bounded-context: BC2-Validation
Evidence: scripts/skill-eval.sh

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-06-04 16:38:57 -04:00
Bo 7e8197ba93 ci(skills): consolidate live skill gates into one required skill-gates job (ag-87sv #skill-gates-job) (#723)
Consolidates the live skill-authoring CI gates into one named, required
`skill-gates` job (pure regroup of already-live gates — no behavior
change). Replaces the phantom `scenario-hash-stability` reference in
`CLAUDE.md` with the real `check-scenario-test-linkage` gate now grouped
under `skill-gates`. Adds the `skill-gates` row to the CI-policy-parity
manifest (`docs/contracts/ci-jobs.yaml` + `AGENTS-CI.md`) and the
`gate:skill-gates` SKU to `registry.json` (11→12 gates). New
`tests/scripts/skill-gates.bats` (12 tests) locks the job's structure,
required-check wiring, and drift-sweep behavior.

Part of epic ag-czzf.

Closes-scenario: ag-87sv#skill-gates-job
Bounded-context: BC2-Validation
Evidence: .github/workflows/validate.yml

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-06-04 16:16:04 -04:00
Bo 3d99d4bf69 fix(corpus): unify secret redaction + compile/CI git-safety (ag-sz3h ag-zi90 ag-y2xy #w1-git-safety) (#720)
Completes the **W1 git-safety arc** of epic ag-lhu0 (memory-system
re-architecture) — three beads, one coherent surface, after #719 wired
scrub-on-write into corpus_fs.

- **ag-zi90 (dedup):** `redactContent` (`ao context assemble`) now runs
canonical `llm.Redact` first — a single source of truth for credential
patterns (AWS / GitHub / GitLab / Slack / GCP / PEM, previously
uncovered in this path) — then keeps the supplements `llm.Redact` does
*not* cover: 2-segment app JWTs, sensitive env-var assignments,
high-entropy strings.
- **ag-sz3h (compile chokepoint):** new `ao redact` (stdin→stdout
canonical scrub) bridges the shell compile path; `compile.sh` pipes
every rendered article through it before `os.WriteFile` (loud fallback
if `ao` absent).
- **ag-y2xy (CI backstop):** `check-corpus-secret-scan.sh` scans
**git-tracked** (committed/allowlisted) `.agents` corpus + canon only —
repo-root `.agents` is local-only by policy, so scrub-on-write (#719 +
`ao redact`) is the primary defense and this is the backstop. Wired into
`validate.yml`; bats covers clean / leak-with-file:line /
untracked-not-scanned.

Scrub-on-write is primary; CI is the backstop. Verified: go
build/vet/test (incl. `TestRunRedact`, JWT-dedup regression test green),
bats 3/3, shellcheck clean, conformance regenerated.

Closes-scenario: ag-sz3h#compile-render-scrub
Closes-scenario: ag-zi90#dedup-redactor
Closes-scenario: ag-y2xy#ci-secret-scan
Bounded-context: BC1-Corpus
Evidence: scripts/check-corpus-secret-scan.sh

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-06-04 14:11:55 -04:00
Bo e74d8899fd feat(ci): gate live-hooks reintroduction in docs/ (ag-rryf #doc-hooks-drift-gate) (#713)
## What

Adds `scripts/check-doc-hooks-drift.sh` — a CI regression guard that
fails when a **live-facing** doc reintroduces a live hooks runtime after
the 3.0 hookless teardown:

- `hooks/hooks.json` (retired manifest), a `hooks/<name>.sh` runtime
path,
- `ao hooks` (not a registered command — verified absent from
`cli/cmd/ao/`),
- a bare `session-{start,end,autostart}*.sh` hook-file ref

…unless the **same line** hedges it as hookless / opt-in / historical /
removed / a git-hook. Companion to `sync-skill-counts.sh` (skill-count
drift) and `check-hookless-cold-start.sh` (fixed-list cold-start gate).

## Scope (and why the exclusions)

`docs/` **minus** surfaces that legitimately/archivally name hooks:
- **archival history** — `CHANGELOG.md`, `releases/`, `learnings/`,
`plans/`, `adr/`, `convergence/`, `sovereignty-proof/` (document the
past / the removal itself)
- **opt-in subsystem** — `contracts/` (hooks-authoring opt-in specs),
`runbooks/` (diagnostic enumeration), `code-map/` (point-in-time audits)

The gate keeps the onboarding / operator / architecture narrative
hookless.

## Also: finishes the ag-t1ca residue the gate flags

The ag-t1ca de-hooking missed 8 live-facing lines (gate caught them);
each is reframed:
- stale `ao hooks` command/smoke refs → removed / `ao doctor`
(`cli-skills-map`, `UPGRADING`, `release-e2e-checklist`,
`contracts/release-readiness`)
- present-tense hooks-as-rules → CI gates (`strategic-direction`)
- hook-runtime mentions → `ao curate constrain` / opt-in framing
(`curation-pipeline`, `leverage-points`, `SCHEMAS` legacy-manifest
label, `workflows/session-lifecycle`)

## Verification

- `scripts/check-doc-hooks-drift.sh` → PASS on this branch (FAILed on
all 8 before the reframes)
- `tests/scripts/check-doc-hooks-drift.bats` → 8/8 (positive /
negative×3 / hedge×2 / exclusion / no-docs error)
- `shellcheck` clean · `markdownlint` 0 errors (9 docs) ·
`validate-cli-skills-map.sh` PASS · `generate-ci-jobs-table.sh --check`
PASS · existing `check-hookless-cold-start.sh` still green
- Wired into `validate.yml` lint/contracts (gated on docs/ci/shell
changes) + auto-run via the bats suite

Closes-scenario: ag-rryf#doc-hooks-drift-gate
Bounded-context: BC2-Validation
Evidence: scripts/check-doc-hooks-drift.sh
2026-06-03 22:38:26 -04:00
Bo 81aec49b1c chore(tests): test-isolation ratchet + migrate testutil chdir helpers to t.Chdir (ag-k38x #lint-ratchet) (#699)
## What
Lands the durable mechanism for ag-k38x (codebase-audit P2-1/P2-2): raw
`os.Chdir`/`os.Setenv` in tests mutate process-global cwd/env — a latent
flake landmine once any test opts into `t.Parallel` (cf. **ag-jfzs**,
the memory-sync flake of exactly this class).

The repo has ~163 `os.Chdir` + 22 `os.Setenv` inline sites across 34
files in **non-uniform patterns** (`defer` / `t.Cleanup` / `defer
func(){}()` with reversed order) — a blind sweep would break the suite.
So this ships the **durable gate + the canonical lever**, not a risky
80-site mass-edit:

- **`scripts/check-test-isolation.sh`** — a baseline ratchet (163/22)
wired into the **process-hygiene** job: raw `os.Chdir`/`os.Setenv` in
`*_test.go` may not **grow**; new code must use `t.Chdir`/`t.Setenv` or
the testutil helpers. This pre-empts the flake class from spreading.
- **`testutil_test.go`** — `chdirTemp`/`chdirTo` migrated from
`os.Chdir`+`t.Cleanup` to `t.Chdir` (the shared helpers most tests
delegate to; canonical example).

## Follow-up
The bulk inline-site migration drives the baseline toward zero
incrementally — **ag-k38x stays open** for it (scenario
`#bulk-migration`). Decomposing an ~80-site, non-uniform migration into
*gate-the-growth now + reduce-incrementally* is the responsible path; a
one-shot sed would be the reckless one.

## Verification
Ratchet PASSes at baseline AND catches a synthetic increase; shellcheck
+ `validate-ci-policy-parity` green; `go vet` clean; chdir-helper-caller
tests pass under `-race`.

Closes-scenario: ag-k38x#lint-ratchet
Bounded-context: BC2-Validation
Evidence: scripts/check-test-isolation.sh
2026-06-03 15:14:36 +00:00
dependabot[bot] 10ddd57ae9 deps(actions): bump actions/cache from 4 to 5 (#677)
Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/cache/releases">actions/cache's
releases</a>.</em></p>
<blockquote>
<h2>v5.0.0</h2>
<blockquote>
<p>[!IMPORTANT]
<strong><code>actions/cache@v5</code> runs on the Node.js 24 runtime and
requires a minimum Actions Runner version of
<code>2.327.1</code>.</strong></p>
<p>If you are using self-hosted runners, ensure they are updated before
upgrading.</p>
</blockquote>
<hr />
<h2>What's Changed</h2>
<ul>
<li>Upgrade to use node24 by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/cache/pull/1630">actions/cache#1630</a></li>
<li>Prepare v5.0.0 release by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/cache/pull/1684">actions/cache#1684</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/cache/compare/v4.3.0...v5.0.0">https://github.com/actions/cache/compare/v4.3.0...v5.0.0</a></p>
<h2>v4.3.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Add note on runner versions by <a
href="https://github.com/GhadimiR"><code>@​GhadimiR</code></a> in <a
href="https://redirect.github.com/actions/cache/pull/1642">actions/cache#1642</a></li>
<li>Prepare <code>v4.3.0</code> release by <a
href="https://github.com/Link"><code>@​Link</code></a>- in <a
href="https://redirect.github.com/actions/cache/pull/1655">actions/cache#1655</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/GhadimiR"><code>@​GhadimiR</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/cache/pull/1642">actions/cache#1642</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/cache/compare/v4...v4.3.0">https://github.com/actions/cache/compare/v4...v4.3.0</a></p>
<h2>v4.2.4</h2>
<h2>What's Changed</h2>
<ul>
<li>Update README.md by <a
href="https://github.com/nebuk89"><code>@​nebuk89</code></a> in <a
href="https://redirect.github.com/actions/cache/pull/1620">actions/cache#1620</a></li>
<li>Upgrade <code>@actions/cache</code> to <code>4.0.5</code> and move
<code>@protobuf-ts/plugin</code> to dev depdencies by <a
href="https://github.com/Link"><code>@​Link</code></a>- in <a
href="https://redirect.github.com/actions/cache/pull/1634">actions/cache#1634</a></li>
<li>Prepare release <code>4.2.4</code> by <a
href="https://github.com/Link"><code>@​Link</code></a>- in <a
href="https://redirect.github.com/actions/cache/pull/1636">actions/cache#1636</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/nebuk89"><code>@​nebuk89</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/cache/pull/1620">actions/cache#1620</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/cache/compare/v4...v4.2.4">https://github.com/actions/cache/compare/v4...v4.2.4</a></p>
<h2>v4.2.3</h2>
<h2>What's Changed</h2>
<ul>
<li>Update to use <code>@​actions/cache</code> 4.0.3 package &amp;
prepare for new release by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/cache/pull/1577">actions/cache#1577</a>
(SAS tokens for cache entries are now masked in debug logs)</li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/cache/pull/1577">actions/cache#1577</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/cache/compare/v4.2.2...v4.2.3">https://github.com/actions/cache/compare/v4.2.2...v4.2.3</a></p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/actions/cache/blob/main/RELEASES.md">actions/cache's
changelog</a>.</em></p>
<blockquote>
<h1>Releases</h1>
<h2>How to prepare a release</h2>
<blockquote>
<p>[!NOTE]<br />
Relevant for maintainers with write access only.</p>
</blockquote>
<ol>
<li>Switch to a new branch from <code>main</code>.</li>
<li>Run <code>npm test</code> to ensure all tests are passing.</li>
<li>Update the version in <a
href="https://github.com/actions/cache/blob/main/package.json"><code>https://github.com/actions/cache/blob/main/package.json</code></a>.</li>
<li>Run <code>npm run build</code> to update the compiled files.</li>
<li>Update this <a
href="https://github.com/actions/cache/blob/main/RELEASES.md"><code>https://github.com/actions/cache/blob/main/RELEASES.md</code></a>
with the new version and changes in the <code>## Changelog</code>
section.</li>
<li>Run <code>licensed cache</code> to update the license report.</li>
<li>Run <code>licensed status</code> and resolve any warnings by
updating the <a
href="https://github.com/actions/cache/blob/main/.licensed.yml"><code>https://github.com/actions/cache/blob/main/.licensed.yml</code></a>
file with the exceptions.</li>
<li>Commit your changes and push your branch upstream.</li>
<li>Open a pull request against <code>main</code> and get it reviewed
and merged.</li>
<li>Draft a new release <a
href="https://github.com/actions/cache/releases">https://github.com/actions/cache/releases</a>
use the same version number used in <code>package.json</code>
<ol>
<li>Create a new tag with the version number.</li>
<li>Auto generate release notes and update them to match the changes you
made in <code>RELEASES.md</code>.</li>
<li>Toggle the set as the latest release option.</li>
<li>Publish the release.</li>
</ol>
</li>
<li>Navigate to <a
href="https://github.com/actions/cache/actions/workflows/release-new-action-version.yml">https://github.com/actions/cache/actions/workflows/release-new-action-version.yml</a>
<ol>
<li>There should be a workflow run queued with the same version
number.</li>
<li>Approve the run to publish the new version and update the major tags
for this action.</li>
</ol>
</li>
</ol>
<h2>Changelog</h2>
<h3>5.0.4</h3>
<ul>
<li>Bump <code>minimatch</code> to v3.1.5 (fixes ReDoS via globstar
patterns)</li>
<li>Bump <code>undici</code> to v6.24.1 (WebSocket decompression bomb
protection, header validation fixes)</li>
<li>Bump <code>fast-xml-parser</code> to v5.5.6</li>
</ul>
<h3>5.0.3</h3>
<ul>
<li>Bump <code>@actions/cache</code> to v5.0.5 (Resolves: <a
href="https://github.com/actions/cache/security/dependabot/33">https://github.com/actions/cache/security/dependabot/33</a>)</li>
<li>Bump <code>@actions/core</code> to v2.0.3</li>
</ul>
<h3>5.0.2</h3>
<ul>
<li>Bump <code>@actions/cache</code> to v5.0.3 <a
href="https://redirect.github.com/actions/cache/pull/1692">#1692</a></li>
</ul>
<h3>5.0.1</h3>
<ul>
<li>Update <code>@azure/storage-blob</code> to <code>^12.29.1</code> via
<code>@actions/cache@5.0.1</code> <a
href="https://redirect.github.com/actions/cache/pull/1685">#1685</a></li>
</ul>
<h3>5.0.0</h3>
<blockquote>
<p>[!IMPORTANT]
<code>actions/cache@v5</code> runs on the Node.js 24 runtime and
requires a minimum Actions Runner version of <code>2.327.1</code>.</p>
</blockquote>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/actions/cache/commit/27d5ce7f107fe9357f9df03efb73ab90386fccae"><code>27d5ce7</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/cache/issues/1747">#1747</a>
from actions/yacaovsnc/update-dependency</li>
<li><a
href="https://github.com/actions/cache/commit/f280785d7b6e1884c7d12b9136eb0f4a1574fcfd"><code>f280785</code></a>
licensed changes</li>
<li><a
href="https://github.com/actions/cache/commit/619aeb1606e195be0b36fd0ff68dcf1aff6b65a7"><code>619aeb1</code></a>
npm run build generated dist files</li>
<li><a
href="https://github.com/actions/cache/commit/bcf16c2893940a4899761e55c7ac3c1cf88a04f6"><code>bcf16c2</code></a>
Update ts-http-runtime to 0.3.5</li>
<li><a
href="https://github.com/actions/cache/commit/668228422ae6a00e4ad889ee87cd7109ec5666a7"><code>6682284</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/cache/issues/1738">#1738</a>
from actions/prepare-v5.0.4</li>
<li><a
href="https://github.com/actions/cache/commit/e34039626f957d3e3e50843d15c1b20547fc90e2"><code>e340396</code></a>
Update RELEASES</li>
<li><a
href="https://github.com/actions/cache/commit/8a671105293e81530f1af99863cdf94550aba1a6"><code>8a67110</code></a>
Add licenses</li>
<li><a
href="https://github.com/actions/cache/commit/1865903e1b0cb750dda9bc5c58be03424cc62830"><code>1865903</code></a>
Update dependencies &amp; patch security vulnerabilities</li>
<li><a
href="https://github.com/actions/cache/commit/565629816435f6c0b50676926c9b05c254113c0c"><code>5656298</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/cache/issues/1722">#1722</a>
from RyPeck/patch-1</li>
<li><a
href="https://github.com/actions/cache/commit/4e380d19e192ace8e86f23f32ca6fdec98a673c6"><code>4e380d1</code></a>
Fix cache key in examples.md for bun.lock</li>
<li>Additional commits viewable in <a
href="https://github.com/actions/cache/compare/v4...v5">compare
view</a></li>
</ul>
</details>
<br />

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-03 11:36:08 +00:00
Bo d9c9321c97 ci: remove claude-review workflow + merge gate (ag-7x7u #remove-claude-gate) (#694)
Removes the claude-review merge gate per operator decision (2026-06-02).

## Why
The Cursor agent (bot author) pushing to PR branches made the required
`claude-review` check abort on *non-human actor: cursor*, deadlocking
merges (blocked #692 ~2h, churned #693). Cursor Bugbot already provides
review.

## What
- Removed `claude-review` from main branch-protection required checks
(now `[summary]`) — done via API.
- Deletes `.github/workflows/claude-code-review.yml` so the failing
check stops appearing entirely.

Supersedes the earlier allowed_bots:cursor approach on this branch.

Closes-scenario: ag-7x7u#remove-claude-gate
Bounded-context: BC2-Validation
Evidence: .github/workflows/claude-code-review.yml
2026-06-02 23:43:15 +00:00
Bo efb7fd66e9 feat(ci): repo-wide path-filter/gate-target coverage audit + gap fixes (ag-g9ex #pathfilter-audit) (#666)
## What

Repo-wide audit of every file-reading CI gate in
`.github/workflows/validate.yml` against the invariant the #634/#638
trunk-heal proved: **a gate that READS a file must be TRIGGERED by a
path-filter covering that file.** A docs-thinning commit (`bbfd278e`)
silently disabled the security redteam canary because the `contracts`
filter didn't cover `AGENTS.md`, and it surfaced only on a later
unrelated PR.

Closes **ag-g9ex** (Slice5[GOVERNANCE]). Derived from
`.agents/retro/2026-05-31-bead-crank-postmortem.md` §4(#2) + §7(#5).

## Audit

`docs/contracts/ci-pathfilter-coverage-audit.md` enumerates every gate
that reads specific files, the files it reads, the filter(s) that
trigger it, and a COVERED/GAP verdict. 13 gates audited + verified
non-findings.

## Gaps found and fixed (2)

1. **AGENTS tiered-split siblings uncovered (direct #634 class).**
`scripts/validate-agents-split.sh` validates `AGENTS.md` +
`AGENTS-{WORKFLOW,CI,CODEX,RUNTIME}.md`, but the gate triggered on
`docs/ci/shell` and the four siblings were covered by **no** consumed
filter. A sibling-only edit would skip the split gate. **Fix:** added
the siblings to the `contracts` filter + `contracts` to the gate trigger
(mirrors #638).
2. **wiring-closure not re-triggered by GOALS.md de-wire.**
`scripts/check-wiring-closure.sh` greps `GOALS.md`/`GOALS.yaml` to
confirm every `check-*.sh` is referenced, but triggered on
`go/hooks/shell/ci` — not `goals`. A GOALS.md-only de-wire would skip
it. **Fix:** added `goals` to the gate trigger.

## Governance (--admin self-merge, §7 #4)

Documented as policy in the audit doc rather than shipped as a
half-working CI gate (GitHub Actions cannot observe `--admin` from
inside a `pull_request` workflow; building a fake gate would be
over-engineering against the bead's explicit guidance). Mechanical
enforcement filed as a follow-up.

## Guard test

Extended `tests/scripts/test-pathfilter-gate-coverage.bats` (the #638
guard) with 3 new assertions: AGENTS-split targets ⊂ `contracts`, split
gate triggers on `contracts`, wiring-closure triggers on `goals`. All 10
tests pass.

## Gates run (local)
- `python3 yaml.safe_load(validate.yml)` → VALID
- `bats test-pathfilter-gate-coverage.bats` → 10/10 ok
- `scripts/check-contracts-structural-floor.sh` → PASS (46 contracts)
- `scripts/validate-ci-policy-parity.sh` → PASS (11 rows)
- `git diff origin/main --stat` → scope-only (4 files)

Closes-scenario: ag-g9ex#pathfilter-audit
Bounded-context: BC2-Validation
Evidence: docs/contracts/ci-pathfilter-coverage-audit.md
2026-05-31 16:31:59 -04:00
Bo 81c9f305ac feat(ci): witness Dolt->JSONL re-derive + hash-compare (ag-lmdx.3 #witness-crosscheck) (#656)
## What

The **watcher that watches the watcher** (ag-lmdx.3). A blocking CI gate
that re-derives the hash-chained JSONL witness **from the Dolt
projection** and hash-compares it against the git-committed witness
ledger — proving Dolt is a *faithful projection* of the canonical event
log.

This is the **inverse** of drrebuild (#646): drrebuild rebuilds the Dolt
context graph FROM the witness (JSONL + git blobs); this rebuilds the
witness FROM Dolt rows. Together they pin both ends of the projection.
Dolt history is rewritable (reset/--force/root) so it is NOT
tamper-evidence; the git-anchored JSONL witness is. A Dolt rewrite that
did not also rewrite the committed witness is caught here, and nowhere
else.

## How

- `cli/internal/drwitness/` — re-derive + cross-check core. Reuses
drrebuild's exact hash discipline (`ComputeEventHashes`: payload_hash =
sha256(canonical-JSON), hash = sha256(payload_hash + "\n" + prev_hash))
and canonical edge ordering. Re-derivation is order-independent
(canonical sort then re-seal), which is what makes the hash-compare
meaningful.
- `cli/cmd/witness-crosscheck/` — standalone helper main (like
`cmd/skill-frontmatter-json`), **NOT** an `ao` subcommand (no CLI
surface added).
- `scripts/witness-dolt-jsonl-crosscheck.sh` — hermetic CI gate. CI has
no live Dolt, so it drives committed fixtures through the helper and
asserts the gate fires correctly.
- `tests/scripts/` + `tests/fixtures/witness/` — bats behavioral proof.

## Scenarios (acceptance)

- **Clean state passes** — the faithful Dolt projection re-derives to
the committed witness; gate exits 0.
- **Tampered Dolt fails** — a rewritten Dolt row no longer matches the
committed witness head; helper reports `DIVERGED`, gate exits non-zero.

## Verification

- `go build ./... && go vet ./... && go test ./...` (full suite: 11948
passed / 71 pkgs; drwitness: 7/7)
- `bats tests/scripts/witness-dolt-jsonl-crosscheck.bats` — 6/6
- `shellcheck -S warning` — clean
- `scripts/validate-ci-policy-parity.sh` — PASS
- diff is scope-only (10 files, additions); the sibling wave-pregate
(#655) is present and untouched.

Closes-scenario: ag-lmdx.3#witness-crosscheck
Bounded-context: BC4-Factory
Evidence: scripts/witness-dolt-jsonl-crosscheck.sh
2026-05-31 14:39:13 -04:00
Bo a6336b7c34 feat(provenance): ao provenance trace --orphans --strict gate (ag-x31t.6 #provenance-orphan-gate) (#653)
## What

Adds `ao provenance trace --orphans --strict`: detects provenance
orphans — artifact nodes with **no inbound authored/inferred edge** — by
generalizing the `goals_trace_orphans` no-inbound chain-gap detection
onto the provenance graph. Wires it as a blocking CI gate.

## How

- **`cli/internal/provenancegraph/orphans.go`** — `ReadGraphRecords`
(parses the goalstrace `Node`/`Edge` JSONL contract that the seeded
fixtures use) + `FindOrphans` (an artifact node is an orphan iff no
edge's `to_id` targets it; it flips green the moment any inbound edge is
added).
- **`cli/cmd/ao/provenance_trace.go`** — the `trace --orphans [--strict]
[--json] [--graph <path>]` subcommand. `--strict` exits non-zero when
orphans exist; `--json` emits one finding per line.
- **`scripts/check-provenance-orphans.sh`** + a new **blocking** step in
`.github/workflows/validate.yml` (in the existing goals/spec-linkage
job, right after the warn-only `ao goals trace --orphans`). The gate
asserts the strict audit **catches** each seeded orphan fixture and
**passes** once an inbound edge wires the artifact back to a directive —
so it is deterministically green while proving the detector is wired.

## Tests (TDD-first)

- `cli/cmd/ao/provenance_trace_test.go` (L2): drives the real
`tests/fixtures/provenance/` fixtures against `expected-orphans.json` —
`--strict` catches each of the 3 seeded orphans
(`gate:scenario-hash-stability`, `artifact:scripts/pre-push-gate.sh`,
`claim:65-jobs`), and a wired graph exits 0. Plus mode/flag-guard cases.
- `cli/internal/provenancegraph/orphans_test.go` (L1): detection,
non-artifact nodes never orphaned, deterministic sort, JSONL parsing +
malformed/missing-file rejection.
- `tests/scripts/check-provenance-orphans.bats`: gate-script behavior.

## Derived surfaces regenerated

- `cli/docs/COMMANDS.md` (new `ao provenance trace` subcommand,
cobra-conformance passes)
- CLI surface smoke fixture + matrix (sub 186→187, all 260→261)
- `registry.json` **unchanged** (provenance is already a top-level
command; subcommands aren't counted) — timestamp-only churn reverted.

## Gates

`go build ./... && go vet ./... && go test ./...` (11959 pass) · gosec
clean on new files (one `#nosec G304` on the operator/CI-supplied graph
path, with reason) · `scripts/validate-ci-policy-parity.sh` PASS.

Closes-scenario: ag-x31t.6#provenance-orphan-gate
Bounded-context: BC4-Factory
Evidence: .github/workflows/validate.yml
2026-05-31 14:07:45 -04:00
Bo 2907503176 fix(ci): cover gate-target files in path filters (ag-nl1u #pathfilter-coverage) (#638)
## What

Closes both **ag-nl1u** and **ag-n4m7** — same class of bug (CI
path-filter gap) on the same surface (`.github/workflows/validate.yml`),
folded into one coherent-arc PR so they don't collide.

A gate that reads a specific set of files must be triggered by a
change-filter covering those files. When it isn't, an edit to a guarded
file SKIPS the gate that guards it, and the required `summary`
branch-protection check goes green-but-incomplete — the regression
reaches `main` and surfaces only on a later unrelated PR.

### ag-nl1u — contracts filter omits redteam-pack targets (#634)
The security-suite redteam pack
(`skills/security-suite/references/agentops-redteam-pack.json`) asserts
behavioral contracts against `AGENTS.md`, `docs/ARCHITECTURE.md`,
`docs/CI-CD.md`, `docs/strategic-direction.md`,
`docs/standards/shell-script-standards.md`, `skills/security/SKILL.md`,
`skills/security-suite/SKILL.md`. The `contracts` filter only matched
`schemas/**` + `docs/contracts/**`, so editing any guarded file skipped
the contracts-sync canaries that guard it (the #634 break, fixed
reactively). **Fix:** extend `contracts` to a superset of every
redteam-pack target glob.

### ag-n4m7 — correctness gates skip on GOALS.md/scenarios (#591/#593)
The directive-to-scenario correctness gates police `GOALS.md` +
`spec/scenarios/**` but ran only on `go || docs || ci`. A GOALS.md-only
edit citing a phantom scenario merged with the gate skipped. **Fix:**
add a `goals` filter (`GOALS.md`, `spec/scenarios/**`,
`docs/adr/ADR-0003*`) and wire `|| needs.changes.outputs.goals` into
goals-validate, F1/F2 spec-link e2e, directive-to-scenario lint,
trace-orphans, scenario-to-test linkage (and the Go setup/build steps
those depend on, since they invoke `cli/bin/ao`).

## Guard test
`tests/scripts/test-pathfilter-gate-coverage.bats` (7 tests) parses the
pack JSON + the validate.yml filters and asserts the `contracts` filter
is a superset of the pack targets and the `goals` filter + gate wiring
stay in place. Prevents this class from regressing.

## Verification
- `python3 + PyYAML` parse of validate.yml: valid; both filters present.
- `bats tests/scripts/test-pathfilter-gate-coverage.bats` -> 7/7 pass.
- `scripts/validate-ci-policy-parity.sh` -> PASS (job blocking
classification unchanged).
- Sibling `test-bats-path-filter-wiring.bats` +
`test-strict-coverage-ci-wiring.bats` -> 8/8 pass (no regression).

## Scope
Only `.github/workflows/validate.yml` + one new guard test under
`tests/`. No other surface touched.

Closes-scenario: ag-nl1u#pathfilter-coverage
Bounded-context: BC2-Validation
Evidence: .github/workflows/validate.yml
2026-05-31 12:35:11 -04:00
Bo c6ecb0adf8 ci(claude-review): soft-fail only on Claude usage-limit (ag-d2e5 #usage-limit-soft-fail) (#635)
## What

Keep `claude-review` a **required** check, but stop it from blocking
merges when the failure is a **Claude account usage limit**
(weekly/5-hour). Real review/execution failures still block.

## Why

When the weekly limit is hit, `anthropics/claude-code-action@v1` calls
`core.setFailed("…You've hit your weekly limit · resets …(UTC)")`,
failing the required check on **every** PR until the limit resets —
forcing `--admin` merges (hit this session on #631/#634).

## How

- `continue-on-error: true` on the action step.
- A gate step re-derives the job conclusion:
  - action `success` → **pass**
- action `failure` + `execution_file` matches the usage-limit signature
→ **skip** (exit 0, `::notice`)
  - any other failure (incl. no `execution_file`) → **block** (exit 1)
- `execution_file` is preserved on error (`run.ts` catch-block) and
carries the SDK error-result text.
- Signature matches `hit your (weekly|usage|N-hour) limit` / `usage
limit reached` / `Claude AI usage limit` — **not** generic `rate limit`,
so a PR whose diff discusses rate-limiting is **not** false-skipped.

Branch protection is **unchanged** — `claude-review` stays required.

## Evidence

Local unit-sim of the gate (5 cases, all expected): `success→pass`,
`limit→skip`, `real-fail→block`, `no-file→block`,
`rate-limit-prose→block`. shellcheck clean;
`validate-ci-policy-parity.sh` PASS. This PR's own `claude-review` run
is the live dogfood — under the active weekly limit it should **skip
cleanly** instead of blocking.

Closes-scenario: ag-d2e5#usage-limit-soft-fail
Bounded-context: BC2-Validation
Evidence: .github/workflows/claude-code-review.yml
2026-05-31 12:07:22 -04:00
Bo 0c42512fdd feat(ci): agent-output-validate.yml — reusable authoritative gate on agent output (ag-mptr #agent-output-validate) (#617)
## What

Reusable, authoritative CI gate over **agent-produced output** — the
hookless guardrail for out-of-session agents (Claude Managed Agent,
Agent SDK loop, or NTM/Codex swarm). A `workflow_call` job runs `ao
validate --gate` (the umbrella composing ratchet/standards/scenario)
against an agent's PR/artifact and propagates PASS/WARN/FAIL to the job
exit code. **CI is the gate, not a ported hook** (AgentOps 3.0 is
hookless).

Second child of epic **ag-7s9fo** (Make Managed/SDK agents
AgentOps-native), after ag-2wln `/agent-native` (#616). Inherits the
richer spec absorbed from superseded ag-toat4; the optional SDK hook
adapter was split out to ag-50m3.

## Files (3, +184/−0)

- `scripts/agent-output-validate.sh` — testable gate logic. Resolves
`ao` (`AO_BIN` override), runs `ao validate --gate "$@"`, propagates
exit (0 pass/warn, 1 fail, 2 error). **Fails loudly if `ao` is missing**
— a missing gate never reads as a clean pass.
- `.github/workflows/agent-output-validate.yml` — `workflow_call`
wrapper (inputs: `changes`, `strict`); builds `ao`, runs the script.
Runtime-agnostic (serves Managed-Agent + NTM-swarm PRs).
`workflow_call`-only **by design** — it's adopted by a caller, not
triggered standalone, so it does not run on this PR.
- `tests/scripts/agent-output-validate.bats` — 7 cases. The
**passing+failing fixture evidence** the bead requires, exercised
deterministically via an injectable fake `ao` (exit 0/1/2), plus
scope-flag forwarding, missing-binary, strict-mode, and workflow-wiring
assertions.

## Evidence

- `bats tests/scripts/agent-output-validate.bats` → **7/7 green**
- `shellcheck scripts/agent-output-validate.sh` → clean (severity=error)
- Real-`ao` smoke: against the live binary the script faithfully
propagates the gate verdict (an empty dir yields ao's exit-2 "no
artifacts" → ERROR, proving no silent pass)
- yaml parses valid

## Note for review

The workflow is `workflow_call`-only, so CI here can't execute it
(nothing `uses:` it yet). The **bats suite is the regression proof** for
the gate logic; the yaml is the thin Actions wrapper a caller adopts.
Bare invocation (no `changes`/`bead`) intentionally surfaces ao's exit-2
— callers scope the gate via the `changes` input.

Closes-scenario: ag-mptr#agent-output-validate
Bounded-context: BC5-Runtime
Evidence: tests/scripts/agent-output-validate.bats
2026-05-30 00:37:21 -04:00
Bo fe731d12b9 feat(skills): skill-flow connectivity gate + closed consumes vocabulary (#597)
## Do all skills flow together?

**No — not until this PR.** Investigating the question surfaced a
structural gap: skill dependencies are declared across **three
overlapping frontmatter fields that drifted apart and were never
reconciled**, and nothing enforced connectivity or constrained the
`consumes` vocabulary.

| Field | Meaning | Read by context-map? |
|-------|---------|----------------------|
| `consumes` | upstream inputs | yes (data-flow table) |
| `context_rel` | DDD relationship | yes (mermaid) |
| `metadata.dependencies` | upstream skill slugs | **no** |

Findings (76 skills):
- **33 skills** where `consumes` (skills) ≠ `metadata.dependencies` —
the two skill-slug fields disagree.
- The generated `context-map.md` **ignored `metadata.dependencies`
entirely** (34 skills, the richest layer).
- Counting all three layers, **16 true orphans** (incl. core skills like
`push`, `session-bootstrap`) with zero skill-to-skill edges — silently.
- `consumes` was an **open free-text field**: no check that a token
resolves to anything.

`scripts/audit-skill-metadata.sh` already flagged the
`consumes`-vocabulary and connectivity checks as *"discovered
follow-ups, not enforced."* This is that follow-up.

## What this PR does

**1. Connectivity gate** — `scripts/validate-skill-flow.sh` (+
`validate-skill-flow` CI job):
- **Closed `consumes` vocabulary**: every token must resolve to a skill
slug, a whitelisted external input (`repo-context`, `external-api`,
`bd`, `github-pr`, `onboard`), or an artifact some skill produces.
Catches typos/undeclared deps.
- **`metadata.dependencies` resolution**: every entry must name a real
skill.
- **Orphan detection** across all three edge layers; un-allowlisted
orphans fail.
- Reports (non-fatal): cross-layer disagreement + dead-end artifacts, so
they can be reconciled later.

**2. Wire the genuine-gap edges** — added `context_rel` for 6
disconnected skills and regenerated `context-map.md`:
- `deps` → `vibe`, `red-team` → `vibe` (mirrors
`security`/`security-suite`)
- `provenance` ↔ `trace`
- `scenario` → `validation`
- `release` → `ship-loop`

**3. Close the vocabulary** — the closed `consumes` namespace is
enforced by the gate and documented in `docs/contracts/skill-flow.md`
(linked in `documentation-index.md`). 14 intentionally-standalone leaves
are allowlisted in `scripts/skill-flow-standalone.txt` with a rationale
per entry.

## Verification

```
$ bash scripts/validate-skill-flow.sh
validate-skill-flow: 76 skill(s), 121 skill-to-skill edge(s)
  orphans: 14 (allowlisted standalone: 14)
OK: skill flow is connected and the consumes vocabulary is closed.
```

- `validate-skill-flow.sh`: exit 0 ✅
- `validate-context-map-drift.sh`: exit 0 ✅
- `check-contracts-structural-floor.sh`: PASS (contract cataloged) ✅
- 11 acceptance tests in `tests/scripts/validate-skill-flow.bats` (bats
not installed locally; exercised via manual smoke + will run in CI
`bats-tests`).

Bounded-context: BC1-Corpus
Evidence: OK: skill flow is connected and the consumes vocabulary is
closed.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01WLdHEva31cixBjxo8z9TXK)_

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-29 18:26:05 -04:00
Bo e33fa8c5e2 feat(eval): deny-by-default holdout-leak CI gate for Outcomes payloads (ag-hdqu0 #leak-gate) (#605)
## What
Adds the **fourth holdout-leak guard** for ag-hdqu0:
`scripts/check-outcomes-holdout-leak.sh` — the authoritative CI check (a
script, not a hook) that refuses any Outcomes rubric/score payload
carrying a `target`/`ground_truth` **key**. Managed Agents are not ZDR,
so a leak is permanent; this backs the by-construction (`ProjectRubric`)
+ re-scan (`ContainsAny`) + drift (`FreshAgainst`) guards with a
deny-by-default gate.

- Deny-by-default: unreadable file → fail; no args → usage error.
- Wired as a **step in the existing `contracts-sync` job** (scans
committed Outcomes payload fixtures; empty set passes, gate armed for
future fixtures). **No new job** → jobs-table unchanged (11 rows),
`ci-policy-parity` green (verified locally pre-push).

## Tests (TDD)
- `tests/scripts/check-outcomes-holdout-leak.bats` — 6 cases: clean→0 ·
target→1 · ground_truth→1 · mixed→1 · unreadable→1 (deny-by-default) ·
no-args→usage. All pass; `shellcheck --severity=error` clean.

Closes-scenario: ag-hdqu0.6#leak-gate
Bounded-context: BC1-Corpus
Evidence: tests/scripts/check-outcomes-holdout-leak.bats
2026-05-29 16:48:30 -04:00
Bo 4107bbed1c refactor(ci): rebuild validate.yml — 67 jobs → 10 purpose-grouped (ag-877 #gate-rebuild) (#592)
## What

First-principles rebuild of `.github/workflows/validate.yml` (ag-877 /
epic ag-5ck, no strangler). Collapses the **67-job** orchestration into
**9 purpose-grouped jobs** (+ `changes` + `summary` = 12 total). All
**59** `scripts/{check,validate}-*` are preserved verbatim — the checks
are the safety logic; the 67-way job fan-out was accretion slop.

Design: `spec/teardown-2026-05-28/gate-rebuild-first-principles.md`

## How

Each purpose job runs its script family as **path-gated steps**
(per-step `if: needs.changes.outputs.X`), with advisory checks marked
`continue-on-error: true` at the **step** level (no whole-job
non-blocking). Notable structural moves:

- **correctness** = matrix `[ubuntu, windows]` — folds `windows-smoke`
in as a Windows leg (it runs `tests/windows/*.ps1`, no `scripts/`
family) instead of a standalone job.
- **doctrine-proof** = the doctrine/GOALS proofs **+ spec-linkage**
(executable-spec link, scenario↔test linkage, AGENTS split,
docs↔learning) — both prove the GOALS/spec apparatus is consistent and
share the Go+ao+Python setup (10→9).
- **summary** absorbs **AP#7** Evidence verification (was
`validate-pr-evidence-claims`) — it needs every sibling job's logs, so
it must run last, not in a parallel group.

## Acceptance (ag-877)

- **ac-gr.1 coverage-proof empty** — `comm -23` of old-vs-new
`scripts/*` invocations is empty (no script dropped). ✅
- **ac-gr.2 job count ≤12** — `grep -cE '^  [a-z][a-z0-9-]+:$'` = 12. ✅
- **ac-gr.3 self-CI green** — this PR runs the new `validate.yml`
against itself (changes `.github/**` → all paths trigger). ⏳ this run.

Coupled job-name contract projections updated in lockstep:
`docs/contracts/ci-jobs.yaml` manifest (64→9), regenerated
`AGENTS-CI.md` table + `registry.json` (gate SKUs 66→11; skills/cli/ref
untouched), and 3 structural bats tests re-anchored to the grouped jobs.
Local: `validate-ci-policy-parity`, `registry-check`,
`validate-sku-catalog-drift`, `check-wiring-closure` all PASS; full bats
suite **740 tests, 0 failures**.

## ⚠️ Operator-gated cutover (do NOT auto-merge)

Branch protection still lists the **64 old** required-check names. This
PR **cannot auto-merge** until an admin swaps them for the new ~10
(**ag-7hpl**), then verifies a no-op PR is gated by exactly the new set
(**ag-1eqi**). Both are repo-admin, repo-wide blast radius →
operator-coordinated.

Closes-scenario: ag-877#uniform-gates-consolidated
Closes-scenario: ag-877#heterogeneous-setup-ported
Closes-scenario: ag-877#changes-preserved-summary-rewired
Closes-scenario: ag-877#coverage-proof-empty-ci-green
Bounded-context: BC2-Validation
Evidence: CI_JOBS_TABLE: PASS
2026-05-29 00:16:47 -04:00
Bo f28ee038f6 feat(workflows): ship operating-loop Workflow script + node --check CI gate (agentops-7m7a #ship-workflow-scripts) (#594)
## What
Ship the seven-move `operating-loop` Workflow-tool script so it reaches
fresh installs, plus an isolated CI gate that `node --check`s workflow
scripts.

## Why
`.claude/workflows/operating-loop.js` was excluded by the blanket
`.gitignore` `.claude/` rule, so it never shipped. **Force-add**
(mirroring the 6 existing tracked `.claude/*` files) makes it ship; a
gitignore *negation* would be a silent no-op under the excluded parent
dir.

## Changes
- **Force-add** `.claude/workflows/operating-loop.js` (363 lines):
closed pre-flight→plan feedback loop (bounded K=2), `plan`/`full` mode,
adversarial refutation before a no-go halts, budget-scaled fan-out,
model/agentType tiering, bounded self-repair.
- New `.github/workflows/workflow-scripts-syntax.yml` — `node --check`
over `.claude/workflows/*.js`, kept out of `validate.yml`'s job registry
on purpose.

## Scope call
Skill wrapper omitted — `operating-loop` already auto-surfaces as
`/operating-loop` from the `.js` `meta.name`; a same-name skill would
conflict and add count-doc churn for no invocation benefit (per
pre-mortem).

## Validation
- `node --check` ✓ · `git check-ignore` exits 1 (tracked, not ignored) ✓
- `scripts/pre-push-gate.sh --fast` passed (shellcheck, no-symlinks,
spec-link integrity)
- Independent code-reviewer (vibe): PASS

Closes-scenario: agentops-7m7a#ship-workflow-scripts
Bounded-context: BC5-Runtime
Evidence:
.agents/rpi/phase-3-summary-2026-05-28-operating-loop-excellence.md

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-05-29 01:27:04 +00:00
Bo e50e9aca80 fix(ci): unbreak claude-review (timeout+cancel) and executable-spec-link-integrity (missing scenario) gates (ag-1kcr) (#593)
Restores two CI gates the 2026-05-29 evolve session left broken/flaky.

## Fix 1 — claude-review hang (the gate you flagged)
`claude-code-review.yml` (required merge gate) + `claude.yml`
(`@claude`) ran `anthropics/claude-code-action@v1` with **no
`timeout-minutes`**. A hung LLM call ran to GitHub's **6h default** →
required check stuck `pending` forever → merge blocked → forced 3×
`--admin` bypass this session (#591 stuck ~17min).
- `claude-code-review.yml`: `timeout-minutes: 20` (hung review → **fast
fail**, re-runnable, not stuck) + `concurrency` (`cancel-in-progress`)
to supersede lingering runs.
- `claude.yml`: `timeout-minutes: 30` (same hang class).

A timed-out required check still blocks merge but is now *actionable*.
Changing required-ness / the 6h default is out of scope
(branch-protection policy).

## Fix 2 — executable-spec-link-integrity RED on main (regression from
#591)
#591 (ag-8ok) added directive **D15** with `**Scenarios:**
s-2026-05-24-015` but never created the scenario file. The lint gate
(`ao goals scenarios --lint`) was **path-filter-skipped** on #591's CI,
so the phantom reference merged and left **main red** (this also blocks
#593's own copy of the gate). Fix: add
`spec/scenarios/s-2026-05-24-015.json` (mirrors -013/-014), properly
linking D15. Lint now: *"No executable-spec link defects found."*

## Note on this PR's own claude-review
`claude-code-action` requires the workflow file to be byte-identical to
main's (anti-tamper). Because this PR *modifies*
`claude-code-review.yml`, its own `claude-review` check fails by design
(the action's error says this is "normal"). This PR must be admin-merged
— the **last** bypass before the timeout protects future PRs. All
deterministic gates (Validate, 65 jobs) are the merge signal.

Closes-scenario: ag-1kcr#claude-review-timeout
Bounded-context: BC5-Runtime
2026-05-28 21:12:07 -04:00
Bo 39f0850640 ci(validation): test-count non-regression gate for cli/internal (ag-h2z #test-count-noregress) (#562)
## Summary

Adds a blocking T1 CI gate that ratchets test coverage in
`cli/internal/` packages: once tests are added, a PR cannot delete them
without a deliberate, recorded justification. Motivated by PR #558–#561,
which added 248 tests across 6 internal packages with nothing preventing
a future PR from silently removing them.

- `scripts/check-test-count-regression.sh` — for each
`cli/internal/<pkg>` with a changed `_test.go` file between the PR base
and HEAD, sums `^func Test` declarations per package at both refs. A net
per-package decrease fails the gate **unless** a `Test-Removal-Reason:`
trailer is present in a commit body in the range. Counts are summed per
package (not per file), so moving a test between files in the same
package is net-zero. Emergency bypass:
`AGENTOPS_TEST_COUNT_NOREGRESS=skip` (logged). Fail-open when the base
ref is unresolvable (shallow clone), fail-closed only on a verifiable
decrease.
- `tests/scripts/check-test-count-regression.bats` — 8 scenarios (add /
remove-no-trailer / remove-with-trailer / rename / skip-bypass / no-op /
unresolvable-base / cross-file move) driven by throwaway git repos.
Auto-discovered by the `bats-tests` job.
- Wired as blocking `validate-test-count-noregress` (T1): job block +
`summary.needs` + `validate-pr-evidence-claims.needs` in
`.github/workflows/validate.yml`, manifest entry in
`docs/contracts/ci-jobs.yaml`, and regenerated `AGENTS-CI.md` (parity
gate green, 66 rows). `fetch-depth: 0` so the base ref resolves for the
before/after diff.

Green by construction on this PR: it changes no
`cli/internal/**/_test.go`, so the gate is a no-op PASS.

## Acceptance scenarios (from ag-h2z)

- PR adds tests to `cli/internal/X` → gate passes (count up or stable).
✅ bats #1
- PR removes a test without a trailer → gate fails, naming the test +
pkg. ✅ bats #2
- PR removes a test **with** `Test-Removal-Reason:` trailer → gate
passes. ✅ bats #3
- PR renames a test (`Test_X` → `TestX`) → net count stable → gate
passes. ✅ bats #4

## Test plan

- `bats tests/scripts/check-test-count-regression.bats` — 8/8 green
- `shellcheck scripts/check-test-count-regression.sh` — clean
- `bash scripts/validate-ci-policy-parity.sh` — PASS (66 rows)

## Follow-ups (out of scope, immutable)

- Promoting `validate-test-count-noregress` to a GitHub
branch-protection *required status* is repo-admin (immutable scope) —
not done here. The job is blocking via `summary.needs`.

Closes-scenario: ag-h2z#test-count-noregress
Bounded-context: BC2-Validation
Evidence: PASS: no cli/internal/*_test.go changes
2026-05-27 23:22:53 -04:00
Bo 071e1bb62f perf(ci): cache pinned Go tool binaries in agentops-contract-canaries (ag-56y #canary-cache) (#555)
## Summary

Cache the version-pinned Go tool binaries that
`agentops-contract-canaries` rebuilds on every CI run. Tier-2 fix from
the bottleneck diagnosis. Single file change, +20/-2 LOC.

## What changed

`.github/workflows/validate.yml::agentops-contract-canaries` — added an
`actions/cache@v4` step between `Set up Go` and `Install AgentOps
contract canary dependencies`, and modified the install step to skip `go
install` on cache hit.

- **Cache path:** `~/go/bin` (gocyclo) + `~/.local/bin` (bd)
- **Cache key:** `canary-tools-${{ runner.os
}}-gocyclo-v0.6.0-bd-v1.0.3` — version-pinned, so any pin bump
auto-invalidates
- **Skip logic:** `if [ ! -x "$HOME/.../tool" ]; then go install ...;
fi` — restores idempotent semantics

## Why

`setup-go@v6`'s module/build cache covers dependencies inside
`cli/go.sum` but NOT `go install` outputs to GOBIN. The two pinned Go
binaries (`gocyclo`, `bd`) rebuild from source on every CI run.

This job is the critical-path of the entire validate workflow at ~393s
(measured on main run #26480320480, confirmed again on PR #554's clean
run at 6m4s — even the post-flake-fix run is bottlenecked here).

## Fitness delta

| Metric | Before | After (expected) |
|---|---|---|
| `agentops-contract-canaries` wall-clock | **393s** | **~275s (-30%)**
|
| Per-PR validate critical path | ~7 min | ~5 min |
| Cache hits on this PR | 0 (first run, expected) | n/a |
| Cache hits on next PR after merge | 0 | 1 (full benefit) |

First post-merge run is a cache MISS — no speedup visible. Speedup shows
from the second run onward. A follow-up measurement after a few PRs land
will confirm the 30% target.

## Sibling pattern

Matches the `actions/cache@v4` shape used by other Go projects on
GitHub. None of the existing validate.yml jobs cache GOBIN today — this
is the first. If it works, the pattern can extend to other
Go-tool-installing jobs in a follow-up.

## Out of scope (deferred, not bundled)

- Caching apt packages (jq, ripgrep) — lower leverage, apt cache
handling in CI is fragile.
- Caching the global bats install — smaller win, npm cache less clean.
- Pre-built container image with tools baked in — bigger change. Revisit
only if this cache approach proves insufficient.

## Validation

```
python3 -c "import yaml; data=yaml.safe_load(...)" → YAML parses
agentops-contract-canaries steps: 5 (was 4) in correct order
diff stat: +20 / -2 (≤300 PR budget)
```

True end-to-end validation comes from CI: this PR's first run is a cache
miss (proves the install path still works); a later PR's run is the
cache hit that proves the speedup.

## Trailers

Closes-scenario: ag-56y#first-run-populates-cache
Closes-scenario: ag-56y#subsequent-runs-use-cached-binaries
Closes-scenario: ag-56y#version-bump-invalidates-cache
Bounded-context: BC5-Runtime
Evidence: .agents/research/2026-05-26-ci-pr-bottleneck-diagnosis.md
Evidence: .github/workflows/validate.yml (steps reordered, 1 step added,
1 modified)

Sibling pattern: standard actions/cache@v4 + version-pinned key shape.
2026-05-27 02:26:24 +00:00
Bo f077c6bfbc fix(ci): allow PR-only skips in release tag validate (#549)
## Summary

- keep release-tag Validate strict for unexpected skipped release lanes
- explicitly allow PR-only evidence checks to skip on tag pushes
- update release-tag CI bats coverage and CI docs so the policy is
locked

## Evidence

- `python3 - <<'PY' ... yaml.safe_load(.github/workflows/validate.yml)`
- `bats tests/scripts/validate-release-tag-full-ci.bats`
- `bash tests/scripts/test-ci-policy-parity.sh`
- `bash scripts/validate-ci-policy-parity.sh`
- `git diff --check`
- `scripts/pre-push-gate.sh --fast`

## Notes

This fixes the release-health contradiction where `v3.0.1` published
successfully but tag Validate failed because PR-only AP#7 was skipped on
a tag event.
2026-05-26 13:27:45 -04:00
dependabot[bot] d242889478 deps(go): Bump golang.org/x/sys from 0.44.0 to 0.45.0 in /cli in the go-minor-patch group (#545)
Bumps the go-minor-patch group in /cli with 1 update:
[golang.org/x/sys](https://github.com/golang/sys).

Updates `golang.org/x/sys` from 0.44.0 to 0.45.0
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/golang/sys/commit/397d5f80920585bc27433d878aba498d062f81e1"><code>397d5f8</code></a>
unix: update to Linux kernel 7.0</li>
<li><a
href="https://github.com/golang/sys/commit/0a387f7a07d7a0e9811f00603c10b4e5a94ab79c"><code>0a387f7</code></a>
cpu: detect zbc extension on riscv64</li>
<li><a
href="https://github.com/golang/sys/commit/758f71cb839d131daf0ba4befa6a2c6ceb21a649"><code>758f71c</code></a>
cpu: add LLACQ_SCREL, SCQ, DBAR_HINTS detection for loong64</li>
<li><a
href="https://github.com/golang/sys/commit/99666ae32e07f6403182a79cb5df0c417cbbf25f"><code>99666ae</code></a>
unix: merge Linux readv/writev implementation with Darwin/OpenBSD</li>
<li><a
href="https://github.com/golang/sys/commit/e4444cbaaaf61cecff8e635874066fcd5c841575"><code>e4444cb</code></a>
windows: add NtSetEaFile, NtQueryEaFile and NtQueryInformationFile</li>
<li><a
href="https://github.com/golang/sys/commit/04396e85d470b7f990a9a1df5c1a44dc8e30c292"><code>04396e8</code></a>
unix: add Readv, Writev, Preadv, Pwritev for OpenBSD</li>
<li>See full diff in <a
href="https://github.com/golang/sys/compare/v0.44.0...v0.45.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=golang.org/x/sys&package-manager=go_modules&previous-version=0.44.0&new-version=0.45.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Bo <boshu2@users.noreply.github.com>
2026-05-25 16:56:28 -04:00
Bo ff6f8569c7 feat(release): tiered release-notes + coverage gate — end the release churn (ag-nc9) (#544)
Makes release-notes scope mechanical per tier so a release can never
again under-scope its notes and burn the re-mining churn (root cause of
the v3.0.x rewrites).

- **Scope rule + tiers** in the standard (+ codex twin): notes = git
range since the previous release (a major = the whole major), with a
major/minor/hotfix table.
- **Coverage gate** in validate-release-notes.sh: maps the diff to
product areas, FAILS if a heavily-touched area (>=3 files) is
undocumented. Wired into release.yml via --since. It immediately caught
a real gap (v3.0.1 missing the Codex area — fixed here).
- **scaffold-release-notes.sh**: mines the range into a tier-correct
draft so notes start comprehensive.
- 6 new bats (14 total), shellcheck clean, codex parity green. v3.0.1
notes verified to PASS coverage over the real v2.41.1..v3.0.1 diff.

Closes-scenario: ag-nc9#tiers-coverage-gate
Bounded-context: BC5-Runtime
Evidence: scripts/validate-release-notes.sh
2026-05-25 11:23:43 -04:00
Bo 7d0d90fbd3 feat(release): enforce release-notes standard + major variant (ag-d0f) (#541)
Fixes two compounding misses on the v3.0.x release notes and makes the
failure mechanically impossible.

**Root cause:** extract-release-notes.sh silently fell back to dumping
the raw CHANGELOG when no curated docs/releases/*-notes.md existed — so
3.0.0/3.0.1 shipped raw dumps, and the curated notes I then wrote did
not follow skills/release/references/release-notes.md (invented
headings, bold-lead-in bullets instead of action labels). The template
also had no major-release variant.

**Standard (template + codex twin):** add a major-release (X.0.0)
variant — required ## Breaking Changes section + migration link;
document the hard gate.

**Conform:** rewrite both 3.0.x curated notes to the standard (major
shape for 3.0.0 w/ Breaking Changes + canonical taxonomy + action
labels; patch shape for 3.0.1).

**Enforce:** scripts/validate-release-notes.sh (existence + sections +
taxonomy + action labels + Breaking-Changes-for-major) + 8 bats cases;
wired as a hard gate in release.yml before notes extraction;
extract-release-notes.sh now fails instead of silently dumping raw
CHANGELOG.

Local: both notes PASS the validator, 8/8 bats, shellcheck clean, codex
parity passes.

Closes-scenario: ag-d0f#release-notes-standard
Bounded-context: BC5-Runtime
Evidence: scripts/validate-release-notes.sh
2026-05-25 09:14:58 -04:00
Bo a1730829e7 chore(release): v3.0.1 — plugin version bump + Nightly Static Validation fix (ag-s3z) (#539)
Two release-engineering surfaces the per-PR validate.yml gate never
exercised:

1. **Plugin updatability.** The 3.0.0 version label never moved across
the 15 commits that landed the skill consolidation + doc reconciliation,
so existing installs got "already at latest version" from `claude plugin
update` and stayed stale. Bump 3.0.0 → 3.0.1 (plugin.json,
marketplace.json metadata+plugin, install-ao.sh) + CHANGELOG 3.0.1 entry
(root + docs, byte-identical).

2. **Nightly Static Validation (red on main).** The hooks rip (#511)
deleted `scripts/validate-hooks-doc-parity.sh` but Nightly still ran it;
`tests/docs/validate-skill-count.sh` greps PRODUCT.md for hook-era
phrasing the 3.0 restructure removed → 2 MISSING_PATTERN fails
(warn-tolerated in validate.yml, fail-closed in Nightly). Removed the
dead step; repointed product_total to the four-layers line; retired
product_layer_total.

Verified locally: validate-skill-count.sh + validate-doc-release.sh pass
(total=75); version-consistency = 3.0.1 across all 3 manifest fields.

Follow-ups: ag-728 (PRODUCT.md daemon/hook prose), ag-x0d (3 golangci
quality findings).

Closes-scenario: ag-s3z#v3-0-1-release-readiness
Bounded-context: BC5-Runtime
Evidence: tests/docs/validate-skill-count.sh
2026-05-25 08:54:37 -04:00
Bo c8142f3a8d fix(release): pre-publish gate accepts WARN_QUALITY — unblock v3.0.0 publish (#536)
The v3.0.0 release blocked at pre-publish-evidence:
gate_status=WARN_QUALITY (golangci-lint quality_high=3; security_high=0,
critical=0). The toolchain explicitly grades WARN_QUALITY as
non-blocking PASS (toolchain-validate.sh:969-970); the release gate
erroneously required ==pass. A release SECURITY gate must block only on
security findings (BLOCKED_HIGH/CRITICAL). Now accepts pass +
warn_quality; prints summary on a real block. Removes the temporary #535
debug. The 3 quality findings are tracked in ag-x0d for a 3.0.x patch.

Bounded-context: BC5-Runtime
Evidence: scripts/toolchain-validate.sh
2026-05-25 00:03:44 -04:00
Bo c705c4b81c chore(release): log security-gate finding on pre-publish failure (#535)
Temporary debug to unblock v3.0.0 publish: print
security-gate-summary.json + per-tool detail before the gate check, so
the next tagged release run logs the finding (currently the summary is
not uploaded on failure → invisible). Revert after the finding is fixed.

Bounded-context: BC5-Runtime
Evidence: .github/workflows/release.yml
2026-05-24 23:53:19 -04:00
Bo c88d0f0d88 feat(ci): skill-body command-ref gate — block stale ao refs in prose (ag-4x8) (#532)
## What

New CI gate `validate-skill-body-refs` (T1) that scans the **inline-code
prose** of every `SKILL.md` + `references/*.md` (under `skills/` and
`skills-codex/`) for `` `ao <command>` `` / `` `ao <command> --flag` ``
tokens and validates each against the **live `ao` help tree**.

This is the systemic fix for oracle gap #3:
`validate-skill-cli-snippets.sh` only inspects fenced/`ao`-leading
snippets, so a stale command/flag named inside an inline code span
mid-sentence (e.g. `` `ao schedule` `` , `` `ao context assemble` ``)
regenerated after every CLI rename. This gate is the **complement** of
the snippet gate — together they cover both fenced snippets and prose.

## How the gate works

- Builds a robust live command tree: `ao capabilities` (authoritative
top-level) + recursive grouped-help subcommand parse (handles `ao
goals`' category-grouped help that the standard "Available Commands:"
parser misses) + command aliases (`quickstart` → `quick-start`) +
per-command flags.
- Only validates `ao` refs inside inline `` `code` `` spans — bare-prose
English ("if ao available", "ao not installed") is never treated as a
command, eliminating false positives while still catching the genuine
(always backtick-quoted) stale refs.
- Justified historical refs skip via an inline marker (`Superseded` /
`Historical` / `removed in` / `retained for history` / `(historical)`)
on the line/file, or `scripts/skill-body-refs-allowlist.txt`.

## Prose sweep (what made main pass)

The gate detected genuine stale refs on main; swept them to keep the
gate green:

- `ao context assemble` → `ao lookup` (8 sites — the command was never
built; `ao lookup` is the real co-cited replacement)
- `ao cite` → `ao metrics cite`
- `ao worktree gc` → `git worktree prune` (4 sites — `ao worktree` never
existed)
- `ao hooks` removal note → reworded to "removed in 3.0" (matches the
`removed in` historical marker)

## Allowlist contents

- `skills/standards/references/cli-wireup-template.md` — illustrative
naming template (deliberately names `ao loop-history` / `ao loop write`
/ `ao loop tail` as good/bad examples + future placeholders)
- `skills/release/references/release-notes.md`,
`skills-codex/release/references/release-notes.md` — historical
changelog prose (e.g. `ao daemon status` from the daemon era)

## Wiring

- `.github/workflows/validate.yml` — new T1 job + `summary.needs` +
`validate-pr-evidence-claims.needs` + summary echo (65 jobs)
- `docs/contracts/ci-jobs.yaml` — manifest entry (keeps
`validate-ci-policy-parity` green)
- `AGENTS-CI.md` — regenerated via `scripts/generate-ci-jobs-table.sh
--write`
- `tests/scripts/validate-skill-body-refs.bats` — 6 cases (clean pass,
stale-cmd fail, stale-flag fail, historical-marker skip, bare-prose
ignore, committed-tree pass)

## Verification (local, green)

- `bash scripts/validate-skill-body-refs.sh` → exit 0
- `scripts/validate-ci-policy-parity.sh` → PASS (65 rows)
- `scripts/validate-skill-cli-snippets.sh` → passed (no regression)
- `scripts/check-codex-parity-drift.sh` +
`validate-codex-generated-artifacts.sh --scope worktree` → passed (codex
hashes regenerated for the 6 edited skills only)
- `bats tests/scripts/validate-skill-body-refs.bats` → 6/6 ok
- `shellcheck scripts/validate-skill-body-refs.sh` → clean
- `markdownlint` on changed docs → clean

Closes-scenario: ag-4x8#body-ref-gate
Bounded-context: BC1-Corpus
Evidence: scripts/validate-skill-body-refs.sh
2026-05-24 22:42:40 -04:00
Bo e90d6d79f9 feat(registry): SKU capability catalog (schema v2) — generated skill×command×BC join (ag-cbm) (#525)
## 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:&lt;sku&gt;), `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
2026-05-24 20:47:04 -04:00
Bo ee748d580d feat(rip)!: retire factory command + contract corpus (soc-2rtm0 #cascade-rip) (#512)
**Wave 1 of the orchestration-substrate cascade rip** (soc-2rtm0). The
full cascade (daemon + dream/overnight + schedule/plans/watch + gc-glue
+ factory) is ~50K LOC across deeply-entangled packages — far past one
reviewable PR. Per the bead's slice directive and the recon map's
serial-by-wave verdict, this ships as sequential PRs by wave. **This is
the factory wave** (the only cleanly-disjoint slice — no KEEP-feature
entanglement).

## What this retires (operator-confirmed FULL factory retire)

**Go command surface** (no external non-test caller — clean delete):
- `cmd/ao/factory.go`, `factory_admit.go`, `factory_pilot.go`,
`factory_admission_adapter.go` (+ all tests)
- `internal/ports/factory_admission.go` +
`inmemory_factory_admission.go` (+ test) — the port was only wired to
the daemon by the deleted production adapter
- severed factory flag save/restore + command-tree assertions from
`cobra_commands_test.go` / `testutil_test.go`

**Contract corpus** (scripts, schemas, contract docs, runbooks,
fixtures, tests):
- `scripts/check-factory-{admission,claim-ledger,yield-ledger}.sh`
- `schemas/factory-{admission,work-order,yield}.v1.schema.json`
- `docs/contracts/factory-{admission,claim-ledger,yield-ledger}.*`
-
`docs/runbooks/{factory-manual-merge,daemon-factory-admission,cloud-frontier-pilot}.md`
- `tests/e2e/factory-operator-canary.sh`,
`tests/integration/test-factory-claim-ledger-ci-job.sh`,
`tests/scripts/test-factory-admission-contracts.py`,
`tests/fixtures/factory-*`
- orphaned `aggregate-observation-log` subsystem (script + integration
test + fixtures) — its only data source was the deleted
`factory-claim-ledger-strict` CI job

**CI / generated reconciliation:**
- removed 3 factory jobs from `validate.yml` (+ both `needs:` arrays +
verdict echoes); removed entries from `ci-jobs.yaml`; regenerated
`AGENTS-CI.md` (63 jobs)
- removed factory gate-ids from `GOALS.md`; trimmed
`contracts-structural-floor` factory references
- regenerated `registry.json` (177→174 CLI commands), `COMMANDS.md` (−70
lines), `cli-skills-map.md` (73→72 headings)
- updated `cli-command-surface-matrix` canary spec + fixture counts
(`top=72 sub=194 all=266`); scrubbed deleted-file links from
`documentation-index.md`, `SCHEMAS.md`, `contracts/index.md`,
`agentopsd-control-plane.md`, `insert-schema-practices.py`

## Deliberately left for the daemon-carve wave
Daemon-owned factory job types (`factory.admission`,
`factory.local-pilot`) and the `agentopsd` routing-policy schema remain
— they belong to `internal/daemon/` and retire when the daemon does.

## Gates verified green locally
`gofmt`/`go build`/`go vet`, full Go test (**11830 pass**),
`validate-ci-policy-parity`, `contract-compatibility`,
`contracts-structural-floor`, `registry --check`, `doc-release` (0
broken links), `smoke-test`, `json-flag-consistency` (0 errors),
**`contract-canaries` (failures=0)**, codex-hashes, `pre-push-gate.bats`
(55).

Diff: 66 files changed, ~16 insertions / ~5680 deletions.

Closes-scenario: soc-2rtm0#cascade-rip
Bounded-context: BC5-Runtime
Evidence: .agents/discovery/2026-05-24-rip-caller-map.md
2026-05-24 12:29:22 -04:00
Bo e431339c40 feat(hookless)!: remove all hooks — skills + CLI only (soc-57b7f #remove-all-hooks) (#511)
## What

AgentOps 3.0 goes **fully hookless**. This removes the entire hook
product surface — the product is now skills + the `ao` CLI with **zero
hooks**. CI is the authoritative gate.

## Deleted

- `hooks/` — 46 hook scripts + `hooks.json` + examples (whole directory)
- `cli/embedded/hooks/` + embedded `hooks.json` +
`embedded/lib/hook-helpers.sh`
- `lib/hook-helpers.sh`
- `ao hooks` CLI command: `cli/cmd/ao/hooks.go`, `hooks_run.go` + 5 hook
test files; `cli/internal/bridge/hooks.go`;
`cli/internal/doctor/fix_hooks.go` + test
- Codex hook manifest (`hooks/codex-hooks.json`) + handler/audit scripts
(`scripts/audit-codex-hooks.sh`, `install-dev-hooks.sh`,
`test-hooks-output.sh`, `test-codex-hookless-lifecycle.sh`,
`test-hookless-rpi-phased.sh`)
- Hook lease surface: `docs/contracts/hook-lease-*`,
`schemas/hook-lease.v1.schema.json`,
`scripts/check-hook-lease-inventory.sh`,
`check-hook-port-replacements.sh`, `generate-hook-lease-inventory.py`
- Hook docs: `docs/HOOKS.md`, `cli/docs/HOOKS.md`,
`docs/architecture/hook-noise-audit.md`,
`docs/contracts/hook-runtime-contract.md`
- `tests/hooks/` (whole dir) + hook test scripts; 5 hook eval suites +
fixtures

## CI gates removed

`hook-preflight`, `validate-hooks-doc-parity`, `hook-output-schema-lint`
(+ scripts/bats), the hook eval suites, the `test-runtime-codex-smoke` /
`test-codex-native-install` / `test-codex-plugin-install` hook-handler
assertions, the cli-integration hook-lifecycle steps, the bats
`tests/hooks/*` glob + orphan-hooks audit, and the `--with-hooks`
install path. `embedded-sync` now validates only lib/skills. Both
summary `needs:` lists + the summary echo +
`docs/contracts/ci-jobs.yaml` updated; `validate-ci-policy-parity`
passes (66 rows). `.codex-plugin/plugin.json` drops the "Hooks"
capability.

## Load-bearing PRESERVED (reported, not forced)

- **`cli/cmd/ao/codex_runtime.go`** — the codex runtime *lifecycle
profile* (`HookCapable`/`HookConfigured` detection) is a
runtime-capability abstraction used by 6 `validate-codex-*` CI gates,
distinct from the hook product. Deleting it is a separate refactor.
- **`hooks-authoring` skill** + `schemas/hooks-manifest.v1.schema.json`
— reframed as an opt-in "author your own hooks" guide. Deleting the
skill cascades into catalog/domain-map/dispositions/context-map golden
files (skill-count sync), out of scope for the hook-product removal.
- **`scripts/pre-push-gate.sh`** — retired local gate (not in CI);
self-skips deleted hook scripts via `[[ -x ]]` guards; its 55 bats tests
still pass.
- Domain-enforcement audit keeps the `enforced`-mode schema for
backward-compatible JSON readers, but the hookless resolver only returns
`audited`/`unavailable`.

## Verification

`go build` / `go vet` clean; `go test ./...` = **11849 passed, 0
failed** (68 pkgs). gofmt clean on all touched files. Green: registry
`--check` (0 hooks), `regen-codex-hashes --check`,
`validate-ci-policy-parity`, `check-wiring-closure`,
`validate-embedded-sync`, `validate-manifests`, `validate-agents-split`,
`doc-release` (0 broken links), `generate-cli-reference --check`,
`heal.sh --strict`, codex/claude/plugin smoke tests, baseline-audit (0
stale). Diff: 231 files, +354 / −39803.

Closes-scenario: soc-57b7f#remove-all-hooks
Bounded-context: BC5-Runtime
Evidence: .github/workflows/validate.yml
2026-05-24 11:19:37 -04:00
Bo f4380065f7 feat(ci): promote executable-spec-link-integrity warn→blocking (soc-x7y9f #spec-link-blocking) (#506)
## What

Now that PR #490/soc-m8tdn linked all 14 GOALS.md directives to
scenarios, the directive↔scenario link lint (`ao goals scenarios
--lint`) is clean. This promotes the `executable-spec-link-integrity` CI
gate from **warn-only** to a **required (T1)** check.

## Changes

- **`.github/workflows/validate.yml`**: removed `continue-on-error:
true`; renamed job `(warn-only, F1.6)` → `(T1, soc-x7y9f)`; `ao goals
scenarios --lint` now runs strict (no `|| echo WARN`), so it fails the
job — and the summary — on any broken directive↔scenario edge. The inner
`ao goals trace --orphans` whole-chain audit stays warn-only inside the
now-required job (it surfaces pre-existing `broken_bead_scenario_claim`
parse defects that are out of this gate's scope, tracked under soc-gqhrz
— flipping it would red main).
- **`docs/contracts/ci-jobs.yaml`**: `reason`/`failure` prose updated
warn-only/Non-blocking → **Blocking (T1, soc-x7y9f)**, mirroring sibling
`validate-scenario-test-linkage`.
- **`AGENTS-CI.md`**: regenerated `### CI Jobs` table via
`scripts/generate-ci-jobs-table.sh --write` (drops the `(non-blocking)`
marker) + hand-maintained blocking-policy + triage-SLA prose updated.

The gate was already present in both summary `needs:` lists and the
summary echo — removing `continue-on-error` is what makes its `failure`
result reach the summary's `contains(needs.*.result, 'failure')`
failset.

## Proof it's safe to flip (blocking-mode local result)

```
$ cli/bin/ao goals scenarios --lint
No executable-spec link defects found.        # exit 0 — all 14 directives resolve
$ bash scripts/validate-ci-policy-parity.sh
CI_JOBS_TABLE: PASS (69 rows)                  # job table synced
```

YAML parse OK on both validate.yml and ci-jobs.yaml.

Closes-scenario: soc-x7y9f#spec-link-blocking
Bounded-context: BC4-Evidence
Evidence: docs/contracts/ci-jobs.yaml
2026-05-23 23:18:35 -04:00
Bo c012b6c1ee feat(bdd): scenario→test linkage runner + CI gate (soc-63xfx #scenario-test-runner) (#503)
## What

Mechanizes the scenario→test linkage gap. 71 `.feature` files existed
under `skills/*/references/` but only a handful had an executing test;
the rest were unverified documentation. This adds a linkage **convention
+ checker + blocking CI gate** — a linkage check, not a full Gherkin
step-interpreter (that scope was deliberately avoided per the bead's
pragmatic-scope guidance).

## Convention

`@covered-by:<test-path>[::<Name>]` Gherkin tag placed directly above a
`Scenario:` (or above `Feature:` for file-wide coverage). Lightest
convention that resolves "scenario X is covered by test Y" without
maintaining a separate registry. The `::<Name>` form additionally
asserts a named test (Go func / bats `@test` / bash func) exists in the
file.

## Deliverables

- **`scripts/check-scenario-test-linkage.sh`** — parses every `.feature`
scenario, resolves `@covered-by` targets, and FAILS on:
unlinked-and-unallowlisted scenarios, dangling links (missing path or
named test), allowlist/tag conflicts, and stale allowlist entries.
Flags: `--warn-only`, `--json`, `--help`. Exit 0/1/2.
- **`scripts/.scenario-linkage-allow`** — 68 doc-only feature files, a
*draining backlog* (drain as skills gain executing tests). The gate is
real: a scenario not here and not tagged fails CI.
- **Starter set** — `rpi`, `goals`, `scenario` features fully linked (11
scenarios) to their `tests/e2e/*` tests, proving the mechanism end to
end.
- **`validate.yml`** — `validate-scenario-test-linkage` T1 blocking job
+ summary `needs` + summary echo; manifest entry in
`docs/contracts/ci-jobs.yaml` + regenerated `AGENTS-CI.md` table (parity
gate green).
- **`docs/contracts/scenario-test-linkage.md`** +
`documentation-index.md` catalog entry.
- **`tests/scripts/check-scenario-test-linkage.bats`** — 13 regression
tests (pass/fail/warn/json/file-level-tag/misuse).

Sibling to `executable-spec-link-integrity` (directive→scenario).
Together they close the GOALS-directive → scenario → test evidence
chain.

## Verification (all green)

- `shellcheck scripts/check-scenario-test-linkage.sh` — clean
- gate run locally — PASS (256 scenarios, 71 files; 11 linked, 245
allowlisted)
- 5 failure paths manually exercised → exit 1; `--warn-only` → exit 0;
misuse → exit 2
- `bats tests/scripts/check-scenario-test-linkage.bats` — 13/13 ok
- `check-contracts-structural-floor.sh` — PASS (new contract catalogued)
- `validate-ci-policy-parity.sh` — PASS (manifest + AGENTS table synced)
- `validate.yml` + `ci-jobs.yaml` YAML parse OK
- diff stat: +639/−2, all-additions, no collateral data loss

Closes-scenario: soc-63xfx#scenario-test-runner
Bounded-context: BC4-Evidence
Evidence: scripts/check-scenario-test-linkage.sh
2026-05-23 23:03:01 -04:00
Bo 29e0acc26e chore(hooks): delete standards-injector + retire its CI gate — hookless S2 (soc-khev6 #rm-standards-injector) (#488)
## What

Hookless epic S2 (`soc-khev6`): delete the `standards-injector`
PreToolUse hook and **fully retire** its dedicated CI gate.

`standards-injector` is a value-negative noise-injector — it injects
language standards on every Edit/Write regardless of relevance (A/B Δ=0,
per `docs/architecture/hook-noise-audit.md`). The replacement is the
`standards` skill, read on demand: a PRODUCT surface, not hook magic.

## Teardown (all of it)

- **Hook + tests:** `git rm hooks/standards-injector.sh`, its embedded
copy, and `tests/hooks/standards-injector.bats`.
- **Manifests:** pruned the Edit + Write entries from `hooks/hooks.json`
and `hooks/codex-hooks.json`, dropping the now-empty matcher groups.
**Codex native handlers 17 → 15** (two entries removed, not one — Edit
retains `codex-parity-warn.sh`, Write group dropped).
- **CI gate retired:** removed the `standards-injector-completeness` job
from `validate.yml` + both `needs:` lists
(`validate-pr-evidence-claims`, `summary`) + the summary echo; `git rm`
the check script + its bats; removed from `ci-jobs.yaml` manifest and
regenerated `AGENTS-CI.md`; removed from `pre-push-gate.sh` + its bats
stub.
- **Codex test assertions (the S1 bite):** `test-runtime-codex-smoke.sh`
and `test-codex-native-install.sh` length `17 → 15`; removed
`standards-injector.sh` from the require-handlers array.
- **Shared iterators:** pruned the `@test`/section blocks from
`test-hooks.bats`, `hook-output-schema.bats`,
`hook-stdin-contracts.bats`, `test-hook-chain.sh`,
`test-hook-lifecycle.sh`, `test-hooks.sh`, and the eval fixture
`hook-context-value-smoke.sh`.
- **Other surfaces:** `validate-hook-preflight.sh`,
`install-codex-plugin.sh`, `install-codex.ps1` owned-script lists;
`hooks_test.go` (swapped synthetic name to a live hook);
`context_compiler.go` port doc comment.
- **Regen:** hook-lease classification + inventory, registry, embedded
sync.
- **Docs:** `HOOKS.md`, `hook-noise-audit.md` (records S2 deletion),
`ENV-VARS.md` (`AGENTOPS_STANDARDS_*` vars), `LOOP-BUDGET.md`,
`cli-skills-map.md`, `software-factory.md`, `SDK-INVENTORY.md`.

## Verify (all green locally)

`check-hook-lease-inventory`, `validate-hooks-doc-parity`,
`check-hook-port-replacements`, `validate-hook-preflight`,
`generate-registry --check`, `validate-ci-policy-parity`,
`tests/smoke-test.sh` (codex smoke + native install),
`test-runtime-codex-smoke.sh`, the 3 bats hook suites, `go
build/vet/test`. `grep standards-injector validate.yml
ci-local-release.sh` → empty.

Diff stat: 39 files, +32 / -1045 (deletion-heavy, as expected for a
teardown).

Closes-scenario: soc-khev6#rm-standards-injector
Bounded-context: BC5-Runtime
Evidence: docs/contracts/hook-lease-inventory.md
2026-05-23 20:54:24 -04:00
Bo f199746916 fix(ci): trigger bats-tests on skills/ + docs/ changes (soc-xrdwp #bats-path-filter) (#425)
bats-tests only triggered on hooks/shell/bats/ci. But several bats tests
assert SKILL.md / reference content — so a skills/docs-only PR that
breaks one didn't trigger bats and merged undetected (how #413's wiring
move hid for 11 PRs until #424 surfaced it).

- validate.yml: add `skills == 'true' || docs == 'true'` to the
bats-tests `if:` (both outputs already defined; matches other jobs'
idiom).

CI half of the #424 wiring-drift fix; aligns with the
path-filter-landmine learning.

How tested: validate.yml parses; +5/-1; this PR is a ci change so
bats-tests runs + confirms green.

NOTE: claude-review infra-red (weekly limit, resets May 25) —
operator-authorized bypass when sole red.

Closes-scenario: soc-xrdwp#bats-path-filter
Bounded-context: BC4-Validation
Evidence: .github/workflows/validate.yml
2026-05-22 21:34:47 -04:00
Bo 0b52e8345d feat(ci): wire 2-of-3 lint scripts as advisory I0 jobs (soc-dspz #wire-2-of-3) (#393)
## Why

soc-dspz asks for 3 advisory I0 jobs to wire in. Two of the three source
scripts are already on `main` (after PRs #373, #379 merged); the third
(`scripts/lint-skill-frontmatter.sh` from #376) is still pending merge.
Per the new agile invariant (soc-5qit, PR #392), the right move is to
ship the unblocked part and leave the parent bead open for the third
when #376 lands — instead of waiting for everything.

## What changed

| Surface | Change |
|---|---|
| `.github/workflows/validate.yml` | Added 2 stanzas:
`lint-evidence-lines-advisory` (PR-event triggered) and
`check-skill-catalog-drift-advisory` (skills/CI path-filtered). Both
`continue-on-error: true` so they emit `::warning::` annotations without
blocking merges. |
| `.github/workflows/validate.yml` summary job | `needs:` extended with
both new advisory jobs. |
| `docs/contracts/ci-jobs.yaml` | 2 manifest entries, alphabetically
slotted. |
| `AGENTS-CI.md` | Regenerated via `scripts/generate-ci-jobs-table.sh
--write` (69 rows). |

## Promotion path

Per soc-dspz acceptance: I0 advisory → T2 required after one clean week
of runs. File a follow-up bead to flip `continue-on-error: true` off and
promote both jobs to required.

## What's NOT in this PR

- `lint-skill-frontmatter-advisory` — depends on PR #376 (open) adding
`scripts/lint-skill-frontmatter.sh`. soc-dspz stays open after this
merges; a follow-up small PR will add that 3rd stanza once #376 lands.

## Validation

- `python3 -c 'import yaml; yaml.safe_load(...)'` parses both YAML files
clean
- `scripts/validate-ci-policy-parity.sh`: `CI_JOBS_TABLE: PASS (69
rows)`

Closes-scenario: soc-dspz#wire-2-of-3
Bounded-context: BC4-CI
Evidence: scripts/validate-ci-policy-parity.sh
2026-05-21 08:48:44 -04:00
Bo 354169aa45 feat(ci): path-filter validate-* + security-* jobs to skip when irrelevant (soc-dorz #ci-path-filter) (#388)
## Why

Operator quote (2026-05-20): _"all cis shouldn't run every time. only if
a file is changed."_

`validate.yml` ran 65 jobs on every push regardless of surface. Median
PR in this repo touches docs/skills only; the Go matrix +
contract-canaries + windows-smoke (each ~5-10 min) dominate wall-clock
for changes that never touch Go or contracts.

## What

Added `if: needs.changes.outputs.X == 'true'` clauses to 15
previously-unconditional jobs. `ci == true` is the safety belt on every
filter so any `.github/**` change still re-runs the full sweep.

| Job | Filter |
|---|---|
| `validate-quarantine-empty` | `skills`, `hooks`, `ci` |
| `validate-registry-drift` | `skills`, `docs`, `ci` |
| `validate-bounded-contexts-drift` | `skills`, `docs`, `contracts`,
`ci` |
| `validate-skill-domain-map-golden` | `skills`, `ci` |
| `validate-wiring-closure` | `go`, `hooks`, `shell`, `ci` |
| `validate-corpus-freshness` | `docs`, `ci` |
| `validate-flywheel-compounding-snapshot` | `docs`, `go`, `ci` |
| `validate-factory-yield-ledger` | `contracts`, `ci` |
| `validate-finding-registry` | `contracts`, `ci` |
| `validate-factory-admission` | `contracts`, `ci` |
| `validate-contracts-structural-floor` | `contracts`, `docs`, `ci` |
| `validate-docs-learning-references` | `docs`, `learning`, `ci` |
| `validate-three-gap-supergate` | `go`, `hooks`, `docs`, `skills`, `ci`
|
| `security-scan` | `go`, `shell`, `skills`, `ci` (also added `needs:
[changes]`) |
| `security-toolchain-gate` | `go`, `shell`, `ci` (also added `needs:
[changes]`) |

Cascaded skips (no edit needed — already gated via go-build dependency):
- `json-flag-consistency` skips when `go-build` skips
- `doctor-check` skips when `go-build` skips

## Safety

- **Summary job** already correctly handles `skipped`: any `failure`
fails the merge gate; `skipped` is OK on regular pushes. On release-tag
pushes (`refs/tags/v*`), `steps.release.outputs.release == 'true'` in
`changes:` forces all path-filter outputs to `true`, so release verdicts
are unchanged — every job runs.
- **Conservative filter design**: each `if:` lists every plausible
surface plus `ci` as safety belt. Bias is toward over-running rather
than under-running.
- **No test/contract files touched** — only the workflow YAML.

## Fitness delta

Jobs per push for a docs-only change: **~65 → ~15** (estimated; will
measure on this PR's own CI run).

## Verification

```bash
python3 -c "import yaml; yaml.safe_load(open('.github/workflows/validate.yml'))"
# YAML parses

bash scripts/validate-ci-policy-parity.sh
# CI_JOBS_TABLE: PASS (67 rows)
```

## Sibling patterns

Matches the existing path-filter shape on `registry-check` (L703 main)
and `validate-sovereignty-proof-citations` (L521 main, from PR #386).
This PR just propagates the pattern to the jobs that missed it.

Closes-scenario: soc-dorz#ci-path-filter
Bounded-context: BC2-Loop
Evidence: .github/workflows/validate.yml
2026-05-21 01:46:40 +00:00
Bo 5dfe1c9194 feat(docs): tiered AGENTS.md split — orientation + 4 siblings (soc-vuu6.3 #agents-md-tiered-split) (#387)
## Summary

Split the 580-line monolithic AGENTS.md into five tiered files so
cold-start sessions pay ≤250 lines for orientation, with operational
detail one hop away.

| File | Lines | Owner area |
|---|---:|---|
| `AGENTS.md` | 97 | Orientation + Quick Reference + sibling pointer
table + 1-release deprecation footer |
| `AGENTS-WORKFLOW.md` | 292 | Workflow phases · Branch/PR shape ·
Multi-agent discipline · Provenance · Doctrine altitudes · Local
Pre-Push · Releasing · Landing the Plane · bd Tracking · Session
Completion |
| `AGENTS-CI.md` | 127 | CI intro · Advisory Triage SLAs · DEFERRED
Hardening matrix · CI Jobs table (67 rows) · Nightly |
| `AGENTS-CODEX.md` | 52 | CLI Skill-Map Refresh + Codex Skill
Maintenance |
| `AGENTS-RUNTIME.md` | 59 | Canonical Root and Worktrees + Key
Constraints |

## Mechanical enforcement

- `scripts/validate-agents-split.sh` — 14 checks: AGENTS.md exists, ≤250
lines, each sibling exists, bidirectional links.
- CI gate `validate-agents-split` (required) — wired into `validate.yml`
`needs:` arrays + summary echo + `docs/contracts/ci-jobs.yaml` manifest.
- `scripts/generate-ci-jobs-table.sh` `AGENTS_PATH` default flipped from
`AGENTS.md` to `AGENTS-CI.md`; 67-row table regenerated.
- 7-case bats coverage at `tests/scripts/validate-agents-split.bats`.

## Fitness delta

Cold-start orientation budget: **580 → 97 lines (-83%)**. Total docs
across the split: 580 → 627 lines (+47 lines of sibling pointers and
deprecation footer — the overhead of the contract).

## Deferred to follow-up beads

- **Operating-loop ASCII spine** — `docs/architecture/operating-loop.md`
12-line flowchart prefix; orthogonal authoring task.
- **Universal init prompt wiring** — `ao session bootstrap` referenced
in AGENTS.md but blocked on soc-vuu6.25.

## Verification

```bash
bash scripts/validate-agents-split.sh
# validate-agents-split: scanned 14 checks
# PASS — AGENTS.md (97 lines) + 4 siblings, links bidirectional.

bats tests/scripts/validate-agents-split.bats
# 1..7 (all pass)

bash tests/docs/validate-doc-release.sh
# PASS: doc-release gate succeeded

bash scripts/validate-ci-policy-parity.sh
# CI_JOBS_TABLE: PASS (67 rows)
```

## Sibling patterns

CI gate shape follows `scripts/check-finding-registry.sh`. Manifest
entry follows the validate-* row shape. Bats fixtures match
`tests/scripts/validate-sovereignty-proof-citations.bats`.

Closes-scenario: soc-vuu6.3#agents-md-tiered-split
Bounded-context: BC1-Corpus
Evidence: AGENTS.md
Evidence: AGENTS-WORKFLOW.md
Evidence: AGENTS-CI.md
Evidence: AGENTS-CODEX.md
Evidence: AGENTS-RUNTIME.md
Evidence: scripts/validate-agents-split.sh
Evidence: tests/scripts/validate-agents-split.bats
Evidence: .github/workflows/validate.yml
Evidence: docs/contracts/ci-jobs.yaml
2026-05-21 01:05:02 +00:00
Bo 81fe2d5ae8 feat(docs): sovereignty-proof page — F6/F7 + RPI-leanness case studies (soc-vuu6.32 #sovereignty-proof) (#386)
## Summary

New \`docs/sovereignty-proof/\` surface with two committed case studies
showing where independent-vendor review caught what same-vendor review
missed:

- **2026-05-15 RPI-leanness council** — Codex (gpt-5.5) independently
reframed "RPI is waterfall" to "plan.md is an artifact-boundary bug",
turning a multi-week methodology rewrite into a single-port surgical
change (PR #275 was already in-flight implementing this).
- **2026-05-16 CDLC research council** — Two Codex judges independently
flagged the F6 session-misjoin bug in
\`cli/cmd/ao/flywheel_citation_feedback.go\` and the F7
\`hooks/citation-tracker.sh\` wiring gap. No Claude judge surfaced
either.

## Why

Council non-negotiable #2
(.agents/council/2026-05-18-validate-agentops-3.0-doctrine.md).
Mixed-model capability must be visible as load-bearing proof of the
sovereignty thesis, not buried as opt-in obscurity. The page is what
makes the sovereignty claim falsifiable + linkable from the README hero
(unblocks soc-vuu6.30).

## Mechanical guarantees

- \`scripts/validate-sovereignty-proof-citations.sh\` scans every
\`.md\` under \`docs/sovereignty-proof/\` for \`file:line\` citations
(\`path/to/file:NN\` or \`path/to/file:NN-MM\`) and verifies each
resolves at HEAD. Currently 16 citations across 3 pages.
- New CI job \`validate-sovereignty-proof-citations\` (required) wires
the script into \`validate.yml\` with broad triggers so any
referenced-surface change re-runs the gate. Added to merge-gate
\`needs:\` arrays + summary echo.
- 8-case bats regression suite at
\`tests/scripts/validate-sovereignty-proof-citations.bats\` (pass /
file-missing / line-overflow / fenced-code-block / backtick / multi-page
/ no-dir / no-markdown).
- mkdocs nav entry under Concepts → Sovereignty Proof.
- \`docs/documentation-index.md\` Concepts entry.

## Acceptance (BDD)

Given a visitor wants to verify the sovereignty claim is real, When they
click "Sovereignty proof" from README hero (slice 1, blocked on this),
Then they land on \`docs/sovereignty-proof/index.md\` showing real
\`/council --mixed\` output from the 2026-05-15 reframe AND 2026-05-16
F6+F7 findings with \`file:line\` citations and verdict file links.

## Verification

\`\`\`bash
bash scripts/validate-sovereignty-proof-citations.sh
# validate-sovereignty-proof-citations: scanned 3 pages, 16 citations
# PASS — all citations resolve.

bats tests/scripts/validate-sovereignty-proof-citations.bats
# 1..8 (all pass)
\`\`\`

Closes-scenario: soc-vuu6.32#sovereignty-proof
Bounded-context: BC1-Corpus
Evidence: docs/sovereignty-proof/index.md
Evidence: scripts/validate-sovereignty-proof-citations.sh
Evidence: tests/scripts/validate-sovereignty-proof-citations.bats
Evidence: .github/workflows/validate.yml
2026-05-21 00:48:38 +00:00
Bo 158d9a0305 fix(ci): registry-check path filter — add hooks/eval/go (soc-xhp6 #path-filter-coverage) (#367)
## Summary

`registry-check` triggered only on `skills` or `ci` changes, but
`scripts/generate-registry.sh` reads from `skills/`, `hooks/`, `evals/`,
AND
`cli/cmd/ao/`. PR #362 added `hooks/session-pr-counter.sh` — only the
`hooks`
filter matched, so registry-check was SKIPPED. The drift sat on main
until
PR #364 (soc-1nsx) touched `.github/workflows/validate.yml`, which DID
re-trigger registry-check via the `ci` filter and surfaced the gap.
PR #365 (soc-h1cr) regenerated the registry as a separate concern.

This PR closes the path-filter-SKIPPED-≠-drift-absent gap by extending
the
`if` condition to also trigger on `hooks`, `eval`, and `go` outputs.

Closes: soc-xhp6
Discovered-from: soc-h1cr (the PR that surfaced the drift)
Encoded-in:
`.agents/learnings/2026-05-20-path-filter-skipped-not-absent.md`

## Session-scope note

This is the 5th PR shipped in this autonomous session. The session-scope
post-mortem (soc-waxr doctrine) was already completed by the
`/post-mortem`
loop cron (output: HEALTHY session, 0 reactive-spiral, 1
self-correction).
This PR is harvested-from a post-mortem finding (this session's own
harvest), so the marginal-PR analysis = **discovery**, not churn.

The just-doctrinated `hooks/session-pr-counter.sh` hook (soc-1aou) will
fire
on `gh pr create` for this PR with `additionalContext` post-mortem
prompts —
that's the dogfood working as designed.

## Sibling pattern

The fixed `if` matches the shape used by `agentops-contract-canaries`
(line 687):
- `contracts || go || skills || ci` — multi-source filter for a
multi-source generator

This PR brings registry-check to the same shape.

## Verification

- `python3 -c "yaml.safe_load(open('.github/workflows/validate.yml'))"`
clean
- Manually compared `if` against `scripts/generate-registry.sh` source
paths (`skills/`, `hooks/`, `evals/`, `cli/cmd/ao/`)
- One-line edit + a comment block; no behavior change to the
registry-check step itself

Bounded-context: BC5-runtime
Evidence: .github/workflows/validate.yml

Co-authored-by: Codex <codex@example.invalid>
2026-05-20 04:15:04 +00:00
Bo 1af67b9cce feat(ci): validate-pr-evidence-claims — per-job log fetch (soc-1nsx #durable-ap7) (#364)
## Summary

Make AP#7 (Evidence-claim verification) **durable** by switching the
`validate-pr-evidence-claims` log fetch from `gh run view --log`
(whole-run,
eventually-consistent) to per-job iteration via
`gh api repos/.../runs/{id}/jobs` + `gh api repos/.../jobs/{id}/logs`.

Closes: soc-1nsx
Discovered-from: soc-eqjd (PR #358 shipped the MVP whose flake motivates
this)

## Why

PR #358's dogfood (also documented in

`.agents/learnings/2026-05-19-gh-run-view-log-eventually-consistent-mid-run.md`)
demonstrated the eventual-consistency window of `gh run view --log`: the
endpoint can return ~81 bytes mid-run even when sibling jobs are already
"completed". The MVP papered over this with a small-log skip threshold —
verification falls back to "unavailable" instead of "fail", which is
safe
but converts a should-be-mechanical gate into a best-effort signal.

The per-job log endpoint
(`/repos/{owner}/{repo}/actions/jobs/{job_id}/logs`) serves an
individual
job's log as soon as that job finishes. There is **no whole-run
eventual-consistency window** on this endpoint — once a job completes,
its
log is immediately available.

## Fix

Replace the single `gh run view "$RUN" --log` call with a paginated loop
that:

1. `gh api repos/.../runs/${run_id}/jobs?per_page=100&page=N` — list
jobs
2. For each completed (non-skipped) sibling job, `gh api
repos/.../jobs/${job_id}/logs >> /tmp/run.log`
3. Break when the page returns <100 jobs
4. Set `log_ok=true` if at least one sibling job's log was fetched

Trade: ~60 API calls per PR run (up from 1) in exchange for the 81-byte
flake going to zero. Validated locally at ~10s wall-time for 65 jobs /
32
completed / 3.7 MB log on PR #363's merge run.

## Verification (local, against real GH Actions data)

- Per-job script run against `gh api ... runs/26137401090/jobs` →
  `jobs_with_logs=32 jobs_seen=65 log_bytes=3,767,898` in **10s**.
- `verify-gate-claim.sh --log /tmp/run.log "pr-363"
"cli/cmd/ao/cobra_commands_test.go"` → PASS (PR #363's actual Evidence).
- `bats tests/scripts/verify-gate-claim.bats` → 15/15 green (script
unchanged).
- `python3 -c "yaml.safe_load(open('.github/workflows/validate.yml'))"`
→ clean.
- `bash -n` of the extracted run script → clean.

This PR will **dogfood the new path** because its own
`validate-pr-evidence-claims` job runs the new code while verifying its
own
`Evidence: .github/workflows/validate.yml` claim. If the per-job fetch
fails for any reason, the AP#7 check fails on this PR — exactly the
self-test soc-eqjd intended but couldn't deliver due to the flake.

## Why this isn't a real behavior change

`verify-gate-claim.sh`'s grep contract is unchanged — only the log-fetch
upstream is replaced. The "verification unavailable" fallback is
preserved
with new, sharper semantics: it now fires only if zero completed sibling
jobs returned logs (verifier ran first, or every sibling was skipped),
which is a real "no data" state, not a transient API-consistency flake.

Bounded-context: BC2-evidence-validation
Evidence: .github/workflows/validate.yml

Co-authored-by: Codex <codex@example.invalid>
2026-05-20 02:56:15 +00:00
Bo 7125a43d76 feat(ci): validate-pr-evidence-claims — AP#7 enforcement in CI (soc-eqjd) (#358)
## Summary

**Wave 3 of soc-g2r9** — migrates AP#7 mechanical enforcement from the
now-retired local pre-push-gate.sh check #39 (soc-o5kq) to a new CI job.
Closes the ~1-day AP#7 gap that PR #357 opens.

For each `Evidence:` line in the PR body, the new job asserts the claim
appears verbatim in this workflow run's logs. False claims now block the
PR at CI time.

Closes: soc-eqjd

## Fitness delta

- AP#7 enforcement: **local-pre-push (retired) → CI validate.yml job
(active)**
- Mechanical anti-pattern protection during the soc-g2r9 transition: **0
→ 1** restored
- CI jobs total: **65 → 66** (summary needs + echo + manifest + AGENTS
table all in lockstep)
- soc-g2r9 follow-up beads closed: **0 → 1**

## What changes

| File | Change |
|---|---|
| `.github/workflows/validate.yml` | New job
`validate-pr-evidence-claims` (~76 lines); `summary.needs` extended;
summary echo added |
| `docs/contracts/ci-jobs.yaml` | New manifest entry (alphabetical) |
| `AGENTS.md` | Auto-regenerated by `scripts/generate-ci-jobs-table.sh
--write` (66 rows) |

## Job shape

Mirrors the existing `validate-*` family — depends on the same broad set
as `summary` so sibling-job logs are present when fetched. Steps:

1. Skip-if-not-`pull_request`
2. Extract `Evidence:` lines via `gh pr view --jq .body | sed`
3. Skip-if-no-Evidence-line (notice, not error)
4. Fetch run logs via `gh run view --log`
5. Call `scripts/verify-gate-claim.sh --log <log>` in passive mode per
claim
6. Fail with `::error::` annotations if any claim is absent

The standalone `scripts/verify-gate-claim.sh` is unchanged; its 15-bats
coverage continues to apply. Only the wiring moves.

## Sibling pattern

Job structure mirrors `validate-codex-parity-drift` shape.
Manifest+AGENTS render path follows the
`scripts/generate-ci-jobs-table.sh` flow from soc-3oij (PR #315).

## Dogfood

This PR's `Evidence:` trailer is `shellcheck` — a substring of the
verdict line nearly every PR run produces (` ok shellcheck` or similar).
When CI runs this PR, the new job will verify its own contract on its
own PR.

## Wave 3 of 4 (soc-g2r9 follow-ups)

| Bead | P | Status |
|---|---|---|
| `soc-bbvw` | 1 | Wave 1 — delete pre-push-gate.sh + helpers (pending)
|
| `soc-uo0c` | 2 | Wave 2 — retire gate-only bats (pending) |
| **`soc-eqjd`** | **1** | **Wave 3 — this PR** |
| `soc-liyr` | 2 | Wave 4 — doctrine sweep (pending) |

Bounded-context: BC0-foundations + BC5-runtime (CI job wiring)
Evidence: shellcheck

Co-authored-by: Codex <codex@example.invalid>
2026-05-19 21:22:02 +00:00