Newer
Older
openstack-caracal-dc-dc / docs / audit / reip-1013-ga-r5-ruling-prep-20260808.md

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 /30s (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.