# Node IPv6 carve -- SCOPE (2026-07-27). Not executed.

Operator-directed scoping ("scope the node v6 carve"). This is a plan, not a status
surface: stage/gate status lives only in `docs/CURRENT-STATE.md` (GA-R1).

## Why this exists, and the correction behind it

When the node-v6-acquisition question was ruled ("MAAS static assignment is correct"),
this session told the operator that static assignment "needs only the subnet, which now
exists". **That was wrong, and the operator's own question exposed it** ("Are we mirroring
the assigned IPv6 octet with the last IPv4 octet like we did before?").

Measured: MAAS `mode=static` means an EXPLICITLY CONFIGURED address. The 90 v4 node links
were set that way by the Stage-4 carve, not auto-allocated. So a v6 address only appears
on a node if it is assigned, exactly as the v4 statics were.

```
superb-piglet   enp1s0  10.12.8.121   mode=static     <- explicitly set, Stage-4 carve
                enp3s0  10.12.12.121  mode=static
                enp4s0  10.12.16.121  mode=static
   v4 links = 6      v6 links = 0
ACROSS ALL 18 Ready nodes:  IPv6 links = 0
```

The v4 side already demonstrates the octet mirror this carve must reproduce in v6: one
stable octet per node across every plane (`.121` above), per D-134.

## Governing decisions

- **D-101** family matrix + its 2026-07-27 GOVERNING RATIONALE: *IPv6 unless IPv4 is
  necessary*. This carve is that principle applied at the node layer -- the layer where
  the IPv4 sizing constraint actually bites.
- **D-134** (2026-07-23 amendment): node band `.100-.200`, one stable octet per node
  across all six planes; control `.100-.119` / compute `.120-.149` / storage `.150-.200`.
- **v6 host-part convention RULED 2026-07-27**: mirror the v4 octet TEXTUALLY. Node `.121`
  becomes `::121`, NOT the hex conversion. (The same trap already caught the band work:
  `printf '%x' 50` yields `32`.)
- **R2 / D-101**: dual-stack where IPv4 is required, IPv6-only where sound, BOTH DCs.

## Size and shape

**108 link assignments: 18 nodes x 6 planes.** Per node, per plane, one v6 static whose
host part equals that node's existing v4 octet, inside that plane's `/64`.

| plane | dc0 v6 `/64` | dc1 v6 `/64` |
|---|---|---|
| provider-public (GUA) | `2602:f3e2:f02:10::/64` | `2602:f3e2:f03:10::/64` |
| metal-admin (ULA) | `fd50:840e:74e2:220::/64` | `fd50:840e:74e2:320::/64` |
| metal-internal (ULA) | `:221::/64` | `:321::/64` |
| data-tenant (ULA) | `:230::/64` | `:330::/64` |
| storage (ULA) | `:240::/64` | `:340::/64` |
| replication (ULA) | `:250::/64` | `:350::/64` |

NOTE the provider leg uses the **node** `/64` (`f0X:10::`), NOT the VIP `/64` (`f0X:11::`)
which holds the hacluster-managed API VIPs already written to the apex.

All 12 subnets EXIST in MAAS (carved earlier this session) and each sits on the SAME vlan
as its v4 twin, so this is addressing on established L2, not new fabric.

## Mechanism -- prior art exists and is v4-only

`scripts/carve-host-interfaces.sh` performed the v4 carve and already makes the exact call
needed:

```
maas <profile> interface link-subnet <system_id> <iface_id> \
     mode=STATIC subnet=<subnet_id> ip_address=<addr>
```

Measured: `grep -ciE 'ipv6|::|inet6'` over that script returns **0** -- it is v4-only, and
its addresses are built as hardcoded v4 bases plus the octet (e.g. `:263`
`ip_address="10.12.8.$OCTET"`). So this carve needs either a v6 arm in that script or a
companion tool. RECOMMEND a companion on the `dc-plane-ipam.sh` pattern already proven this
session: site-keyed, **dry by default**, idempotent, and READ BACK per write.

Precedent that the operation is safe on `Ready` nodes: the Stage-4 carve did 90 of these
on Ready nodes with a read-back each, and all 18 stayed Ready.

## Preconditions

1. Nodes stay in `Ready` (powered off). `link-subnet` does not require a boot, and MAAS
   renders these into node netplan at deploy -- which is why doing it BEFORE Stage 5 makes
   them land at first boot rather than needing a re-deploy.
2. Octet source is the node's EXISTING v4 assignment, read live -- never a table. The
   mirror must be derived from what MAAS actually holds, so a node whose octet ever drifted
   cannot silently get a mismatched v6 address.
3. The v6 subnets' `gateway_ip` and `dns_servers` are UNSET (measured). That is consistent
   with the no-external-v6-routing posture, but see the open item below -- it is a separate
   decision from this carve and does NOT block it.

## Risks, and the one the operator named

- **Charm/module IPv6 support is the real risk.** The operator's own rationale records that
  v4-first was chosen partly to avoid "troubleshooting IPv6 configurations around modules
  that may not have supported it as default". A node carrying v6 on all six planes is
  exactly what surfaces those modules. **That argues for carving BEFORE the deploy, not
  after** -- discovering a charm that mishandles a v6 leg during the bundle deploy is far
  more expensive than discovering it on a static-assignment read-back.
- **Blast radius is per-link and reversible**: `interface unlink-subnet` removes one. No
  node is powered on; no tfstate, no running service.
- **Do NOT re-commission to apply these.** The 2026-07-20 incident regenerated all 9 node
  NIC MACs on an in-place apply and stranded MAAS's records; a carve must touch links only.

## Verification owed

1. **Per-write read-back**, as every mutation this session has done.
2. **A gate assertion**: extend `dc-plane-ipam.sh check` so 18/18 nodes carrying their
   mirrored v6 octet on all six planes is a NAMED executable check rather than a count in
   prose -- the same reason the bands got a gate.
3. **At Stage-5 first boot (G17)**: that the node actually brings the address up. MAAS
   holding a link is not the same as an interface carrying it -- this session has already
   been bitten by asserting existence instead of content.

## Explicitly OUT of scope

- v6 `gateway_ip` on any plane, and v6 `dns_servers` -- open, and deliberate under the
  no-external-v6-routing posture.
- Rack-bridge v6 legs -- NOT needed under the static-assignment ruling.
- NAT64/DNS64 -- REJECTED 2026-07-27 (Roosevelt has native v6 edge transport, so it has no
  Roosevelt analog). Recorded on D-101.
- Tenant addressing -- Neutron-managed, never in the apex (D-016/D-074).
