| 2026-07-30 |

P7: the Octavia amphora PKI becomes an actual pre-deploy gate
...
Operator: 'Yes, I accept the recommendation. Convert to a gate.'
THE GAP: octavia-pki.sh was invoked by NOTHING executable. P4's CHECK 0 asserts
only that the overlay FILE EXISTS, and the harness runs against throwaway
fixtures -- so 'gauntlet ALL GREEN' said nothing about the live PKI and the only
real check ran when a human remembered to type it. That is how F9 survived.
Three problems solved, each from a measured precedent:
1. rc 3 must not reach note()'s *) arm. octavia-pki.sh uses exit 3 for REFUSE,
but note() calls any rc>=3 'UNEXPECTED exit code'. P7 maps rc explicitly, so
a REFUSE on the headend FAILS and is NAMED as an unevaluated trust domain.
2. HOST AUTHORITY. The PKI lives on the headend (D-109 (b)); elsewhere P7 says
NOT EVALUATED (WARN) -- never a silent pass, never a hard FAIL for being on
the jumphost. The binding is read from the SAME host-identity file
octavia-pki.sh reads, so they cannot drift. P5's F6 defect designed out.
3. A PASS VERDICT IS NOT SUFFICIENT. While os-public-hostname is unset, A12/A13
RECORD a wrong-zone cert rather than failing it, so verify exits 0 on exactly
the defect this gate exists to catch. P7 also requires the literal line
'DNS SANs are all in this DC.s expected zone'. Satisfiable by construction
now that GEN.c derives the zone and both DCs were reissued.
Also skips cleanly for vr0-dc0 (a gate that fires on a target it does not apply
to is how gates get bypassed) and FAILS CLOSED if the checker is absent.
HARNESS 26/26 -> 33/33. Ten existing cases went RED when P7 landed -- correctly,
the fixtures had no octavia-pki.sh. Fixed by making mkfix model a healthy headend,
NOT by patching cases one by one: that is the moment a gate gets demoted to a
warning to go green. T27 is the case that matters -- a wrong-zone cert on which
verify exits 0 must still FAIL P7.
Two harness bugs of my own, caught by running not reading: a stub emitted DC.s
where the real line has an apostrophe, then \x27\x27\x27 produced three of them
because \x27 in a printf FORMAT already yields one. Fixed the stub, not the gate.
Preflight's overall verdict is still FAIL -- that is P5 working as designed on
the pre-existing red register. P7 itself passes on the headend.
gauntlet ALL GREEN (89); repo-lint 0 fail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

octavia-pki: a hardened, tool-driven controller-cert REISSUE path (F9's remedy)
...
Operator: 'Let's reissue now. Engineer the artifact, variable, based workflow to
reduce the chance of errors.' Five GA-R5 rulings, one exchange each, quoted in
the changelog; ruling 5 was 'Full script minter now'.
THE TOOL IS BUILT; THE MINT IS NOT YET RUN (gated operator action, 1.0-REISSUE).
scripts/octavia-pki.sh gains 'reissue <site> [--force] [--dry-run]': controller
LEAF only, signed by the EXISTING controller CA (neither CA regenerated, amphora
trust domain untouched), fresh P-256 key, zone DERIVED from the site token and
never typed, printf-built config (the F8 payoff), stage-assert-promote, and a
single-value overlay surgery that never puts the bundle in argv.
SIX GATE DEFECTS FOUND BY VALIDATION AGENTS, ALL CLOSED. Mutation testing is
what earned this: deleting the three assertions I had just written left the
harness fully GREEN every time, so they were decoration until each got a
failing-direction fixture.
- A15 keyUsage/EKU on the ISSUED cert. A cert with NEITHER minted, promoted and
passed everything. keyUsage appeared once in the file: the printf writing it.
- A16 validity. openssl x509 -req defaults to -days 30, and checkend/notAfter/
enddate appeared ZERO times in verify -- a 30-day cert passed forever.
- A17 the overlay must decode to what the workspace holds. A10 graded shape,
A1-A16 graded the workspace, nothing joined them; a desynced overlay read
PASS 0-failed and the overlay is what reaches the charm.
- The re-run guard REFUSED to fix broken certs: names-correct + stale IP SAN
gave verify FAIL and reissue exit 4 'nothing to fix'. Now gated on verify.
- A8's negative proves its instrument loads first (openssl verify exits non-zero
for 'could not load the CA' too, so a corrupt CA passed it vacuously).
- Two pre-existing overlay defects moved pre-mint, so they stop producing a
false exit-5 with the workspace already promoted.
Harness 23/23 -> 48/48. Runbook Step 1.0-REISSUE appended AFTER GEN.e
deliberately (F10 line-anchored mint-refs; creds-matrix reports no S4 findings
after the append). Skill gains two standing invariants for future DC standups:
every per-DC secret needs a ROTATION tool not just a generation recipe -- a
REFUSE-IF-PRESENT generation gate makes the dangerous path the only path -- and
a hand-pasted openssl chain is a defect class, with F8 and F9 as the receipts.
Also corrected: D-137 fork 1 is NOT open (sub-ruling 1, 2026-07-25, ruled
'Blocking in preflight' and explicitly declined the PreToolUse guard), so the
queued Q2 premise is stale in the same way Q1 was.
gauntlet ALL GREEN (89); repo-lint 0 fail; creds-matrix 5 findings, all
pre-existing and named; ledger-scan unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

DOCFIX-205: Q1 withdrawn -- D-117 ruled it 2026-07-13; annotate the four it owed
...
The operator asked to rule on queued Q1 (is substrate vr1-dc1 'dc1' by token or
'dc2' by position, for cert/DNS identity). Measured before presenting options.
The question was WITHDRAWN, not ruled: D-117 (ADOPTED 2026-07-13, four days after
D-106) names the supersession in its own Status line -- "Supersedes ... the D-106
dc1/dc2 zone labels" -- and rules the replacement: "The repo's dc1/dc2 labels are
retired in favour of dc0/dc1". vr1-dc0 -> dc0, vr1-dc1 -> dc1.
WHY IT RESURFACED, and this is the reusable half: D-117 ruled that the ADOPTED
decision texts D-101/D-106/D-111/D-115 be ANNOTATED in place. Measured: ZERO of
the four carried any D-117 annotation. It stayed invisible because D-117's OWN
Status line claimed "FULLY EXECUTED BY D-119", while D-119 scopes its discharge
to the SELECTOR half only. A reader checking whether the annotation was owed was
told it was already done. Ruled is not built -- check the artifact.
Landed (operator-approved batch, "All four + code fixes"):
- All four decisions annotated in place, per D-117's own ruled treatment. D-101
gets the hardest one: it carries BOTH namespaces, so it is annotated BY DATE
(dc1 means different datacenters in its two halves -- D-117 TRAP 2).
- D-117's Status line corrected by measurement (GA-R1/C2) to "EXECUTED IN TWO
HALVES", with the standing lesson that a Status line is a CLAIM about
execution, not evidence of it.
- phase-6 EXECUTABLE DEFECT fixed: os-public-hostname was built as
"keystone.omega.${DC}.vr1..." with $DC the D-119 selector, expanding to
omega.vr1-dc0.vr1... -- the region TWICE -- and would have been baked into
Vault-issued SANs at Stage 7. Both labels are now DERIVED from the site token
(${DC%%-*} / ${DC#*-}); verified by running it, not by reading it.
- octavia-pki.sh A12: refusal -> assertion on the full derived zone; a SAN-less
cert now also fails. Harness 21/21 -> 23/23, with T21 REPLACED (not deleted)
and new T21b (the other DC's label in the right region FAILS -- the cross-DC
mix-up region-only checking passed) and T21c (per-DC derivation proven).
- CURRENT-STATE + ledger corrected in the same commit (L10). "BLOCKING" was
wrong as stated: A12 measures INERT and PASSES live on both DCs. F9's reissue
obligation stands.
gauntlet ALL GREEN (89); repo-lint 0 fail; ledger-scan unchanged (3 decisions,
21 SEC, D 138 / BUNDLEFIX 053; DOCFIX 205 -> next-free 206).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

voffice1 reconcile: a gate that was RED on the only host that deploys
...
Operator asked whether the headend clone was fully reconciled. It was not, and running the
gauntlet there -- which had never actually been done -- found a real host-dependent gate defect.
RECONCILE. The headend was 1 commit behind with 0 local commits, now at HEAD, tfstate sha256
byte-identical before and after. No stale remote-tracking refs. One asymmetry found and fixed:
opentofu/vr1-dc1-substrate/.terraform.lock.hcl was UNTRACKED and not gitignored, while dc0's
equivalent IS tracked. Same provider version (0.9.8), so a content divergence was ruled out by
measurement, but a fresh clone could not reproduce dc1's provider resolution the way it can
dc0's. Landed, byte-identical to the headend copy.
THE GATE DEFECT. The gauntlet read ALL GREEN (89) on the jumphost and 1/89 FAILED on the headend,
on opentofu-validate. Cause: `tofu fmt -recursive` walks EVERY .tf/.tfvars under the tree,
including GITIGNORED local files. The sole flagged file was
opentofu/vr1-dc0-substrate/d124-inner.auto.tfvars -- gitignored operator input that exists only
on the host which runs the applies. So the gate was RED ON THE ONLY HOST THAT DEPLOYS and green
where it did not matter, the same host-dependent-verdict class as the LC_ALL manifest sort.
Ruled out first: both hosts run tofu 1.12.4, so this was not version drift.
The fmt check is now scoped to repo content, and a gitignored file is SKIPPED with the reason
printed rather than silently. Proven both ways: a badly-formatted TRACKED file still FAILS (T16),
a gitignored local tfvars is skipped and does not fail (T17). T17 also fails loudly if the ignore
rule ever stops covering per-DC tfvars, so the case cannot silently stop testing what it claims.
Owned: my first T16 read as a failure because `bash "$SCRIPT" | grep -q` inherits the gate's
correct exit 1 under `set -o pipefail`, even though grep matched -- a passing assertion reading as
a failure. Same strict-bash family as the IFS and inherit_errexit traps this repo has hit before.
Captured first, then grepped.
opentofu-validate harness 16/16; repo-lint 0 fail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

A12: the DNS SAN check ARMS ITSELF, so F9 cannot be missed
...
Operator direction: make the DNS SANs part of the process so it is not a step that gets missed
in future.
The risk in F9 is not the defect, it is that a future session forgets. So A12 arms from the
change that makes the SANs load-bearing -- os-public-hostname appearing as a real option key in
the merged deploy input -- rather than from a note somebody has to read. Unarmed, it states the
names are inert and why; armed, it asserts them.
WHAT IT CAN ASSERT, AND A GAP IN A RULED DECISION THAT IT CANNOT. D-008 fixes the shape
<service>.<cloud>.<dc>.<region>.cloud.neumatrix.local and D-106:2563 instantiates VR1 as
"omega.dc1.vr1" / "dc2.vr1" -- while the substrate's DCs are vr1-dc0 and vr1-dc1. So vr1-dc1 is
"dc1" by token or "dc2" by position: the DC1/DC2 ambiguity item 3.1 retired elsewhere, surviving
inside a ruling, where it decides certificate identity. The REGION is unambiguous and is
asserted. The DC LABEL REFUSES pending a ruling rather than blessing a name that cannot be
validated. Both live certs currently say dc0.vr0 -- the VR0 region, wrong for a VR1 DC under
either reading.
A PRECEDENCE BUG THE HARNESS CAUGHT. Because the armed branch always refuses on the label, a
definite wrong-region SAN was being reported as "could not evaluate" -- REFUSE was checked before
FAIL and masked a confirmed defect. A known defect outranks an unevaluated item, so the verdict
now reports FAIL first and still names the refusal, which is never lost.
Harness 21/21 (was 18). T19 proves the unarmed live posture still PASSES -- without it, arming
A12 would have broken every current verify, the same trap T17 guards for the deleted
intermediates. T20 is F9 armed. T21 proves the right region still refuses on the unruled label.
repo-lint 0 fail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

tfstate backup path built; E2/E3 closed live; a gate disagreement found and fixed
...
BACKUP PROVEN BY RESTORE, not by existence. Both PKI archives were extracted and compared
byte-for-byte against the live tree: 12/12 sha256 MATCH across both DCs, covering both encrypted
CA keys, both passphrases, both CA serials and both controller bundles. Archives are 600 inside
700 folders and differ between DCs, so per-DC independence survives into the backup.
E2/E3 CLOSED LIVE. Operator ran the chmod and the intermediate removal; octavia-pki.sh verify
now reads PASS 26/0 on BOTH DCs, workspaces 12 -> 10 files, and P5 on the headend fell 18 -> 8.
A GATE DISAGREEMENT, recorded as the pattern rather than the incident. verify read 26/0 while
creds-matrix E2 still reported the CA serial mode 664 on both DCs. The chmod list predated the
serial being declared, and verify's mode assertions covered six private files and three certs --
the serial was in neither. Two gates that can both see a file must not disagree about it. Fixed
in three places: verify now mode-checks the serial (integrity, not secrecy -- rewriting it forces
the next issuance to reuse a serial), the generator's chmod includes it, and T18 pins it. The
harness then caught its own fixture creating the serial at the inherited umask; baseline went red
until the fixture matched the corrected generator. 18/18.
TFSTATE BACKUP as dc-dc-phase2 step 13, 2 rows, notes key n-tfstate-backup. Register 99 rows;
schema, render-drift, mint-ref and notes checks clean.
Three deliberate differences from the PKI backup: tfstate is DYNAMIC so the serial is recorded
both sides and the step must be re-run after every apply (restoring a stale state is actively
dangerous -- tofu then believes everything created since does not exist); the pull block proves
RESTORE by decompressing to check the archive parses as JSON and reports a serial, because a
truncated gzip passes a size check; and mint-stage is stage3, which is REACHED, so P5 correctly
reports these EXPECTED-BUT-ABSENT until the step runs rather than deferring them.
Classification recorded honestly: the outer state is credential-bearing (DOCFIX-175, MAAS API key
in plaintext); the inner states show no credential-shaped key names, consistent with the inner
root using only libvirt over qemu+ssh. Strong evidence, NOT proof -- a name-based absence cannot
exclude material inside a value -- so they are registered and stored as if sensitive. Uncertainty
resolves toward more auditing, and an unaudited backup directory beside audited ones is how the
SEC-022 shadow stores happened.
Two of my own defects caught by the gates: repo-lint L9 rejected a hardcoded clone name in the
new step (the repo has been renamed once, D-110) -- now $REPO; and two mint-refs had drifted when
a later edit added lines above their anchors, which S4 cannot catch because it only checks
line <= EOF. Both re-anchored.
Also corrected: phase-01:605 said the overlay must be in the backup set. It is derivable from the
archived workspace by 1.0-GEN.d, so the line implied a gap that does not exist.
Known, not fixed: both live inner states are 664, group-writable, and they are the authority tofu
trusts. Tightening needs its own gated change plus proof the provider preserves the mode.
creds-matrix 65/65, octavia-pki 18/18, repo-lint 0 fail / 621 files.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

E3 resolved: declare the two artifacts that persist, delete the two intermediates at source
...
Operator-approved disposition after review. The four undeclared per-DC generator outputs were
separated by whether anything will read them again.
DECLARED. The controller certificate, because octavia-pki.sh verify reads it for the subject,
chain and SAN assertions -- a gate that depends on an undeclared artifact is incoherent. And
the CA serial file, because it is issuance STATE rather than residue: delete it and the next
-CAcreateserial starts a fresh sequence, so the same CA can issue a duplicate serial.
Reissuance is scheduled, not hypothetical -- the controller cert is 2-year and F9's D-106 work
will want new SANs.
DELETED AT SOURCE. The signing request is spent the moment the cert is issued. The openssl
config is fully derived from $DC plus the VIP overlay, so the repo already determines it, and
verify now asserts the SAN set on the CERT, which is where the F8 failure actually shows.
WHY NOT DECLARE ALL FOUR, which was lower-risk and less work: a register that accumulates rows
for artifacts that should not exist trains the reader to add a row rather than ask whether the
file belongs -- the register-as-rubber-stamp failure, the same family as this project's
false-green problems. Concretely, both intermediates are 664, so declaring them would have
produced four MORE world-readable findings for files nothing reads.
Also fixed at source, beyond the three requested steps and flagged as such: the generator now
chmod 600s all three certificates, so the next DC is correct by construction instead of by a
remembered follow-up.
Register 95 rows / 22 octavia rows; schema, mint-ref and notes checks clean; no new findings
(still 7 on the jumphost, the same pre-existing set). verify A2 extended 9 -> 10 so the register
and the gate agree on what should exist.
Harness 17/17 (was 15). T16 proves a missing serial FAILS. T17 proves a workspace with the
intermediates DELETED still PASSES -- without it, adding the serial to A2 would have silently
failed every clean workspace.
Gauntlet ALL GREEN (89), repo-lint 0 fail / 621 files.
Still owed, operator-executed: the one-off chmod on the six existing certs and removal of the
four existing intermediates. The guard blocks both for the session -- including the chmod, which
is worth recording as fork-1 evidence since it blocks an operation that strictly improves
posture.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|
| 2026-07-29 |

octavia-pki.sh verify: the SAN assertion the runbook never had (harness 15/15)
...
Operator direction: "Build the verify mode first, then we'll rule the guard fork."
WHY. Step 1.0-GEN is a copy-paste chain, and executing it today surfaced two defects that
only a human noticing caught. F8: the heredoc is a paste hazard whose output is a controller
certificate with NO SANs, while every command in the chain still prints OK -- and NOTHING in
this repo asserted the SAN set. E2: the CA certificates landed group-writable, i.e.
substitutable trust anchors, which this session initially dismissed as harmless. Prose cannot
be tested; a script can, which is the entire argument.
SCOPE, deliberately narrow. verify only. It mints nothing and writes nothing. `generate`
exits 2 by design: whether a sanctioned script may mint is D-137 open fork 1, UNRULED, and
building it now would silently convert the guard from "blocks secret minting" into "blocks
secret minting except through any script". That posture change is the operator's to make.
CANNOT LEAK KEY MATERIAL BY CONSTRUCTION: every openssl call uses -noout, file contents are
only counted or pattern-matched, and there is no cat or base64 anywhere. That property is
what makes it runnable from an agent session.
Asserts, all derived rather than hardcoded: the workspace and its 9 artifacts; private
material 0600 and certificates not group/world WRITABLE; both CA subjects naming THIS DC
(the sibling of F8 -- the runbook guards ${DC:?} but not ${DC_LABEL}); both self-signatures;
the chain against the CONTROLLER CA and, as a negative, NOT against the issuing CA, since
only checking the positive would pass a chain built against the wrong trust domain; the SAN
set against the per-DC VIP overlay; the overlay's mode, key count, ASCII and gitignore state;
and cross-DC independence.
Host binding is READ from creds-manifests/host-identity rather than hardcoded, so it cannot
drift from the register, and on any other host it REFUSES rather than measuring a filesystem
it should not be describing -- the F6 lesson, exit 3.
Harness 15/15. T1 is the non-zero floor. T2 is F8's SAN-less certificate. T13 guards the
opposite error: openssl prints v6 SANs expanded and uppercase while the overlay carries the
compressed form, so a naive compare would FAIL a CORRECT cert. T9/T10 prove REFUSE, T11
proves an un-generated DC FAILS rather than refusing -- we looked, and nothing was there.
Added OCTAVIA_PKI_REPO / _HOME / _THIS_HOST overrides so the harness can point at fixtures.
Not a convenience: creds-matrix.py's V2 check shipped with zero cases precisely because it
had no fixture path, and this repo records that as the reason it went untested.
Manifest 88 -> 89. repo-lint 0 fail.
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
|

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
|

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
|

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
|

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
|

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
|

Ruling 3 commit 2: dual-stack ADD -- R2 and R11 are now BUILT, L3-9 resolved
...
Both per-DC VIP overlays re-rendered dual-family: every app carries
prefer-ipv6: true and a six-address vip, and vault .61 + designate .62 are
built. Measured both DCs: 13 clustered VIPs (13 dual-family) + 12 hacluster
principals all carrying a VIP -> PASS. With dc-ha-scaleup stacked: 13
principals, PASS -- R6's ruled ordering (VIPs before the HA overlay) is now
satisfied and executable. Two ruled-but-never-built decisions became
artifacts in one pass.
L3-9 RESOLVED. dc-dc-ipv6-family-matrix.yaml is narrowed to ceph-mon only;
its ten duplicate vip + prefer-ipv6 pairs (every value an unrendered token)
are gone, removed TOGETHER -- removing the vips alone would have recreated
the defect deliberately. Both merge orders now PASS, i.e. order-independent,
which is the actual fix. ceph-mon's ULA-only networks survive because they
exist in no other file. The two Launchpad citations arguing octavia lb-mgmt
IPv6 was an open risk are removed: both were read in full and both failed,
and R8 closed that question on 2026-07-27.
Also corrected at source: CURRENT-STATE described L3-9 as "the dangerous
merge order is the one that PASSES". Measured -- that stopped being true
when invariant 9 shipped on 2026-07-28. The merge semantics it describes
remain accurate; only the green-over-loss conclusion is retired.
Gate repairs the agents caught BEFORE the edit:
- phase-01's deploy guard would have ABORTED THE DEPLOY AGAIN -- its HI regex
(5[0-9]|60) excludes .61/.62, so it would have read 13/11/0 against a
required 11/11/0. Widened, counts moved to 13, verified 13/13/0 -> DEPLOY.
Second near-miss on this one guard across two commits.
- pre-flight-checks CHECK 1 hardcoded if(n!=3) and would have failed all 13.
Now takes a triple OR a sextet and asserts a sextet's last three legs are
really v6. Proven able to fail on both new paths.
Harnesses re-pointed, never deleted. T16/T17 asserted R11's gaps were OPEN;
inverted to the surviving invariant and PAIRED with new T16b/T17b that remove
a VIP and demand the check still fires. T20/T21 had become no-ops that would
have gone GREEN testing nothing -- inverted. A v4-only twin fixture keeps the
pre-dual-stack cases testing their own invariants. The renderer harness now
proves reproduction on both shapes, including new T3b against the live
dual-family overlay.
provider-bundle-check 33/33; render-dc-overlays 18/18; render-baseline 10/10;
preflight 16/16; gauntlet ALL GREEN (85) on vcloud; repo-lint 0 fail / 611.
PREFLIGHT: BOTH STANDING REDS CLEARED, 3 fatal -> 2 fatal. The remaining two
are the deliberately-absent octavia-pki overlay and MAAS unreachable from
vcloud, which R10 measured clears on voffice1.
FLAGGED, not hidden: octavia's dual-family API VIP is an inference from R2
applied evenly, not a quoted ruling. Worth one operator confirmation.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

Values-file provenance symmetry; repair the mangled ledger rotation note
...
Two small corrections found on review.
(a) The values file recorded `apex_record` only on the dual-family path, so
the dc0 v4 file carried no provenance at all -- and the asymmetry would have
read as a bug once commit 2 gives dc1 the apex field. Added `source_v4`,
recorded on BOTH families, because the v4 prefixes come from lib-net.sh and
NEVER from the apex: a values file carrying only `apex_record` misattributes
half its own inputs. Re-derived dc0; the rendered overlay is byte-identical
(a3b30e40...) and dc1 still reproduces exactly.
(b) docs/session-ledger.md's third rotation note was left MANGLED by an
earlier edit -- a sentence broke off mid-number and ran into a stray fragment
of the 2026-07-25 MAAS-admin-recovery summary. Flagged at this session's open;
repaired here rather than only reported, with a note saying so and a pointer
to that summary's archived body.
Gauntlet ALL GREEN (85) on vcloud; repo-lint 0 fail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

Ruling 3 commit 1: bundle.yaml is VIP-free; dc0's VIPs are now a RENDERED overlay
...
The 11 inline vip: lines are gone from bundle.yaml and live in
overlays/vr1-dc0-vips.yaml, GENERATED by scripts/render-dc-overlays.py from
render/values/vr1-dc0-vips.yaml -- the renderer's first real output.
placement's then-empty options: block was dropped rather than left parsing
as null. Ruling 3's dual-stack ADD is commit 2 and is NOT in here.
PROVEN NEUTRAL by the ruling's own named check: provider-bundle-check on the
pre-extraction bundle and on the merged pair produce line-for-line identical
output, same known designate failure included. A structural diff separately
confirmed only vip moved -- applications 56 -> 56, relations 108 -> 108,
every other key byte-identical.
The 11 inline comments were carried, not dropped: extracted verbatim by
parser rather than retyped, via new optional per-app comment support in the
renderer. An app with no comment emits none, so dc1 still reproduces
byte-for-byte at 1634 bytes. The B1/B5 tokens stay DEFINED in bundle.yaml's
header and the overlay header names that, so the references do not dangle.
The blast-radius repair is the bulk of this, and every item was found BEFORE
the edit by the read-only agent sweep:
- preflight.sh P2 validated the bare base (11 phantom failures post-move) ->
merged input.
- pre-flight-checks.sh CHECK 1 read the base as raw text and would have seen
ZERO VIPs. This is BLOCKER-1, fixed the ruled way and not by bumping the
count. Measured trap: the file sets IFS=$'\n\t', so a space-joined string
reached grep as ONE filename, grep exited 2, pipefail propagated and the
gate died silently mid-check. Fixed with a bash array.
- phase-01's RUN block gates juju deploy on 11/11/0 and would have read
0/0/0 and ABORTED THE DEPLOY. Both guards now span base + overlay, and a
second latent bug was fixed in the same edit: grep -c over two files prints
one count PER FILE, so the bare $( ) captured a two-line string and every
numeric test would have failed anyway. Now awk-summed; verified 11/11/0.
- phase-01's Octavia SAN derivation raised KeyError, swallowed by || true
into an empty VIP. Now reads the overlay, deriving 10.12.4.57.
- phase-00 teardown, dc-dc-phase4's "NO VIP edits" guidance, appendix-A L3,
and render-baseline's now-unverifiable bundle hash all repaired.
Harness 30 -> 31. The fixture base is now bundle + dc0 overlay, since that
pair is the deploy input; T16 renamed to match. NEW T31 asserts the bare base
fails for every clustered principal, so "deployed without its overlay" is a
tested state rather than an assumption.
Gauntlet ALL GREEN (85) on vcloud; repo-lint 0 fail / 610 files scanned.
Still red and expected: CHECK 1 at OK=11 want 13, and designate -- R11's
.61/.62 remain ruled-but-not-built, which is commit 2's job.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

Render-pipeline step 4: the renderer, reproducing a reviewed artifact byte-for-byte
...
scripts/render-dc-overlays.py. Two stages, split exactly where forward item
F2 froze the interface: `derive` (apex + lib-net.sh -> a per-DC values file)
and `render` (values -> overlay text). `render` is PURE -- no apex, no
network, no clock -- so it is offline-testable and a future CI job re-runs
only `derive`.
ORDER INVERTED BY MEASUREMENT. The plan was ruling-3's VIP extraction first.
Three read-only agents measured two facts that reversed it: the byte-for-byte
reproduction window is still open (the live dc1 overlay is still identical to
the frozen fixture) and closes on its own when the R2/R11 reconciliation
lands; and the extraction's blast radius is far larger than ruling 3's "2
commits" -- it breaks the phase-01 deploy gate (juju deploy runs only on
11/11/0), the Octavia SAN derivation, preflight in two places, and 8 of 30
harness cases. Better done BY the renderer, so dc0's overlay is a validated
output rather than a hand edit.
A TEXT emitter, not yaml.dump: a round-tripper destroys the comment header,
normalises the quoted vip scalar, and imposes its own key order -- all three
are load-bearing bytes. Two things are INPUT rather than invented: the 12-line
comment header (lifted verbatim via --from-overlay, so a reproduction run is
honest about which bytes the tool actually generates) and the ascending-octet
emission order (sorting by name reproduces nothing). The ruled app->octet map
is READ with ast out of dc-plane-apex-import.py rather than restated as a
second drifting copy.
RESULT: output hashes to 3f93ecb3..., identical to both the committed overlay
(1634 bytes) and the frozen fixture. PROVEN ABLE TO FAIL, not merely observed
passing -- three seeded faults all fail the compare (wrong octet, dropped
header, dc0 prefixes against the dc1 artifact).
THE ARITY WORK PAID FOR ITSELF WITHIN THE HOUR. The first dual-family output
was malformed: the values file stores each v6 /64 base with trailing colons
stripped, and the emitter joined with a single colon -- 2602:f3e2:f03:11:50,
not a valid address, and it still looks like one. provider-bundle-check,
fixed yesterday for exactly this class, rejected all 13 applications. Locked
by T13/T13b.
End to end: apex -> values -> renderer -> overlay -> gate. The dual-family
dc1 render with R11's ruled-but-unbuilt apps produces 13 VIPs, all
dual-family, PASSING provider-bundle-check --dc vr1-dc1 including the new
hacluster invariant. Nothing committed has been regenerated by it yet.
tests/render-baseline gains T6, the reproduction case its README invited --
ADDED, with T1-T5 deliberately not repointed at live, and comparing against
the FIXTURE because live is supposed to diverge. T6 proven able to fail by
making the renderer sort alphabetically.
New harness 17/17; render-baseline 10/10 (was 9); gauntlet ALL GREEN (85) on
vcloud, manifest re-recorded after the drift gate correctly caught the
addition; repo-lint 0 fail / 608 files scanned.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|
| 2026-07-28 |

VIP arity gap CLOSED + R11's three ruled gate changes executed
...
provider-bundle-check.py could not express what R2 and R11 already ruled:
:137 required exactly 3 addresses per vip (a dual-family VIP is 6, so it
would have failed EVERY application) and :149's octet extraction returned
the whole string on a v6 literal, leaving every v6 leg trivially unique
and unchecked. The apex now holds the dual-family set a renderer will
emit, so the only gate that validates overlays could not validate what is
about to be produced.
Arity: a vip is now a v4 triple OR a dual-family sextet. The v6 legs are
validated against the per-DC v6 /64s READ FROM THE APEX RECORD (D-136
option (D)), reusing dc-plane-ipam.sh's (role, kind) keying -- the
provider leg takes the dedicated GUA VIP /64, admin/internal their plane
/64s. The v6 host part must MIRROR the v4 octet textually. prefer-ipv6
and the v6 legs are COUPLED in both directions: the measured L3-9 finding
is that the merge order which keeps prefer-ipv6 while dropping the v6
legs is the one that exits 0. An unreadable apex REFUSES at exit 2; a
v4-only bundle needs no apex.
R11 (D-020 amendment, ruled 2026-07-27), same pass: band 50-60 -> 50-99
in both separately-named sites; VIP_COUNT_EXPECT 11 -> 13;
EXPECT_PUBLIC_VIP deliberately stays 11 (measured -- neither vault nor
designate has a public binding); and an hacluster principal with no vip
now FAILS, since cluster_count is asserted nowhere and a 3->1 rewrite of
all 20 values produces a byte-identical PASS.
Measured consequence, not glossed: two sub-checks flip PASS -> FAIL
(designate's missing VIP; CHECK 1 at OK=11 want 13). preflight was
ALREADY exit 1 before this change and is still exit 1 after -- no deploy
path that was open is closed. Both reds are the ruled gate reporting real
work owed; per R6 the .61/.62 VIPs land before the HA overlay.
Harness 15 -> 28. Three cases RE-POINTED rather than deleted: the fixture
base splits into a pristine repo bundle and one carrying designate's
ruled .62, with new case T16 asserting the pristine bundle DOES trip the
new invariant. When .62 lands, T16 gets re-pointed, not deleted. Proven
able to both fail and pass on the same check (T16 red / T18 green).
Gauntlet ALL GREEN (84) on vcloud; repo-lint 0 fail / 604 files scanned.
Scope was operator-gated: "Fix the arity gap first, then start the
renderer", with the arity-alone vs arity-plus-R11 fork put separately and
answered "Arity gap + R11's three ruled changes (Recommended)". OPS under
GA-R3, no D-number.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|
dc-node-v6-carve.py: mirror each node's v4 octet into v6, dry by default
...
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|
dc1 FIP pool RULED 10.12.65.0-10.12.67.254 (GA-R5); lib-net dc1 arm + assertions re-pointed
...
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|
dc-plane-ipam: v6 bands are VERIFIED against MAAS's default reservation, not created
...
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

dc-plane-ipam: carve-v6 and reserve actions, dry by default, harness 25/25
...
Both are DRY BY DEFAULT, IDEMPOTENT, and READ BACK every write -- this repo has
been bitten by a script whose `apply` always silently dry-ran, so a create that
reports success is re-read before being called done.
carve-v6 places each v6 plane on the SAME MAAS vlan as its v4 twin, so the plane
is genuinely dual-stack on one L2 rather than a parallel fabric, and REFUSES to
create anything if that vlan cannot be resolved. The provider GUA VIP /64 is
skipped by design.
reserve does the D-134 bands plus the FIP pool. Two deliberate refusals:
- dc1's FIP pool is UNSET in lib-net BY DESIGN ("UNSET so any use fails loud").
Mirroring dc0's shape would be an inferred value, so it refuses and reports
that R4 cannot be fully executed for dc1 until the pool is ruled. Real finding.
- a same-bounds range of the wrong type is REFUSED, not overwritten.
v6 bands are skipped until carve-v6 has run -- R4's forced sequencing, surfaced
rather than silently passing.
TWO BUGS CAUGHT PRE-SHIP, neither by review:
- printf '%x' 50 yields 32, so the v6 bands would have been created at ::32-::63
-- a plausible-looking band that is NOT the one ruled. The ruling mirrors the
DIGITS (v4 .50 -> ::50). T19 locks it.
- `shift 2` with a single argument fails, leaving "$@" holding the action, which
the option loop rejected as an unknown option instead of printing usage. Found
by the harness.
The header claiming the mutating actions were absent is also corrected -- it went
stale the moment they landed.
Gauntlet ALL GREEN (83), repo-lint 0 fail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|
dc-plane-ipam: add carve-v6 and reserve actions, dry by default
...
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|
dc-plane-ipam.sh: read-only gate for D-134 bands + R2 v6 carve (WIP, check only)
...
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

R15(2): the gauntlet pins a harness MANIFEST, completing the R15 gate work
...
tests/HARNESS-MANIFEST (82 names) plus a drift gate in run-tests-all.sh, with
--record-manifest for deliberate re-recording. The zero-floor already existed;
what was missing was any pin on WHICH harnesses ran, so a renamed or deleted
harness was neither run nor failed while the gauntlet still printed ALL GREEN.
The figure 81 existed only as prose.
Proven able to fail on both cases, including the one a bare count cannot catch:
renaming a harness is reported, and renaming one WHILE adding a decoy -- so the
count holds at exactly 82 -- is still caught, naming both the missing and the
unpinned entry. That case is why the ruling chose a manifest over a count.
Sequenced LAST of R15's three deliberately: the ruling warns a manifest must be
seeded from a verified tree, not from whatever is on disk. Seeded here from a
tree with the gauntlet ALL GREEN and repo-lint 0 fail in the same session.
The gate runs only on a full gauntlet -- a filtered run legitimately executes a
subset. A missing manifest is itself a FAIL, since ALL GREEN would otherwise be
unfalsifiable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

R15(3): preflight no longer fails open on an unexpected sub-gate exit
...
Reproduced first, in isolation with the exact note() from preflight.sh:27 --
sub-gate exits 127 (command not found), 126 (not executable), 130 (interrupted)
and 3 (undefined) ALL left "PREFLIGHT: PASS -- clear to add-model / deploy".
This is the gate that authorises the deploy.
rc=2 handled PER GATE rather than blanket-remapped, because R15's stated
rationale is only partly right on measurement: repo-lint and pre-flight-checks
both DOCUMENT rc2 as a legitimate warning, while provider-bundle-check and
creds-matrix use it only for could-not-evaluate. A blanket 2->FAIL would have
turned the standing L1 legacy-ASCII warn into a deploy blocker. channel_assert
uses 2 for BOTH meanings, so it is ambiguous in the checker and kept WARN; its
missing-PyYAML root cause is caught by P2 anyway, which shares it and is strict.
Flagged rather than guessed -- fixing that belongs in channel_assert.py.
Harness 16/16 (was 10): T11-T13 lock the fail-open cases, T14/T15 lock
could-not-evaluate -> FAIL, and T16 locks the non-over-correction (P1 rc=2 stays
WARN). Gauntlet ALL GREEN (82). Real-repo preflight still exits 1 for the same
known reasons with no new failure classes fired.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

R15(1): repo-lint can no longer report PASS over zero files
...
Reproduced before fixing, both cases printing "PASS: repo lint (0 fail, 0 warn)"
with exit 0: `repo_lint.py --recordd` and `repo_lint.py /nonexistent-path`. Only
the two known flags were stripped, so any other --flag became argv[0] i.e. the
ROOT; it resolved nowhere, rglob yielded nothing, and zero findings read as
clean. This is the gate whose "0-fail" every GA-R6 stage close in this project's
history cites, and it could not tell a clean repo from an unexamined one.
Fix: reject unrecognised options (the actual root cause) and >1 positional root;
require the root to be a directory carrying repo markers, refusing rather than
defaulting to success; add a files-scanned floor; and PRINT the files-scanned
count -- Phase 0 found repo-lint emitted no such figure at all, so a reader could
not distinguish a thorough pass from a vacuous one.
The floor SCALES, and a flat one was wrong: the first implementation used a flat
100 and failed 20 of the 47 existing harness cases, because tests/repo-lint
legitimately builds ~7-file fixture repos. The strong floor now applies only to a
full checkout of THIS repo, detected by two files no fixture creates; any other
tree must still scan at least one file. Caught by running the harness.
Harness 54/54 (was 47) -- T48-T54 lock each reproduced defect, including that the
floor does NOT punish a legitimate minimal tree. Gauntlet ALL GREEN (82).
repo-lint on the real repo: 0 fail, 1 standing warn, 596 files scanned.
No consumer breaks -- preflight P1 reads the exit code, not the summary text;
harness regexes match the unchanged "PASS: repo lint" prefix. Both verified.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|