# 2026-07-21 -- G12 [V] leg: vr1-dc1 substrate authoring (session changelog)

Session scope: the G12 remaining [V] leg per the D-124 AMENDMENT (2026-07-21):
apex confirm-free -> vr1_dc1 vars -> dc1 substrate authoring -> gated build.
Branch: `dc-dc-g12-dc1-substrate` (off post-Stage-3-merge `main`, per the
stage-close rule). One changelog for the session (GA-R2/D1); every item
carries its revert.

## Item 1 -- dc1 apex confirm-free capture (read-only; the [V] leg's first step)

Read-only GETs on office1-netbox (token used on-host, never printed), captured
to `docs/audit/dc1-apex-confirm-20260721.txt`. Findings:

- The six dc1 plane /22s ALREADY EXIST in the apex, scoped `vr1-dc1`, exactly
  matching the ratified D-124-amendment scheme (provider-public 10.12.64.0/22
  ... replication 10.12.84.0/22, roles per D-101). Confirm-CONSISTENT: the
  assignment landed at the original D-101/D-115 import; the ruled derivation
  matches it.
- Transit 172.31.0.4/30 FREE (container 172.31.0.0/24 + dc0's /30 are its only
  occupants; zero ip-addresses in the /30).
- Uplink 172.30.3.0/24 already registered to vr1-dc1 (D-115 edge import,
  2026-07-17). Rack IP 10.12.68.2 + forwarder .3 free (zero ip-addresses in
  10.12.68.0/22). Site `vr1-dc1` and role `transit` exist.

Consequence: the ONLY apex write still owed for dc1 is the transit /30 + rack
metal-admin IP -- exactly the dc-rack-mgmt-import.py surface (dc0 precedent:
those are the only two objects dc0 registered either; transit endpoint IPs and
the D-131 forwarder alias are NOT apex objects for dc0 and dc1 mirrors that).

Revert: delete the capture file (no state touched).

## Item 2 -- dc-rack-mgmt-import.py: per-site support (vr1-dc0 + vr1-dc1)

What: the importer was dc0-hardcoded (SITE_SLUG/METAL_ADMIN/RACK_DNS/desc
constants). Replaced with a `SITES` map (dc0: 10.12.8.0/22 / vvr1-dc0, ratified
2026-07-16; dc1: 10.12.68.0/22 / vvr1-dc1, D-124 amendment 2026-07-21) selected
by a new REQUIRED `--site {vr1-dc0,vr1-dc1}` flag (env: RACK_SITE). Explicit by
design: one DC's values can never land scoped to another site. An env-supplied
site is validated against SITES in code (argparse `choices` does not validate
env-default values). ROLE_SLUG / CONTAINER / D-120 band offsets stay global
(convention-wide). Also generalized dc0-specific error/usage text and fixed two
stale "(Cloud)" labels on the container messages (the container has been
172.31.0.0/24, not Cloud, since 2026-07-16).

Why: unblocks the one owed dc1 apex write; the D-124 scheme "generalizes
per-leg" and the tool now encodes that instead of forking a dc1 copy.

Harness: `tests/dc-rack-mgmt-import/` extended in the same change -- all CLI
cases pass `--site`, new cases: missing `--site` dies; bogus RACK_SITE env
dies; dc1 happy path (site id binding, /22 mask, dns vvr1-dc1); cross-site
guard (dc0 rack IP under --site vr1-dc1 rejected, zero writes); dc1 band edges
.2/.49 accepted, .1 gateway rejected; missing vr1-dc1 site precondition dies.
Structural pins moved from the retired globals to the SITES map.
**117/117 PASS** (was 96).

Live dry-run vs the real apex (read-only, on office1-netbox):
`docs/audit/dc1-rack-import-dryrun-20260721.txt` -- whole preflight PASS,
plan = would-create exactly 172.31.0.4/30 (role transit, scope vr1-dc1) +
10.12.68.2/22 (dns vvr1-dc1). The `--commit` is a GATED operator step (apex
mutation), not run here.

Revert: `git checkout main -- netbox/dc-rack-mgmt-import.py tests/dc-rack-mgmt-import/`
(restores the dc0-only tool); delete the two capture files. No apex state
touched (dry-run only).

## Item 3 -- IN FLIGHT AT DISCONNECT (drop-state record, landed 2026-07-22 by the successor session; NOT a delivery)

The session DISCONNECTED mid-item, before its bookend. This item records what
exists so the next session resumes without re-derivation; nothing here claims
delivery. Authored and sitting UNCOMMITTED in the tree at the drop:

- `opentofu/variables.tf`: `vr1_dc1_planes` (apex-verified D-124-amendment
  /22s), `vvr1_dc1_{vcpu,memory_mib,disk_bytes}` sizing, `vr1_dc1_ssh_pubkey_path`,
  and the deliberately NO-DEFAULT rack/transit vars (tfvars-only after the
  gated apex `--commit` -- hard rule 2).
- `opentofu/main.tf`: `module "vr1_dc1_uplink"` (172.30.3.0/24) +
  `module "vvr1_dc1"` containment VM (dc0 pattern of record, dedicated dc1 key).
- `opentofu/vr1-dc1-substrate/` (NEW): full inner root mirroring dc0's
  (same file set + variable shape); all 54 node MACs PRE-PINNED from first
  apply, scheme `52:54:01:d1:NN:PP` (locally-administered, outside libvirt's
  52:54:00 auto space).
- `scripts/lib-net.sh`: vr1-dc1 arm (literals land; unruled OpenStack-layer
  values UNSET) + a one-selection-per-shell guard.
- `tests/dc-selector/run-tests.sh`: reconciled, 40/40 PASS.

DROP POINT: after the dc-selector harness went green, BEFORE the full
gauntlet. Successor-run gauntlet = **6/76 FAILED** (capture
`docs/audit/gauntlet-20260722-g12-dropstate.txt`): carve-host-interfaces,
dc-dc-dr-drill, dc-dc-radosgw-multisite, dc-dc-rbd-mirror,
phase-00-maas-standup, reenroll-hosts -- all still assert the retired
"vr1-dc1 fails loud (NetBox gap)" selector behavior that the new dc1 arm
retires. Also sighted for the reconcile sweep: `tests/dc-dc-prefixes-import/
test_logic.py` greps the old message (currently passing); dc-dc-rbd-mirror's
reminder text prints dc0's replication CIDR under `dc=vr1-dc1` (cross-DC
value surfacing). repo-lint 0-fail; `tofu fmt -check` clean on all touched tf.

OWED to finish the item: reconcile the six harnesses (+ the two sighted
surfaces) -> gauntlet ALL GREEN -> delivery entry here -> commit + push.
THEN G12's next gated step is unchanged: operator-gated apex `--commit`
(transit /30 + rack IP), tfvars, build.

Revert (drops the in-flight work, NOT items 1-2): `git checkout --
opentofu/main.tf opentofu/variables.tf scripts/lib-net.sh
tests/dc-selector/run-tests.sh && rm -rf opentofu/vr1-dc1-substrate/`;
delete the gauntlet capture file.

## Item 4 -- harness reconcile: item 3 delivered (successor session, 2026-07-22)

What: the six red harnesses reconciled to the ratified dc1 selector arm, plus
the two sighted surfaces dispositioned. Per script:

- `phase-00-maas-standup.sh`: NEW PLANES/lib-net parity guard (exit 2,
  precondition) -- the PLANES table is a DC0-hardcoded D-052/D-053 literal, and
  with dc1 now selectable the script would otherwise have planned DC0 topology
  under `DC=vr1-dc1` (the drop-state gauntlet showed exactly that: exit 0 +
  WOULD: leak). Any selected DC whose lib-net plane set diverges from the table
  is refused. Harness asserts the refusal (exit 2, no WOULD:/DO: leak).
- `dc-dc-rbd-mirror.sh` + `dc-dc-radosgw-multisite.sh`: the sighted cross-DC
  value surfacing FIXED IN BOTH (radosgw carried the same defect as the sighted
  rbd-mirror): the $DC gate ran only in a subshell, so the replication-plane
  reminder always read the file's flat DC0 defaults -- under `--dc vr1-dc1` it
  printed 10.12.36.0/22 (dc0's). On gate success the script now selects the DC
  in its own shell; new harness cases pin the dc1 reminder to 10.12.84.0/22.
- `dc-dc-dr-drill` / `radosgw-multisite` / `rbd-mirror` harnesses: the dc1
  gate-refusal tests (want rc 3) became gate-OK dry-run + juju-guarded --apply
  (rc 2) tests. The enforce path (exit 3) is retained in the scripts as
  defense-in-depth but is no longer CLI-reachable (arg validation admits only
  the two ratified tokens) -- noted in the test comments.
- `carve-host-interfaces` / `reenroll-hosts` harnesses: vr1-dc1 now fails at
  the HOSTS layer like vr1-dc0 ("no enrolled hosts yet"); tests assert that
  message + the no-mutation guard. Stale dc1/dc2-era header comments in both
  scripts (and phase-00) updated.
- Sighting re-check: `tests/dc-dc-prefixes-import/` carries NO grep of the
  retired selector message at HEAD -- the drop-state sighting does not
  reproduce; harness passes unmodified (91 checks). No edit made there.

Verification: the six harnesses individually green; full gauntlet **ALL GREEN
(76 harnesses)** -- `docs/audit/gauntlet-20260722-g12-reconcile.txt`;
repo-lint 0 fail. This item + the item-3 tree state land in one commit
(this delivery); G12's next gated step is unchanged: operator-gated apex
`--commit`, then tfvars + gated build.

Revert: `git checkout main -- scripts/phase-00-maas-standup.sh
scripts/dc-dc-rbd-mirror.sh scripts/dc-dc-radosgw-multisite.sh
scripts/carve-host-interfaces.sh scripts/reenroll-hosts.sh
tests/phase-00-maas-standup/ tests/dc-dc-rbd-mirror/
tests/dc-dc-radosgw-multisite/ tests/dc-dc-dr-drill/
tests/carve-host-interfaces/ tests/reenroll-hosts/`; delete the reconcile
gauntlet capture (item-3 revert covers the authored substrate).
