diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index 951b3df..8ef2c0f 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -1018,6 +1018,37 @@ supports it (D-100's management-only fiber, D-128's Plane-2-on-voffice1 model), and what would make it D-admissible later (a named executable check that the push came from Office1 against a DC0 NOC, plus a ruled DC0-first ordering). **No D-number assigned; next-free stays 138.** +- **RENDER-PIPELINE BUILDOUT OPENED 2026-07-27** (operator direction: "Start processing and work + as autonomously as possible", after agreeing the carve-outs and the revised order of + operations). The operator's directive is to reach an APEX POSTURE as quickly as possible -- + import current, correct, as-built information into every authoritative data source *even where + that information was hand-written before the source existed*, then build the rendering tools, + then dry-run them to confirm they pull correctly, then audit the + `data source -> rendering tool -> deployment mechanism` chain. + **CARVE-OUTS PINNED (operator-agreed; "make sure they are pinned in a place they will not be + lost as they will be required items in the future"):** forward items **F2** (render-pipeline + AUTOMATION half -- CI runner, event delivery, status-back -- DEFERRED because two GitBucket + requirements are unverified and Jenkins placement is an open ruling) and **F3** (the NetBox + scope boundary for MACs and VLANs -- permanent, not pending), both in + `docs/dc-dc-deployment-workflow.md`. **D-136's MAC scope-out reasoning was CORRECTED at source + in the same pass** -- its "drift-free across substrate/lib-hosts/bundle/discovery" premise is + FALSIFIED by measurement, so MACs are scoped out for AVAILABILITY (the apex holds no + `dcim/devices` or `dcim/interfaces` at all) and `ovn-chassis` becomes renderer OUTPUT. + **STEP 1 DONE -- REPRODUCTION FIXTURES FROZEN** (`tests/render-baseline/`, harness **9/9**, + gauntlet now **ALL GREEN (82 harnesses)**, was 81). WHY THIS WENT FIRST: the strongest test of + a generator is byte-for-byte reproduction of a known-good artifact, and the only such + artifacts -- `overlays/vr1-dc1-vips.yaml` and the VIP set inline in `bundle.yaml`, both + reviewed and mutually consistent octet-for-octet -- are DESTROYED by the reconciliation ahead + (R11 takes 11 applications to 13; R2 doubles every triple to dual-family; the 2026-07-25 + ruling extracts dc0's VIPs out of the base entirely). After that there is nothing left to diff + a renderer against and its first output would be its own first draft -- exactly the objection + D-136 raises against its own option (A). The fixtures are hash-pinned and the harness asserts + their INTEGRITY, deliberately NOT that they match live: live is supposed to diverge, and an + assertion against live would turn the harness red for the very change it exists to support + (the `tests/creds-matrix` T24 trap, avoided by design here). **The harness was PROVEN ABLE TO + FAIL before being trusted** -- two seeded faults, both caught: a silently edited fixture (T2b) + and an unpinned file added to the fixture dir, where `sha256sum -c` PASSED vacuously and only + the coverage assertion T3 caught it. - Position inside Stage 3: deploy step A EXECUTED 2026-07-19 (6/0/6 exact; convergence zero -- `docs/audit/outer-plan-20260719-postA-converged.txt`). **Deploy step B diff --git a/docs/dc-dc-deployment-workflow.md b/docs/dc-dc-deployment-workflow.md index 827384b..c18cc2a 100644 --- a/docs/dc-dc-deployment-workflow.md +++ b/docs/dc-dc-deployment-workflow.md @@ -310,6 +310,60 @@ wrong home because GA-R4 caps and rotates it. Operator answer, exact utterance: **"use the workflow doc as a named forward item"**. +### F2. Render-pipeline AUTOMATION half (CI runner, event delivery, status-back) + +**CARVE-OUT AGREED BY THE OPERATOR 2026-07-27**, when the render-pipeline tool buildout was +scoped. The renderer itself is built now; this half is DEFERRED. Pinned here because it is a +REQUIRED item later and would otherwise survive only in a session transcript. + +**What is deferred:** the automated path around the renderer -- a CI job that renders on change, +delivers the event reliably, and writes validator verdicts back onto the merge request. + +**Why (both reasons are measured gaps, not caution).** Per D-136 open question 5, TWO +requirements are **NOT YET VERIFIED against the running GitBucket instance**, and neither may be +promoted to a must-have until checked: + +- **(a) Reliable event emission** -- webhook delivery with retry, a delivery log, and manual + replay. A silently dropped webhook renders nothing and nobody notices, which is WORSE than no + automation at all. +- **(b) A status-back API** -- so the runner can write the `provider-bundle-check` and + `repo-lint` verdicts onto the merge request as commit status/checks. This is the requirement + that PROTECTS the approve-on-rendered-diff gate: without it the gate cannot see the validator + verdict, and a human approving a rendered diff is approving output no gate has judged. + +**PLACEMENT IS ALSO OPEN** and needs an operator ruling when this is taken up: the runner is +**Jenkins**, which is operator-side and already running (v1 K8s + app deploys) -- nothing needs +standing up and no git-host/Actions-style runner is required. The candidate placement is a +Jenkins job on `voffice1` for VR1 (D-128 already routes Plane 2 there, and the render reads +Office1-side NetBox + git), with per-DC utility-node agents as the Roosevelt shape once D-132 +ratifies -- BUT D-132 is PROPOSED and per-DC utility nodes are NOT built in VR1. Also unresolved: +whether an Office1 job emitting BOTH DCs' overlays brushes D-105's cross-DC roll-up prohibition. + +**Interface to freeze now so this lands cleanly later:** the renderer is a deterministic +command-line tool whose inputs are the per-DC values file and whose outputs are committed, +reviewable artifacts. Nothing about automating it should require changing the renderer. + +**Carry-forward trigger:** this is the revisit point for the deferred "Jenkins CI poll job" at +`docs/handoff-20260703-open-items.md:159`. + +### F3. NetBox scope boundary for MACs and VLANs -- PERMANENT, not pending + +**CARVE-OUT AGREED BY THE OPERATOR 2026-07-27.** Recorded as a forward item so a future session +does not read "the apex is the source of truth" and try to close a gap that is not one. + +- **MAC pins:** the apex holds NO `dcim/devices` and NO `dcim/interfaces` (measured in + `netbox/draft/vr1-office1-current-20260725.json`: the record carries only `ipam/*` plus + `dcim/regions` and `dcim/sites`). There is therefore no NetBox front half to build for MACs. + They stay sourced from `scripts/lib-hosts.sh` and become renderer OUTPUT into the per-DC + overlay. See the D-136 correction of 2026-07-27 -- the earlier claim that MACs were + "drift-free across substrate main.tf <-> lib-hosts.sh <-> bundle ovn-chassis <-> discovery" + is FALSIFIED and the reason MACs are scoped out is availability, not drift-freedom. +- **VLANs / spaces / fabrics:** correctly absent and PERMANENTLY out of apex scope. VR1 is + untagged-per-fabric on every plane in both DCs (D-133, superseding VR0's VID-103/104), so a + VLAN object would document tagging that does not exist on the wire. The bundle binds by SPACE + NAME -> MAAS subnet by CIDR, never by VLAN/fabric/subnet id. This layer is MAAS-sourced; + NetBox is IPAM only. + --- ## Cross-cutting discipline (applies at every stage, every session) diff --git a/docs/design-decisions.md b/docs/design-decisions.md index d48b134..3c0e949 100644 --- a/docs/design-decisions.md +++ b/docs/design-decisions.md @@ -5609,9 +5609,27 @@ tagging that does not exist on the wire. The bundle binds by SPACE NAME -> MAAS subnet by CIDR, never by VLAN/fabric/subnet id. VLAN/space/fabric is MAAS-sourced; NetBox is IPAM only. -- OUT: MAC pins. They already have a working source of record and are drift-free across +- OUT: MAC pins -- as a NETBOX SOURCE. They stay substrate/`lib-hosts`-sourced; this is not + a NetBox gap, because the apex holds NO `dcim/devices` and NO `dcim/interfaces` at all + (measured 2026-07-27 in `netbox/draft/vr1-office1-current-20260725.json`: `ipam/*` plus + `dcim/regions` and `dcim/sites` only). There is nothing to pull. + **CORRECTION 2026-07-27 -- the ORIGINAL REASONING FOR THIS SCOPE-OUT IS FALSIFIED, and the + conclusion changes.** This bullet previously read that MAC pins "are drift-free across substrate `main.tf` <-> `lib-hosts.sh` `HOST_BOOT_MAC` <-> bundle ovn-chassis <-> - discovery. They stay substrate/lib-hosts-sourced; this is not a NetBox gap. + discovery." MEASURED: `bundle.yaml:462-464` sets `bridge-interface-mappings: + br-ex:52:54:01:d1:04:02 br-ex:52:54:01:d1:05:02`, and per `scripts/lib-hosts.sh:132` the + `52:54:01:d1:NN:01` scheme is **vr1-dc1's** pinned convention (`:04`/`:05` = compute-01/-02). + So the file documented as vr1-dc0's source of truth carries **dc1's** compute provider MACs + -- not drift-free at all. It is a deliberate residue of the 2026-07-24 dc1-first render, and + vr1-dc0's own boot MACs are a DIFFERENT, NON-schematic set (`52:54:00:be:69:c5`, ..., + lib-hosts:118-122) that cannot be derived from a pattern the way dc1's can. + **CONSEQUENCE: `ovn-chassis` `bridge-interface-mappings` is a PER-DC value and must become + renderer OUTPUT into the per-DC overlay, sourced from `lib-hosts.sh`.** It must also move OUT + of the base bundle for the 2026-07-25 symmetric-overlay ruling to be truthful. Left in place + it means a dc0 deploy matches no local MAC, `ovn-chassis` builds no br-ex mapping, provider + egress is dead on dc0 compute, and NO GATE FAILS. This was latent while the plan was + dc1-first; the 2026-07-27 ruling that both DCs deploy by the same procedure makes a dc0 + deploy certain. Forward item F3 in `docs/dc-dc-deployment-workflow.md` carries the boundary. **Roosevelt-delta (A1).** Roosevelt is N bare-metal DCs. Hand-rendering is O(N) human renders of a ~170-literal dual-family artifact; a generator plus one values file per DC diff --git a/tests/render-baseline/README.md b/tests/render-baseline/README.md new file mode 100644 index 0000000..b2d98ac --- /dev/null +++ b/tests/render-baseline/README.md @@ -0,0 +1,81 @@ +# render-baseline -- frozen reproduction fixtures for the per-DC overlay renderer + +**Captured 2026-07-27, deliberately, BEFORE the value reconciliation that destroys them.** + +## Why this exists + +The strongest available test of a generator is: *does it reproduce a known-good artifact +byte-for-byte?* When the per-DC overlay renderer (D-136) is built, that test needs a +known-good artifact to diff against. + +Right now one exists. `overlays/vr1-dc1-vips.yaml` is hand-authored, reviewed, passes +`scripts/provider-bundle-check.py`, and is consistent with `bundle.yaml` octet-for-octet. +So is the VIP set inline in `bundle.yaml` (the pre-extraction dc0 baseline). + +**Both are about to stop existing in this form.** The ruled changes ahead: + +- **R11** (D-020 AMENDMENT, 2026-07-27) adds `vault` at octet `.61` and `designate` at + `.62` -- 11 applications become 13. +- **R2** (D-101 ruling note, 2026-07-27) makes every VIP DUAL-FAMILY -- each three-address + string becomes six. +- The 2026-07-25 symmetric-overlay ruling extracts dc0's VIPs out of `bundle.yaml` + entirely, so the base bundle becomes VIP-free. + +After those land there is **no known-good artifact left to diff a renderer against**, and +the renderer's first output would be its own first draft -- unverifiable. That is precisely +the objection D-136 raises against its option (A): "its first output has nothing to diff +against." + +So the validation window is open now and closes at reconciliation. These files hold it open. + +## What is frozen + +| Fixture | Content | Provenance | +|---|---|---| +| `fixtures/vr1-dc1-vips.v4-11app.yaml` | byte-identical copy of `overlays/vr1-dc1-vips.yaml` | `cp`, hash-verified equal to the live file at capture | +| `fixtures/bundle-vips.v4-11app.txt` | the 11 `app -> vip` rows inline in `bundle.yaml` | extracted via `yaml.safe_load`, not by grep -- so YAML semantics, not text shape | + +`SHA256SUMS` pins both. + +Live-source hashes **at capture time** (a historical fact -- these WILL diverge, by design, +and divergence is NOT a failure): + +``` +3f93ecb3205cea8b303f5b2cbbbde343a2762156575277ca84684369da8bf896 overlays/vr1-dc1-vips.yaml +7d6261c55d50f36261bda751bd2159a8508c9c3696313fbc37fafabd6728b219 bundle.yaml +``` + +## How the renderer is meant to use these + +Two-step validation, in this order. The point is that only DATA changes between the steps, +never renderer code, so any diff is attributable: + +1. **Reproduction.** Feed the renderer a values file derived from TODAY's literals. It must + emit `fixtures/vr1-dc1-vips.v4-11app.yaml` byte-for-byte. This proves the mechanical path + -- template, ordering, quoting, comment handling -- against a known answer. +2. **Evolution.** Feed it the reconciled dual-family 13-application data. Now the output is + new, but the machinery that produced it has already been proven against a known answer. + +Doing step 2 without step 1 is how you ship a renderer whose output nobody can check. + +## What the harness asserts, and what it deliberately does NOT + +`run-tests.sh` asserts the **integrity of the frozen fixtures themselves** -- that they still +match `SHA256SUMS` and are still structurally what they claim to be. + +It deliberately does **NOT** assert that the fixtures match the live `overlays/` or +`bundle.yaml`. They are a historical snapshot; live is *supposed* to move away from them. +An assertion against live would turn this harness red the moment the reconciliation lands +-- punishing the very change it exists to support. That trap is on the record here: a test +that asserts a literal finding string turns the gauntlet red when the finding is remediated +(`tests/creds-matrix` T24). When the renderer lands, ADD its reproduction case; do not +repoint these assertions at live files. + +## Shelf life + +The fixtures are permanent. Their *usefulness* as a reproduction target ends once the +renderer has been proven against them -- after that they are provenance, showing what the +pre-dual-stack v4 artifact looked like and that the renderer could reproduce it. + +Do not "update" them to match new artifacts. If a new baseline is wanted, add a new fixture +alongside with its own date and app-count in the filename. diff --git a/tests/render-baseline/SHA256SUMS b/tests/render-baseline/SHA256SUMS new file mode 100644 index 0000000..8c6a3f9 --- /dev/null +++ b/tests/render-baseline/SHA256SUMS @@ -0,0 +1,2 @@ +21a9206385c16ad3de94f3e54630626da5c492b0221f66b328d44b3daa33f031 fixtures/bundle-vips.v4-11app.txt +3f93ecb3205cea8b303f5b2cbbbde343a2762156575277ca84684369da8bf896 fixtures/vr1-dc1-vips.v4-11app.yaml diff --git a/tests/render-baseline/fixtures/bundle-vips.v4-11app.txt b/tests/render-baseline/fixtures/bundle-vips.v4-11app.txt new file mode 100644 index 0000000..995a248 --- /dev/null +++ b/tests/render-baseline/fixtures/bundle-vips.v4-11app.txt @@ -0,0 +1,14 @@ +# Frozen 2026-07-27: the VIP set inline in bundle.yaml (the pre-extraction dc0 baseline). +# Source: bundle.yaml applications..options.vip, via yaml.safe_load. +# 11 application(s) carry a vip. +barbican 10.12.4.51 10.12.8.51 10.12.12.51 +ceph-radosgw 10.12.4.60 10.12.8.60 10.12.12.60 +cinder 10.12.4.52 10.12.8.52 10.12.12.52 +glance 10.12.4.53 10.12.8.53 10.12.12.53 +keystone 10.12.4.50 10.12.8.50 10.12.12.50 +magnum 10.12.4.54 10.12.8.54 10.12.12.54 +neutron-api 10.12.4.55 10.12.8.55 10.12.12.55 +nova-cloud-controller 10.12.4.56 10.12.8.56 10.12.12.56 +octavia 10.12.4.57 10.12.8.57 10.12.12.57 +openstack-dashboard 10.12.4.58 10.12.8.58 10.12.12.58 +placement 10.12.4.59 10.12.8.59 10.12.12.59 diff --git a/tests/render-baseline/fixtures/vr1-dc1-vips.v4-11app.yaml b/tests/render-baseline/fixtures/vr1-dc1-vips.v4-11app.yaml new file mode 100644 index 0000000..1b226ae --- /dev/null +++ b/tests/render-baseline/fixtures/vr1-dc1-vips.v4-11app.yaml @@ -0,0 +1,46 @@ +# overlays/vr1-dc1-vips.yaml +# Per-DC VIP overlay for vr1-dc1 (the runbook's "DC2"). D-135 amendment / +# phase-4 Step 4: bundle.yaml hardcodes the API VIPs in vr1-dc0's bands, so +# deploying vr1-dc1 re-homes them into ITS bands. Base bundle stays the vr1-dc0 +# source of truth; this overlay is applied only for the dc1 deploy: +# juju deploy ./bundle.yaml --overlay ./overlays/vr1-dc1-vips.yaml +# +# Mapping (MEASURED from scripts/lib-net.sh lib_net_select_dc vr1-dc1, PLANE_NAME): +# provider-public 10.12.4 -> 10.12.64 metal-admin 10.12.8 -> 10.12.68 +# metal-internal 10.12.12 -> 10.12.72 (host octets .50-.60 UNCHANGED) +# The three VIPs per service are public / admin / internal API endpoints +# (D-101 inherits the family layout; only the network prefix moves per DC). +applications: + keystone: + options: + vip: "10.12.64.50 10.12.68.50 10.12.72.50" + barbican: + options: + vip: "10.12.64.51 10.12.68.51 10.12.72.51" + cinder: + options: + vip: "10.12.64.52 10.12.68.52 10.12.72.52" + glance: + options: + vip: "10.12.64.53 10.12.68.53 10.12.72.53" + magnum: + options: + vip: "10.12.64.54 10.12.68.54 10.12.72.54" + neutron-api: + options: + vip: "10.12.64.55 10.12.68.55 10.12.72.55" + nova-cloud-controller: + options: + vip: "10.12.64.56 10.12.68.56 10.12.72.56" + octavia: + options: + vip: "10.12.64.57 10.12.68.57 10.12.72.57" + openstack-dashboard: + options: + vip: "10.12.64.58 10.12.68.58 10.12.72.58" + placement: + options: + vip: "10.12.64.59 10.12.68.59 10.12.72.59" + ceph-radosgw: + options: + vip: "10.12.64.60 10.12.68.60 10.12.72.60" diff --git a/tests/render-baseline/run-tests.sh b/tests/render-baseline/run-tests.sh new file mode 100755 index 0000000..6e49e0d --- /dev/null +++ b/tests/render-baseline/run-tests.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash +# tests/render-baseline/run-tests.sh +# +# Integrity harness for the frozen renderer reproduction fixtures (see README.md). +# +# ASSERTS: the fixtures still are what they were when frozen. +# DOES NOT ASSERT: that they match the live overlays/ or bundle.yaml. They are a +# historical snapshot and live is SUPPOSED to diverge from them once the R2/R11 +# reconciliation lands. Repointing these at live files would turn this harness red +# for the exact change it exists to support -- the tests/creds-matrix T24 trap. +set -uo pipefail + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +FIX="$HERE/fixtures" +PASS=0; FAIL=0 + +ok() { PASS=$((PASS+1)); printf ' PASS %s\n' "$1"; } +bad() { FAIL=$((FAIL+1)); printf ' FAIL %s\n' "$1"; } + +t_eq() { # label expected actual + if [ "$2" = "$3" ]; then ok "$1"; else bad "$1 (expected '$2', got '$3')"; fi +} + +echo "== render-baseline: frozen reproduction fixture integrity ==" + +# T1 -- the fixture directory and both fixtures exist. +for f in vr1-dc1-vips.v4-11app.yaml bundle-vips.v4-11app.txt; do + if [ -f "$FIX/$f" ]; then ok "T1 fixture present: $f" + else bad "T1 fixture MISSING: $f -- a frozen baseline cannot be regenerated later; recover from git"; fi +done + +# T2 -- SHA256SUMS exists and every fixture matches it. This is the core assertion: +# silent edits to a baseline make every future renderer diff meaningless. +if [ -f "$HERE/SHA256SUMS" ]; then + ok "T2a SHA256SUMS present" + if ( cd "$HERE" && sha256sum -c --status SHA256SUMS ); then + ok "T2b every fixture matches its pinned hash" + else + bad "T2b FIXTURE HASH MISMATCH -- a frozen baseline was modified. Do NOT re-pin to make this green; recover the original from git and add a NEW dated fixture instead" + fi +else + bad "T2a SHA256SUMS MISSING -- fixtures are unpinned and cannot be trusted as a baseline" +fi + +# T3 -- SHA256SUMS covers EVERY file in fixtures/ (a fixture added without a hash line +# would pass T2 vacuously -- `sha256sum -c` only checks what is listed). +if [ -f "$HERE/SHA256SUMS" ]; then + n_fix="$(find "$FIX" -maxdepth 1 -type f | wc -l | tr -d ' ')" + n_pin="$(grep -c . "$HERE/SHA256SUMS" 2>/dev/null || echo 0)" + t_eq "T3 SHA256SUMS covers every fixture (no unpinned file)" "$n_fix" "$n_pin" +fi + +# T4 -- the YAML fixture still parses and still carries the 11-application v4 shape it +# was frozen for. Structure, not just bytes: a valid-but-wrong file would pass T2 +# if someone re-pinned it, so assert the CONTENT claim the filename makes. +if command -v python3 >/dev/null 2>&1; then + read -r n_apps n_bad3 <<<"$(python3 - "$FIX/vr1-dc1-vips.v4-11app.yaml" <<'PY' +import sys, yaml +d = yaml.safe_load(open(sys.argv[1])) or {} +apps = d.get('applications') or {} +n = 0; bad3 = 0 +for _, spec in apps.items(): + v = (spec.get('options') or {}).get('vip') + if not v: + continue + n += 1 + if len(v.split()) != 3: + bad3 += 1 +print(n, bad3) +PY +)" + t_eq "T4a YAML fixture carries 11 vip application(s)" "11" "$n_apps" + t_eq "T4b every frozen vip is a v4 TRIPLE (pre-dual-stack)" "0" "$n_bad3" +else + bad "T4 python3 absent -- cannot verify fixture structure ('could not look' is not 'nothing there')" +fi + +# T5 -- the extracted bundle fixture still declares 11 rows and its rows are tab-shaped. +# (Instrument note: this file is TAB-separated by construction. The creds-matrix.tsv +# lesson -- a space-aligned file parsed with -F'\t' returns zero rows and looks like +# a real result -- is why this asserts the count rather than trusting the parse.) +n_rows="$(grep -vc '^#' "$FIX/bundle-vips.v4-11app.txt" 2>/dev/null || echo 0)" +t_eq "T5a bundle fixture carries 11 app rows" "11" "$n_rows" +n_tab="$(grep -v '^#' "$FIX/bundle-vips.v4-11app.txt" | grep -Pc '\t' 2>/dev/null || echo 0)" +t_eq "T5b every bundle fixture row is tab-separated" "11" "$n_tab" + +echo +echo "RESULT: PASS=$PASS FAIL=$FAIL" +[ "$FAIL" -eq 0 ] || exit 1