diff --git a/docs/session-ledger.md b/docs/session-ledger.md index 0ed041f..ccd376f 100644 --- a/docs/session-ledger.md +++ b/docs/session-ledger.md @@ -60,8 +60,12 @@ (gw 172.30.1.1). Routing + automatic NAT, egress 0.0% loss, 8 LAN pass rules in pf, serial console + getty alive, and `kea-dhcp4` serving DHCP on udp/67 (subnet 10.10.0.0/24, pool .100-.199). **DHCP is API-MANAGED (D-113(a2)).** -- **OpenTofu repo/state are IN SYNC** -- `tofu plan` reports "No changes". tofu v1.12.3; the - tree validates (10/10 modules); `opentofu/.terraform.lock.hcl` is committed. +- **OpenTofu repo/state DIVERGED BY D-119 (2026-07-14), APPLY GATED.** `tofu plan` now shows + **11 to add, 0 to change, 11 to destroy** -- the DC module/object renames (`dc1_*`->`vr1_dc0_*`, + `dc2_*`->`vr1_dc1_*`). Every replaced object is EMPTY (measured; Stage 3 hasn't run), and Office1's + live objects are NOT in the plan. `moved{}` blocks make it read as clean replacements. This is a + DESTROYING apply and is operator-gated -- do it BEFORE any Stage 3 `tofu apply`, so Stage 3 does not + silently bundle the rename. tofu v1.12.3; tree validates (10/10 modules); lock committed. - **VR0 / DC0 testcloud:** fleet fully current (juju 3.6.25, 91 agents); D-011 CLOSED except item 6 (manual unseal, blocked on SEC-003). devteam is a live tenant running their own cluster. No beta cluster, no foil tenant, no orphan trustees (see State facts). diff --git a/tests/dc-dc-prefixes-import/test_logic.py b/tests/dc-dc-prefixes-import/test_logic.py index d780669..44e0b7a 100644 --- a/tests/dc-dc-prefixes-import/test_logic.py +++ b/tests/dc-dc-prefixes-import/test_logic.py @@ -384,6 +384,31 @@ "D-119 BREAK-1: no description contains the munged 'VR1 VR1-DC0'") # ----------------------------------------------------------------------------- +# WHOLE-PLAN ROLE PREFLIGHT (2026-07-14 hardening). +# +# find_role() used to be called lazily INSIDE the write loop, so against an apex +# missing a six-plane role a --commit would create the site + the first prefixes, +# then die mid-loop: a HALF-WRITTEN datacenter in the production IPAM. Every other +# test in this file preseeds ALL roles, so they exercise the preflight only when it +# PASSES -- silently. This exercises it FAILING, and asserts it wrote NOTHING. +# Without this, the preflight could be deleted and the harness would stay green. +# ----------------------------------------------------------------------------- +fake_missing = fake_pynetbox.FakeApi() +fake_missing.preseed_roles([r for r in T.PLANE_ORDER if r != "storage"]) # one role short +os.environ["ORG_ULA_48"] = "fd50:840e:74e2::/48" +os.environ["DC_GUA_PREFIX"] = "2602:f3e2:f02::/48" +try: + with captured_stdout() as _out: + run_main(["--dc", "vr1-dc0", "--commit"], fake_missing) + no("preflight: a missing IPAM role is REJECTED before any write") +except SystemExit: + ok("preflight: a missing IPAM role is REJECTED before any write") +check(fake_missing.dcim.sites.get(slug="vr1-dc0") is None, + "preflight: the rejected run created NO site (no half-written datacenter)") +check(sum(1 for _ in fake_missing.ipam.prefixes.all()) == 0, + "preflight: the rejected run created NO prefixes") + +# ----------------------------------------------------------------------------- # Summary # ----------------------------------------------------------------------------- print()