| 2026-07-31 |

permissions: promote 36 read-only rules to committed policy; SEC-030 records the bypass
...
Operator-ruled 'Promote a curated safe subset' and 'Leave them as they are'.
settings.local.json is gitignored, so its 286 rules survive on this host but not
a rebuild (F1). 36 safe, recurring rules promoted to the committed settings.json
(allow 56 -> 92); ~210 one-off literals left behind as noise.
Four wildcards NARROWED before promotion -- ip route / ip neigh / bridge fdb /
tc qdisc all permit add/del on the host running every DC node; only show/get
promoted. The whole-of-HOME Read() wildcard was deliberately EXCLUDED: it
reaches the per-site creds folders, vault-init, as-executed and tenant dirs that
CLAUDE.md forbids reading into context.
SEC-030: an ssh <host> '<cmd>' matches on the outer ssh command, so the committed
ask-gating on maas admin mutations is bypassed in practice, and python3 * permits
arbitrary execution. Shown verbatim and ruled to keep; recorded as accepted and
known, and NOT promoted to team policy so it does not reach Roosevelt.
Guard misfires #8 and #9: the hook blocked the commands writing the SEC row and
this changelog item, because both are prose ABOUT a hazard rather than the
hazard. Rewritten and staged through files, not worked around.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|
skill: /savegame -- the end-of-session bookend + transcript sweep
...
Four operator rulings: prepare-then-stop (never commits), archive at stage
close, sweep by model-enumerate-then-grep, repo-specific. Named savegame at
operator request.
The sweep is mechanical: every candidate is grepped against the repo, so
FIRST SURFACE items are proven rather than asserted. Five structurally
invisible classes always swept, including gitignored permission rules and
measurements that contradict a standing doc.
Two live conditions found while writing it: the ledger is at ~295 against the
300-line cap so the next close breaches it, and GA-R4 rule 2's literal text
(body moves at session close) diverges from ruled practice (stage close) --
DOCFIX owed, logged not taken.
repo-lint L9 caught the first draft hardcoding the repo name (D-110); fixed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

Bundle deploy attempt 1 FAILED: prefer-ipv6 unsupported on 6 of 13 VIP charms
...
ERROR cannot deploy application 'barbican': unknown option 'prefer-ipv6'.
Model measured EMPTY afterwards -- juju validated and aborted atomically, so
nothing is half-applied and Steps 1-3.5 remain good.
The dry-run passed and the deploy did not: --dry-run resolves charms and plans
relations but does NOT validate config option names. A green dry-run is not a
gate on option validity.
Measured against charmhub's own config-yaml per pinned channel: 7 of 13 VIP
charms declare prefer-ipv6, 6 do not. barbican never had it at any channel, so
it was not an obsoleted option -- R2's uniform application was never valid.
Not fixed: invariant 9 couples prefer-ipv6 to the v6 VIP legs, so this needs a
GA-R5 ruling, not a mid-deploy edit.
Owned: my first charmhub query used a wrong field and returned a uniform
'no config' across 13 charms -- implausible, which is what caught it.
Capture: docs/audit/stage5-dc0-deploy-attempt1-20260731.txt
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|
Step 3.5 complete: model vr1-dc0, spaces gate PASS, apt-mirror set
...
add-model guarded; spaces gate 0 fatal, all six ruled spaces present;
apt-mirror key VERIFIED against the live client (not guessed) and set to
http://10.12.8.4/ubuntu, asserted on content via dists/jammy/Release.
The mirror gate was RED on content -- one package, a transient upstream 500 on
linux-tools-intel-iotg. debmirror's all-or-nothing exit failed the whole run.
Re-sync took 15s and the gate is green. This is the assert-on-content fix
working as designed.
Owned: my first wait-loop polled systemctl immediately after an async start and
reported the sync done while the status file said RUNNING. Re-polled on content.
Recorded: D-138 moved the client to the rack but the rack has no repo clone --
same class as queued F1.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|
FINDING: preflight.sh is region-blind, 19 false negatives for dc0
...
DC=vr1-dc0 preflight reports every machine 'not enrolled in MAAS' while
resolving profile=admin -- the OFFICE1 region, which no longer owns them.
Re-run with MAAS_PROFILE=vr1-dc0-region: zero node failures, RED on P5 only.
The message reads as a deployment-state problem when the true statement is
'wrong region'. Fourth consumer of the MAAS_PROFILE=admin default.
Logged not fixed: a blanket refusal of 'admin' for vr1-* would be WRONG because
dc1's nodes still live in Office1. Needs per-DC resolution plus a region-identity
assert, with its own harness.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|
JUJU CONTROLLER BOOTSTRAPPED: vr1-dc0-controller live, active/idle
...
Fourth attempt, first success. Each earlier failure was a real defect one layer
deeper and all three are now closed: D-138 (client into the DC), the controller
under-carve fix (default route), and D-132 q1 (per-DC MAAS region).
controller/0 active/idle on juju-controller rev 311; machine 0 started
ubuntu@22.04 as subtle-grouse; MAAS reads arfr7p Deployed ubuntu/jammy.
Transcript evidence: agent binaries resolved on attempt 1 (339-retry class stays
closed); targeting took the tagged VM not a role node; juju offered the v6 leg
restored earlier this session before connecting on metal-admin.
Capture: docs/audit/stage5-bootstrap-dc0-20260730.txt
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

Juju re-pointed at the dc0 region: credential minted, proven, registered (SEC-028)
...
Operator-ruled: 'Mint juju-vr1-dc0 on the new region (Recommended)' and
'Add it to allow'. juju-vr1-dc0 minted in the dc0 region as a superuser,
mirroring the measured Office1 shape. Never printed; moved host-to-host with
all three sha256 digests compared; staging shredded.
Proven to AUTHENTICATE before bootstrap -- login + users/machines/rack reads
confirming superuser scope and DC-local region identity. Cloud re-pointed to
10.12.8.6:5240 and read back; the stale Office1-keyed credential removed first.
DOCFIX-206: Step 2.0's credential gate was not region-scoped, and I hit it --
all three checks passed while the user lived in Office1, so the SKIP branch
would have led to an auth failure that reads as a network fault.
Two snap-confinement traps recorded: private /tmp, and the home interface
refusing dot-directories. Neither error names confinement.
Registered BEFORE bootstrap: SEC-028, 8 matrix rows, dc0 manifest, and the
first 'rack' rows in vm-secret-locations. creds-matrix 65/65; repo-lint 0 fail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|
v6 carve complete 54/54; controller v6 restored; Juju re-point blocked on a credential mint
...
v6: applied=54 errors=0, read back 54/54. Ran after the v4 carve because the
provider v6 static lands on br-ex. The tool walks only the nine tagged role
nodes, so the controller's metal-admin v6 was applied separately -- restoring
ruled state the re-enrolment dropped. Its provider GUA stays deliberately absent.
Juju survey on the rack: cloud still points at the OFFICE1 region, which is what
killed bootstrap 3. juju unregister is NOT needed -- no controllers registered.
Blocker: the new region has no juju-vr1-dc0 MAAS user. Re-establishing it mints
a new API key, so it is operator-gated. Nothing was minted; the refusal was a
permission rule failing to match, not a classifier fault.
Gauntlet ALL GREEN (93); repo-lint 0 fail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|
dc0 v4 node carve COMPLETE: 10/10 machines, gate 134/0, diff vs pre-migration EMPTY
...
Canary-first then node by node, never batched. 11 + 13x8 + 4 mutations, every one
read back. Nine role nodes on six planes with OVS br-ex; the juju controller on
two planes raw with no br-ex and enp3-6 left on auto VLANs.
Verified by diffing against the Office1 pre-migration capture: every node's six
v4 legs match address for address. Compared by LEG SET, since system_id and
hostname are both re-minted at re-enlistment.
Instrument note: the first extraction read the wrong capture field and returned
'-' for every old address, which would have read as 'the old region had no
statics'. Caught by the implausible shape, not by the parse.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

dc-node-carve: convert the commissioning auto link, found by a live dry-run
...
The first live plan refused enp1s0 on every machine: it holds an `auto` link on
metal-admin out of enlistment, and the "wrong address on the right subnet is a
FAILURE" rule was too broad. A commissioning link is the expected starting
state, not a conflict -- so the tool could not carve the metal-admin leg at all.
Narrowed BY MODE: auto/dhcp/link_up gets unlinked and re-created as STATIC
(MAAS will not convert a link's mode in place); a `static` link with the wrong
address is still refused, because something deliberate put it there.
The `raw` fixture modelled the auto link correctly -- what was missing was any
assertion that `apply` over the raw state EXITS 0. T21-T24 only grepped for
planned strings, so a refusal on the very first NIC read as green. T26/T27 now
cover both directions and both are mutation-proven.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|
| 2026-07-30 |

dc-node-carve.sh: the v4 node interface carve, mutation-proven 40/40
...
The last no-tool gap between the dc0 region migration and the Juju deploy:
60 NIC re-homes, 9 OVS br-ex bridges, 54 statics. The Stage-4 equivalent was
done ad-hoc and logged outside the repo, so there was nothing to re-run when
D-132 q1 forced a re-enrolment.
Proved the four-call MAAS cycle on ONE live interface (canary 6q4syf enp3s0)
before writing the tool -- nothing had exercised `interface update vlan=` on a
link_up-only interface on an auto fabric, which is the state all 60 NICs are in.
Measured in passing: link-subnet REPLACES a link_up link, so only the br-ex
member needs an explicit unlink.
No default profile; maas-profile-assert.sh before any read or write; every id
resolved live by stable key (boot MAC / CIDR / interface name); every mutation
read back and compared. Role asymmetry derived from the host token, and the
controller's deliberately-uncarved NICs are asserted ABSENT.
Mutation pass found the br-ex static compare could not fail -- Pattern B was
being caught by the type/parent assertions instead. Added the brexip fixture
and re-proved the kill. Also found that neutering an assertion's MESSAGE only
proves it exists, not that its predicate works.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|