# GA-R5 ruling PREP -- VR1 re-IP 10.12.0.0/16 -> 10.13.0.0/16

**PREP PACKAGE, NOT A RULING.** This file does not rule, does not mint a D-number, and
mutates nothing (no live infra, no `lib-net.sh`, no `design-decisions.md`, no
`CURRENT-STATE.md`). It exists so the operator can rule the re-IP from one place with the
full blast radius, the decision reconciliation, and the owed live checks in view.

- Author: background agent (ruling-prep task), 2026-08-08. READ-ONLY session.
- Inputs read in full: `docs/audit/netbox-1013-subnetting-draft-20260808.md` (the 10.13
  subnetting draft), `docs/audit/queued-findings-20260808-dc0-tailscale-incident-reip-pivot.txt`
  (F1, F12-F16), `docs/design-decisions.md` D-101/D-115/D-124/D-134 (each block in full),
  `scripts/lib-net.sh` (defaults + the three selector arms).
- Method: document analysis + read-only repo grep. Every count in Section E is a measured
  `grep` this session, not an estimate. No operational command was improvised (tool-index
  consulted; the checks that need live/control-plane access are listed as OWED, Section D).
- **Next-free D-number: D-143** (verified: D-142 is the highest ASSIGNED entry, PROPOSED,
  `docs/design-decisions.md:7966`; every `D-143` hit in the repo is "next-free" bookkeeping,
  not an assignment). **NOT minted here** -- grep next-free again at ruling time.

---

## A. Decision reconciliation

For each governing decision: what it ruled, how the re-IP interacts with it, and the verb
(supersedes / amends / terminates / consistent-with). **One framing in this section
DIVERGES from the draft and from F12/F13 -- flagged inline so the operator can overrule it.**

### D-101 -- DC-DC address plan and IPv6 family matrix -> **TERMINATES (one clause)**

D-101 (ADOPTED 2026-07-09, `docs/design-decisions.md:2243`) rules the per-DC v4 layout and
the IPv6 family matrix. The load-bearing v4 clause, verbatim:

> "Per-DC distinct v4: DC1 INHERITS the DC0 six-plane v4 layout (provider-public
> 10.12.4.0/22 ... replication 10.12.36.0/22) unchanged, so DC1 equals the validated
> template." (`docs/design-decisions.md:2304`; per the DOCFIX-205 annotation at :2247, in
> this 2026-07-09 text "DC1" = VR1's first DC = `vr1-dc0` and the inherited "DC0" = VR0's
> DC0 = `vr0-dc0`, the LIVE cloud.)

**Interaction:** the live 10.12 cloud IS `vr0-dc0`. Moving VR1 to 10.13 means `vr1-dc0` no
longer inherits `vr0-dc0`'s v4 layout -- it gets a distinct, non-overlapping one. That is
the **termination of D-101's inherit-VR0-DC0-v4-unchanged clause** (F13). Note the *reason*
D-101 gave for inheritance (":2304" -- "minimize delta to Roosevelt", reuse the validated
bundle) is what the re-IP trades away, so the ruling should acknowledge the cost, not just
the mechanics. The IPv6 family matrix, the "IPv6 unless IPv4 is necessary" standing
principle (:2282), and the dual-stack ruling are **untouched** -- this is a v4-only re-IP.
**Verb: TERMINATES the v4-inheritance clause; CONSISTENT-WITH everything else in D-101.**

### D-115 -- VR1 Office1 addressing / the v4 role carve -> **AMENDED (a factual premise), NOT reversed**

D-115 (ADOPTED 2026-07-13, `docs/design-decisions.md:3711`; amendment at :3827) ruled: v4
stays **role-based**; offices get a `/22`; and DC2 (`vr1-dc1`) moves INSIDE the Cloud `/16`
at `10.12.64.0/19`. The clause that names 10.13, verbatim:

> "This SUPERSEDES `docs/dc-dc-netbox-buildout-scope.md`'s `DC2_V4_SUPERNET = 10.13.0.0/19`,
> which sat outside every allocated block." (`docs/design-decisions.md:3856-3858`)

and the finding it rested on:

> "DC2's planned `10.13.0.0/19` is OUTSIDE the Cloud `/16`. ... NetBox's Cloud role is
> `10.12.0.0/16`; `10.13.x` is unallocated." (`docs/design-decisions.md:3766-3767`)

**Interaction -- and the framing divergence.** F12 and the draft (OQ (c)) call the re-IP a
"D-115 **SUPERSESSION**." I read the text more narrowly and flag it for operator override:
D-115's **ruling** (v4 role-based; /22 per office; DC2 inside the Cloud /16) is NOT being
reversed -- the re-IP keeps v4 role-based and keeps the octet-preserving structure. What the
re-IP overturns is one **factual premise**: that "10.13.x is unallocated / outside every
allocated block" makes it unusable. Post-pivot, 10.13.0.0/16 is *deliberately* allocated
under the `10.0.0.0/8` Corp Private parent. So the defensible verb is **AMENDS D-115** (its
role-based principle holds; its 10.13-is-unallocated finding is superseded by the collision
discovery), not a wholesale supersession. If the operator prefers the draft's stronger word,
that is their call -- but the text at :3856 only supersedes a *scope-doc recommendation*, not
a rule, so "amendment" is what the record supports. **Verb: AMENDS D-115 (factual premise);
the role-based ruling is CONSISTENT-WITH the re-IP.** The apex fork this opens is Section B.

### D-124 -- Office1-region <-> DC-rack management overlay -> **CONSISTENT-WITH (routes re-point)**

D-124 (ADOPTED 2026-07-16, `docs/design-decisions.md:4961`; addressing pinned at :5019, dc1
at :5039) rules **Scheme A**: a dedicated `transit` role on `172.31.0.0/24` (NOT under
Cloud), region<->rack `/30`s (`172.31.0.0/30` dc0, `172.31.0.4/30` dc1), and the rack
metal-admin statics (`10.12.8.2` dc0, `10.12.68.2` dc1).

**Interaction:** the transit supernet `172.31.0.0/24` is **not in 10.12** and does not move
-- D-124's addressing stands. BUT the rack metal-admin statics ARE in 10.12 (`10.12.8.2` ->
`10.13.8.2`, `10.12.68.2` -> `10.13.68.2`; draft Section 3.7) and the **routes carried over
the transit /30s point at 10.12.* DC destinations and must re-point to 10.13.*** (draft
Section 6 caveat). Also note (:5036) the NetBox `--commit` for D-124 addressing runs ON
office1-netbox because the apex token is unreachable from vcloud -- same access limit that
makes the live-NetBox 10.13 free-check an OWED foreground step (Section D). **Verb:
CONSISTENT-WITH; transit addresses stay, rack statics shift by the 12->13 rule, transit
ROUTES re-point.**

### D-134 -- VR1 DC node static addressing (per-role octet bands) -> **CONSISTENT-WITH (survives unchanged)**

D-134 (ADOPTED 2026-07-23, `docs/design-decisions.md:5858`; contiguous-band amendment at
:5900; the standing octet map at :5935 and :5984) rules the per-plane last-octet bands
(`.4-.49` utility, `.50-.99` VIP, `.100-.200` nodes, `.201-.254` dynamic) and the STANDING
cross-DC utility octet map (`.4` artifact / `.5` juju / `.6` MAAS region / `.7` tailscale).
The load-bearing half, verbatim:

> "THE OCTET MAP IS A STANDING CROSS-DC STANDARD. ... They FOLLOW ALL DC DEPLOYMENTS so
> that standardized configuration is upheld through multiple datacenter stand ups."
> (`docs/design-decisions.md:5965-5968`)

**Interaction:** D-134's bands and octet map are **offset-relative to the plane /22**, not
tied to the second octet. `10.12.8.5` -> `10.13.8.5` preserves "juju = .5" exactly. The
re-IP therefore **preserves D-134 by construction** -- and the standing cross-DC rule (":a
new DC standup reads the same table") is precisely what makes an octet-preserving shift the
natural mapping. This is a POSITIVE: D-134 survives the re-IP unchanged and actively argues
FOR the 1:1 shift. **Verb: CONSISTENT-WITH (survives unchanged; endorses the octet-preserving
map).**

**Summary of verbs:** D-101 TERMINATES (v4-inheritance clause only) | D-115 AMENDS (factual
premise; role-based ruling holds) | D-124 CONSISTENT (routes re-point) | D-134 CONSISTENT
(survives, endorses the map).

---

## B. The core question the ruling must answer (from F12): the IPAM apex fork

The live cloud occupies `10.12.0.0/16` (NetBox **Cloud** role) and, per the operator's
standing constraint ("never editing live infra"), it STAYS there untouched. The re-IP
therefore does not "move a subnet"; under the plain reading of D-115 it **creates
`10.13.0.0/16` as a NEW allocation under the `10.0.0.0/8` Corp Private parent** (which
already covers 10.13 -- draft Section 4 confirms the `10.0.0.0/8` aggregate needs no change).
The fork is *where that new /16 lands in the role model*:

**Option B1 -- Cloud role gains 10.13.0.0/16 as a SECOND prefix (10.12 retained as vr0-dc0's record).**
- One role, two /16 prefixes: 10.12 (VR0/live) + 10.13 (VR1 rebuild).
- Tradeoff: cheapest role-model change; keeps everything "Cloud" under one role, matching
  D-115's role-based intent. But two /16s under one role blurs which is live-VR0 vs
  rebuild-VR1 unless prefix descriptions carry it. No live record is re-labelled.

**Option B2 -- NEW role (e.g. "Cloud -- VR1 rebuild") owns 10.13.0.0/16; Cloud stays 10.12-only.**
- Two roles: `Cloud` (10.12, VR0/live) + a new VR1-scoped role (10.13).
- Tradeoff: cleanest separation of the live cloud from the rebuild; makes the VR0/VR1 split
  explicit in the apex (mirrors how D-115 seeded new `Office`/`Edge` roles and D-124 seeded
  `transit`). Costs one new role + a small conceptual asymmetry (two "Cloud-ish" roles). No
  live record is re-labelled.

**Option B3 -- Cloud role MOVES to 10.13; 10.12 re-homed to a legacy/VR0 role.**
- `Cloud` becomes 10.13 (VR1); 10.12 is relabelled onto a new legacy/VR0 role.
- Tradeoff: **FLAG -- this re-labels a LIVE PRODUCTION record.** It is the option that most
  directly rubs against the operator's "never edit live infra" constraint (the very
  constraint driving the whole pivot). Not recommended for that reason; listed for
  completeness.

**Recommendation surfaced (operator decides):** B1 or B2 over B3. B2 if the apex should make
the VR0-live / VR1-rebuild split explicit (most defensible against "never edit live infra");
B1 if minimizing role churn is preferred. B3 only if the operator explicitly wants "Cloud"
to always mean the current build and accepts touching a live record.

---

## C. The GA-R5 ruling QUESTION drafted for the operator

GA-R5 = one decision per exchange, must reconcile D-101/D-115/D-124/D-134, quote verbatim
when ruled. The re-IP actually contains **THREE** ruling-grade sub-questions. GA-R5's
one-decision-per-exchange rule means the operator may want to take them in sequence (C.1
first -- it governs the others), or rule all three in one exchange if they prefer. Presented
so each can be quoted verbatim.

### C.1 -- the apex fork (the load-bearing one; Section B)

> "The VR1 rebuild moves off 10.12.0.0/16 (which collides with the live vr0-dc0 cloud) onto
> a newly-allocated 10.13.0.0/16 under the 10.0.0.0/8 Corp Private parent. The live cloud
> stays on 10.12, untouched. In the NetBox IPAM apex, does 10.13.0.0/16: (B1) join the
> existing Cloud role as a second prefix alongside 10.12; (B2) become a NEW role
> ('Cloud -- VR1 rebuild') with Cloud staying 10.12-only; or (B3) become the Cloud role,
> with 10.12 re-homed to a legacy/VR0 role? This AMENDS D-115 (its v4-role-based ruling
> holds; its finding that 10.13.x is unallocated is superseded by the collision discovery),
> TERMINATES D-101's 'VR1 DC0 inherits VR0 DC0's v4 layout unchanged' clause, is
> CONSISTENT-WITH D-124 (172.31 transit stays; routes re-point) and CONSISTENT-WITH D-134
> (the octet bands/map survive unchanged)."

### C.2 -- the mapping rule (draft OQ (b))

> "Adopt the octet-preserving 10.12.a.b -> 10.13.a.b shift for the entire VR1 Cloud DC space
> (both DCs, second octet 12->13, octets 3/4 unchanged), preserving the six-plane layout,
> dc1's /19 supernet, the D-134 octet map, the VIP columns, and the FIP pool offsets? Or use
> this greenfield /16 to regularize dc0's non-contiguous plane offsets (4/8/12/16 then
> 32/36) into a contiguous /19-shaped block symmetric with dc1 -- at the cost of breaking
> octet-preservation and diverging further from the Roosevelt/VR0 template?"

Recommendation surfaced: the 1:1 octet-preserving shift (C.2 first option). D-134's standing
cross-DC octet map (:5965) actively endorses it, and it minimizes the blast-radius diff.

### C.3 -- the lib-net.sh structural fork (NEW -- not in the draft, not in F13)

**This sub-question is surfaced by my read of `scripts/lib-net.sh:22-83`, which the draft did
not carry.** The file's flat defaults (`PLANE_CIDRS`, `PLANE_ROLES`, `PLANE_GW` incl.
`10.12.8.1`, `VIP_PREFIX_*`, `FIP_POOL_*`, `KEYSTONE_VIP_DEFAULT`) **ARE vr0-dc0's real
as-built values** -- the file says so at line 37: *"10.12.8.1 is VR0's REAL as-built value
(phase-00-maas-standup.sh:119)"*. The `vr0-dc0` selector arm is a documented **no-op** over
these defaults (:119-121); the `vr1-dc0` arm currently *inherits* them and overrides only
`PLANE_GW` (drops the metal-admin router) and unsets the VID (:123-159). So the re-IP is not
"edit the literals" -- it is a **structural inversion of the file**, and which shape it takes
is a ruling-grade choice:

> "In scripts/lib-net.sh the flat defaults ARE vr0-dc0's live as-built values and the
> vr0-dc0 arm is a no-op over them. After the re-IP: (i) keep the flat defaults at 10.12
> (= vr0-dc0 live) and give BOTH VR1 arms (vr1-dc0, vr1-dc1) their own complete 10.13 literal
> blocks; or (ii) make the flat defaults 10.13 (VR1 is the forward build) and give vr0-dc0 a
> full explicit 10.12 override arm?"

- **(i)** preserves the invariant line 37 documents -- "the defaults are the live cloud's
  measured values" -- which keeps the vr0-dc0 no-op honest. More code in the VR1 arms.
- **(ii)** reads more naturally for a repo whose future IS VR1, but **silently re-purposes
  the defaults away from the live cloud's measured values, breaking line 37's invariant**.

Either way, the comment at `scripts/lib-net.sh:124-134` ("Same PLANE values as vr0-dc0 ...
INHERITS VR0 DC0's v4 layout UNCHANGED") becomes FALSE and owes an update in the same change
(F13). NOT recommended for the operator here beyond noting (i) keeps the documented
invariant and (ii) breaks it.

---

## D. Live-free check status

Separated: **verified read-only by me** vs **OWED live/control-plane checks** (F16(a)).

### Verified read-only by me (repo-only evidence)

- **10.13 appears NOWHERE as a LIVE cloud allocation in the repo.** Every in-repo `10.13`
  hit (full grep, excl `.git`) is one of: (1) this pivot's own artifacts (the draft, the
  findings, CURRENT-STATE's pivot banner, session-ledger); (2) **historical/superseded**
  references to the old `10.13.0.0/19` DC2 default (`docs/archive/...`,
  `docs/dc-dc-netbox-buildout-scope.md:158,190`, `docs/design-decisions.md:3766/3797/3809/3856`);
  (3) **stale/test literals** (see the collision hazard below). None is a live-object record.
- **Repo greps confirm the blast radius** (Section E) -- measured, not estimated.
- **CAVEAT (carried from the draft, keep it):** the draft's 10.13 scan was against
  `netbox/draft/vr1-office1-current-20260801.json`, a DATED SNAPSHOT. Absence there is NOT a
  measured LIVE absence. The 10.12 collision that caused this whole pivot was found by
  discovery at Headscale, not by any scan -- so 10.13 must be proven free the same way,
  LIVE, before any dependent work.

**Naming-collision hazard I verified (feed into the ruling / a DOCFIX):** 10.13 already
carries TWO pre-existing meanings in-repo before this re-IP mints a third:
- `netbox/README.md:49` -- stale importer default `VR1_DC1_V4_SUPERNET=10.13.0.0/19`
  (pre-D-115; D-115 moved dc1 to `10.12.64.0/19`). Draft Section 5 flags this.
- `tests/dc-rack-mgmt-import/test_logic.py:265` -- uses `10.13.0.5` as a live test literal
  (`--rack-ip`); mirrored in `docs/archive/stage3-review-base.patch:3667`. **The draft
  MISSED this test-fixture use.** Reusing 10.13.0.0/16 as the rebuild /16 will make a rack-IP
  test literal fall inside the new Cloud space -- reconcile so the three 10.13 meanings do
  not confuse.

### OWED live / control-plane checks (I do NOT have access; do NOT run blind)

Per `docs/tool-index.md` discipline, I name the check AND whether a tested artifact exists.
None of the three below has a tool that answers "is this /16 in use," so each is documented
as a procedure, not improvised:

1. **Headscale advertised/approved route table (OWED, foreground/operator-only).** No
   artifact in `docs/tool-index.md` covers Headscale route enumeration; it is control-plane
   access I do not have. This is the exact overlap class that caused the pivot. Check: list
   the advertised + approved routes on the tailnet and confirm **no route overlaps
   10.13.0.0/16** (Headscale has no 4via6; routes must be non-overlapping). Also confirm the
   dc0 `.7` subnet router's UNAPPROVED `10.12.8.0/22` advert (findings F1 / already-on-surface)
   is not later approved onto a 10.13 conflict.
2. **Live NetBox apex (office1-netbox, `http://10.10.1.10:8000`) -- OWED, operator token.**
   The apex token is operator-held and unreachable from vcloud (D-124 amendment,
   `docs/design-decisions.md:5036`; also findings F11). Check (read-only): query prefixes /
   aggregates for any object under `10.13.0.0/16`; confirm the /16 is free and that adding it
   under `10.0.0.0/8` collides with nothing. Run on office1-netbox where the token lives.
3. **Live vr0-dc0 cloud + MAAS -- OWED.** The 10.12 collision was found by discovery, not a
   scan. Symmetric read-only check owed on 10.13: confirm no live MAAS subnet / neutron
   provider net / route / DHCP range anywhere touches 10.13.0.0/16. `scripts/dc-egress-check.sh`
   and `scripts/maas-profile-assert.sh` exist but neither answers "is this /16 in use" -- so
   this is a **tooling gap**: if the operator wants it repeatable, propose a small read-only
   "is-this-prefix-free" checker rather than improvising `maas ... subnets read` grep at the
   prompt.

---

## E. Blast radius (measured greps this session, 2026-08-08)

`10.12.` literal references, **excluding frozen history** (`asbuilt/`, `docs/audit/`,
`docs/archive/`, `.git/`). Counts are `grep -rl` (files) and `grep -rn` (line hits).

| Surface | Files | Line hits | Change class | Key members |
|---|---:|---:|---|---|
| `scripts/` | 28 | 182 | **MUST CHANGE** | `lib-net.sh` (source of truth, 47 hits), `phase-00-maas-standup`, `dc-rack-net`, `dc-snap-proxy`, `provider-bundle-check.py` (independent octet band) |
| `tests/` | 69 | 566 | **MUST CHANGE** (twins + fixtures) | `dc-selector` (re-parses overlay), `provider-bundle-check`, `render-baseline` fixtures, `phase-04` fixtures |
| `netbox/` | 11 | 222 | **MUST CHANGE** | `dc-dc-prefixes-import`, `dc-plane-apex-import`, `d115-office-carve`, importers + draft JSONs |
| `runbooks/` | 22 | 144 | **MUST CHANGE** | procedure text citing plane CIDRs |
| `opentofu/` | 5 | 31 | **MUST CHANGE** | `variables.tf` (`vr1_dc1_planes` -- lib-net.sh's named twin), per-DC substrate `main.tf` |
| `overlays/` | 3 | 27 | **MUST CHANGE** (ratified VIP source) | `vr1-dc0-vips.yaml`, `vr1-dc1-vips.yaml`, `vr1-dc0-machines.yaml` |
| `render/` | 2 | 6 | **MUST CHANGE** | `render/values/vr1-dc{0,1}-vips.yaml` |
| `bundle.yaml` | 1 | 13 | **MUST CHANGE** | deploy input (VIPs) |
| `docs/` (excl audit+archive) | 31 | 549 | **MUST NOT REWRITE** | `design-decisions.md`, `CURRENT-STATE.md` -- prose record; D-117 ruled treatment = ANNOTATE in place / new dated entries, NOT rewrite |

**Total live-surface `10.12` hits (excl asbuilt/audit/archive/.git): 3765 across 326 files.**
Read that number correctly: the ~549 `docs/` prose hits are the historical record and are
**not** a remap target (D-117: annotate, do not rewrite) -- the actual code/config remap work
is the eight MUST-CHANGE rows above (~1191 hits). Do not read 3765 as the work estimate.

**Twin-file coupling (must change in the SAME commit, per CLAUDE.md delivery discipline):**
`lib-net.sh` <-> `opentofu/variables.tf` (`vr1_dc1_planes`), and `lib-net.sh` <->
`overlays/*-vips.yaml` (via `tests/dc-selector`). Each script change ships its
`tests/<name>/run-tests.sh` green + `repo-lint` clean + a changelog entry with a revert.

**NetBox object counts (from the draft, `vr1-office1-current-20260801.json`):** 13 prefixes,
24 ip-ranges, 80 ip-addresses (78 VIP legs + 2 rack `.2` statics), 0 aggregate changes
(`10.0.0.0/8` already covers 10.13). Plus 2 FIP pools that are **MAAS reserved ipranges, not
NetBox objects** (record gap, Section F).

**Held (not in 10.12; do NOT remap):** `172.31.0.0/24` transit (routes re-point), `10.10.x`
office (D-115), `172.30.0.0/16` edge (D-115), `10.16/10.17` dev clouds, all IPv6
(`2602:f3e2::/36`, `fd50:840e:74e2::/48`), the `10.0.0.0/8` aggregate.

---

## F. Open questions the draft leaves (F16(b),(c)) surfaced for the operator

1. **1:1 shift vs. regularize dc0 while greenfield (draft OQ (b) / F16(b)).** The octet
   shift preserves dc0's non-contiguous offsets (`4/8/12/16` then `32/36`, skipping
   `20/24/28`) and the dc0-vs-dc1 asymmetry (dc1 fits a /19; dc0 does not). Greenfield is the
   only cheap moment to regularize -- at the cost of octet-preservation and further Roosevelt
   divergence. Drafted as C.2.
2. **The lib-net.sh flat-defaults fork (NEW, Section C.3).** Not in the draft or F13 --
   surfaced from `scripts/lib-net.sh:22-83,37`. Needs a ruling on whether the defaults stay
   10.12 (live vr0-dc0, invariant preserved) or become 10.13 (VR1 forward, invariant broken).
3. **Record gaps to reconcile at ruling time (draft Section 4 / F15):**
   - FIP pools are MAAS reserved ipranges, NOT NetBox objects (asserted by
     `scripts/phase-04-network-verify.sh`) -- standing NetBox record gap.
   - The dc1 `/19` supernet has **no** NetBox prefix object (only its six child /22s).
   - Stale `10.13.0.0/19` importer default at `netbox/README.md:49` (pre-D-115).
   - **(added by me)** `tests/dc-rack-mgmt-import/test_logic.py:265` uses `10.13.0.5` as a
     rack-IP test literal -- a third 10.13 meaning; reconcile to avoid confusion.
4. **Framing to confirm/overrule (Section A, D-115).** I framed the re-IP as **AMENDING**
   D-115's factual premise rather than the draft's / F12's "SUPERSESSION." The record at
   `docs/design-decisions.md:3856` supersedes only a scope-doc *recommendation*, not a rule;
   the operator should confirm "amendment" or rule "supersession" explicitly, since GA-R5 is
   graded on this reconciliation.
5. **Owed non-ruling follow-ups (findings F1(b)):** `docs/CURRENT-STATE.md` pivot/checkpoint
   update, and a GA-R5 ruling for the MAAS-topology-no-migration posture (F4) and the
   checkpoint scope (F6) -- separate from this re-IP ruling.

---

**PREP PACKAGE -- not ruled, not committed, no D-number minted.**
