diff --git a/docs/architecture/runtime-authority-map.md b/docs/architecture/runtime-authority-map.md index 5023f4f..257d3fa 100644 --- a/docs/architecture/runtime-authority-map.md +++ b/docs/architecture/runtime-authority-map.md @@ -149,6 +149,70 @@ source line from `actools.sh`: it is invoked from `cli/actools` (the copied operator-CLI surface), which is why the union with the entry-point grep, not the closure alone, is required. +## File-level wiring within live modules (C4 inventory) + +> Recorded at C4. The "Standalone modules" section above proves WHICH module dirs +> are live; this section proves, within the 6 live dirs, WHICH FILES are reached +> on the live path vs which ship unwired. Human-readable mirror of +> `tests/guards/live_module_file_inventory_test.bats`, which fails CI on any +> unclassified or wiring-flipped file. **C4 changed NO module file** — disposition +> of the unwired files is deferred (see the disposition column). Baseline `d482818`. + +The 6 live modules ship **35 files: 21 wired, 1 documentation, 13 unwired.** The +13 unwired files reside on the box (in-place install + `chown -R`, `actools.sh:94`/`:405`) +but never execute on the live path. + +### Wired (21) — reached on the live path +| file | reached via | +|---|---| +| `modules/audit/audit.sh` | EXECUTED by `cli/actools:313` (the `audit` command) | +| `modules/audit/lib/output.sh` | sourced by `audit.sh:57` (and `cli/actools:317`) | +| `modules/audit/lib/drupal.sh` | sourced by `audit.sh:58` | +| `modules/audit/lib/integration.sh` | sourced by `audit.sh:59` | +| `modules/audit/lib/stack.sh` | sourced by `audit.sh:60` | +| `modules/audit/lib/security.sh` | sourced by `audit.sh:61` | +| `modules/audit/lib/report.sh` | sourced by `audit.sh:62` | +| `modules/backup/cron.sh` | source-closure of `actools.sh` (`:516`) | +| `modules/db/core.sh` | source-closure of `actools.sh` (`:457`) | +| `modules/drupal/provision.sh` | source-closure of `actools.sh` (`:181`) | +| `modules/host/age.sh` | source-closure — host loop (`:193`) | +| `modules/host/docker.sh` | source-closure — host loop | +| `modules/host/firewall.sh` | source-closure — host loop | +| `modules/host/kernel.sh` | source-closure — host loop | +| `modules/host/logrotate.sh` | source-closure — host loop | +| `modules/host/packages.sh` | source-closure — host loop | +| `modules/host/swap.sh` | source-closure — host loop | +| `modules/stack/caddyfile.sh` | source-closure — stack loop (`:204`) | +| `modules/stack/compose.sh` | source-closure — stack loop | +| `modules/stack/images.sh` | source-closure — stack loop | +| `modules/stack/mycnf.sh` | source-closure — stack loop | + +### Documentation (1) — ships, executed by no code +| file | note | +|---|---| +| `modules/audit/docs/fix_catalog.md` | audit fix-catalog reference doc; referenced by no code | + +### Unwired (13) — ship on the box, OFF the live path; disposition deferred +| file | what it is | disposition (deferred) | +|---|---|---| +| `modules/backup/encrypted_backup.sh` | "Phase 4.5 Item 2" — age-encrypted backups | **E2** (encrypted backups): reconcile/wire/harden | +| `modules/backup/binlog-rotate.sh` | hourly binlog rotation/encryption/upload | **E3** (binlog/PITR) | +| `modules/backup/db-full-backup.sh` | daily full dump (PITR baseline) | **E3** | +| `modules/backup/pitr-restore.sh` | point-in-time restore | **E3** | +| `modules/backup/cli-pitr.sh` | `actools` CLI integration for PITR | **E3** | +| `modules/backup/deploy-pitr.sh` | manual PITR deploy script (entrypoint for the above) | **E3** | +| `modules/backup/mariadb-binlog.cnf` | binlog MariaDB config | **E3** | +| `modules/backup/99-binlog.cnf` | binlog MariaDB config | **E3** | +| `modules/backup/docker-compose.binlog.yml` | binlog volume compose override | **E3** | +| `modules/backup/actools-db-backup.cron` | backup cron entries | **E3** | +| `modules/audit/deploy-audit.sh` | self-declared UNWIRED + STALE; superseded by `lib/*.sh` | **Phase 5**: reconcile-or-delete | +| `modules/drupal/prepare.sh` | "Stage 1" extraction; superseded (`provision.sh` inlines it); unsourced | **Phase 5**: reconcile-or-delete | +| `modules/drupal/secure.sh` | self-declared UNWIRED; "Phase 5 decision" | **Phase 5**: wire-or-delete | + +The 10-file `backup/` cluster is a **partial implementation of E2 + E3** — those +phases reconcile/test/harden it rather than build from scratch. C4 records and +guards these files; it wires, deletes, and moves nothing. + ## Update rule Every phase must update this map if it changes authority. diff --git a/docs/runbooks/PHASE0_LEDGER.md b/docs/runbooks/PHASE0_LEDGER.md index 042559a..188a02c 100644 --- a/docs/runbooks/PHASE0_LEDGER.md +++ b/docs/runbooks/PHASE0_LEDGER.md @@ -86,6 +86,52 @@ Approved / Needs revision / Blocked ### Forbidden next scope ```` +## Entry 024 — C4: file-level orphan inventory + file-level wiring guard + +Date: +Phase: C4 (Track C — cleanup; the repurposed slot after vocabulary folded into D1) +Baseline: d482818 (#52) + +### Objective + +Inventory + pin the FILE-level wiring inside the 6 live modules (the dir-level C1 +guard only proved which module DIRS are live). Of the 35 files: 21 wired +(source-closure of actools.sh, or executed/sourced via the cli/actools `audit` +command), 1 documentation (audit/docs/fix_catalog.md), and 13 UNWIRED that ship to +prod (in-place install + chown -R) but never execute — the 10-file backup/ "Phase +4.5 Item 2" PITR/binlog/encrypted-backup cluster (a partial E2/E3 implementation, +manually deployable via deploy-pitr.sh), audit/deploy-audit.sh (self-declared +stale), and drupal/{prepare,secure}.sh (superseded/unwired extractions; provision.sh +is the wired stage). Adds a new guard (live_module_file_inventory_test.bats) that +fails CI if any live-module file is unclassified or if an unwired file flips onto +the live closure; records the inventory in runtime-authority-map.md. + +C4 changes NO module file. Disposition is DEFERRED: backup/* -> E2/E3 +(reconcile/wire/harden the existing drafts); audit/deploy-audit.sh + +drupal/{prepare,secure}.sh -> Phase 5 (per their own self-declared notes). + +### Runtime authority changes + +None. No module/code/installer file is touched; the live source-closure is +byte-identical to d482818. C4 adds one doc subsection, one new guard test, and this +ledger entry. **Behavior-free -> no branch e2e required** (as with C1). + +### Files + +New: tests/guards/live_module_file_inventory_test.bats. Edited: +docs/architecture/runtime-authority-map.md (add the "File-level wiring within live +modules" subsection only; the existing Standalone section + line-45 row are +untouched, left to D1), this ledger. + +### Verdict + +Pending — the Review Gate ratifies on merge (this coding window does not +self-approve). Verify in order: see SPEC-C4 §6. + +### Commit SHA + +Sandbox commit on top of d482818; operator stamps the squash/merge SHA on apply. + ## Entry 023 — C3: quarantine the 7 4.5-seed modules into experimental/ Date: @@ -132,8 +178,7 @@ Moved (git mv, 7 dirs / 11 files): `modules/{ai,compliance,dr,network,observabil ### Verdict -**Pending** — the Review Gate ratifies on merge (this coding window does not -self-approve). Verify in order: see SPEC-C3 §6. +**APPROVED — ratified (): C3 merged to `main` as `d482818` (#52); branch e2e #87 reached `MariaDB ready.`, confirming the seed quarantine is transparent to the live path. `d482818` is the verified baseline of C4, and this ratification rides with the C4 patch — which adds the file-level inventory + guard on top of the C3 dir-level quarantine and re-runs all guards green.** *(Original pending text, for the record:)* **Pending** — the Review Gate ratifies on merge (this coding window does not self-approve). Verify in order: see SPEC-C3 §6. ### Commit SHA diff --git a/tests/guards/live_module_file_inventory_test.bats b/tests/guards/live_module_file_inventory_test.bats new file mode 100644 index 0000000..25747b9 --- /dev/null +++ b/tests/guards/live_module_file_inventory_test.bats @@ -0,0 +1,103 @@ +#!/usr/bin/env bats +# ============================================================================= +# live_module_file_inventory_test.bats — FILE-LEVEL wiring guard (Phase C4) +# +# The dir-level guard (orphan_inventory_guard_test.bats) proves WHICH module dirs +# are live. This guard proves, WITHIN the 6 live dirs, which files are reached on +# the live path vs which ship unwired — so an unwired file cannot (a) ship +# unflagged (every file must be classified in the manifest below) or (b) silently +# flip wiring (an unwired draft cannot enter the live source-closure without +# failing CI). +# +# C4 changes NO module file; disposition of the unwired files is deferred — +# backup/* -> E2/E3; audit/deploy-audit.sh, drupal/{prepare,secure}.sh -> Phase 5. +# +# Update this manifest when a file is added to / removed from a live module, or +# when a file's wiring changes. Non-vacuity demos are recorded in HANDOFF-C4. +# ============================================================================= + +load live_closure # build_live_closure + CLOSURE (reused byte-unmodified from C1) + +REPO="" +setup() { + REPO="$(cd "$(dirname "$BATS_TEST_FILENAME")/../.." && pwd)" +} + +# --- the classified manifest (the 35 files of the 6 live modules) --- +# WIRED: reached on the live path (source-closure of actools.sh, OR executed / +# sourced via the cli/actools `audit` command). +EXPECTED_WIRED_FILES=( + modules/audit/audit.sh + modules/audit/lib/output.sh + modules/audit/lib/drupal.sh + modules/audit/lib/integration.sh + modules/audit/lib/stack.sh + modules/audit/lib/security.sh + modules/audit/lib/report.sh + modules/backup/cron.sh + modules/db/core.sh + modules/drupal/provision.sh + modules/host/age.sh + modules/host/docker.sh + modules/host/firewall.sh + modules/host/kernel.sh + modules/host/logrotate.sh + modules/host/packages.sh + modules/host/swap.sh + modules/stack/caddyfile.sh + modules/stack/compose.sh + modules/stack/images.sh + modules/stack/mycnf.sh +) +# DOC: ships as module documentation, executed by no code. +EXPECTED_DOC_FILES=( + modules/audit/docs/fix_catalog.md +) +# UNWIRED: ship on the box (in-place install) but OFF the live path. Disposition +# deferred — see header. These MUST stay off the live source-closure (test 2). +EXPECTED_UNWIRED_FILES=( + modules/backup/encrypted_backup.sh + modules/backup/binlog-rotate.sh + modules/backup/db-full-backup.sh + modules/backup/pitr-restore.sh + modules/backup/cli-pitr.sh + modules/backup/deploy-pitr.sh + modules/backup/mariadb-binlog.cnf + modules/backup/99-binlog.cnf + modules/backup/docker-compose.binlog.yml + modules/backup/actools-db-backup.cron + modules/audit/deploy-audit.sh + modules/drupal/prepare.sh + modules/drupal/secure.sh +) + +@test "every file in the 6 live modules is classified (no surprise file ships)" { + local expected actual + expected="$(printf '%s\n' "${EXPECTED_WIRED_FILES[@]}" "${EXPECTED_DOC_FILES[@]}" "${EXPECTED_UNWIRED_FILES[@]}" | sort -u)" + actual="$(cd "$REPO" && find modules -type f | sort -u)" + if [[ "$expected" != "$actual" ]]; then + echo "Live-module file set drifted from the C4 manifest." + echo "A file was added/removed/renamed in a live module without updating this" + echo "guard. Classify it (wired / doc / unwired) and update the inventory in" + echo "runtime-authority-map.md. diff (expected vs actual):" + diff <(printf '%s\n' "$expected") <(printf '%s\n' "$actual") || true + return 1 + fi +} + +@test "no unwired live-module file is reached by the live install closure" { + build_live_closure "$REPO" + local breach="" f + for f in "${EXPECTED_UNWIRED_FILES[@]}"; do + if printf '%s\n' "${CLOSURE[@]}" | grep -qxF "$f"; then + breach="$breach $f" + fi + done + if [[ -n "$breach" ]]; then + echo "An UNWIRED live-module file is now on the live source-closure:" + printf ' %s\n' $breach + echo "If it was wired intentionally, move it from EXPECTED_UNWIRED_FILES to" + echo "EXPECTED_WIRED_FILES and update the runtime-authority-map inventory." + return 1 + fi +}