diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index 52cf29f..61cc468 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -209,7 +209,7 @@ |---|------|----------------|-------|---------------------------| | G1 | Audit Phase 3: fresh-agent grounding test | 3 clean-context probes score the 7-question set against this doc; holes map made | session | CLOSED 2026-07-18: 3 probes, 21/21 PASS, holes H1 (amended into G9) + H2 (no action) -- `docs/audit/phase3-grounding-test-20260718.md` | | G2 | Audit Phase 4: GA-R1..R7 structural rulings + the stage-status vocabulary A/B | ruling-type gate (GA-R6 rule 6): closes when every item carries a GA-R5 Status block | operator | CLOSED 2026-07-18: all seven GA-R + vocabulary (Option A + H1) RATIFIED, utterances quoted (`docs/audit/ga-rulings.md`, through commit `fe4f1c4` + this one) | -| G3 | Audit Phase 5: repair sweep of GA-F01..F15 (incl. memory hygiene GA-F05..F08, skill sweep) | operator-gated fix batches, each commit naming its GA-F | operator + session | FREEZE in effect; zero fixes applied; sweep plan REVIEWED (PASS, amendments S1-S5 applied -- `docs/audit/phase5-sweep-plan.md`); batches await per-batch operator gates | +| G3 | Audit Phase 5: repair sweep of GA-F01..F15 (incl. memory hygiene GA-F05..F08, skill sweep) | operator-gated fix batches, each commit naming its GA-F | operator + session | Batch 0 OPENED by operator 2026-07-19; item 0.1 landed (repo-lint L10 status-coupling, GA-R1/C1 -- this commit); 0.2-0.4 pending; FREEZE holds for all un-gated surfaces | | G4 | The two D-130 verifications (charter-exempt, read-only/throwaway): (v7) v0.9.8 volume-replace-under-running-domain on a throwaway domain; (v8) lifecycle ignore_changes suppression of the forced replacement | run them, capture output | session | TO RUN (`docs/audit/env-snapshot-20260718.md:47-49`) | | G5 | D-130 mechanism ruling (seed-volume durable fix) | operator rules in Phase 5, quoting G4's captured output | operator | D-130 unassigned; next-free D confirmed 130 (`ledger-scan` run 2026-07-18; no `^## D-130` in design-decisions.md) | | G6 | State reconcile of autostart + seed WITHOUT bouncing guests | gated mechanism per the finding: `tofu import` (reads autostart back) or state surgery, or an operator-scheduled maintenance-window apply; `-refresh-only` is PROVEN a no-op | operator | Operator earlier selected reconcile-first (finding doc:136); superseded into audit sequencing by `2b718f5`; nothing reconciled yet | diff --git a/scripts/repo-lint.sh b/scripts/repo-lint.sh index e313aa6..d467437 100644 --- a/scripts/repo-lint.sh +++ b/scripts/repo-lint.sh @@ -5,7 +5,7 @@ # Mutates NOTHING -- except under --record-clientdocs-sweep (DOCFIX-104, rewrites # ONLY clientdocs/sweep-receipt.txt) or --record-guide-skill-coupling (rewrites # ONLY clientdocs/guide-skill-coupling.txt, the L8 pin); both then lint as usual. -# See the .py docstring for the check catalog (L1-L8). +# See the .py docstring for the check catalog (L1-L10). # Exit: 0 clean | 1 FAIL findings | 2 warnings only. ASCII + LF. set -uo pipefail HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/scripts/repo_lint.py b/scripts/repo_lint.py index c88877b..ff74d63 100644 --- a/scripts/repo_lint.py +++ b/scripts/repo_lint.py @@ -56,12 +56,32 @@ $HERE in scripts (see scripts/repo-lint.sh), $REPO set once per session (via git rev-parse --show-toplevel or an explicit export) threaded through runbook RUN blocks. + L10 status-couple GA-R1 rule 8 (amendment C1, ratified 2026-07-18 -- + docs/audit/ga-rulings.md): a change set that modifies a + state-bearing surface -- anything under docs/audit/ + (captures, ga-rulings.md), a **Status: line in + docs/design-decisions.md, or a gate row (| G |) in + docs/session-ledger.md -- without touching + docs/CURRENT-STATE.md is "defective by construction". + Scans the PENDING change set (working tree vs HEAD) when + the tree is dirty, else the HEAD commit -- so the flag + fires BEFORE the commit in the delivery loop, and at + session bootstrap for an already-landed one. Skipped when + git or a HEAD commit is absent (static fixtures). + Severity: WARN during sweep Batch 0, then FAIL -- flip + L10_FAIL (phase5-sweep-plan Batch 0: "WARN for one + batch"). Exit: 0 clean | 1 FAIL findings | 2 warnings only. ASCII + LF. Usage: python3 scripts/repo_lint.py [--record-clientdocs-sweep] [--record-guide-skill-coupling] [repo-root] (marker: repo-lint: allow-stale-tokens -- this file names them by necessity) """ -import re, sys, pathlib, collections, hashlib +import re, sys, pathlib, collections, hashlib, shutil, subprocess + +# L10 severity gate: False = WARN (sweep Batch 0), True = FAIL. +# Flip to True at Batch 1 open per docs/audit/phase5-sweep-plan.md Batch 0 +# ("the C1 check is a WARN for one batch before becoming FAIL"). +L10_FAIL = False RECORD_FLAG = "--record-clientdocs-sweep" RECORD_CMD = "bash scripts/repo-lint.sh " + RECORD_FLAG @@ -328,6 +348,56 @@ # (band-limited regex, mention-counting incl. pointer lines, tests/ fixture # ingestion) and collided with ledger-scan, the single numbering authority. + # ---- L10 status-coupling (GA-R1 rule 8 / amendment C1) ---- + # State-bearing surfaces per the ratified C1 utterance: docs/audit/ + # captures + ga-rulings, design-decisions Status lines, ledger gate rows. + # (The gate-row TABLE currently lives in CURRENT-STATE.md itself, where a + # row edit satisfies the rule by construction; the session-ledger pattern + # below guards gate rows reappearing there.) A triggering change set must + # include docs/CURRENT-STATE.md, same commit. + def git(*args): + return subprocess.run(["git", "-C", str(R)] + list(args), + capture_output=True, text=True) + if shutil.which("git") and \ + git("rev-parse", "--is-inside-work-tree").stdout.strip() == "true" and \ + git("rev-parse", "--verify", "-q", "HEAD").returncode == 0: + if git("status", "--porcelain").stdout.strip(): + scope = "pending change set" + changed = set(git("diff", "--name-only", "HEAD").stdout.splitlines()) + changed |= set(git("ls-files", "--others", "--exclude-standard") + .stdout.splitlines()) + def l10_diff(path): + return git("diff", "HEAD", "--", path).stdout + else: + scope = "HEAD commit" + changed = set(git("diff-tree", "-r", "--no-commit-id", "--name-only", + "--root", "HEAD").stdout.splitlines()) + def l10_diff(path): + return git("diff-tree", "-p", "--no-commit-id", "--root", + "HEAD", "--", path).stdout + def diff_hits(path, rx): # changed +/- lines matching rx (headers excluded) + return any(rx.match(ln) for ln in l10_diff(path).splitlines()) + triggers = [] + audit_hits = sorted(p for p in changed if p.startswith("docs/audit/")) + if audit_hits: + triggers.append("docs/audit/: " + ", ".join(audit_hits[:3]) + + (" (+%d more)" % (len(audit_hits) - 3) + if len(audit_hits) > 3 else "")) + if "docs/design-decisions.md" in changed and diff_hits( + "docs/design-decisions.md", + re.compile(r"^[+-](?!\+\+|--).*\*\*Status:")): + triggers.append("design-decisions.md Status line") + if "docs/session-ledger.md" in changed and diff_hits( + "docs/session-ledger.md", + re.compile(r"^[+-](?!\+\+|--)\s*\|\s*G\d+\s*\|")): + triggers.append("session-ledger.md gate row") + if triggers and "docs/CURRENT-STATE.md" not in changed: + (fails if L10_FAIL else warns).append( + "L10 %s modifies state-bearing surface(s) [%s] without touching " + "docs/CURRENT-STATE.md -- GA-R1 rule 8 (C1): a status-changing " + "commit updates CURRENT-STATE.md in the SAME commit" + % (scope, "; ".join(triggers))) + # ---- L7 clientdocs sweep-on-change receipt ---- if cdir.is_dir(): if not receipt.exists(): diff --git a/tests/repo-lint/run-tests.sh b/tests/repo-lint/run-tests.sh index ea29758..1731c26 100644 --- a/tests/repo-lint/run-tests.sh +++ b/tests/repo-lint/run-tests.sh @@ -150,5 +150,48 @@ # no guide in the fixture -> L8 is skipped entirely (clean repo still passes) D=$(mkcd l8e); rec "$D"; run 0 'PASS: repo lint' "T28 L8 skipped when no guide present" "$D" +# ---- L10 status-coupling (GA-R1 rule 8 / amendment C1) ---- +mkgit() { # mkrepo + state-bearing surfaces + git history (for L10) + local d; d=$(mkrepo "$1") + mkdir -p "$d/docs/audit" + printf '# rulings\n' > "$d/docs/audit/ga-rulings.md" + printf '# current state\n' > "$d/docs/CURRENT-STATE.md" + printf '# ledger\n| G4 | gate | closes it | owner | TO RUN |\n' > "$d/docs/session-ledger.md" + printf '## D-001\n**Status: PROPOSED.**\n' > "$d/docs/design-decisions.md" + git -C "$d" init -q -b main + git -C "$d" add -A >/dev/null 2>&1 + git -C "$d" -c user.email=t@t -c user.name=t commit -qm base >/dev/null 2>&1 + echo "$d" +} +# non-git fixtures skip L10 entirely: every earlier T-case above already +# asserts its exact rc with L10 present, so no separate skip test is needed. +D=$(mkgit l10a); printf 'amended\n' >> "$D/docs/audit/ga-rulings.md" + run 2 'L10 pending change set.*docs/audit/' \ + "T34 L10 dirty audit-surface edit w/o CURRENT-STATE WARNs" "$D" +D=$(mkgit l10b); printf 'amended\n' >> "$D/docs/audit/ga-rulings.md" +printf 'updated\n' >> "$D/docs/CURRENT-STATE.md" + run 0 'PASS: repo lint' \ + "T35 L10 same change set touching CURRENT-STATE PASSES" "$D" +D=$(mkgit l10c); printf '## D-001\n**Status: ADOPTED.**\n' > "$D/docs/design-decisions.md" + run 2 'L10 .*design-decisions.md Status line' \ + "T36 L10 Status-line change w/o CURRENT-STATE WARNs" "$D" +D=$(mkgit l10d); printf 'non-status prose line\n' >> "$D/docs/design-decisions.md" + run 0 'PASS: repo lint' \ + "T37 L10 non-Status design-decisions edit does NOT fire" "$D" +D=$(mkgit l10e); printf 'amended\n' >> "$D/docs/audit/ga-rulings.md" +git -C "$D" add -A >/dev/null 2>&1 +git -C "$D" -c user.email=t@t -c user.name=t commit -qm drift >/dev/null 2>&1 + run 2 'L10 HEAD commit.*docs/audit/' \ + "T38 L10 clean tree flags the HEAD commit" "$D" +D=$(mkgit l10f); printf '# ledger\n| G4 | gate | closes it | owner | OPEN |\n' > "$D/docs/session-ledger.md" + run 2 'L10 .*session-ledger.md gate row' \ + "T39 L10 ledger gate-row change w/o CURRENT-STATE WARNs" "$D" +D=$(mkgit l10g); printf 'ledger prose, no gate row touched\n' >> "$D/docs/session-ledger.md" + run 0 'PASS: repo lint' \ + "T40 L10 non-gate-row ledger edit does NOT fire" "$D" +D=$(mkgit l10h); printf 'plan output\n' > "$D/docs/audit/outer-plan-x.txt" + run 2 'L10 pending change set.*docs/audit/' \ + "T41 L10 untracked new capture file WARNs" "$D" + echo; echo "RESULT: PASS=$PASS FAIL=$FAIL" [[ "$FAIL" -eq 0 ]] && { echo "ALL PASS"; exit 0; } || exit 1