# 2026-07-14 -- D-113 AMENDMENT: the LAN IPv6 the REST API cannot set

Retroactive changelog for commit `522ba7d` (the script) and `3b01dd4` (the D-113 amendment).
The code shipped with a green harness and a clean lint, but **without this entry** -- the
delivery rule requires all three. Closing that gap; no code changes here.

## The measurement that forced it

D-113(a2) asserts the OPNsense edge is configured "via the REST API", and lists **interfaces**
among the API-covered surfaces. **Measured on the live Office1 edge (OPNsense 26.1): that is
false.** `Interfaces > [LAN]` is served by the LEGACY page `/interfaces.php?if=lan`. Only the
Devices and Neighbors sub-trees were ever migrated to MVC. **There is no REST endpoint for a base
interface's `ipaddrv6`.**

The claim was **inferred** from DHCP and firewall being API-native, and never measured. It sat in
an ADOPTED decision as if it were a fact. D-113 now carries the amendment.

## What shipped (already committed)

- `scripts/opnsense-set-iface-v6.php` -- runs ON the edge; loads the LIVE config through
  OPNsense's own `Config` singleton, mutates exactly two leaf fields (`ipaddrv6`, `subnetv6`),
  asserts the interface count is unchanged, and lets the vendor's own code serialize it.
- `scripts/opnsense-set-interface-v6.sh` -- ships the setter over SSH, runs it, applies with
  `configctl interface reconfigure`, then proves the address is **on the wire**.
- `tests/opnsense-set-interface-v6/run-tests.sh` -- offline harness, **40/40 PASS** (ssh/scp
  stubbed; no edge contacted).

**DRY BY DEFAULT.** Without `--commit` nothing is written and nothing is applied.

## Why this is NOT the forbidden config.xml push

The distinction is the whole point, and it is the difference between this and the path D-113(a2)
deleted. **It never authors a config.** It mutates two leaves of the edge's own live config
object through the vendor's own model -- the identical code path `interfaces.php` runs on Save.
Nothing is replaced wholesale, so the **API-managed Kea DHCP and the firewall rules cannot be
clobbered** (standing lesson 1: a full rendered config.xml push WOULD clobber them).

Same principle as `opnsense-bootstrap-apikey.sh`: **call the vendor's interface, do not
re-implement the internals.**

## Two traps it is built against

- **root's shell on the edge is `tcsh`.** A `$(...)` inside a quoted remote command dies with
  "Illegal variable name", and it dies QUIETLY. Every remote command here is fed to `sh -s` over
  stdin (standing lesson 5).
- **Saving the config does NOT reconfigure the kernel.** Hence the explicit `configctl interface
  reconfigure`, and hence the verify reads `ifconfig` -- the KERNEL -- and not the config file. A
  `{"result":"saved"}`-style self-report is not evidence.

## Status: WRITTEN AND TESTED, NEVER RUN AGAINST THE LIVE EDGE

This is Stage 2 close-out item **C1**, and C1 is still **OPEN**. The script exists; the address is
not on the edge. Executing it is a live mutation and remains operator-gated.

Two things to carry into that execution:

- **The RA half is API-native** and is a separate step (`/api/radvd/settings/addEntry`). The
  address alone is not the deliverable; the LAN needs RA to be useful.
- **IPv6 does not egress the lab** (measured 2026-07-13: v6 gateway reachable, v6 internet 100%
  loss, v4 fine). So this proves v6 ADDRESSING / RA / services-binding -- most of D-101's family
  matrix -- but NOT internet GUA reachability, which is unprovable in VR1. The WAN v6 leg is
  DEFERRED per D-115's amendment; it would be a path to nowhere.

## Reusable by the DC edges

Stage 3 builds DC0's and DC1's edges from the same module. This script takes them verbatim -- only
the host and address change. That was the operator's stated reason for scripting it rather than
clicking it once in the GUI: **the same treatment gets paid three times.**

## Known limitation (logged, not actioned)

The interface-name-to-device mapping is a hardcoded `lan->vtnet0 / wan->vtnet1` sed. It is correct
for the Office1 edge as built, but it is an ASSUMPTION about device ordering that a DC edge with a
different NIC count could break. It would fail loudly (the verify reads the wrong device and the
script exits 2), not silently. Worth deriving from the edge's own config before Stage 3 reuses it.

## Revert

    git rm scripts/opnsense-set-interface-v6.sh scripts/opnsense-set-iface-v6.php
    git rm -r tests/opnsense-set-interface-v6/
    git rm docs/changelog-20260714-opnsense-set-interface-v6.md

Nothing live depends on these: **the script has never been run with `--commit`,** so the live edge
is unaffected by its existence. The D-113 amendment prose (`3b01dd4`) is a separate revert and
should be KEPT regardless -- it records a measurement, not a tool.
