| 2026-07-31 |

Option D half 1: explicit base on all 56 apps, gated by new invariant 12
...
PROVEN AGAINST THE REAL MODEL, NOT INFERRED. The earlier one-app test only
showed the error MOVING to the next app. The fully-based bundle was staged as a
throwaway on the rack and --dry-run against the LIVE 23-application model --
the same command that errored on barbican 265-vs-261 -- exits 0.
Subordinates are in scope, measured: mysql-router is 1154 at 22.04 and 1178 at
24.04. hacluster sharing rev 166 across both bases is not a property to depend
on.
TWO SILENT UNDER-MATCHES, both caught by cross-checking the edit against the
parsed file rather than by review:
(1) a first pass keyed on the block form inserted 44 of 56 -- the twelve
-hacluster apps are single-line FLOW MAPPINGS. The inserter asserts its
app list equals what yaml.safe_load sees, and refuses otherwise.
(2) overlays/dc-ha-scaleup.yaml DEFINES vault-hacluster, an application that
exists nowhere in bundle.yaml, and it had no base. That overlay is a LATER
deploy step, so this would have re-opened the trap AFTER the deploy
succeeded. Found by the new gate on its FIRST run, in a file the fix was
not looking at -- the same later-step-overlay class as the ceph-mon miss
earlier today.
INVARIANT 12 (NEW): every application carries an explicit base equal to the
bundle's OWN default-base -- keyed on that value, not a literal. A bundle with
no default-base REFUSES rather than passing vacuously. Without this, deleting
the 56 lines is a byte-identical PASS and the trap returns at the next partial
failure, at any DC.
Five cases, each mutation-proven individually (harness 50 -> 55/55): neutering
nobase kills T49/T50/T52, wrongbase kills T51, the refuse branch kills T53.
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
|

RULED 2026-07-31 (GA-R5): option D -- fix the bundle AND clear the model
...
Operator utterance, verbatim: "I want to do option D: Fix the bundle by
defining the base 22.04 on each app and then clearing the modeling and
deploying from a clear model. It is very cheap to clear and redeploy."
I offered three options; the operator composed a fourth that is strictly
stronger than any of them. The durable fix lands (explicit per-app base, so
the bundle survives a partial failure), AND the deploy runs the path that is
MEASURED to work -- a clean deploy onto an empty model, exactly what run 2a
did before it hit the missing resource file -- rather than juju's incremental
re-run path, which is the one carrying the defect.
OPS under GA-R3, no D-number: an explicit base per application states what
bundle.yaml:85's default-base already means. No ruled surface changes.
The invariant it establishes is skill material at stage close: a bundle whose
applications rely solely on default-base is deployable exactly ONCE, and a
production bundle that cannot survive a partial failure is a trap.
Committed and pushed BEFORE the dependent work.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

Deploy attempt 2: prefer-ipv6 fixed, two new defects, model partially populated
...
The prefer-ipv6 failure did NOT recur. Two new ones did, and for the first time
the model is not empty: MEASURED 23 applications, 0 machines, 0 units.
Attempt 1 aborted during VALIDATION; this one reached EXECUTION, which is why
it left residue. NOTHING IS PROVISIONED -- no MAAS machine left Ready, no disk
written.
DEFECT 1: the rack's ~/repo-stage is a PARTIAL COPY of the repo with no
policies/, and bundle.yaml:214 is the only local-file reference in the deploy
input. juju resolves it relative to the bundle, so it must exist ON THE HOST
THAT DEPLOYS. Nothing caught it: --dry-run does not upload resources (the
SECOND --dry-run blind spot in two days), attempt 1 aborted before resource
upload, and preflight runs on voffice1 where policies/ DOES exist -- the same
wrong-host instrument class already recorded twice, and the second instance
this session. Fixed forward, both files sha256-verified.
DEFECT 2: a bundle relying on default-base is deployable exactly ONCE. The
re-run failed on barbican 265-vs-261. Measured: 265 is the ubuntu@22.04
revision, 261 the ubuntu@24.04 one. The 23 existing apps are all 22.04, so run
2a honoured bundle.yaml:85 -- but on a re-run, for an app that ALREADY EXISTS,
juju resolves without that default. A model-level default-base has NO EFFECT
(tested, then RESET). An explicit per-application base WORKS (tested read-only
on a throwaway; the error moved to the next app).
OWNED: two of those read-only tests were staged under /tmp and failed with
"no charm was found" -- the juju snap's PRIVATE /tmp, a trap this repo has
already recorded in CURRENT-STATE and the phase-4 runbook.
The rollback decision tree is written for tofu apply and defaults to
fix-forward, which is measured not to work here. A partial juju deploy is a
failure mode the repo does not cover. Recovery is an operator decision;
options are enumerated in the capture and the model residue is untouched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

RULED 2026-07-31 (GA-R5): the five new P5 findings accepted; dc0 deploy is clear
...
Operator utterance, verbatim: "Accept the five and proceed to the dc0 deploy".
The 2026-07-30 acceptance covered SIX findings and says it covers "these six,
enumerated, and nothing else". This preflight run reports eleven: all six still
present, plus FIVE new -- maas-region-db-password, maas-region-admin-password,
maas-region-api-key.txt (SEC-027) and maas-juju-api-key.txt,
maas-juju-user-password (SEC-028), all vr1-dc1. Every one is the D-137 forward
register correctly reporting that dc1's MAAS region is authored but not applied.
Carried as known, by-design absences on their existing SEC rows. preflight keeps
exiting FAIL on P5 for the rest of this stage; that RED is ruled-accepted and
must NOT be made green by deleting or weakening a matrix row. Like its
predecessor this covers these FIVE, enumerated, and nothing else -- total
accepted at P5 is now ELEVEN across the two rulings.
Also corrects this changelog's header, which claimed the session was read-only
throughout. True for items 1-5; after the ruling landed it edited the renderer,
the gate, both overlays and both values files. No cloud state changed at any
point.
Committed and pushed BEFORE the deploy.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

Pre-deploy: rack input refreshed + hash-verified, preflight captured, dry-run green
...
(a) The dc0 rack's ~/repo-stage is a COPY, not a clone -- nothing updates it, and
it still held the pre-ruling vips overlay. Exactly one of four files differed;
copied, then ALL FOUR hash-compared against repo HEAD. The gitignored
octavia-pki overlay was deliberately not touched (SEC-029) and verifies
byte-identical at 5fc117f1, still 0600. A sweep is evidence about the deploy
only if the swept bytes ARE the deployed bytes.
(b) Preflight captured (242 lines, exit 1), run with
MAAS_PROFILE=vr1-dc0-region -- without it preflight is REGION-BLIND and emits 19
false negatives. Instrument proven current first: maas-profile-assert.sh
vr1-dc0-region hot-kid exits 0. P1-P4 and P7 PASS, incl. aligned VIPs 13 OK / 0
bad against the RE-RENDERED overlay. P5 FAIL, 11 findings.
(c) The P5 delta is ENUMERATED. The 2026-07-30 acceptance covered six findings
and says it covers "these six, enumerated, and nothing else". All six are still
present; FIVE NEW are all vr1-dc1 S2 rows (SEC-027/-028) -- the D-137 forward
register correctly reporting that dc1's region is not built. Diff appended to
the capture. Needs its own GA-R5 exchange before the deploy.
(d) Step 4.2 --dry-run against the fixed input: exit 0, 56 apps / 108 relations
/ 33 unit placements. The ceph-osd tags=openstack exposure is now MEASURED, not
reasoned: the plan places ceph-osd/0..3 on machines 5-8 by explicit id, so the
absent tag never has to match. The residual (a later UNPLACED add-unit) stands.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

Fix a defect in my own build: invariant 9a was VIP-scoped and the charm list was short
...
Found by a repo-wide grep, not by any gate. Two faults, one cause -- I
measured and gated the population the QUESTION named (the 13 VIP charms)
rather than the population the INVARIANT covers (every application juju
validates).
(1) PREFER_IPV6_CHARMS was WRONG. Re-measured across ALL 33 charms in
bundle.yaml: TWELVE declare the option, not seven. The five missing are
ceph-mon 491, ceph-osd 953 (squid/stable), nova-compute 894, hacluster 166
(2.4/stable) and mysql-innodb-cluster 164 (8.0/stable).
(2) 9a was written INSIDE the VIP loop, so an application with no vip never
reached it -- and the repo ALREADY has that case:
overlays/dc-dc-ipv6-family-matrix.yaml sets prefer-ipv6 on ceph-mon, which
carries no VIP. That overlay is a LATER deploy step, so the miss would have
passed attempt 2 cleanly and surfaced at the step after, looking like a new
fault.
9a now runs over EVERY application, before the VIP loop; the VIP loop skips an
app 9a already flagged so the diagnosis is not doubled. ceph-mon's own in-file
claim of the option is now INDEPENDENTLY VERIFIED from the artifact rather
than taken from the comment.
Harness 48 -> 50/50. T47 (non-VIP app whose charm lacks the option FAILS) and
T48 (ceph-mon PASSES) both mutation-proven: re-scoping 9a back to the VIP loop
kills T47; dropping ceph-mon from the list kills T48.
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
|

Sweep both deploy inputs for option NAMES and VALUE TYPES: clean, and proven able to fail
...
The two extensions owed after the ruling, now run:
dc0 75 assignments / 20 schemas / 0 unknown name / 0 type mismatch / 0 note
dc1 75 assignments / 20 schemas / 0 unknown name / 0 type mismatch / 0 note
Both EXIT 0. dc1 introduces no new charm@channel pair -- checked, not assumed.
PROOF OF TEETH, because a clean sweep across two inputs is exactly the
implausibly-uniform result this repo has been burned by. Three defects planted
in a THROWAWAY copy, all three caught, EXIT 1:
[FAIL name] barbican does not declare option 'prefer-ipv6' (attempt-1 defect)
[FAIL type] keystone 'prefer-ipv6': declared boolean, got str ('true')
[note] glance 'vip': declared string, value is unquoted float
Type judgement is deliberately conservative: a mismatch is reported only where
juju's own coercion cannot save it. A scalar where a STRING is declared is a
NOTE, not a failure -- but it is still surfaced, because an unquoted value is
how a version string silently becomes a float. An UNRECOGNISED declared type
REFUSES rather than passing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

Build the 2026-07-31 ruling: renderer + invariant 9 replaced, overlays re-rendered
...
Three parts, all mutation-proven.
(i) provider-bundle-check.py gains PREFER_IPV6_CHARMS -- the measured authority,
keyed by CHARM name, carrying the revisions and a re-measure warning -- and
invariant 9 is REPLACED, never deleted:
9a prefer-ipv6 on a charm that does NOT declare it FAILS. Asserted on
PRESENCE, not truthiness: 'prefer-ipv6: false' is the same fatal
'unknown option' to juju as true.
9b on a DECLARING charm it still travels with the v6 legs -- the L3-9
protection is retained unchanged for those seven.
9c arity.
The old text's mechanism claim ("prefer-ipv6 makes HAProxy bind :::port") is
refuted by this session's measurement and is recorded as such in-file.
(ii) render-dc-overlays.py emits the option only for a declaring charm, READING
the set from provider-bundle-check with ast rather than restating it (the same
rule APP_OCTET already uses). render() keeps its purity property: the set is a
PARAMETER and the default REFUSES rather than defaulting to empty, because an
empty set renders a plausible artifact with the option nowhere. Both overlays
re-rendered: exactly SIX lines removed per DC and nothing else -- every vip
string byte-identical, so all v6 legs are retained.
(iii) tests/provider-bundle-check 44 -> 48/48. T43 reproduces the attempt-1
defect; T44 proves presence-not-truthiness; T45 is the positive control; T46
asserts the DIAGNOSIS.
MUTATION PASS -- six mutations, every one killed tests. And one NEW assertion
was found to be DECORATION and was REPLACED rather than kept: T46's first form
could not fail under any mutation, because a non-declaring charm with no option
reaches neither branch. Re-written to catch the fold-9a-into-9b simplification
and re-proven.
Gauntlet ALL GREEN (93); repo-lint 0 fail. The fixed dc0 deploy input now
sweeps CLEAN on option names: 75 assignments, 20 schemas, 0 unknown.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

prefer-ipv6 research: size option (b) and scope the sweep claim
...
Two corrections to my own framing, both measured:
(1) Option (b) is NOT a values-file edit. 'prefer-ipv6: true' is INJECTED
UNCONDITIONALLY by scripts/render-dc-overlays.py:243-246 whenever fam ==
"dual", and 'family: dual' is a single TOP-LEVEL key in
render/values/vr1-dc{0,1}-vips.yaml:3 -- not per-app data. Implementing (b)
means teaching the renderer which charms declare the option, re-pointing
tests/render-drift, and replacing invariant 9: a code change with a harness.
Recorded before the fork is put to the operator so the ruling is taken on the
real cost.
(2) The option-name sweep's claim is scoped inline. It checked NAMES, dc0
only. Option VALUE TYPES (the 'type:' field in every schema already
downloaded) and the dc1 input are both owed before the redeploy, and each
could cost an attempt the same way.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

prefer-ipv6 research DONE: the option never drove the v6 HAProxy bind
...
Answers the 2026-07-31 ruled question ("Research what those 6 charms do with
v6 VIPs first"). Measured by downloading each charm at its pinned channel for
amd64/ubuntu@22.04 and reading its templates and code -- not Charmhub's
config-yaml, which answers only whether the knob is declared.
FINDING: in BOTH template families the ':::port' frontend bind is gated on
ipv6_enabled = not is_ipv6_disabled(), a read of the kernel
net.ipv6.conf.all.disable_ipv6 sysctl -- MEASURED 0 on a live MAAS-deployed
jammy node in this DC. The v6 VIP is assigned by pacemaker on per-address
family detection. vault ships no haproxy template at all; its listener is the
hardcoded literal [::]:8200. prefer-ipv6 is a UNIT ADDRESS-FAMILY switch, and
the one value it sets that sounds like a bind address (haproxy_host) is
consumed by no template in any charm downloaded.
So option (b) -- drop the option from the six, keep their v6 VIP legs -- is
what the measurement supports. NOT TAKEN: the ruling is the operator's, the
overlay is untouched and R2 is unamended.
Also recorded: provider-bundle-check invariant 9's mechanism claim is wrong
and must be REPLACED (never deleted) when a ruling lands; a new and separate
question about the SEVEN charms that do declare the option; a full
option-name sweep of the dc0 deploy input (56 apps / 81 assignments / 20
schemas / 0 unresolvable) showing prefer-ipv6 on the six is the ONLY unknown
option, so nothing else is waiting for attempt 2; and a runbook defect --
the dc0 deploy block omits vr1-dc0-machines.yaml, which is NOT a no-op (it
carries ovn-chassis bridge-interface-mappings).
Read-only session: no cloud mutation, no overlay edit, no ruling adopted.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|