# Changelog -- 2026-07-29 successor session: Stage-5 preconditions

ONE changelog for the session (GA-R2/D1). Branch `dc-dc-stage5-preconditions`, base `1cd45cd`.
Under blanket approval this file IS the review surface: every item states WHAT changed, WHY
(with evidence), and HOW TO REVERT.

Operator direction, verbatim: *"Run through all items left all the way through the end of
stage. Dispatch agents to assist and orchestrate their assigned projects."* Six agents ran
(three read-only reconciliation, four delivery -- one agent served both roles). All cloud
mutations remained gated; **nothing in this session touched the live cloud.** The single
live-mutation item (Octavia PKI generation) is approved but operator-executed -- see item 12.

Verification at close, run by the orchestrator as final arbiter rather than taken from agent
reports: **gauntlet ALL GREEN (88 harnesses) in the default locale AND under `LC_ALL=C`,
identical**; **repo-lint 0 fail / 1 standing legacy warn / 618 files**; working tree clean.

---

## 1. Five Stage-5 findings raised, none executed (`ab4c842`)

WHAT: new `docs/audit/stage5-findings-20260729-successor.md`, plus the CURRENT-STATE entry.
WHY: F1-F5, each measured before being put to the operator, per the standing rule that a
finding is an observation not a conclusion.
REVERT: `git revert ab4c842`. Documentation only; nothing depends on it.

## 2. F1 + F4 -- the Octavia PKI generator is per-DC end to end (`e80a32e`)

WHAT: `runbooks/phase-01-bundle-deploy.md` Step 1.0-GEN rewritten: `DC`/`DC_LABEL`/`REPO`/
`VIP_OVERLAY`/`OCTAVIA_PKI_OVERLAY` export once in 1.0-GEN.0; `WORKDIR="$HOME/octavia-pki/$DC"`;
every re-derivation carries `${DC:?}`. Added a REFUSE-IF-PRESENT gate and a `git check-ignore`
gate. `.gitignore` widened to `overlays/*octavia-pki.yaml`. Step 1.3's VIP guard replaced by a
call to `provider-bundle-check.py`.
WHY: the D-109 amendment rules per-DC independent Octavia CAs, but R7 parameterised only the CA
subject and the VIP gate -- `$DC` appeared in NO path, so generating dc1 after dc0 would
overwrite dc0's issuing-CA key, controller-CA key and both passphrases, and leave one
fixed-name overlay carrying the wrong DC's CA for a later dc0 redeploy to apply. The gitignore
was an exact path, so the rename would have made a file holding CA key blobs plus a plaintext
passphrase committable in a repo SEC-004 records as PUBLIC. Glob proven both ways (per-DC and
legacy names IGNORED; `overlays/vr1-dc0-vips.yaml` negative control NOT ignored).
REVERT: `git revert e80a32e`. **If any PKI has been generated by then, do NOT revert blind** --
the old single-path generator would overwrite it. Move `~/octavia-pki/<dc>/` aside first.

## 3. Phase-3 batch re-measured; 13 items delivered (`caab852`)

WHAT: `runbooks/dc-dc-phase4-juju-bundle-per-dc.md` 434 -> 929 lines; items 3.1, 3.2, 3.4,
3.11 (phase-4 side), 3.13, 3.14, 3.15, 3.16, 3.17, 3.18, 3.19, 3.20, 3.21.
WHY: the 21-item batch was authored 2026-07-27 and logged-not-executed; re-verified against HEAD
it is 2 FIXED / 19 REMAIN / 0 SUPERSEDED plus 5 NEW. Two of the readiness doc's own numbers were
wrong and were corrected by measurement (3.6's consumer count; the D-133 guard already satisfied).
REVERT: `git revert caab852`. Restores a runbook that hard-aborts on dc1 -- acceptable only if
Stage 5 is not being run.
OWED: a human read of the expanded runbook. Length is not correctness.

## 4. F2 -- credential register per-DC (`2b31c8a`)

WHAT: `creds-matrix.tsv` nine `singleton` Octavia ids -> 18 `per-DC` rows, `mint-stage=stage5`,
mint-refs re-derived; `creds-matrix-notes.md` records the reasoning.
WHY: `s5_per_dc_symmetry` enforces both-DC existence ONLY for `per-DC` rows, so the register
behind the BLOCKING P5 gate was structurally blind to a missing second PKI set. This is R13
Part 1, already RULED 2026-07-27, which names R7 as making it urgent. Mint-refs had to be
re-derived because item 2 shifted the line numbers and `s4_mint_ref` only checks within-EOF.
PROVEN: 91 rows / 7 findings with both DCs declared (same 7 as the 82-row baseline, so none
added); 8 findings with one dc1 row removed.
REVERT: `git revert 2b31c8a`. Re-blinds P5 to a missing second PKI set.
NOT DONE: `stage5` stays `pending`; the vault-init and admin-openrc remainder of R13 Part 1 is
untouched (its blast radius reaches an operator-only one-shot directory).

## 5. Items 3.9 + 3.10 -- teardown and the gap register (`520be68`)

WHAT: `runbooks/dc-dc-teardown-rollback.md` Step 2 replaced by a two-lens machine census;
Step 3 retargeted (all 8 targets verified to resolve); mesh names measured; Step 4 moved to
`qemu+ssh`; decision tree gains a no-destroy-without-Step-2 question. Gap register items 17,
19a/19b and 20 corrected.
WHY: the false clear existed in THREE places, not the one the audit named -- including the
header block, which is read FIRST and so bypassed any Step-2-only fix. It routed to destroying
a containment VM out from under 18 live MAAS records. Step 3's six `module.dc1_*` targets exist
in neither root.
REVERT: `git revert 520be68`. **Highest-risk revert in this session** -- restores a teardown
runbook that produces a permanent false clear under time pressure.

## 6. 3.8 per-DC, and the wrong-NIC discovery (`3b91c6a`)

WHAT: `bridge-interface-mappings` out of `bundle.yaml`, into `overlays/vr1-dc0-machines.yaml`
(new) and `vr1-dc1-machines.yaml`. NEW-1 CIDR, NEW-2/4/6/7 stale and phantom overlay names.
R5/D-106 phase-6 rewritten configure-not-deploy.
WHY: dc0's file carried dc1's MACs. **The fix exposed worse:** audit row U5 and the D-124
correction both name the `[0]` PXE MAC rather than the `[1]` provider leg, and D-124 says to
source it from `lib-hosts.sh`, which carries only `HOST_BOOT_MAC` and cannot. Following either
would bridge `br-ex` onto the boot NIC -- a wrong mapping that comes up and misbehaves.
Confirmed three ways (substrate `main.tf` list order, macpin capture, live MAAS `enp2s0`).
REVERT: `git revert 3b91c6a`, then re-run `provider-bundle-check` for both DCs -- reverting
restores a bundle whose dc0 deploy has no matching local MAC.

## 7. F6 -- P5 was measuring the wrong host (`b6429f9`)

WHAT: new `creds-manifests/host-identity`; `creds-matrix.py` gains `load_host_identity`,
`--host-identity`, `--this-host`, and a wrong-host NOT-PROBED path. `vm-secret-locations`
Octavia entries per-DC. Harness 60 -> 65.
WHY: `local` meant "whatever machine this runs on", so running P5 on the headend probed the
jumphost's declared paths against voffice1's own SEC-022 shadow stores -- 34 findings against
the true 7, 26 pure artefact. That, with P3/P4 only runnable on the headend, is why no host
produced a correct preflight verdict.
PROVEN: unchanged 91/7 on the declared host; 14 NOT PROBED on a simulated headend; absent
binding / unbound role / stale declaration / malformed line each REFUSE.
REVERT: `git revert b6429f9` (also removes the new declaration file).

## 8. Dangling-reference sweep (`92c66fe`)

WHAT: `bundle.yaml`'s pointer to the nonexistent `runbooks/01a-octavia-pki-generation.md`
corrected to phase-01 Step 1.0-GEN; the retired `dc1`/`dc2`-hostnames spelling region-qualified.
WHY: swept every repo-relative path in this session's changed files; two of the hits were real.
REVERT: `git revert 92c66fe`. Cosmetic-to-navigational; nothing executable depends on it.

## 9. P4 DC-awareness, 3.7, and lib-net's dc1 arm (`6aaaddc`)

WHAT: `pre-flight-checks.sh` DC references 1 -> 45, system_ids resolved by pinned boot MAC on
VR1; VID-103 and metal-admin-gateway expectations RETIRED not deleted; `preflight.sh` validates
and exports `DC`; `lib-net.sh` dc1 arm populated. New `tests/pre-flight-checks` 31/31;
`preflight` 26/26; `dc-selector` 69 checks.
WHY: 8 of P4's 11 fatals were the gate probing VR0's `openstack0..3`. An unreadable VLAN
previously satisfied the untagged branch -- `.vlan.vid // 0` rendered "missing" and "untagged"
identically.
REVERT: `git revert 6aaaddc`, then `bash scripts/run-tests-all.sh` -- this commit adds a harness,
so the manifest must be reconciled after a revert.
VERIFIED INDEPENDENTLY: the D-133 guard holds -- `METAL_INTERNAL_VID`/`IFACE` unset on both arms.
STILL OWED: R9's generator half; and the two `phase-04-network-*` scripts need the selector AND
the hardcoded `PROVIDER_CIDR` derived in the SAME edit or they abort under `set -u` (reproduced).

## 10. Four gate-integrity defects (`cfae8a7`)

WHAT: `provider-bundle-check.py` gains a `cluster_count`-vs-`num_units` rule and machines-overlay
no-op detection; `cloud-assert.sh` gains a 14/14 arity check; `dc-cache-proxy.sh` gains
`node <site>` with exit 3 = REFUSE; `run-tests-all.sh` sorts under `LC_ALL=C`.
WHY: each was a demonstrated false green -- notably a 3->1 rewrite of all 20 `cluster_count`
values producing a byte-identical PASS.
PROVEN: re-verified independently -- the same rewrite now yields 13 DECORATIVE HA failures and
the real overlay still passes; gauntlet identical in both locales.
REVERT: `git revert cfae8a7`.
CAVEAT: G17's node-side check has a FIXTURE green only. No node has run it.

## 11. Corrected rather than executed

An agent logged that `preflight` P2 should also assemble `dc-ha-scaleup.yaml`. Measured: the
overlay is DELIBERATELY excluded from the Step 4 deploy per R6, and no step anywhere applies it
yet. Adding it would make the gate validate an input the deploy does not use -- the chain-audit
defect in reverse. LOGGED, NOT EXECUTED. No revert (nothing changed).

## 12. Octavia PKI generation -- APPROVED, NOT EXECUTED

Operator approved ("Go on Octavia too"). The PreToolUse guard HARD-BLOCKS it as a one-shot
secret mint, the class CLAUDE.md designates operator-only. **Not worked around.** The operator
runs phase-01 Step 1.0-GEN.0/.a/.b/.c/.d once per DC; verification afterwards is read-only
metadata (5 `lb-mgmt-*` keys, ASCII clean, both overlays gitignored, CA subjects differing
between DCs, IP SANs matching each DC's provider VIP).
STATE LEFT BEHIND: `~/octavia-pki/vr1-dc0/{issuing-ca,controller-ca,controller}` exist and are
EMPTY (the orchestrator ran 1.0-GEN.0's `mkdir` before the guard fired). The REFUSE-IF-PRESENT
gate keys on `issuing-ca.key.enc` and the overlay, so it still correctly reports safe-to-generate.
`mkdir -p` is idempotent; run the block as written.

## Guard interactions, disclosed

The PreToolUse secret guard fired three times: twice as false positives on prose/register work
(a filename string appearing as a DECLARATION in `creds-matrix.tsv`, and a secret directory path
written in a commit message) -- completed with the file-edit tool and by rewording, never by
circumventing -- and once CORRECTLY, on the PKI generation itself. Worth tightening the first
class of match rule; the third is the guard working as designed.

## 13. `scripts/octavia-pki.sh verify` + harness (`a22bcb4`, `7aef401`)

WHAT: new verify-only gate, 29 assertions, harness `tests/octavia-pki` 21/21, manifest 88 -> 89.
WHY: F8 established that nothing in the repo asserted the SAN set, so a controller certificate
with NO SANs passed every check. Prose cannot be tested. `generate` deliberately exits 2 pending
the D-137 fork-1 ruling -- building it would silently convert the guard from "blocks secret
minting" to "blocks it except via any script".
REVERT: `git revert 7aef401 a22bcb4`, then re-run `run-tests-all.sh` -- this adds a harness, so
the manifest must be reconciled after a revert.

## 14. E2/E3 closed at source + the serial omission (`8091f15`, `cfae8a7` follow-on)

WHAT: generator `chmod 600`s all three certs AND the CA serial; `rm -f` the two build
intermediates; 4 register rows; `verify` A2 9 -> 10 and A3 covers the serial.
WHY: certs landed 664 -- group-WRITABLE trust anchors, an INTEGRITY exposure this session first
mis-called harmless. The serial was missed in the first chmod list and caught by P5 while
`verify` still read 26/0: two gates disagreeing about a file both could see.
REVERT: `git revert` the pair; live modes are unaffected (they were changed by hand).

## 15. Both backup paths (`2801b97`, `9a96843`, `f581599`)

WHAT: `phase-01` 1.0-GEN.e (PKI) and `dc-dc-phase2` step 13 (inner tfstate), 4 register rows,
notes keys `n-pki-backup` / `n-tfstate-backup`.
WHY: three surfaces asserted a backup set that did not exist. Both restores were PROVEN, not
asserted -- PKI 12/12 sha256 vs live, tfstate byte-identical with correct serials.
REVERT: doc/register only. **Do NOT delete the archives** -- they are the only copies.

## 16. F9 self-arming + the operator's working commands (`68987ec`)

WHAT: A12 arms from `os-public-hostname`; the operator's portable `base64` form, `VIP_OVERLAY:?`
guard, heredoc column-1 CAUTION and `${DC_LABEL:?}` all folded in; all 24 mint-refs re-anchored.
WHY: the commands that ACTUALLY produced both DCs' PKI were not in the repo -- the runbook
described a `base64 -w0` nobody ran. Found only because the operator asked before the bookend.
REVERT: `git revert 68987ec` restores a runbook describing unexecuted commands. Not advised.

## 17. Headend reconcile + a gate red on the deploy host (`893d958`)

WHAT: dc1's provider lock tracked (dc0's already was); `opentofu-validate`'s fmt check scoped to
repo content; harness 16/16.
WHY: the gauntlet had never been run on voffice1. It failed there and passed on vcloud, because
`tofu fmt -recursive` walks gitignored local tfvars that exist only on the deploy host.
REVERT: `git revert 893d958` re-breaks the headend gauntlet.

## Agent deviation, disclosed and verified harmless

One delivery agent used `git stash push/pop` on one file despite being forbidden any git write.
Checked because agents were writing the tree concurrently: `git stash list` empty, the prior
commit intact at HEAD, the D-133 guard still holding, modified set exactly its declared files.
No loss. Recorded because it was disclosed rather than hidden.
