# Audit-sweep convention

Closure scripts (`tests/audit-v*.*-closure.sh`) delegate their zero-regression
sweep — the long iterative phase that re-runs every `tests/audit-*.sh` to
prove no unrelated audit regressed — to a shared helper at
`tests/lib/audit-sweep.sh`. The helper:

1. **Parallelizes** the sweep (`PARALLELISM=8` default; override via env).
   Each audit already creates its own `mktemp -d` working dir per the Idiom 2
   header convention, so concurrent slots do not collide. Wall-clock for a
   typical milestone closure drops from ~30 min to ~4 min.

2. **Honors the same exclusion / allowlist semantics** the inline closure
   loops used:
   - `EXCLUDED_AUDITS[]` — audits skipped entirely (already-run, self,
     prior-milestone closures, environment-hazard carry-forwards).
   - `PREEXISTING_FAILURES[]` — audits whose non-zero exit is verified
     pre-existing baseline noise; classified as `PRE-EXISTING`, do not
     fail the sweep, and must be removed (promotion-by-removal) when
     the underlying issue is fixed.

3. **Optionally honors a changed-paths gate.** When the closure script sets
   `CHANGED_PATHS` (e.g., from `git diff --name-only <prev-tag>..HEAD`) and
   passes `'changed-paths'` as the mode arg, audits whose declared
   `AUDIT_INPUTS` are untouched by the milestone diff are skipped with a
   `SKIP-UNTOUCHED` marker. Audits without an `AUDIT_INPUTS` declaration
   ALWAYS run (default-on safety; undeclared audits cannot be reasoned
   about, so they run unconditionally).

## The AUDIT_INPUTS header

To opt an audit into the changed-paths gate, declare its inputs as a
header comment block immediately under the shebang:

```bash
#!/usr/bin/env bash
# AUDIT_INPUTS:
#   bin/lib/vault-sync.js
#   bin/lib/vault-sync.test.cjs
#   tests/audit-extraction-correctness.sh
#
# audit-foo.sh -- regular header continues here ...
```

Rules:
- File entries match the changed-paths list exactly (no glob expansion).
- Directory entries (suffix `/`) match by prefix — e.g., `evals/diag/fixtures/`
  triggers the audit if any file under that directory changed.
- An audit declaring inputs is opted-in. It will skip when those inputs are
  untouched between the milestone's baseline and HEAD.
- An audit without an `AUDIT_INPUTS` block is opted-out. It always runs in
  the sweep regardless of the diff.

## When to add AUDIT_INPUTS

Worth adding when:
- The audit has a small, stable set of source files it inspects.
- The audit is heavyweight (>10s) and frequently irrelevant to milestones
  whose scope does not touch its targets.

Skip declaring when:
- The audit reads broadly (e.g., grep across the whole repo).
- The audit's behavior depends on dynamic content (timestamps, git state,
  environment) the diff cannot capture.
- You are unsure: leave it undeclared (default-run is the safe default).

## Example: enabling the gate in a closure script

```bash
# Compute the milestone diff once.
PREV_TAG="v1.5.3"
CHANGED_PATHS="$(git diff --name-only "$PREV_TAG..HEAD")"
export CHANGED_PATHS

# Source the helper and run with the gate active.
source "$REPO/tests/lib/audit-sweep.sh"
sweep_audits 'v1.6-closure' 'changed-paths' "$REPO/tests" \
  || fail "ZERO-REGRESSION" "sweep_audits returned non-zero"
```

The first closure to use the gate should pilot it with `mode=''` (gate off)
on one run and `mode='changed-paths'` on the next, then diff the results
to confirm only declared-untouched audits skip.
