| 2026-07-28 |
Record the deferred skill-sweep candidate for the Stage-5 close
...
Operator direction: leave the skill sweep for the stage close. GA-R6 ties the
sweep to a STAGE close and this session closed none; batching it there also avoids
regenerating the dated Chat snapshot twice.
The candidate is recorded rather than left in the transcript -- which is the exact
failure this session's close sweep spent its time correcting. It is a COMPLEMENT
to an existing skill invariant, not a new one: "a checker that cannot fail is not
a gate" has an inverse that also bites. Three gates built this session could only
ever FAIL, because everything they assert was absent, and each needed a
constructed fixture case to prove it could go GREEN. dc-node-v6-carve then
demonstrated it live, flipping dc0 to PASS while dc1 still read FAIL.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|
Close sweep: correct a step-3 heading that went stale within the session
...
The final step-3 entry read 'MAAS, lib-net AND APEX HALVES COMPLETE'. True when
written, but an understatement once the node carve landed above it -- a reader
skimming headings would have concluded a half was still owed. Corrected to name
all four layers, with the node half cited, and with what is deliberately NOT part
of step 3 stated explicitly (v6 gateway/DNS, and G17's first-boot verification).
Same stale-surface class this project keeps finding, and the same lesson: caught
by a close sweep, not by a gate. Nothing reads prose.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

Close sweep: graduate three platform traps, rescue two changelog-only questions, fix a latent import
...
A session-close sweep for content that would have been lost. Four items.
(1) THREE PLATFORM BEHAVIOURS were recorded ONLY in CURRENT-STATE, which is a
status document that gets consolidated over time -- a durable trap does not
belong there alone. Graduated to references/platform-traps.md: MAAS auto-reserves
::1-::ffff:ffff on EVERY IPv6 subnet (so an explicit v6 band write is both
impossible and unnecessary, and is invisible in `ipranges read` -- only
`subnet reserved-ip-ranges` shows it); MAAS mode=static means EXPLICITLY
CONFIGURED, so creating a subnet addresses nothing and the carve is still owed;
and NetBox's live API returns scope.name as the DISPLAY name while this repo's
dumps normalise it to the slug, so a dump-written tool matches zero objects live.
(2) TWO OPEN QUESTIONS existed only in the session changelog -- which is
session-scoped scratch, explicitly not citable as status and consolidated away at
stage close. Both would have been lost: which host is authoritative for the gates
(now sharper, since executing R15(3) made preflight-on-voffice1 permanently
unpassable against P0-2's host-blind P5), and the provider-bundle-check arity gap
(now imminent rather than theoretical -- the apex holds the dual-family set the
renderer will emit, so the gate cannot validate what is about to be produced).
Also recorded: voffice1 tracks the precondition branch and must return to main at
merge.
(3) A latent import defect in netbox/dc-plane-apex-import.py: urllib.parse was
imported inside the __main__ guard while main() uses it. It worked only because
another module had already bound the submodule -- fragile, and it would NameError
if the file were ever imported. Moved to module scope.
Gauntlet ALL GREEN (84), repo-lint 0 fail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

LIVE: node IPv6 carve both DCs -- 108 links, IPv4 untouched, 18 Ready unchanged
...
Operator-gated dc0 then dc1. 54 applied per DC, 0 errors, read-back 54/54 each.
IPv6 links 0 -> 108; IPv4 links 108 unchanged; 18 Ready unchanged. Both
dc-node-v6-carve gates PASS and dc-plane-ipam still reads 24/24 on both DCs.
The octet mirror holds across all six planes per node -- superb-piglet .121 ->
::121 everywhere, big-trout .100 -> ::100. enp2s0 correctly received nothing: the
D-100 raw provider NIC has no v4 link to mirror, and br-ex carries provider-public
instead, on the node /64 rather than the VIP /64. That falls out of deriving from
"already carries v4" rather than naming planes in a table.
The gate discriminated rather than agreeing with what it found -- it flipped dc0
to PASS while dc1 still read FAIL, before dc1 was carved.
This closes the node half of step 3, which was the correction the operator's
octet-mirror question surfaced: v6 does not arrive with the subnet, because MAAS
mode=static means an explicitly configured address.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

D-101 governing rationale recorded; node v6 carve scoped; two of my claims corrected
...
The operator's question -- "are we mirroring the assigned IPv6 octet with the last
IPv4 octet like we did before?" -- exposed two errors in what this session recorded.
(i) I said MAAS static assignment "needs only the subnet, which now exists". Wrong:
mode=static means an EXPLICITLY CONFIGURED address, which is how the Stage-4 carve
set the 90 v4 links. Measured -- all 18 Ready nodes carry ZERO IPv6 links, while
their v4 side is correctly octet-mirrored. So VIPs are mirrored (156 apex objects)
and nodes are not; 108 assignments are owed. (ii) v6 gateway_ip and dns_servers are
unset on all 12 v6 subnets, unnamed before I called step 3 complete. That
"complete" is withdrawn -- the MAAS/apex/lib-net population stands, the node layer
is the remaining half.
D-101 gains its GOVERNING RATIONALE, quoted verbatim, because it existed in no repo
surface: IPv6 unless IPv4 is NECESSARY, driven by real IPv4 sizing constraints in
future expansion -- a commercial requirement, not a preference. It records that
v4-first was DELIBERATE RISK REDUCTION so a future session does not read v4
surfaces as neglect and "correct" them; that Roosevelt has full v4 and v6 edge
transport; and that NAT64/DNS64 was considered and REJECTED on that basis -- a shim
with no Roosevelt analog, against ULA planes that are internal by design.
Node v6 carve scoped in docs/audit/node-v6-carve-scope-20260727.md: 108 assignments
mirroring each node's live v4 octet, prior art measured v4-only, blast radius
per-link and reversible, and the operator's own rationale arguing to carve BEFORE
the deploy since v6-unfriendly modules surface far more cheaply on a static
read-back than mid-bundle.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

Node IPv6 acquisition RULED (MAAS static assignment); STEP 3 COMPLETE
...
Operator, exact utterance: "MAAS static assignment is correct". Recorded on the
D-134 R4 amendment. The question offered MAAS static assignment, DHCPv6, or
SLAAC/RA -- materially different, only the first already satisfied.
It CLOSES work rather than opening it: the v6 carve already applied is sufficient
for node addressing; the rack-bridge v6 legs are NOT needed and were not applied,
so dc-rack-net.sh's LEGS table stays v4-only; dhcpd6 staying off is correct rather
than a gap; and no plane needs a router advertising RAs, so the DC edges take no
new role. It mirrors how v4 node addressing already works here.
RESOLVES U17. Its "the rack bridges need v6 too" was a correct observation on the
data path, but under static assignment nothing consumes a rack v6 leg, so the
widening it proposed does not follow -- the observation held, the conclusion did
not, which is this project's standing lesson about lens findings.
Verification owed at Stage-5 first boot, since nodes are powered off: PROPOSED,
not adopted, a fourth G17 assertion that a booted node carries a global v6 address
from its plane's /64 in the ruled ::100-::200 band. Amending a gate row is the
operator's call, so G17's three assertions are unchanged here.
STEP 3 COMPLETE. MAAS, lib-net and the apex all carry the ruled values. D-134's
bands and the D-020/R11 VIP set are now artifacts rather than prose. Machines 18
Ready + 2 Deployed unchanged across every mutation. Gauntlet ALL GREEN (83).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

Rack v6 legs measured: nothing consumes one yet, and the real question is how nodes acquire v6
...
Post-write safety check first: the new v6 metal-admin subnet landed on VLAN 5005,
which carries dhcp_on=True for the v4 subnet commissioning depends on. Measured
after -- rack vvr1-dc0 reports 9 services, zero degraded, dhcpd running, dhcpd6
off. Nothing disturbed; dhcpd6 off is legitimate, since MAAS starts it only for a
v6 dynamic range and none exists.
Prior art: dc-rack-net.sh already owns rack bridge legs via a site-keyed LEGS
table installed as a reboot-persistent unit, so v6 legs extend that table rather
than needing a new tool. Current table is three legs per site (rack .2, DNS
forwarder .3, edge-LAN .2), not six planes.
U17 is confirmed at the host layer -- zero global v6 on the rack, zero v6 links
known to MAAS for either rack controller. But nothing consumes a rack v6 leg
today, and the substantive question U17 did not ask is how NODES acquire v6:
MAAS static assignment (needs only the subnet, which now exists), DHCPv6 (needs a
dynamic range and dhcpd6, both absent by design), or SLAAC/RA (needs a router
advertising on the plane). Only the first is already satisfied, and they differ
materially. Presented rather than chosen; legs NOT applied.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

LIVE: apex populated -- bands + 156 VIP objects, both DCs, read-back clean
...
D-136 option (D) rules the apex be populated first and the renderer generated from
it. Applied both DCs: 12 ip-ranges + 78 ip-addresses each, read-back 12/12 and
78/78, errors=0, idempotent re-run plans zero. Apex ip-ranges 3 -> 27,
ip-addresses 4 -> 160 (IPv6 0 -> 78), 156 VIP objects.
Derived rather than hardcoded except the ruled app->octet map: planes from
lib-net, v6 prefixes read from the apex itself, VIP legs computed as the three
plane bases + octet (not VIP_PREFIX_*, which lib-net unsets for dc1 per R9), and
the v6 host part as the textual octet mirror with the provider leg in the
dedicated GUA VIP /64.
vault .61 and designate .62 included though ruled-but-not-built -- reserving a
planned address is what an IPAM apex is for, and it spares a second reconciliation
when the renderer emits them.
CORRECTION TO D-136: its prose says sandbox-fidelity-check.py "gates every apex
reconciliation this coupling depends on". Measured, that script compares two JSON
dumps and touches no NetBox -- it validates the SEEDER against the upstream draft,
not apex writes. Read-back is the real verification for a write. The extension is
still owed for the sandbox loop but was not a blocker here.
A live-vs-dump shape difference bit the first version: the live API returns
scope.name as the display name ("VR1 DC0") with scope.slug as the site key, while
the repo's dumps normalise name to the slug. Built against the dump, the tool
matched zero prefixes live and REFUSED rather than silently writing nothing -- the
refusal is what surfaced it. Now matches slug-then-name.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

dc1 FIP pool applied -- R4's ruled scope now complete for BOTH DCs
...
Operator: "Rule dc1 FIP pool 10.12.65.0-10.12.67.254". Validated before recording
-- inside 10.12.64.0/22, 767 addresses (identical in size to dc0's), no overlap
with the D-134 bands. Applied 1/1, read back.
Final state both DCs: check pass=24 fail=0 AND reserve planned=0 skipped=19
errors=0 -- converged and idempotent. ipranges 3 -> 29, both FIP pools reserved.
Machines 18 Ready + 2 Deployed unchanged throughout.
The ruling narrows dc1's deliberate unset list by exactly one pair; the VIP group
(R9: generated from the overlay) and the D-133-retired metal-internal stack stay
unset, now with harness guards so a future edit cannot widen it silently.
Two stale assertions RE-POINTED rather than deleted, per the standing rule against
going green by removing an assertion: dc-selector asserted dc1 unsets
FIP_POOL_START, and dc-plane-ipam T20 asserted dc1 refuses the pool. Both are
replaced with stronger checks -- the ruled value, the still-unset guards, and that
dc1 plans its OWN pool and never dc0's, which is what proves the tool reads a
per-DC value rather than a shared literal.
dc-selector 48 -> 51 checks. Gauntlet ALL GREEN (83).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

LIVE: D-134's bands now EXIST -- both DCs at pass=24 fail=0, from a 6/18 baseline
...
Operator-gated, dependency-ordered: dc1 v6 carve (6 subnets), dc0 reserve (13
ranges), dc1 reserve (12 ranges). Each pre-apply re-verified in the same session;
every write read back. Subnets 18 -> 30, ipranges 3 -> 28. Machines 18 Ready + 2
Deployed unchanged throughout -- no node, no tfstate, no running service touched.
This closes the "RULED IS NOT BUILT" finding for D-134: its band table had been
prose since 2026-07-23 while MAAS held zero reserved ranges.
FINDING -- MAAS already reserves the entire low IPv6 block. Every explicit v6 band
create failed with "Requested reserved range conflicts with an existing range".
Measured via `subnet reserved-ip-ranges`: MAAS auto-reserves ::1-::ffff:ffff on
every IPv6 subnet plus :: per RFC 4291 s2.6.1, with allocatable space starting at
<prefix>:0:1::. The ruled bands sit entirely inside it, so the write is impossible
AND unnecessary. R4's "v6 bands as a second pass" is not executable in MAAS and
need not be; the tool now verifies coverage instead of writing. Execution-level
correction -- R4's intent is satisfied. Same on ULA and GUA.
Harness T19 asserted a literal string the fix removed. Re-pointed at the surviving
invariant (textual 4-99, never hex 4-63) rather than deleted, per the standing rule
against going green by removing an assertion. 25/25, gauntlet ALL GREEN (83).
dc1's FIP pool still correctly REFUSED -- unset by design, so R4 cannot close for
dc1 until ruled.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

LIVE: dc0 v6 plane carve applied -- 6 MAAS subnets, each read back
...
Operator-gated ("Run carve-v6 vr1-dc0 --commit"). dc0 only; dc1 not authorised
and not touched.
Pre-apply re-verified in the same session per the G8 precedent -- plan unchanged
at 6/1/0 -- then applied: 6 applied, 1 skipped, 0 errors, every create READ BACK
on its intended vlan. MAAS subnets 18 -> 24, v6 1 -> 7.
Each v6 plane landed on the SAME vlan as its v4 twin, so the plane is genuinely
dual-stack on one L2 rather than a parallel fabric. The provider GUA VIP /64 was
deliberately not created -- it holds hacluster-managed API VIPs, not node
addresses, so MAAS never allocates from it.
Idempotency proven LIVE rather than only in fixture: --commit ran twice (the
second to read the script's true exit code rather than a pipeline's) and the
post-state carries zero duplicate CIDRs.
Nothing else moved: 18 Ready + 2 Deployed unchanged, no node touched, no tfstate
involved, dc1 still measures 6 v6 planes absent. The gate now reports dc0's six
v6 planes [ok]; its remaining reds are the 12 unreserved D-134 bands.
Changelog item 9 carries the revert, with the 2026-07-21 pod-delete cascade
lesson attached: read occupancy BEFORE deleting a subnet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

Step 3: dc-plane-ipam.sh check -- D-134's missing executable gate, with a measured baseline
...
R4's D-134 amendment ruled a site-keyed tool that "also gives D-134 an EXECUTABLE
gate instead of prose". This is that gate. Read-only half only; the mutating
actions are deliberately not in this commit (hard rule 3 -- the gate ships and
runs first, so the mutation is planned against a MEASURED baseline).
Expected state is derived, never hardcoded: v4 planes from lib-net's DC selector,
v6 planes from the NetBox apex record (D-136 option (D) applied -- the apex is the
source, so this does not create a second hand-maintained table), bands from the
D-134 2026-07-23 amendment.
LIVE BASELINE, both DCs, symmetric at 6 pass / 18 fail: all six v4 planes present;
ZERO of six v6 planes present; ZERO of twelve D-134 bands reserved. MAAS holds 17
v4 subnets and exactly one v6, which is Office1's -- a third-layer confirmation of
U17. Cloud-wide there are 3 ipranges, all dynamic, matching R4's measurement.
Proven able to BOTH fail and pass: every live run fails because everything it
asserts is absent, so T6 drives a fully-provisioned fixture to green. A gate only
ever observed failing is as untrustworthy as one only ever observed passing. It
REFUSES rather than reporting clean on an unreachable MAAS, an unreadable apex, or
a band of unrecognised type, and distinguishes an ABSENT maas binary from an
unreachable MAAS -- the misdiagnosis class this audit found three times.
The provider GUA VIP /64 is reported but NOT asserted as a MAAS subnet: it holds
hacluster-managed VIPs, not node addresses, so MAAS cannot hand one out. Whether it
should also be a MAAS subnet is left open rather than decided by a gate.
Harness 14/14. Gauntlet ALL GREEN (83) -- the R15(2) manifest gate correctly caught
the new harness as unpinned drift and was re-recorded deliberately, which is the
mechanism working end to end in its first real use.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

D-136 ADOPTED at option (D); IPv6 host-part convention RULED (both GA-R5)
...
Operator utterance, quoted verbatim in both entries: "Rule D-136 option D,
mirror the v4 octet for both legs but make a note to review at the end of the
project to review for adjustment."
GA-R5 NOTE, recorded rather than glossed: this single exchange carried TWO
rulings, and GA-R5 says one per exchange. Both recorded because each clause is a
specific unambiguous answer to a question already put -- not the template
"yes to all" the rule exists to reject. Flagged to the operator; either can be
re-put separately and re-taken.
D-136: option (D) was WRITTEN INTO the entry before being adopted, because the
directed shape matched none of the recorded (A)/(B)/(C) and adopting an option
the entry did not contain would leave a future reader unable to find what was
decided. (D) populates the apex first, then builds the renderer, then dry-runs it
without gating this deployment -- differing from (C) by closing the apex gap now
rather than hand-maintaining VIPs/bands indefinitely, and from (A) by keeping an
unproven tool off the deploy's critical path. Its validation advantage over both
is the reproduction baseline frozen earlier today. Now ADOPTED; ledger-scan
confirms it dropped off the open-decision list (4 -> 3) and the ledger's
machine-derived block is re-seeded.
IPv6 host part: mirrors the v4 octet on BOTH leg types, closing the mapping R4's
D-134 amendment left explicitly unruled -- the blocker on populating any v6
value, since the apex holds every v6 prefix and zero per-address v6 objects.
Accepted cost recorded as deliberate: the GUA leg's dedicated VIP /64 is used
from ::50 up, leaving ::1-::49 unused, in exchange for one rule per plane instead
of a per-leg special case in the renderer. Review owed at the close of THIS
deployment as forward item F4 -- whose trigger differs from F1-F3, so the section
intro now says each item carries its own trigger.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|
CURRENT-STATE: R15(2) is done -- correcting an entry that went stale within the hour
...
The R15 entry stated the manifest was "deliberately NOT done yet". It was
committed shortly after, making this document's own claim false. Corrected in
place rather than left, since CURRENT-STATE is the status authority and a stale
status entry created by the same session is the exact defect class the Stage-5
audit's close sweep found three of.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

VIP/band reconciliation measured; two ruled decisions found to have an unruled gap
...
Step 4 prep for the render pipeline. Read-only enumeration of the four
disagreeing VIP surfaces, with every consequential claim re-verified directly
before recording. Nothing adopted.
NEW GAP: R11 ruled three gate changes (band .60->.99, VIP_COUNT_EXPECT 11->13,
FAIL on hacluster-without-vip) but NOT the arity change R2's dual-stack forces.
provider-bundle-check.py:137 requires exactly 3 addresses; a dual-family vip is
6, so under R2 the checker fails every application. :149's octet extraction also
returns the whole address string on a v6 literal, and a v6 address tests False
against an IPv4Network without raising -- so a naive arity fix just relocates the
failure. Two RULED decisions whose combined end state the gate cannot express.
TRAP: EXPECT_PUBLIC_VIP must STAY 11 while VIP_COUNT_EXPECT goes to 13. Measured
against bundle.yaml -- neither vault nor designate carries a `public` binding, so
neither joins that count. Reading "11 -> 13" and bumping both breaks the gate.
The L3-9 overlay collision is worse than recorded and the dangerous merge order
is the GREEN one: vips-last exits 0 while dropping every IPv6 leg, and
prefer-ipv6: true survives as a separate key -- charms would bind :::port with no
v6 VIP for pacemaker. Measured through the checker's merge MIRROR, not juju
(absent here); flagged for --dry-run confirmation rather than asserted.
CORRECTED one of the agent's own conclusions: dc1 "inheriting dc0's band bounds"
is not a defect. The octet band is DC-invariant by design, so lib-net.sh:157
correctly unsets the prefixes and keeps the bounds. Acting on the uncorrected
framing would have invented per-DC bounds that do not exist.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

Render pipeline step 1: carve-outs pinned, reproduction fixtures frozen
...
CARVE-OUTS PINNED at operator request ("in a place they will not be lost as
they will be required items in the future"). New forward items in the workflow
doc: F2 the render-pipeline AUTOMATION half (CI runner / event delivery /
status-back), deferred because two GitBucket requirements are unverified --
reliable webhook emission with retry+replay, and a status-back API without
which the approve-on-rendered-diff gate cannot see a validator verdict -- plus
open Jenkins placement; and F3 the NetBox scope boundary for MACs and VLANs,
permanent rather than pending.
D-136's MAC scope-out was CORRECTED AT SOURCE rather than only contradicted
elsewhere. Its stated reason -- MACs "drift-free across substrate main.tf <->
lib-hosts.sh <-> bundle ovn-chassis <-> discovery" -- is falsified: bundle.yaml
:462 carries 52:54:01:d1:04:02 / :05:02, which lib-hosts:132 identifies as
vr1-dc1's scheme, in the file documented as vr1-dc0's source of truth. The
correct reason is availability (no dcim/devices or dcim/interfaces in the apex
at all), and the consequence changes: ovn-chassis becomes renderer OUTPUT and
must leave the base bundle.
STEP 1 -- tests/render-baseline/ freezes the two known-good artifacts a renderer
can be validated against, BEFORE the R2/R11 reconciliation destroys them. After
that lands there is nothing left to diff against and a renderer's first output
is its own first draft, which is D-136's own objection to option (A).
The harness asserts fixture INTEGRITY and deliberately not agreement with live
-- live is supposed to diverge, and asserting against it would turn the harness
red for the change it exists to support (the creds-matrix T24 trap).
Proven able to fail before being trusted: seeded a silent fixture edit (caught
by T2b) and an unpinned file in fixtures/ where sha256sum -c passed vacuously
and only the coverage assertion T3 caught it.
Harness 9/9; gauntlet ALL GREEN (82, was 81); repo-lint 0 fail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

Q1 RULED (GA-R5): NOC posture lives in the workflow doc as a named forward item
...
Operator utterance: "use the workflow doc as a named forward item". Executed in
this commit rather than left as a ruling-without-artifact -- the audit found two
RULED-BUT-NEVER-BUILT decisions and this one is a doc edit, so there is no reason
for it to become a third.
docs/dc-dc-deployment-workflow.md gains a "Forward items -- DEFERRED BY RULING to
the next deployment" section, opening with why forward items are NOT D-numbers
(GA-R3 rule 1(b) as amended by A1, quoted). F1 records the four-step Office1 ->
DC0-as-NOC -> remote-push-to-DC1 test verbatim, why it was deferred, what already
supports it so the next deployment does not start cold (D-100's management-only
Office1<->DC fiber; D-128 already executing Plane 2 on voffice1, which IS the
"technician at Office1" model -- what is missing is the DC0-first ORDER and a
gate proving the push came from there), and what would make it D-admissible.
No D-number assigned; next-free stays 138.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|
| 2026-07-27 |

Stage-5 deploy order RULED (GA-R5): no DC ordering this deployment; NOC posture deferred
...
Question as presented: does Stage 5 deploy vr1-dc0 first -- restoring the phase-4
runbook's own "DC1 first" sequence, whose DC1 IS vr1-dc0 (export DC=vr1-dc0) --
and make DC0 the NOC, or proceed dc1-first as the artifacts assume? Raised after
the operator described the test posture (Office1 as simulated regional office ->
dark fiber to DC0 -> DC0 fully bootstrapped becomes the NOC -> technician at
Office1 pushes a deployment to DC1) and observed those steps were not in the
roadmap.
Operator utterance recorded verbatim in CURRENT-STATE. Effect: no ruled DC
ordering for this deployment, both DCs deploy from voffice1 by the same
procedure, and the NOC-ordering posture moves to the NEXT deployment. The
dc1-first artifact state is therefore no longer a divergence.
Confirmed by measurement: 'NOC' appears nowhere in the live repo, nor does the
technician scenario; D-100 defines the fabric and calls the Office1<->DC fiber
management-only but never states the sequenced test.
The "only differences should be the NetBox assignments" target is nearly right
and its exceptions are the point. NetBox-derived and symmetric: the VIP overlay
is a pure prefix remap. Per-DC but NOT NetBox-derived: the MAAS DC tag, the
per-DC octavia-pki CA overlay, and ovn-chassis bridge-interface-mappings.
That last one is PROMOTED FROM LATENT TO LIVE by this ruling. bundle.yaml:462
carries 52:54:01:d1:04:02 / :05:02 -- measured against lib-hosts.sh, that is
vr1-dc1's pinned MAC scheme, in the file documented as vr1-dc0's source of
truth. dc0's MACs are a different, non-schematic set that cannot be derived from
a pattern. On a dc0 deploy no local MAC matches, ovn-chassis builds no br-ex
mapping, provider egress is dead on dc0 compute, and no gate fails. Harmless
while dc1-first; now certain to be reached. Logged, not fixed (hard rule 1).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

Phase 0 delivery cleanup: session changelog with reverts, and a mis-filed finding corrected
...
Three gaps in the Phase 0 delivery, plus one new measured finding.
1. SESSION CHANGELOG ADDED (GA-R2/D1) -- docs/changelog-20260727-stage5-phase0.md.
It was missing, and it is the only revert surface: no logged window was
opened for this session, so the changelog + capture are the entire
as-executed record of five live mutations (two branch deletions on origin,
two on voffice1, a 68-package install on the region host). Each item now
carries WHAT / WHY / HOW TO REVERT with the pre-state hashes recorded
(39e8988, 61c416e, 57836b1) and the apt purge scoped with a caution that
68 packages were pulled and some are shared.
2. CURRENT-STATE section 7 no longer carries two rows for one component. The
prior "ABSENT ON BOTH HOSTS" state is folded into the single pin row as
history, following the Juju row's in-row supersession precedent. A pin
table asserting both presence and absence of the same client is the same
stale-surface class the audit close-out sweep found the audit itself
creating.
3. A FINDING I MIS-FILED IS CORRECTED BY MEASUREMENT. P4's
"metal-admin gateway=none (want 10.12.8.1)" was dismissed as covered by
readiness item 3.7. It is not -- 3.7 is specifically the VID-103 /
br-internal assertions. Measured from the dc0 rack: 10.12.8.1 answers 0/2
pings with an INCOMPLETE ARP entry (never resolved -- nothing holds it),
against a control ping to the edge at 10.12.4.1 at 0% loss. So MAAS is
right to carry no gateway and lib-net.sh:37's expectation is the defect, a
VR0 inheritance; the D-134 carve ruled .1 gateways for the PROVIDER subnets
only. Logged as P0-5, not fixed (hard rule 1). The dc1 arm (lib-net.sh:149)
carries the same shape at 10.12.68.1.
Also queued for one GA-R5 exchange, no D-number assigned: which host is
authoritative for the gates. R10 (preflight on voffice1) and R15(3) (preflight
stops failing open) executed together make preflight-on-voffice1 permanently
unpassable given P0-2, and R15(2)'s manifest does not reach P0-1 -- so the R15
execution scope needs this answered first.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

Stage-5 Phase 0 EXECUTED: clone advanced, refs pruned, openstack client installed
...
Readiness-doc preconditions 0.1/0.2/0.3, operator-approved step by step
("Approve A, B, and C, and retire the audit branch"). NO STAGE OPENED.
0.1 voffice1 is at main (HEAD == origin/main == 6495cfb, asserted). The real
risk was that both DCs' inner tfstate -- the substrate's state-of-record --
lives inside that working tree; every state artifact was proven gitignored and
origin/main proven to track an identical file set at those paths BEFORE the
switch. Both tfstate sha256s are byte-identical after. The two dc1 overlays are
now present and bundle.yaml is the 9-node role-separated layout.
0.3 three stale remote-tracking refs pruned, two stale local branches deleted
(the record named one), containment proven first. The audit branch was retired
on origin BEFORE the fetch so the clone could not be handed a fresh stale ref.
0.2 python3-openstackclient 6.6.0-0ubuntu2 on voffice1, verified behaviourally.
The snap was refuted by measurement: no Caracal channel (newest stable zed,
2023), plus the home-only confinement already recorded at design-decisions:638.
Two NEW findings from the payoff runs, logged not fixed (hard rule 1):
- the gauntlet is HOST-DEPENDENT (2/81 on voffice1 vs ALL GREEN on vcloud, same
commit); both failures are host-portability defects, one of them an aggregate
FAIL over all-passing sub-checks
- preflight P5 is HOST-BLIND (34 findings vs 7), and between the two hosts there
is no single host on which preflight is currently correct
R10's ruled consequence is confirmed: P3 now verifies all 33 charm-channel pins
(previously zero) and P4 reports MAAS reachable.
Capture: docs/audit/stage5-phase0-20260727.txt
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|
Record the grounding-audit merge to main (607813b) in CURRENT-STATE
...
GA-R1/C1: the merge commit itself cannot carry a doc edit, so the merge is
recorded here in a follow-up commit -- the same shape Stage 4 used (merge
6f5701d recorded by 1023596).
States explicitly that no stage opened or closed, cites the post-merge
gauntlet (81) and repo-lint, and flags that both figures are read under R15:
the harness count is compared against the record by hand because the manifest
R15(2) rules is not built, and repo-lint emits no files-scanned figure, which
is R15(1)'s finding observed rather than assumed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

Session-close sweep: three stale surfaces the audit itself created, plus transcript-only facts
...
Operator direction before the bookend: "complete a session sweep for anything
missed that should be committed that would be lost on session end". Precedent:
queued-findings-20260726.txt and -20260727.txt. Capture:
docs/audit/queued-findings-20260727-stage5-audit.txt.
THE SWEEP FOUND THREE STALE SURFACES THIS AUDIT ITSELF CREATED, which is the
exact defect class it was convened to find:
1. docs/audit/stage5-readiness-20260727.md -- the "read this first" artifact --
still carried THIRTEEN NEEDS-RULING markers after every one of R1-R15 had been
ruled or withdrawn. At that moment it was the single most misleading surface in
the repo. Fixed with a supersession banner that names the three authorities in
order and states what in the document is STILL true and worth reading: the
ordered precondition sequence, the evidence column, the what-breaks column. The
row-level markers were deliberately NOT rewritten -- they are the record of what
was owed at the time, and rewriting them would destroy that record.
2. queued-rulings-20260727.md still opened with "Nothing here is adopted" after
fourteen adoptions. Fixed with a STATUS block naming design-decisions.md and
CURRENT-STATE.md as the ruling authorities rather than that file; the original
sentence is struck through, not deleted.
3. SIX sections of that file had BLANK OPERATOR UTTERANCE lines -- R8, R9, R10,
R12, R13, R15 -- for rulings that WERE properly recorded in design-decisions
and CURRENT-STATE. GA-R5 was satisfied; the question sheet was not, and a
future session reading only that file would have believed six questions were
still open. All six filled with the exact utterance and where the ruling lives.
R14-ORIGINAL's blank line is CORRECT and left alone -- it is superseded text
kept for the audit trail.
TRANSCRIPT-ONLY OPERATIONAL FACTS NOW CAPTURED: the repo-lint L5 heading trap (a
heading LEADING with a D-number counts as a second definition unless the line also
contains AMENDMENT or RESOLVED -- it bit this session twice and is on no
author-facing surface); the commit-gated-on-lint discipline adopted after a red
commit was pushed, which then caught the second occurrence before it landed; the
read-only live-apex poll procedure that keeps the token in env and prints nothing;
that the office1 VMs answer from vcloud but NOT via voffice1, so "unreachable" from
the wrong host must never be recorded as "absent"; and that creds-matrix.tsv is
SPACE-aligned despite the .tsv extension, so awk -F'\t' returns zero rows and looks
like a real result.
META-FINDING RECORDED FOR THE NEXT COMMITTEE: the lenses' OBSERVATIONS were
reliable, their CONCLUSIONS repeatedly were not. Three of this audit's own premises
needed correcting by measurement -- R2a (literals already assigned under D-111), R9
(one failure mode not two, two scripts not twenty), R14 (the register already
attributed and explained the findings, and already warned against the suppression
the question contemplated) -- and R8's entire evidence base collapsed on reading
the cited bugs. A lens finding is an OBSERVATION, not a conclusion; measure before
putting it to the operator as a question.
VERIFIED DURING THE SWEEP, after an API disconnect mid-turn: nothing was lost or
corrupted. The earlier lens-5 API casualty was fully recovered -- its 18 findings
and section header are in the committed record, and all seven lenses are present.
The disconnect this turn fell cleanly between edits and a commit; the two modified
files parsed intact and lint was red only for the L10 this commit satisfies.
NOT DONE AND NOT OWED YET: the skill sweep. This session closed no STAGE, so the
stage-close fold-in and snapshot regeneration are not due. B1/C1/C2 in the capture
are the candidates when Stage 5 closes.
Revert: git revert this commit; the capture is new and the two repairs are
additive (banner, status block, filled utterance lines).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

R15 RULED: all three gate floors, with a harness manifest rather than a count (OPS)
...
GA-R5: question and exact utterance quoted, dated, pushed. Operator utterance:
"All three, with a harness MANIFEST rather than a count (Recommended)". OPS under
GA-R3 -- three script fixes, no D-number.
IT OPERATIONALISES GA-R6. That ruling lets a stage close only on a NAMED
EXECUTABLE CHECK. These three checks were not capable of failing, so the closes
they certified were weaker than they read.
1. repo-lint gains a valid-root check and a files-scanned floor.
repo_lint.py:132-135 strips only the two KNOWN flags, so any other --flag
becomes argv[0] i.e. the ROOT, with no is_dir() check and no floor. A
one-character typo of either the flag or the path resolves to a nonexistent
directory, rglob yields nothing, and it reports PASS (0 fail, 0 warn) over
ZERO files -- reproduced twice this session. This is the gate whose "0-fail"
every GA-R6 stage close in this project's history cites, and it cannot
distinguish a clean repo from an unexamined one.
2. The gauntlet pins a checked-in MANIFEST of harness NAMES, drift-checked, not
a count. run-tests-all.sh:35 already has a zero-floor; what is absent is any
pin on WHICH harnesses ran, and 81 exists only as prose. The manifest choice
is the substance of this ruling: a bare count does NOT catch a rename, since
adding one harness while removing another holds the count. It mirrors two
proven in-repo patterns -- clientdocs/sweep-receipt.txt hash-pinning and the
D-137 creds-manifests derive-plus-drift gate.
3. preflight stops failing open. note() tests only rc -eq 1 and rc -eq 2, so
127/126/130 and any rc>=3 leave "PREFLIGHT: PASS -- clear to add-model /
deploy" -- measured with a sub-gate exiting 127. And rc=2, which is precisely
how these checkers signal "I could not evaluate anything", is remapped to WARN
in P1/P2/P3. The project already fixed exactly this for P5 (harness T9), so
this extends a proven pattern rather than inventing one. Preflight authorises
the deploy, which is why it was included rather than deferred.
ORDERING TRAP RECORDED FOR WHOEVER EXECUTES THIS: these changes alter what
"green" MEANS, so the gauntlet and lint runs that certify them must be read with
that in mind -- and a harness manifest introduced mid-session must be seeded from
a tree that is itself verified, not from whatever happens to be on disk. Seeding
it from an unverified tree would pin the very drift it exists to detect.
ALL QUEUED RULINGS ARE NOW CLOSED: R1-R13 and R15 ruled, R2a and R14 withdrawn as
raised in error, G18 opened deferred-and-gated.
Revert: git revert this commit; the CURRENT-STATE entry is additive.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

R13 RULED: register-first credential fixes, no new tool (D-137 sub-ruling 6)
...
GA-R5: question and exact utterance quoted, dated, pushed before dependent work.
Operator utterance: "Register-first, no new tool: fix the staging AND flip the 7
keypairs (Recommended)".
Measuring showed R13 was THREE problems, not the two the queued framing carried.
The third is the sharpest and had not been surfaced as a decision at all:
THE REGISTER MIS-STAGES WHAT STAGE 5 ACTUALLY MINTS. The vault-init, Octavia-PKI
and admin-openrc rows are cardinality=singleton with mint-stage in
vr0-phase01/02/03, and creds-manifests/stages-reached marks all three pending --
so flipping stage5 to reached never makes them expected. The measured consequence
is a FALSE GREEN over the largest minting event of the deployment: P5 reports
"[ok] E1 18 expected artifact(s) deferred as not-yet-minted". R7 made this MORE
urgent rather than less: per-DC independent Octavia PKI means two CA mints where
the register expects none.
The retroactive half needs no new tooling, which is what made register-first the
efficient answer: S4 ALREADY resolves runbook:<path>:<line> references, so
recording each mint as a numbered runbook step and flipping mint-ref from
operator-terminal converges the debt using machinery that exists and is tested.
30 rows / 16 ids carry that ref today and grep -rnI "ssh-keygen" returns ZERO
hits repo-wide.
Priority within that half is the SEVEN unrecoverable-in-place keypairs, where
irreproducibility is an operational risk rather than hygiene: both edge keys
(SEC-007/-015 make edge SSH the ONLY management path to the DC edges), both svc
keys, both power keys, and office1-svc-key. A jumphost rebuild today locks the
operator out of both DC edges.
creds-mint.sh STAYS QUEUED for its own ruling and is deliberately not built here.
It is orthogonal -- it would prevent the NEXT unregistered mint but makes no
existing key reproducible and fixes no staging. Bundling it would have made the
urgent no-tool work wait on unscoped tooling work.
Carried caveat for whoever executes this: the T24 finding-class baseline covers
TIER 1 ONLY, so a tier-2/3 regression introduced by these matrix edits would not
turn the gauntlet red on its own.
PROCESS NOTE, owned: the first attempt at this commit tripped repo-lint L5 by
titling the sub-ruling "### D-137 SUB-RULING 6" -- a heading LEADING with a
D-number counts as a second definition unless it also contains AMENDMENT or
RESOLVED. That is the same rule I hit on R5 and then documented. The lint gating
caught it before the commit landed this time, so nothing was pushed red; the
heading is now "### SUB-RULING 6 (D-137)", matching the convention.
Revert: git revert this commit; the sub-ruling is additive.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

R12 RULED and EXECUTED: G17 reshaped -- time check folded in, artifact check made falsifiable
...
GA-R5: question and exact utterance quoted, dated, pushed. Operator utterance:
"Fold time verification into G17 and fix the check to assert content
(Recommended)".
Unlike the other rulings this one is EXECUTED in the same commit, because what it
rules on IS a gate row, and gate rows live in CURRENT-STATE section 6 by GA-R6.
Recording the ruling without reshaping the row would have left the defect in place.
TWO DEFECTS IN ONE ROW, both now fixed:
1. THE CHECK COULD NOT FAIL. The prior text specified `curl -sI http://10.12.8.4/`.
Measured: curl -sI exits 0 on 404/403/500 -- a planted 404 printed "404 File not
found" with curl exit 0, while curl -fsI exited 22 -- and the dc0 URL is an nginx
autoindex root created EMPTY by dc-mirror.sh:330 before any sync, so / answers
200 whether or not last-sync.status says OK. It reintroduced the
existence-not-content class that dc-mirror.sh check was fixed for on the SAME
DAY. The dc1 half named no command at all, although the real one already existed
at dc-cache-proxy.sh:210-217. Now: a real package-path fetch with an exit-code
predicate for dc0, the existing named check for dc1, and an explicit refusal on
unrecognised or unreachable results.
2. THE SCOPE WAS WRONG. docs/dc-dc-deployment-workflow.md:206 and
runbooks/dc-dc-phase4-juju-bundle-per-dc.md:46 BOTH assign the node time source
to G17, while chronyc appeared ZERO times in CURRENT-STATE -- so the check two
surfaces required had no home in the gate meant to carry it. Now folded in.
Worth stating precisely because it was nearly misread: DOCFIX-204 struck "NTP
from the DC's own OPNsense edge" because D-129(iv) gave the edge no NTP role. It
did NOT strike time verification. Option (c) -- declaring it struck and deleting
the two conflicting surfaces -- would have discarded a check a recorded decision
deliberately kept.
Fixed in ONE edit rather than two because both defects live in the same row and
share the same ONE-TIME observation window at first boot; splitting them risked one
landing without the other, and the window does not come back.
chronyc mentions in CURRENT-STATE: 0 -> 3.
Revert: git revert this commit to restore the prior G17 [V] text.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

R14 WITHDRAWN: the register already does everything the question assumed it could not
...
Withdrawn without a ruling, following the R2a precedent. R14 asked whether the
credential matrix needs a ruled-exception field, on the premise that three S5
power-key asymmetries were "ruled correct by SEC-016" and so reported a permanent
red a reader learns to ignore.
All three parts of that premise fail on measurement:
1. They are NOT ruled correct. SEC-016 ruled per-DC ISOLATION -- dc1 gets its own
dedicated power key rather than reusing dc0's -- which is satisfied. It never
blessed the filename, host and custody divergence. That is SEC-021(b), an OPEN
ledger defect whose own disposition reads "needs a naming/custody reconciliation
to the dc1 shape", and whose stated complaint was "Per-DC rows that should be
symmetric are not, and nothing compares them". S5 is the thing that now compares
them, so the finding is the register working as designed.
2. The register already ATTRIBUTES them: those rows carry sec-ref=SEC-021 and
notes-ref=n-dc0-power-key-divergence.
3. The register already EXPLAINS them: creds-matrix-notes.md carries the
n-dc0-power-key-divergence note describing the name and host-role divergence and
why the expected jumphost rows fail EXPECTED-BUT-ABSENT.
And the notes file already warns against the exact move this question contemplated.
The line immediately preceding that note reads: "failure, not a matrix error: do
not delete the row to make the checker green."
So no schema change is needed and none should be made. Adding a suppression
mechanism would have HIDDEN an open security-ledger item -- the opposite of what
the register exists for. The red clears when SEC-021(b) is remediated, which is the
intended behaviour.
This is the third premise this audit has had to correct in its own framing, after
R2a (literals already assigned under D-111) and R9 (one failure mode, not two).
Worth noting the pattern: the audit's lens findings were sound as observations and
repeatedly wrong as conclusions, and measurement caught it every time.
Residual recorded so it is not mistaken for an oversight: whether a ruled-exception
field is EVER needed is now hypothetical. If one arises, notes-ref and sec-ref are
the place to start, not a new column.
Revert: git revert this commit; the R14 entry is rewritten in place with the
original retained beneath as R14-ORIGINAL for the audit trail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

R10 RULED: fix the stale clone and make all working directories current (OPS)
...
GA-R5: operator direction recorded verbatim -- "R10, we need to fix the stale
commit issue and make sure that all working directories are current." OPS under
GA-R3: an environment fix, no D-number assigned.
This resolves R10 by REMOVING the reds rather than recording an exception basis
for proceeding against them. The measurement showed that is the stronger answer:
the red set is substantially an artifact of running the gate on the wrong host,
not a set of conditions Stage 5 has to accept.
SCOPE ENUMERATED BY MEASUREMENT rather than assumed, because "all working
directories" could have been broader than the one clone already known:
- voffice1:~/openstack-caracal-dc-dc -- 105 behind origin/main on
dc-dc-g12-dc1-substrate, a branch DELETED upstream. THE ONLY STALE CLONE.
- vcloud:~/openstack-caracal-dc-dc -- current, on the audit branch, in sync.
- dc0 rack, dc1 rack -- NO CLONE. By design: the dc-* scripts are piped in over
ssh with 'sudo bash -s', never cloned to the rack.
- office1-netbox, office1-tailscale -- NO CLONE. Measured FROM VCLOUD after a
probe via voffice1 returned "unreachable"; unreachable is not absent, so it
was re-run from a host that can actually see them rather than recorded as a
negative finding.
- ~/ops-toolkit on vcloud is a DIFFERENT repository
(git.baldurkeep.com/git/ops/ops-toolkit.git, single commit) and is out of scope.
RECOVERY SHAPE MATTERS AND IS NOT THE OBVIOUS ONE: a plain `git pull` on voffice1
does NOT work, because its tracked branch no longer exists upstream. It needs
`git fetch origin && git switch main` with a HEAD-equals-origin/main assertion
afterward (finding L5-4). The untracked
opentofu/vr1-dc1-substrate/.terraform.lock.hcl there is not tracked on main, so
the checkout will not conflict.
CONSEQUENCES ONCE CURRENT: preflight becomes runnable on voffice1, which clears
P3's 33 warns and P4's "MAAS unreachable" -- both missing-binary artifacts, since
maas and juju are PRESENT on voffice1 and ABSENT on vcloud. The octavia-pki
absence and the 7 credential findings are host-independent and remain.
THEN OWED: repo-lint and the gauntlet ON voffice1. These were deliberately
deferred throughout this audit precisely because running them against a
105-commit-stale tree would have produced a number that meant nothing. The
deferral now discharges.
ALL STAGE-5 BLOCKING RULINGS (R1-R11) ARE NOW CLOSED. Remaining: R12-R15
standing, plus G18 deferred-and-gated.
Revert: git revert this commit; the CURRENT-STATE entry is additive.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

R9 RULED: derive lib-net's dc1 arm from the overlay with a drift check (D-119 amendment)
...
GA-R5: question and exact utterance quoted, dated, pushed before dependent work.
Operator utterance: "Derive lib-net's dc1 arm from the overlay, with a drift check
(Recommended)".
Recorded as a D-119 AMENDMENT -- D-119 owns the region-qualified per-DC arms in
lib-net.sh, so the question of how the dc1 arm is populated belongs there.
RULED SHAPE: lib-net.sh's vr1-dc1 arm is GENERATED from overlays/vr1-dc1-vips.yaml
with a render-drift check that fails the gauntlet on divergence. Consumers keep
sourcing lib-net unchanged -- no script learns to parse YAML -- and exactly one
authored copy of the values exists.
WHY THIS IS NOT NEW MACHINERY: D-137 sub-ruling 2 already ruled and built the same
shape for credentials, with creds-manifests DERIVED from creds-matrix.tsv and the
gauntlet failing on rendered-vs-checked-in drift. This applies a proven in-repo
pattern to network literals rather than inventing one.
IT DOES NOT PRE-EMPT D-136, and that was the deciding property. D-136 is PROPOSED
and unruled. This is the smaller compatible step: the overlay becomes the single
authored source for script-facing values. If D-136 is later adopted the apex
becomes the source and BOTH the overlay and this derived arm become generated --
a sub-case, not a competitor.
MEASURED BASIS, which inverts the intuitive reading: lib-net.sh:76-79 states the
backward-compatibility design in terms -- sourcing without the selector keeps
VR0/DC0 values "completely unchanged". So the unset block fires ONLY for
selector-callers. The 8 correctly-updated scripts break LOUDLY under set -u; the
20 never updated fail SILENTLY on dc0's literals. The silent half is the
dangerous half.
GUARD CARRIED FROM R11, and it matters for the generator's implementation: the
dc1 arm unsets NINE variables for TWO different reasons. METAL_INTERNAL_VID and
METAL_INTERNAL_IFACE are CORRECTLY unset because D-133 abolished the VLAN-103 /
br-internal stack for VR1 -- those facts genuinely do not exist. Only the
VIP/FIP/keystone group is superseded and derivable. A generator that populated
all nine would silently reintroduce a stack D-133 retired.
COUPLED: R2 makes the derivation dual-family, not v4-only. R11 moves
VIP_COUNT_EXPECT 11 -> 13 and widens the octet band to .99, and the two band
constants (OCTET_LO/HI in provider-bundle-check.py, VIP_OCTET_MAX in lib-net.sh)
are separately named in two files.
STILL OWED AND EXPLICITLY NOT COVERED: the consumer sweep. This ruling decides
where values live; it does not make the 20 non-selector scripts call the
selector. Stage-5 exposure is two of them (phase-03-core-verify.sh,
deploy-watch.sh); carve-host-interfaces.sh:48-67 (DOCFIX-166) is the in-repo
precedent for wiring one correctly.
Execution is a separate gated step under standard delivery discipline.
Revert: git revert this commit; the amendment is additive.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

Final pre-ruling measurements R9/R10/R12-R15: two premises corrected
...
Operator direction: gather everything remaining so the last decisions can be
worked through without stopping to measure. Capture:
docs/audit/r9-r15-final-measurements-20260727.txt.
TWO OF THESE CHANGED MATERIALLY, and both are corrections to the audit's OWN
earlier framing rather than new findings.
R9 -- there is only ONE failure mode, not two, and the Stage-5 blast radius is
TWO scripts rather than twenty. lib-net.sh:76-79 states the design explicitly:
sourcing without the selector "continues to populate PLANE_CIDRS ... exactly as
above (VR0/DC0's real, measured values) -- every existing script that sources
lib-net.sh keeps working completely unchanged". So the unset block fires ONLY
for selector-CALLERS. The 20 non-selector consumers fail SILENTLY with dc0
literals; the 8 correctly-updated ones are the ones that break loudly under
set -u. That inverts the intuitive reading -- the scripts nobody fixed are the
dangerous ones. And grepping the two runbooks Stage 5 actually uses, only
phase-03-core-verify.sh and deploy-watch.sh are invoked there; the rest bite at
later stages.
R14 -- THE PREMISE IS WRONG and the question largely dissolves. I framed it as
"the matrix cannot express a RULED exception, so three S5 asymmetries ruled
correct by SEC-016 report as a permanent red". Measured: they are NOT ruled
correct. SEC-016 ruled per-DC ISOLATION -- dc1 gets its own dedicated power key
rather than reusing dc0's -- which is satisfied. It never blessed the filename,
host and custody divergence. THAT is SEC-021(b), an OPEN defect whose own
recorded disposition reads "needs a naming/custody reconciliation to the dc1
shape", and whose stated complaint was "Per-DC rows that should be symmetric are
not, and nothing compares them". S5 is the thing that now compares them. The
register is RIGHT and the finding is REAL. Suppressing it would have hidden an
open security-ledger item.
R15 refinement: the gauntlet ALREADY has a zero-floor -- run-tests-all.sh:35
exits 2 when RAN is 0. What is missing is a MINIMUM-count floor, since 81 is
pinned nowhere executable. repo_lint.py:132-135 has NEITHER a valid-root check
nor a files-scanned floor. The two gates need different fixes, which the earlier
framing conflated.
R12 and R10 confirmed as previously stated, with exact line citations.
Revert: git revert this commit; the capture is new and CURRENT-STATE additive.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

G18 OPENED: IPAM apex completeness for the Octavia lb-mgmt plane (blocking, deferred)
...
Operator direction, verbatim: "leave this as an open decision that will need a
ruling once we have the cloud live and we have a better read on the network and
how everything is functioning with the addition of the new IPv6 configurations.
Make this a gated decision so we cannot close the project (or whatever phase you
think it best ruled in) without a ruling on this item."
WHAT IT IS. R8 ruled that Octavia creates and owns its own IPv6 lb-mgmt network.
The measured consequence is that the octavia charm exposes NO CIDR, address-family
or router configuration option -- create-mgmt-network (default True) is the only
related option -- so the prefix is CHARM-GENERATED and cannot come from the D-111
carve. NetBox is therefore knowingly incomplete for exactly one plane. That is the
authority-inversion concern the audit's lens 7 raised: the apex being back-filled
to match a deploy rather than driving it.
WHY DEFERRED RATHER THAN RULED NOW. It is not answerable from artifacts -- it
needs the cloud live and an observed read on IPv6 behaviour. It is also adjacent
to the UNRULED D-136 NetBox-coupled render pipeline, which covers the same
apex-authority ground, so ruling it early would pre-empt that decision.
PLACEMENT. Recorded as gate G18 in CURRENT-STATE section 6, the gate authority.
[R] ruling-type: closes ONLY on a GA-R5 recorded ruling with the exact utterance,
dated, committed and pushed. ANSWERABLE from Stage 5 onward, since the prefix
exists once Octavia deploys. BLOCKING at the FINAL stage close / project close --
the deployment may not be declared complete while it is open. That placement gives
the widest window in which live evidence can accumulate while still guaranteeing
the question cannot be lost.
Options are pre-recorded in the gate row so a future session does not have to
re-derive them: (a) back-fill the charm-created prefix into NetBox post-deploy as
a documented record; (b) record the plane as charm-owned and explicitly out of
apex scope; (c) fold it into D-136's render-pipeline ruling if that is taken first.
ALSO CROSS-RECORDED, because it is exactly the kind of thing that gets "fixed" by
mistake: the absence of an lb-mgmt :x80 prefix in the VR1 ULA carve is CORRECT
under R8, not a gap. Noted in the D-101 R8 ruling note and in the G18 row.
Mirrored into docs/audit/queued-rulings-20260727.md as R16 (gated, deferred) with
CURRENT-STATE named as the authority, so the queue and the gate table agree.
Revert: git revert this commit; the gate row and cross-references are additive.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|