| 2026-07-29 |

F7: office1-tailscale is labelled a subnet router and advertises no routes
...
Raised from an operator report of Permission denied (publickey) reaching voffice1 "from my
workstation via tailscale". Measured rather than assumed, and the premise did not survive.
tailscale is absent from BOTH voffice1 and vcloud -- no binary in any standard location, no
state dir, no interface, service not running. Checked with explicit path probes after an
initial `command -v`, because tool-absent-from-PATH reported as feature-absent is a trap this
repo has hit repeatedly.
The tailnet node is a separate LXD guest INSIDE voffice1: office1-tailscale, RUNNING,
100.64.0.53, site leg 10.10.1.11. Its tailscale status reports AdvertisedRoutes: <none>. So
nothing on the tailnet can reach 10.10.0.20 -- there is no route and there never was.
Two records made the false belief look supported: the ssh stanza labels it "Office1 subnet
router (D-107)", and the operating skill's routing table says the workstation reaches voffice1
over the tailnet, citing D-107 -- which is titled "Airgap posture, per-DC artifact mirror, and
NTP (VR1)" and rules nothing of the sort. A real citation that does not support its claim.
RULED (operator): option (a), ProxyJump through vcloud. Option (b), advertising 10.10.0.0/24
onto the tailnet, was presented and not taken -- it exposes the isolated site net, a posture
change against the airgap subject D-107 actually governs.
Fixed operationally outside the repo: vcloud's ssh config now matches Host voffice1 10.10.0.20,
so the raw IP resolves to the office1 service key with IdentitiesOnly yes instead of falling
through to the id_* defaults, which was the actual cause. Both forms verified, with a negative
control proving the key is not offered to unrelated hosts.
Third wrong record this session, after D-124's MAC-sourcing instruction and the skill citation.
Two of the three surfaced only because something adjacent was being fixed; nothing here gates
prose, so no check could have caught them.
repo-lint 0 fail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

Execute the ruled generation host: Step 1.0-GEN and the register move to the headend
...
Dependent work for the D-109 ruling note (b), "Generate on voffice1 (Recommended)". Repo-side
only -- no PKI has been minted.
All seven RUN markers inside Step 1.0-GEN now say voffice1, with $REPO stated to mean the
HEADEND clone. The 18 Octavia register rows move to host-role=headend, the Octavia entries in
vm-secret-locations become headend, and host-identity binds headend -> voffice1.
The Octavia locations are declared `local` rather than with the voffice1 ssh-target on purpose:
that routes them through the F6 host binding, so the checker refuses to measure them from the
wrong machine instead of quietly probing whatever filesystem it is standing on.
Measured both ways:
on vcloud -> all 8 locations report NOT PROBED, naming the host they actually live on
on headend -> probed for real, reporting not-yet-minted
Verdict unchanged at 7 findings on vcloud; tests/creds-matrix 65/65.
Consequence recorded rather than discovered later: P5's octavia rows are now judged ONLY on
voffice1, so the credential half of the Stage-5 entry gate must be read on the headend -- the
same host P3/P4 already require. A narrowing of where preflight is authoritative, and a direct
consequence of the ruled generation host.
repo-lint 0 fail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

RULED (GA-R5): each DC's Octavia PKI is generated ON the Plane-2 headend
...
Operator utterance, exact: "Generate on voffice1 (Recommended)". Recorded as a D-109 ruling
note, which is the authority. OPS under GA-R3 -- it fixes an execution location for an
already-ruled artifact, so no new D-number; next-free stays 138.
Found by a closing check rather than by the audit, and it would have bitten within the hour:
the generation was already approved and the handoff written against phase-01 Step 1.0-GEN's own
"RUN -- jumphost" label. Measured before execution, three surfaces disagreed -- that label and
the eighteen host-role=jumphost register rows (bound to vcloud) against dc-dc-phase4's RUN
LOCATION of voffice1, "NOT the vcloud jumphost", which explicitly expects the octavia overlay to
be present there. juju is ABSENT on vcloud and 3.6.27 on voffice1, so following the runbook's
label would have minted the PKI on a host the deploy cannot read it from.
Generate-then-transfer was declined on R7's own posture ground: a second at-rest copy of a CA
private key plus a plaintext passphrase widens the SEC-004 exposure rather than containing it.
Timing was the substance. F3 established both hosts hold nothing, so this is a free path edit;
after the first mint it becomes a key move.
Execution is a separate gated step and is NOT authorised by this ruling.
repo-lint 0 fail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

Session changelog: 12 items with per-item reverts (GA-R2/D1)
...
One changelog for the session, per GA-R2/D1. Under blanket approval this file IS the review
surface, so every item states what changed, why with evidence, and how to revert -- including
the two reverts that are themselves hazardous (the teardown runbook, and reverting the per-DC
PKI generator after any PKI exists).
Records what was NOT done and why: stage5 stays pending in stages-reached; R13 Part 1's
vault-init remainder is untouched because its blast radius reaches an operator-only one-shot
directory; R9's generator half is still owed; G17's node-side check has a fixture green only.
Also records, rather than smoothing over: the guard fired three times (twice as false positives
on prose and register work, once correctly on the PKI mint), and one delivery agent used git
stash despite being forbidden any git write -- verified harmless, and disclosed rather than
hidden.
repo-lint 0 fail / 619 files.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

Four gate-integrity defects fixed, each demonstrated RED then GREEN
...
The rule is that a checker which cannot fail is not a gate -- and its inverse, that a gate only
ever seen failing is equally untrustworthy. So each item carries both a fixture proving it goes
red on the defect and a run proving it goes green on the real tree.
4.2 DECORATIVE HA. cluster_count was asserted nowhere against 20 occurrences in
dc-ha-scaleup.yaml, so rewriting all 20 from 3 to 1 produced a byte-identical PASS. Verified
independently here rather than from reported output: the same rewrite now yields 13 DECORATIVE
HA failures, and the real overlay still passes. Missing cluster_count and the scale-down
direction fail too.
4.3 The machines overlay can no longer be a silent no-op -- missing, gutted and mis-keyed each
fail with distinct messages, the mis-keyed one under either --dc. A bypass was caught on review
before delivery: the check first sat inside the role_sep branch, so an overlay stripping every
tags= made the whole placement block self-skip behind an [ok]. Hoisted out and locked by a case.
4.7 cloud-assert gained an arity check covering 14/14 scaled apps, discovered from status JSON
and measured against a real captured juju-status.json. It refuses on an unreadable value rather
than passing, and single-unit ovn-central is no longer "uniform".
4.8 G17 has a real node-side check: dc-cache-proxy.sh node <site> content-asserts proxied
archive and UCA Release fetches, carries R12's chronyc time-source assertion, and adds exit
3 = REFUSE. Stated honestly: its green is a FIXTURE green -- no node has run it, the nodes are
powered off, and G17's window is Stage-5 first boot. G17 stays OPEN.
THE GAUNTLET NOW AGREES ON BOTH HOSTS. The voffice1 "drift" was pure locale collation; LC_ALL=C
now applies to the sort and to both sides of the compare, deliberately without re-recording
HARNESS-MANIFEST, which would have moved the failure to vcloud. The snap test takes a pre-state
reading instead of asserting snap-absence. Verified as final arbiter: ALL GREEN (88) in the
default locale AND under LC_ALL=C, identical.
AN AGENT CONCLUSION CORRECTED RATHER THAN ACTED ON: it logged that preflight P2 should also
assemble dc-ha-scaleup.yaml. Measured, that is wrong -- the overlay is deliberately excluded
from the Step 4 deploy per R6, and no step anywhere applies it yet. Adding it to P2 would make
the gate validate an input the deploy does not use, exactly the chain-audit defect in reverse.
Logged, not executed.
Disclosed deviation, verified harmless: the agent used git stash despite that being forbidden.
Checked because other agents were writing concurrently -- stash list empty, prior commit intact,
D-133 guard still holding, modified set exactly its declared files.
repo-lint 0 fail / 618 files.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

P4 is DC-aware, 3.7 closed, and lib-net's dc1 arm populated
...
DC references in pre-flight-checks.sh went from 1 (a comment) to 45. CHECK 2/4 now iterate the
selected DC's own HOSTS and resolve system_ids by pinned boot MAC on VR1 -- the cause of 8 of
P4's 11 fatals, which were the gate probing VR0's openstack0..3. Role-node scoping reuses
maas-role-tags.sh's existing D-134 utility-band rule rather than a second copy; the D-104
controller at .5 is named and excluded with its reason, its MAAS record still asserted, and
deliberately not asserted Ready since it is legitimately Deployed after bootstrap.
The two VR0-inherited expectations were RETIRED, NOT DELETED: VID 103 (D-133) and the
metal-admin gateway pin (D-134 -- measured, 10.12.8.1/10.12.68.1 are held by nothing, so the
pin was the defect). A spurious gateway still warns and a missing provider-public gateway still
fails, so both checks kept their teeth.
Item 3.7's guards are not skips: each guarded branch asserts D-133's own invariant instead --
VID must be 0 on VR1, and the metal-internal link must be MAAS type == physical, which asserts
the flat carve and is falsifiable. An UNREADABLE VLAN now REFUSES rather than satisfying the
untagged branch: `.vlan.vid // 0` had rendered "missing" and "untagged" identically.
F5: DC is validated against the D-119 token set and refuses before any gate runs, is exported,
and appears in the header and verdict -- keeping the PREFLIGHT: <verdict> token contiguous,
which ten existing cases grep.
R9 residue: dc1 arm populated (KEYSTONE_VIP_DEFAULT=10.12.64.50). The D-133 guard HELD --
METAL_INTERNAL_VID/IFACE stay unset on both VR1 arms, verified independently here, not from
reported output. Two dc-selector assertions were replaced with the new invariant and explained
in-file, never deleted to go green. The arm is hand-authored with a drift check; R9's generator
half is still owed and is recorded as such.
Corrected from prediction to measurement, and it changes the remaining work: all four other
DC-dependent consumers carry set -u, so adding the selector ALONE to
phase-04-network-{create,verify}.sh makes them ABORT with PLANE_GW[...]: unbound variable on
dc1 (reproduced). The hardcoded PROVIDER_CIDR must be derived in the same edit.
Harnesses: pre-flight-checks 31/31 (new), preflight 26/26 (was 16), dc-selector 69 (was 45).
repo-lint 0 fail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

Dangling-reference sweep: one dead pointer, and the retired DC1/DC2 spelling in two places
...
Swept every repo-relative path referenced by this session's changed files against the tree.
Most hits were regex artefacts from wrapped prose; two were real.
bundle.yaml pointed the Octavia PKI at runbooks/01a-octavia-pki-generation.md, which does not
exist and has no history in this repo. The generator is phase-01-bundle-deploy.md Step
1.0-GEN, now run once PER DC with DC exported. A reader following the old pointer would have
found nothing and had no way to know whether the step existed elsewhere.
The phase-6 hostnames-overlay proposal still used the dc1/dc2 spelling that item 3.1 retired
this session -- the exact ambiguity 3.1 measured, where "DC1" meant vr1-dc0 on one surface and
vr1-dc1 on another. Region-qualified, with the reason recorded inline. The surrounding text is
deliberately left as-is otherwise: it correctly states the files do NOT exist and must never be
passed to --overlay, which is the honest framing for a proposed-but-unauthored pattern.
provider-bundle-check still PASSes for both DCs; repo-lint 0 fail / 618 files.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

F6: P5 was measuring the wrong host's filesystem and reporting it as fact
...
This is what made the Stage-5 entry gate unreadable, and it is worse than the "could not look
is never nothing there" class this repo already knows: it looked in the wrong place and
asserted the answer.
ROOT CAUSE. vm-secret-locations declares jumphost paths with ssh-target `local`, implemented
as "the filesystem of whatever machine this run is on". Run on the headend, every
`jumphost ... ~/vr1-dcN-creds/*` location was probed against voffice1's OWN ~/vr1-dcN-creds/
directories -- the SEC-022 headend shadow stores -- so the jumphost's expected credential set
was compared against a different host's directories and the mismatches became findings.
Measured: 34 findings on voffice1 against the same tree's true 7 on vcloud, 26 pure artefact.
Combined with P3/P4 being runnable only on the headend (vcloud has no juju/maas/openstack), no
single host produced a correct full preflight verdict.
FIX. New declared binding creds-manifests/host-identity (role -> hostname), exhaustive by
construction exactly like stages-reached: a role using `local` that is unbound FAILS, and a
bound role using no `local` target is a stale declaration. Neither is defaulted -- both
defaults are wrong in a dangerous direction. On a hostname mismatch the location is NOT PROBED
and its scope joins the existing NOT-JUDGED set, so absence is never asserted over it.
DEMONSTRATED BOTH WAYS, plus all four refusal paths:
declared host -> unchanged 91 rows / 7 findings (no regression)
simulated headend -> 14 locations NOT PROBED instead of manufacturing findings
absent binding / unbound role / stale declaration / malformed line -> each REFUSES
Harness 65/65 (was 60), cases T60-T64. T60 exists specifically so T61 proves something: a gate
that never probes would otherwise always "pass".
THE HARNESS HAD THE SAME BUG AS THE THING IT TESTS -- its fixtures fell through to the real
repo's binding file, the identical trap that once left V2 with zero cases. run() now derives a
fixture-scoped binding the way it already derives stages.
Also fixed, a coupling the F1 rename created: vm-secret-locations still pointed at the
pre-rename ~/octavia-pki/* and overlays/octavia-pki.yaml, which now match nothing. The Octavia
locations are declared PER DC, deliberately not as one shared `-` scope -- both DCs' workspaces
hold identically-named basenames, and a shared scope would collapse them into one namespace
where a missing dc1 file is indistinguishable from a present dc0 one.
repo-lint 0 fail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

3.8 fixed per-DC -- and the fix exposed two repo records naming the wrong NIC
...
bridge-interface-mappings is out of bundle.yaml and per-DC in overlays/vr1-dc0-machines.yaml
(new) and vr1-dc1-machines.yaml. The machines overlay was chosen over the vips overlay for a
measured reason: both *-vips.yaml are marked GENERATED / do-not-hand-edit, so a key added
there is clobbered on the next render, and preflight.sh already assembles ${DC}-machines.yaml.
Creating the dc0 file also closes NEW-3 from the other side -- preflight's [ -f ] guard was
silently skipping a file the deploy needs.
THE WORSE DEFECT THE FIX EXPOSED. dc0's compute provider-public MACs are 52:54:00:8c:2a:8c /
52:54:00:50:48:88 -- position [1] in each node's macs list. Audit register row U5 and the
D-124 correction both name the [0] values, which are the PXE/boot MACs, and D-124 further
tells the reader to source them "from lib-hosts.sh" -- verified independently: lib-hosts.sh
declares only HOST_BOOT_MAC and cannot supply a provider MAC at all. Following either record
would have bridged br-ex onto the boot NIC: not a missing mapping that fails loudly, but a
wrong one that comes up and misbehaves. Confirmed three ways -- substrate main.tf:109-116 list
order, the macpin plan capture, and live MAAS showing br-ex over enp2s0. The correct values
and the [0]-vs-[1] trap are now recorded in the overlay, next to the data.
NEW-1: 10.12.12.0/22 was named as the geneve/DATA plane; that is metal-internal, data-tenant
is 10.12.16.0/22. Deliberately not swept repo-wide -- the 10.12.12.5x VIP legs are correct
metal-internal values.
NEW-2 phantom vr0-dc0-testcloud.yaml removed. NEW-4/6 stale octavia-pki name and the phantom
${DC}-hostnames.yaml removed (juju errors on a missing overlay). NEW-7 scale-up removed from
the VIP deploy command per R6, ${DC1_MODEL} -> ${DC_MODEL}.
R5/D-106 BUILT: phase-6 Step 5 rewritten configure-not-deploy, the unsatisfiable diff gate
replaced by a presence-and-state assertion, and the "no designate block exists" claim
corrected against bundle.yaml. os-public-hostname stays unset, per the ruling's refused
option (c).
Verified independently rather than from reported output: provider-bundle-check PASSes for
both DCs in preflight P2's own overlay order. repo-lint 0 fail / 617 files.
Logged not fixed: provider-bundle-check treats an absent bridge-interface-mappings as a silent
skip and never checks the MAC prefix against --dc, so it could not fail on either variant.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

Items 3.9 + 3.10: the teardown false clear existed in three places, not one
...
3.9 was the readiness audit's most dangerous item -- the teardown runbook is what an operator
reaches for DURING a failed Stage 5, under time pressure.
The false clear was in THREE places, not the one the audit named. Besides Step 2's vm-host
read -- which can never return, since this repo uses per-machine power_type=virsh and
instantiates no maas_vm_host module -- the same wrong premise sat in the "READ BEFORE ANY DC
TEARDOWN" header telling the operator to remove the maas-vm-host record, and in the
Relationship-to-D-061 claim that no VR1 DC had reached Stage 4. The header instance is the
consequential one: it is read FIRST, so it bypassed any fix confined to Step 2.
Step 2 is now a two-lens MACHINE census run from the headend (maas is measurably absent on
vcloud): lens 1 enumerates what exists and ends in a countable RECORDS REQUIRING ATTRIBUTION,
lens 2 corroborates against lib-hosts pinned boot MACs and exits 1 on any hit. Demonstrated
three ways against a fixture -- records present -> exit 1, zero -> exit 0, empty roster ->
REFUSE -- so it is a gate rather than a formality. The 2026-07-21 pod-cascade precedent is
retained as the reason associations are read before any destroy.
Step 3's six phantom module.dc1_* targets are retired; all 8 targets now resolve, verified
independently against ^module "X" across all three roots. The real insight: scoping a DC is a
ROOT choice, not a -target choice, since each substrate root holds exactly one site.
Step 4's VERIFY moved to qemu+ssh from the headend behind a virsh version REFUSAL, because an
unreachable URI, a stopped VM and a bad key otherwise return the same empty result. The
decision tree gains "no branch reaches a destroy without Step 2 passing" -- it never mentioned
the MAAS gate -- and the virsh destroy vs tofu destroy verb distinction. Also fixed: Step 1
backed up the wrong state file, "Two paths" pointed twice at a nonexistent Step 6, and $REPO
silently meant two different clones.
3.10: item 17 CLOSED with measured evidence, and its own stated fix corrected (it closed by
D-125 bridge-in, not the replication the entry claimed). Item 19 disambiguated 19a/19b rather
than renumbered, because both are cited by number from outside the file. Item 20 MEASURED, not
asserted -- verdict no leg required, with the rule mismatch written in rather than resolved
silently, plus an expiry condition; the voffice1-side reboot durability recorded as UNMEASURED
with the commands that would resolve it.
repo-lint 0 fail / 615 files, both files ASCII+LF.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

F2 BUILT: the Octavia PKI rows are per-DC, so P5 can now see a missing second CA set
...
The nine Octavia PKI ids were cardinality=singleton with site-key=- and
mint-stage=vr0-phase01. creds-matrix.py's s5_per_dc_symmetry enforces both-DC existence ONLY
for rows marked per-DC, so the register behind the BLOCKING P5 gate was structurally blind to
a missing second PKI set -- while R7 rules that there must be exactly two.
This turned out to be RULED work, not a judgment call: R13 Part 1 (D-137 sub-ruling 6, RULED
2026-07-27) already requires these rows staged to what Stage 5 actually mints, and names R7 as
making it more urgent -- "per-DC independent Octavia PKI means TWO CA mints where the register
expects none".
Nine ids -> 18 rows (9 x 2 DCs), per-DC, region-qualified site-keys, mint-stage=stage5, and
the overlay row's filename follows the F1 rename.
Mint-refs RE-DERIVED from the edited phase-01: s4_mint_ref only checks that a line number is
within EOF, so the F1 commit had silently left all nine pointing at wrong lines with no gate
able to notice.
PROVEN CAPABLE OF FAILING before being trusted -- the inverse of "a checker that cannot fail
is not a gate", which this repo established bites both ways:
both DCs declared -> 91 rows, 7 findings (the same 7 as the 82-row baseline: none added)
one dc1 row removed -> 8 findings, S5 ASYMMETRY naming the missing vr1-dc1 counterpart
tests/creds-matrix 60/60 PASS. repo-lint 0 fail / 615 files.
stage5 deliberately STAYS pending in stages-reached: the rows are now correctly ATTRIBUTED,
not yet expected, and the flip belongs with the actual mint. Trap recorded for that flip --
stage5 is one coarse token also covering eight existing rows.
Remainder of R13 Part 1 NOT done: the vault-init and admin-openrc rows carry the same
mis-staging, and vault-init the same per-DC defect under D-109's original text. Deliberately
not bundled: its blast radius reaches a directory CLAUDE.md designates operator-only one-shot.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

Phase-3 batch re-measured and 13 items delivered into the Stage-5 runbook
...
The 21-item Phase-3 remediation batch was written 2026-07-27 and logged-not-executed.
Re-verified against HEAD: 2 FIXED / 19 REMAIN / 0 SUPERSEDED, plus 5 NEW. Zero superseded
is load-bearing -- ruling 3 CREATED 3.3's hazard rather than retiring it, and the
no-DC-ordering ruling promoted 3.8 to live rather than closing it.
Two measurements corrected the readiness doc instead of inheriting it: 3.6's "21
non-selector consumers" is really 23 consumers / 8 selector-calling / 15 not / only 5
carrying DC-dependent values (the doc counted grep hits, including four files that never
source lib-net), and the D-133 guard is already satisfied at lib-net.sh:175.
Delivered into runbooks/dc-dc-phase4-juju-bundle-per-dc.md (434 -> 929 lines): 3.1 (the
DC1/DC2 namespace, which meant vr1-dc0 in one place and vr1-dc1 in another), 3.2, 3.4
(controller-tag constraint derived from maas-role-tags.sh, gated on exactly one matching
machine), 3.11 phase-4 side, 3.13, 3.14 (asserts the ovs-vsctl observable, refuses on
no-encap), 3.15, 3.16 (four VERIFY-LIVE gates), 3.17 (dry-run now precedes the deploy it
gates), 3.18, 3.19, 3.20, 3.21. Per-DC octavia overlay name threaded through to match F1.
Three items deliberately NOT claimed as fixed: the dc0 apt-mirror model-config key rests on
a single repo comment with no client here to verify it; the juju create-backup flag shape is
corrected only where established and marked unverified-at-authoring, with the gate moved onto
the resulting file; and whether --unit <app>/leader resolves for a subordinate could not be
established, so the ovn-chassis step probes a named unit per existing repo precedent.
NEW-6/7/8 logged not fixed. A human read of the expanded runbook is owed -- it is lint-clean
and its claims were re-grepped, but length is not correctness.
repo-lint 0 fail / 615 files, zero non-ASCII.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

F1+F4 BUILT: the Octavia PKI generator is per-DC end to end, and the gitignore glob follows it
...
F1. The D-109 amendment rules per-DC independent Octavia CAs. R7 parameterised the CA
subject and the VIP gate but left every PATH fixed, so generating a second DC's PKI
overwrote the first DC's issuing-CA key, controller-CA key and both passphrases, and left
one fixed-name overlay carrying the wrong DC's CA for any later redeploy to pick up.
Step 1.0-GEN is now per-DC end to end: DC/DC_LABEL/REPO/VIP_OVERLAY/OCTAVIA_PKI_OVERLAY
export once in a rewritten 1.0-GEN.0; WORKDIR="$HOME/octavia-pki/$DC"; every WORKDIR
re-derivation and the overlay write carry ${DC:?} so an unset DC refuses rather than
silently reusing the old shared path.
Two gates added that did not exist:
- REFUSE-IF-PRESENT: aborts if this DC already has PKI material. Regeneration invalidates
every amphora already issued against that CA, so it must be a deliberate act rather than
the default outcome of re-running a step.
- F4 gate: `git check-ignore -q "$OUT"` immediately before writing key material, asserting
the ACTUAL ignore decision for that path rather than a pattern someone has to remember.
F4. .gitignore:40 pinned the single exact path overlays/octavia-pki.yaml, so the per-DC
rename would have left a file holding CA key blobs plus a plaintext issuing-CA passphrase
UNIGNORED and committable, in a repo SEC-004 records as PUBLIC. Widened to
overlays/*octavia-pki.yaml and proven BOTH ways, not assumed: all per-DC and legacy names
read IGNORED, and a negative control (overlays/vr1-dc0-vips.yaml) reads NOT ignored, so the
glob is not swallowing tracked overlays.
Also fixed, same dc0-freeze: Step 1.3's VIP guard was a hand-rolled grep triple anchored on
the literal 10\.12\.4\. -- it hard-aborted on dc1, counted IPv4 only so R2's ruled v6 legs
were guarded by nothing (chain-audit finding 22), and its prose said 11/11/0 while its code
demanded 13. It now calls provider-bundle-check.py on the merged per-DC input, which already
encodes the bands, the count and the 2026-07-28 dual-family arity coupling.
The 2026-06-03 as-built line keeps the old command verbatim: it is history, not instruction.
repo-lint 0 fail / 615 files.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

Five Stage-5 findings raised, none executed: R7's per-DC Octavia PKI is half built
...
F1 (HIGH): the D-109 amendment rules per-DC independent Octavia CAs, and the
2026-07-29 work correctly made the CA subject and the VIP gate per-DC -- but $DC
appears in no PATH. phase-01-bundle-deploy.md:293 is a fixed $HOME/octavia-pki and
:473 writes a fixed overlays/octavia-pki.yaml, so generating dc1 after dc0
overwrites dc0's issuing-CA key, controller-CA key and both passphrases, and a
later dc0 redeploy reads the fixed overlay name and applies dc1's CA to dc0 -- the
cross-DC shared amphora root R7 explicitly refused. The existence checks at
phase-01:86 and pre-flight-checks.sh:65 cannot detect it: they ask whether the file
exists, never whose CA it is. The gap is in R7's own "Work implied" list.
F2: creds-matrix.tsv:103-111 declares all nine Octavia credentials `singleton`,
while creds-matrix.py:368-398 enforces both-DC existence only for `per-DC` rows --
so the register behind the BLOCKING P5 gate is structurally blind to a missing
second PKI set. Baseline captured (82 rows, 7 findings, exit 1) so the delta is
attributable. Must land before generation.
F3: both ~/octavia-pki/ and overlays/octavia-pki.yaml are ABSENT (existence only,
no contents read), so F1/F2 are fixable at zero risk right now; the window closes
when the first DC's PKI is generated.
F4: .gitignore:40 is an exact path, so a per-DC overlay rename would un-ignore a
file holding CA key blobs plus a plaintext passphrase, in a repo SEC-004 records as
PUBLIC. The F1 fix must widen the glob in the same commit.
F5: preflight.sh:99 sets DC without exporting it and pre-flight-checks.sh never
reads DC at all, so a dc1 invocation runs a DC-aware P2 beside a dc0-frozen P4
under one combined verdict.
Logged not executed per hard rule 1. repo-lint 0 fail / 615 files.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

GA-R4 session close: bounded bookend + the 2026-07-29 findings sweep
...
Bookend is 14 content lines against the 15-line cap, measured. Ledger 263
lines against the 300 cap, so no rotation is owed this close.
Close sweep captured docs/audit/queued-findings-20260729.txt -- transcript-
only material preserved before compaction, per the queued-findings-20260726
and -20260727 precedent. The substantive content:
- A strict-bash quoting trap FAMILY with three measured instances in one
session: IFS=$'\n\t' defeats word-splitting on a space-separated string
(killed pre-flight-checks CHECK 1 silently); a bare under
inherit_errexit ABORTS the run instead of reaching a refusal branch; and an
apostrophe inside the single-quoted shell string wrapping embedded python
makes bash parse python.
- Two greps that looked like findings and were not: 'grep mac' matches
type_machine, and grep -c 'macs = [' counts COMMENTS -- the same class as
the ledger-scan defect where a doc quoting a token inflated a counter.
- Five MAAS behaviours, including that enlistment is NOT commissioning and
that MAAS auto-configures power only for IPMI machines, so a virsh VM must
be power-configured out of band first.
- Two guards defeatable by accident: lib_hosts_select_dc's one-DC-per-shell
guard muted by 2>&1, and NetBox's ?site= filter silently not filtering.
- A process note on two parallel live-ops agents: safety came from agents
working the voffice1 clone while the orchestrator edited vcloud's, so no git
race was possible -- at the cost that agents barred from repo writes cannot
leave captures, and dc1's evidence had to be promoted by hand from /tmp.
The skill sweep stays DEFERRED to the Stage-5 close per the 2026-07-27
operator direction; this capture is its input, not a substitute.
Gauntlet ALL GREEN (87) on vcloud; repo-lint 0 fail / 614 files.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

LIVE: MAC adoption applied, controllers commissioned, all ruled MAAS tags created
...
Operator: 'Commission and process all the listed tasks.' Four gated
mutations, sequenced deliberately.
MAC adoption FIRST, while the VMs were idle -- commissioning power-cycles
them and an in-place domain update must not race that. Both roots 0 add /
1 change / 0 destroy with zero creates and destroys; then MAC drift 0 across
all 20 nodes and both roots re-plan 'No changes'. The pins are now real in
state: until this apply the config asserted them but state did not record
them, so they protected nothing.
Both controllers commissioned on the DEFAULT path -- deliberately not
skip_networking=1, since unlike the storage nodes they had no configuration
to preserve. Both New -> Ready in ~200s. Power control was the only thing
missing, which confirms the earlier diagnosis by measurement.
All ruled tags created and applied, every write read back. dc0 created all
four; dc1 correctly reused the three global role tags and created only
juju-controller-vr1-dc1. Twenty nodes tagged.
Independently verified from MAAS rather than from script output: fleet 22 =
20 Ready + 2 Deployed, no New remaining; control 6 / compute 4 / storage 8 /
juju-controller-vr1-dc0 1 / juju-controller-vr1-dc1 1 / openstack-vr1-dcN 9
each. check PASSES on both DCs.
THE STAGE-5 ALLOCATION BLOCKER IS CLOSED: every tag the bundle machines block
constrains on now exists and is applied.
Gauntlet ALL GREEN (87) on vcloud; repo-lint 0 fail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

maas-role-tags owns the D-104 controller tag too; harness 11 -> 12
...
The per-DC Juju controller carries its OWN tag and none of the role tags, so
a bootstrap constrained on tags=juju-controller-<dc> targets it
deterministically and no OpenStack role constraint can cross-grab it. The
script used to SKIP it as 'not a role node'; it now emits that tag as the
host role, so every stage -- existence check, NEED/HAS, apply, read-back --
handles it with no special case.
Doing it in the script rather than as two one-off maas commands is the direct
consequence of the 2026-07-29 ruling that these assignments follow ALL DC
deployments: a one-off command does not travel to the next standup.
T10 re-pointed (the controller is TAGGED, not skipped) and T10b added so a
missing controller tag is gated like the three role tags.
Owned: my first attempt put an apostrophe inside the single-quoted shell
string wrapping the python block, so bash parsed python and every case
failed. Reworded; the block now carries none.
Gauntlet ALL GREEN (87) on vcloud.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

voffice1 pulled current; power_type=virsh set on both controllers
...
Pull c9cc79f -> 75e3657. Both inner tfstates confirmed git check-ignore
IGNORED before the pull and sha256 byte-identical after -- the Phase-0 safety
proof, since those files are the substrate's state-of-record and live inside
the working tree.
A diff the pull revealed, explained rather than waved through: both inner
roots now plan 0 add / 1 change / 0 destroy. That is MAC ADOPTION, not drift
-- the plan adds mac = { address = ... } blocks carrying values IDENTICAL to
live, because tofu state has no mac block for a domain created unpinned.
Exactly the 2026-07-21 shape. NOT applied: a separate gated in-place update
on a running domain, which 'set power_type' did not authorise.
Power set scoped to the vr1-dcN-juju prefix so the nine already-configured
role nodes per DC were not re-touched. Both verified by a real
query-power-state returning off, which is ground truth -- a stored parameter
is not power control.
WHY power_type was not set during commissioning, answered with evidence:
(1) these machines were never commissioned; New is post-enlistment and
pre-commissioning, and those are distinct phases. (2) Even commissioned, MAAS
auto-configures power only for IPMI-based machines by probing the BMC in-band
-- per this MAAS's own CLI, 'skip_bmc_config ... for IPMI based machines'. A
KVM guest has no BMC, and the virsh parameters (qemu+ssh URI, credential,
libvirt domain name) are hypervisor facts nothing inside the guest can
discover. That is the chicken-and-egg behind 2026-07-20: commissioning needs
power control, but for a virsh VM power config cannot be discovered by
commissioning. Structural and already known; the only gap was that nobody had
run the step for two brand-new machines.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

LIVE: OSD carve + D-104 controllers complete across both DCs (two agents)
...
Operator directed two background agents: one to finish dc0 storage-02/03/04,
one for all of dc1. Both completed.
Eight re-commissions, every one the same single-change diff: vdb at
536870912000 bytes (500 GiB) added; links 12 -> 12; per-interface MACs and
link lists identical. dc0 at 204/196/202s, dc1 all ~182s. dc1's substrate
applied 6 add / 4 change / 0 destroy with MAC drift 0 and re-converged to
ZERO DIFF.
I verified every claim from MAAS directly rather than accepting the reports:
fleet 22 (18 Ready + 2 Deployed + 2 New), 216 links all static, both DCs
blockdevs {1:5, 2:4}, tagcount {2:9}. All matched. I also diffed a raw dc1
before/after pair myself -- exactly the vdb hunk plus one power_state line.
Captures promoted from voffice1:/tmp (which does not survive a reboot) to
docs/audit/osd-carve-20260729/. Both agents were barred from repo writes, so
dc1 would otherwise have had no audit record at all.
Two unprompted agent findings worth keeping: MAAS tags SURVIVED
re-commissioning on all 18 nodes (checked because the snapshot code
structurally could not see tags -- removes a would-be Stage-5 placement
blocker); and lib_hosts_select_dc's one-DC-per-shell guard fires correctly
but can be muted with 2>&1, silently yielding one DC's data labelled as the
other's.
One disclosed judgment call, and it was correct: three dc1 pairs also showed
power_state moving off/error. Agent B declared a STOP, re-measured, then
excluded power_state from its verdict while still printing it, because 8 of 9
dc1 nodes read error BEFORE any mutation. Vindicated -- all 18 role nodes now
read off, including five dc1 nodes never commissioned. A transient MAAS
power-query flap.
Still blocking for both controllers: power_type is EMPTY, the state that
stalled all nine role nodes on 2026-07-20. Now fixable since both are in
lib-hosts at the ruled .5 with MACs pinned.
Gauntlet ALL GREEN (87) on vcloud; repo-lint 0 fail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

Pin juju-01 MACs, add both controllers to lib-hosts at the ruled .5
...
Twelve measured MACs (six per DC, virsh domiflist) pinned into both
substrate roots; <dc>-juju-01 added to lib-hosts.sh with HOST_OCTET=5 per
the D-134 amendment and its measured boot MAC. Done now because both VMs are
ENLISTED -- the boundary modules/node-vm warns about, past which an in-place
apply can regenerate unpinned MACs and strand the node, as happened to the
nine role nodes on 2026-07-21.
No race with the running agents: they operate on the voffice1 clone; this
edit is on vcloud's, and nothing reaches their tree until someone pulls
there.
Recorded rather than smoothed over: dc1's juju-01 MACs are libvirt-generated
52:54:00: values, not dc1's schematic 52:54:01:d1: scheme -- a direct
consequence of the deliberate macs = [] at first apply.
Three exact-count assertions re-pointed to the new invariant and kept EXACT
rather than relaxed: dc-selector fleet 9 -> 10 (label corrected, since Option
C is a 9-node ruling and the 10th is D-104's), node-vm T8 9 -> 10 and T9
54 -> 60.
A real harness defect surfaced doing it: T8's grep -c 'macs = [' counts
COMMENTS, and my own comment explaining the deliberate macs = [] inflated the
count to 11 -- which reads exactly like a spurious extra node. Fixed to strip
comment lines. Same class as the ledger-scan next-free defect where a doc
merely quoting a token inflated the counter.
maas-role-tags.sh taught the utility band: adding juju-01 to HOSTS made it
refuse, because .5 is in no ROLE band -- correct behaviour meeting a new
fact. Per the amendment a utility-band host with no role token is now SKIPPED
and REPORTED, while the refusal keeps its teeth for genuine disagreement.
New cases T10 and T11; harness 9 -> 11.
Gauntlet ALL GREEN (87) on vcloud; opentofu-validate PASS; repo-lint 0 fail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

RULED (GA-R5): juju controller takes utility-band .5; the octet map is a standing cross-DC standard
...
D-134 AMENDMENT (2026-07-29) is the authority.
Question as presented: the D-104 controller VM auto-enlisted into MAAS, but
D-134's bands cover no such node -- .4-.49 utility, .50-.99 VIP, .100-.200
NODES split into three OpenStack ROLE sub-bands, none of which a Juju
controller is. A measured occupancy table was put to the operator (dc0
metal-admin has only NINE allocated addresses; .1 held by nothing, .2 rack
leg, .3 D-131 forwarder, .4 mirror/proxy, .5-.49 free and RESERVED in MAAS)
with three options: .5 in the utility band, .103 in the control band, or a
new named sub-band.
Operator answer, exact utterance: "Rule .5 for the juju controller, with the
other utility nodes. These assignments will follow all DC deployments to make
sure standardized configuration is upheld through multiple datacenter stand
ups."
(1) <dc>-juju-01 takes octet .5 on every plane, inside the reserved utility
band so MAAS never auto-allocates there. This resolves the node-vs-utility
tension in favour of FUNCTION: the utility band is now the band for per-DC
INFRASTRUCTURE the OpenStack nodes consume, host-level or MAAS-managed alike.
.100-.200 stays for OpenStack ROLE nodes.
(2) The load-bearing half: the octet map is a STANDING CROSS-DC STANDARD, not
a per-DC choice. Every DC's juju-01 is .5, artifact service .4, rack leg .2,
forwarder .3. A new per-DC infrastructure service takes the next free utility
octet AND THE SAME ONE IN EVERY DC. Divergence between DCs at the same octet
is a DEFECT, not a local decision -- which is what makes dc-plane-ipam.sh
check meaningful across sites rather than merely per-site. Roosevelt analog:
the map transfers, not just the method.
Scope: this assigns the octet and sets the standing rule. It does NOT create
the MAAS record, the juju-controller-<dc> tag, or the tofu MAC pin -- all
still gated. The controller cannot be commissioned yet: power_type measured
EMPTY at enlistment, the same state that blocked all nine role nodes on
2026-07-20.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

LIVE: canary re-commission closes R1 precondition 3 by measurement
...
Operator: 'Commission storage-01 as the canary'. maas admin machine
commission kghggm skip_networking=1, sysid matched by pinned boot MAC never
by name. Commissioning -> Ready in ~200s, no timeout, no SERVFAIL.
The full before/after diff of status, power, block devices, interfaces, MACs
and links is EXACTLY ONE HUNK: vdb (536870912000 bytes = 500 GiB) added to
blockdevices. links 12 -> 12; MACs identical; links identical. All twelve
static links survive with addresses, modes and subnets intact, including the
D-134 octet .150 mirrored across all six planes in both families -- the v6
host part ::150 being the 2026-07-27 D-136 mirror ruling visible in live
data.
So skip_networking=1 preserves the network EXACTLY while storage is still
re-scanned, which is the entire point. Precondition 3 was previously answered
from the API help text; it is now answered by measurement. The amendment's
concern was legitimate -- the default path does reconfigure networking -- and
the vendor's own control is sufficient.
Remaining: dc0 storage-02/03/04, then all of dc1.
Capture: docs/audit/dc0-canary-commission-20260729.txt
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

LIVE: dc0 substrate applied -- R1 OSD volumes + the D-104 controller VM
...
Operator approval: "Apply dc0 first". Executed from voffice1 via a SAVED
PLAN with a same-session pre-apply re-verify (G8 precedent), so what was
applied is exactly what was reviewed.
Apply complete: 6 added, 4 changed, 0 destroyed. Four libvirt_volume.osd +
the vr1-dc0-juju-01 domain and its disk; the four storage domains updated
in-place to attach vdb.
THE CHECKPOINT HELD -- MAC DRIFT COUNT 0. Verified by virsh domiflist
against lib-hosts' pinned MACs BEFORE anything touched MAAS: all 9 nodes
pinned==live, 6 NICs each. That sequencing is the point. 2026-07-20 became a
fleet-wide outage because MAAS was told to re-commission while its records
were already stale, after a 0/9/0 in-place apply regenerated every MAC
without the plan showing it. So the clean plan was necessary and explicitly
not sufficient. First live evidence that the 2026-07-21 MAC pinning survives
an in-place domain update.
Convergence restored (precondition 1): the dc0 inner root re-plans ZERO
DIFF. MAAS undisturbed -- 20 machines (18 Ready + 2 Deployed), 216 links,
every one still mode=static. MAAS still sees ONE block device per dc0 node,
which is expected and is why the re-commission is genuinely required.
Also recorded: the saved plan's MAC audit returned one hit that was a false
positive -- type_machine = "q35" inside the juju-01 create block, since
"mac" is a substring of "machine".
NOT DONE, gated separately: the re-commission with skip_networking=1, and
dc1 entirely. Capture: docs/audit/dc0-osd-juju-apply-20260729.txt
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|
Record the precondition-3 verification in CURRENT-STATE (L10 repair)
...
The previous commit added docs/audit/r1-precondition3-verify-20260729.txt
without touching CURRENT-STATE, which repo-lint L10 correctly rejects under
GA-R1 rule 8 (C1). I ran the lint but did NOT gate the commit on it, so a
red-lint commit reached origin -- the second time on this branch. The
gate worked; my sequencing did not.
Records: the Octavia merge question resolved as a clean negative (deep-merge,
verified against juju 3.6's pinned charm v12.1.1 source); both Stage-5
blockers authored and NOT applied; and R1 precondition 3 answered from
voffice1 read-only, with skip_networking=1 identified as the vendor control
and the tofu diff confirmed to touch no NIC.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

R1 precondition 3 VERIFIED read-only from voffice1: skip_networking=1 is the control
...
The amendment required verifying, BEFORE the apply, that re-commissioning
preserves the D-134 statics and the pinned MACs. Both halves now answered
from the headend, read-only, no mutation.
HALF 1 -- baseline measured: 18 Ready nodes, 7 interfaces, 12 links each,
mode=static on every link (6 planes x 2 families). MAAS 3.7's commission API
carries skip_networking: 'Whether to skip re-configuring the networking on
the machine after the commissioning has completed.' The option exists BECAUSE
the default reconfigures, which is exactly the flagged risk. The combination
R1 needs is skip_networking=1 with skip_storage UNSET -- network preserved,
storage re-scanned so the new /dev/vdb enters inventory.
HALF 2 -- the tofu change does not touch NICs. Both inner roots plan
6 add / 4 change / 0 destroy; the four in-place diffs change devices.disks
ONLY, gaining vdb. A grep for mac/interface across the whole diff returns one
hit and it is inside the NEW juju-01 create block. Risk is materially lower
than 2026-07-20 because MACs are now config-pinned -- the control introduced
after that incident precisely because the plan was untrustworthy.
NOT overclaimed: a clean plan was necessary but not sufficient in 2026-07-20,
when 0/9/0 in-place regenerated every MAC. So the sequence puts a MAC
verification BETWEEN the apply and the re-commission -- that incident became
fleet-wide because MAAS was told to re-commission while its records were
already stale.
Capture: docs/audit/r1-precondition3-verify-20260729.txt
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

Author both Stage-5 blockers (NOT applied); Octavia merge question resolved
...
Octavia RESOLVED as a clean negative. The chain audit flagged that
octavia-pki.yaml lands last and writes the same applications.octavia.options
map as the vips overlay, so under replace semantics octavia would lose its
vip and prefer-ipv6 with every offline gate still green. Researched against
source: juju 3.6 pins charm v12.1.1, and mergeStructs does
dstMap.SetMapIndex(srcKey, srcMapVal) per key for map-kind fields.
ApplicationSpec.Options is a Go map, so it deep-merges -- both overlays' keys
survive. The merge path is byte-identical between 2.9 and 3.6. The checker's
"MIRRORS juju's documented merge" comment is now sourced rather than assumed.
Logged: LP #2002371 (Triaged/High, unfixed) -- an EMPTY/null options: key
WIPES the base map rather than merging. Our overlays lack that shape and
nothing gates against acquiring it.
R1 AUTHORED. modules/node-vm gains osd_disk_size_bytes, default 0 = creates
nothing; the volume is count-gated and the disk entry appended via concat, so
an opted-out node has no extra resource and no plan diff -- precondition 4
verbatim. Four storage nodes per DC set osd_gib = 500: 8 volumes, not 18.
500 GiB is D-121's own budgeted figure (4x500Gi/DC = PASS, 5.31 TiB margin),
and D-121 records that footprint as an assumption pending an OSD-sizing
decision, so it is evidenced but not separately ruled. Headroom caveat
surfaced: dc0 has 2.0T available against 2.0 TiB nominal -- thin-provisioned,
so creation is cheap, but there is no room for those OSDs to fill.
D-104 AUTHORED. A 10th keyed entry per root, <dc>-juju-01, planning 1 add /
0 change / 0 destroy. The nine role nodes are untouched. 8 GiB / 4 vCPU are
the values the capacity gate actually modelled; disk 100 GiB is the authoring
value the amendment left open. MACs deliberately empty -- correct only before
enlistment, and they must be pinned from measurement after the first apply or
this reproduces the 2026-07-21 MAC-regeneration trap.
NEITHER IS APPLIED. opentofu-validate PASS on both roots; no apply run. Both
are live substrate mutations and individually operator-gated.
R1 precondition 3 is NOT satisfied and I am not treating it as such: the
amendment requires verifying BEFORE the apply that re-commissioning preserves
the D-134 statics and pinned MACs, and that needs live MAAS, absent here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

Chain-audit deploy path: DC-aware preflight, dc1's real overlay set, Fork-3 role tags
...
Findings 16-20 from the D-136 chain audit, actioned.
preflight P2 is DC-AWARE and validates the deploy's ACTUAL overlay set. It
hardcoded --dc vr1-dc0 with no dc1 branch while dc-dc-phase4 invokes it BARE
as the dc1 gate and tells the operator to expect PASS -- so a dc1 operator
got a green gate that validated dc0's numbers against dc0's bands and never
parsed a dc1 artifact. It also passed one overlay where the deploy passes
two. DC=vr1-dc1 now merges vips + machines for that site.
dc1's deploy command names ALL its overlays. phase-4 Step 4 named only the
vips overlay, so a dc1 deploy run as written merged a machines block still
saying tags=openstack-vr1-dc0 -- grep for vr1-dc1-machines across runbooks
and scripts returned ZERO, i.e. that overlay was in no executable path. The
step now carries the full command, states dc-ha-scaleup is deliberately
excluded per R6, and adds the --dry-run machines-merge confirmation the
overlay's own VERIFY-LIVE note required and no step performed.
phase-6 deployed with NO VIP overlay and named a file that never existed.
Both blocks -- including the real apply against a live model -- passed
overlays/${DC}-hostnames.yaml, never authored, and no VIP overlay;
post-ruling-3 that is a merged input with zero VIPs including the designate
those commands exist to add.
scripts/maas-role-tags.sh ships Fork 3 (harness 9/9). Every machine
constrains on two tags; only the site tag is ever created, so the three role
tags exist nowhere and juju's allocation constraint matches no machine.
Role is DERIVED from two signals that must agree -- the ruled D-134 octet
band and the host's own name token -- and disagreement REFUSES rather than
picking one. Nodes match by pinned boot MAC, never hostname. Dry by default,
every write read back, absent CLI distinguished from unreachable service.
apply --commit is a live MAAS mutation and is NOT run here.
OWNED, third appearance of the IFS trap: ROLES="control compute storage"
under IFS=$'\n\t' does not word-split, so the script reported one absurd tag
named 'control compute storage'. Caught by its own harness pre-ship. Same
class as commit 1's CHECK 1 death and phase-05's aborting captures -- these
strict-bash gates should use arrays, never space-separated string lists.
Also fixed: the role-derivation refusal swallowed its own diagnostic.
Gauntlet ALL GREEN (87) on vcloud; repo-lint 0 fail / 614 files; preflight
verdict unchanged in kind.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

D-136 step 6: the chain audit -- R9's drift gate built, three refusals, two dead cases
...
Three read-only lenses over data source -> renderer -> deployment mechanism,
the ruled final step of D-136 option (D). Lens 3 was adversarial and made
the chain produce wrong-but-green THREE times, all executed.
All three converged on one root cause: dc1's overlay was byte-gated and
dc0's was gated by nothing. render/values/ was referenced by zero scripts
and zero harnesses, while preflight P2 feeds the deploy gate dc0's overlay.
R9's render-drift check had been RULED and never built.
Executed wrong-but-green:
- transposing two apps' octets in the dc0 overlay (consistent, in-band,
correct prefixes) -> PASS
- changing ONE WORD, family: dual -> v4, silently reverting dc0 to IPv4-only
against R2's ruling -> PASS, because dropping prefer-ipv6 and the v6 legs
together satisfies invariant 9, and vip_dual is printed but never asserted
tests/render-drift/ is R9's gate, built. It iterates render/values/*.yaml,
reads each file's own output: field (inert until now), renders and
byte-compares. A new per-DC values file is gated the moment it is committed.
Non-zero floor plus a proof-of-teeth case that mutates a copy and requires
red.
Three refusals where the chain took the last writer silently:
- duplicate (role, kind) in the apex, in BOTH readers. Appending one spare
prefix -- routine IPAM work -- re-homed every dc0 VIP into a different /64
and went green, because the gate checks overlay-vs-apex agreement and both
sides read the same wrong value. The winner depended on JSON array order.
- duplicate app name / duplicate octet / out-of-band octet in APP_OCTET. A
duplicate name emitted two YAML blocks, safe_load kept the last, and one
app's ruled octet vanished with nothing red.
Two harness cases that could not fail:
- T3b's byte-compare was CIRCULAR -- header derived --from-overlay $LIVE
then compared --against $LIVE, so a third of the bytes were self-copied.
Proven by rewriting a header line to a false claim and still matching.
- T29's own fixture was inert; its assertion held from the overlay alone.
OWNED: my first T29 re-point asserted a --show-legs flag THAT DOES NOT
EXIST. I invented an interface instead of reading one. The harness caught it
immediately; replaced with real behaviour and recorded rather than quietly
fixed.
Gauntlet ALL GREEN (86) on vcloud; repo-lint 0 fail / 612 files. The larger
deploy-path half is logged not actioned -- see the changelog, items 16-25.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

D-109 ruling note: the Octavia controller cert gains IPv6 IP SANs
...
Operator ruling (GA-R5), exact utterance: "Yes, add the v6 IP sans."
Recorded as a D-109 RULING NOTE (2026-07-29); OPS under GA-R3 -- a
generator change extending an already-ruled principle to a surface D-109
governs -- so no new D-number and next-free stays 138.
The generator now emits IP.1 = this DC's provider v4 leg and IP.2 = its
provider v6 leg, both derived from the same per-DC overlay under R7's $DC
selector. Verified: dc0 -> 10.12.4.57 + 2602:f3e2:f02:11::57; dc1 ->
10.12.64.57 + 2602:f3e2:f03:11::57; and a v4-only control emits NO v6 SAN,
so the generator stays correct on a tree where the dual-stack ADD has not
landed. Admin and internal legs stay EXCLUDED exactly as before --
DOCFIX-067's design has always been provider-leg-only.
Two repo guards fired and both were right: repo-lint L5 rejected a heading
that LED with the D-number (it reads as a second definition -- the third
time this trap has bitten on this branch), and L10 rejected the change for
touching a design-decisions Status line without CURRENT-STATE in the same
commit.
Observation logged, not acted on: amphorae reach the controller on o-hm0's
charm-generated fc00::/64 ULA, not on any VIP leg, so neither SAN matches
that path. Measured upstream, the controller verifies the amphora by UUID;
the reverse direction was not established from source. Settle by inspection
at the Octavia step rather than assuming.
Gauntlet ALL GREEN (85) on vcloud; repo-lint 0 fail / 611 files.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

Octavia: R8a BUILT (record was false), two stale surfaces, R7 generator de-frozen
...
Follows an upstream/vendor research pass answering "can Octavia run full
IPv6" -- yes; nothing inside Octavia requires IPv4. charm-octavia creates
the lb-mgmt subnet with ip_version: 6 and has no IPv4 code path at all; the
health manager is v6-capable both directions; the amphora agent binds '::'
and verifies certs by amphora UUID, not IP; and Nova metadata is not a
dependency (config_drive=True). Surviving v4 pressure is soft or external.
R8a BUILT. phase-05-octavia-verify.sh now compares o-hm0's MTU against
lb-mgmt-net's (resolved BY TAG, never by name), fails in either direction,
and REFUSES when a value cannot be read. This closes the LP#2018998
obligation, live on our exact pin after its 2025-12-31 recurrence.
THE DECISION RECORD HAD CLAIMED THIS SHIPPED. design-decisions.md carried a
present-tense "Delivery: the assertion ships in ..." written on the day of
the ruling, while the file held zero MTU references and its last commit
predated the ruling by a month. Corrected in place. Sharper than the usual
ruled-but-not-built class: the decision doc was the false witness.
TWO OWNED ERRORS:
- I concluded "no octavia harness exists" from a NAME grep. The script's
harness has always been tests/phase-05. Duplicate deleted, R8a cases
folded into the real one.
- My first MTU draft ABORTED the script: a $( ) under set -euo pipefail with
inherit_errexit exits the run rather than reaching the refusal branch. The
pre-existing harness I had just declared nonexistent caught it. Same class
as commit 1's IFS word-splitting trap; both now locked by cases.
Two live surfaces still contradicted R8 -- dc-dc-phase4 and the workflow doc
both called lb-mgmt IPv6 "a real, open risk" and recommended keeping it
v4-only, citing the two refuted bugs. A reader would have re-litigated a
closed ruling in the wrong direction. Both superseded in place.
R7 executed: phase-01 Step 1.0-GEN gains a $DC selector; the baked
/CN=VR0 DC0 CA subjects derive from it, and the VIP gate derives this DC's
provider prefix from the same overlay it read the VIP from -- R7's explicit
"read the MERGED input" caveat. dc1's 10.12.64.57 used to hard-ABORT, so no
dc1 artifact could be produced. Verified both DCs, negative control still
aborts.
Logged not built: the controller cert's CN/DNS SANs still carry dc0.vr0
(outside R7's ruled scope, inert while os-public-hostname is unset), and it
gains no IPv6 IP SAN though the VIP is dual-family -- a gap, not a break,
and adding v6 SANs needs its own ruling.
tests/phase-05 14 cases (was 9); gauntlet ALL GREEN (85) on vcloud;
repo-lint 0 fail / 611 files.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|