diff --git a/bundle.yaml b/bundle.yaml index 8968ae8..f6fed0d 100644 --- a/bundle.yaml +++ b/bundle.yaml @@ -83,6 +83,15 @@ B3 nova Ceph-RBD ephemeral; B4 octavia amphora-pipeline options. C3 radosgw unchanged (already correct). default-base: ubuntu@22.04/stable +# EVERY application below ALSO carries an explicit `base: ubuntu@22.04/stable`, and that +# redundancy is deliberate. MEASURED 2026-07-31 (capture +# docs/audit/stage5-dc0-deploy-attempt2-20260731.txt): a bundle whose applications rely +# SOLELY on this default is deployable exactly ONCE. On a RE-RUN, for an application that +# ALREADY EXISTS, juju resolves the charm WITHOUT the default and lands on the ubuntu@24.04 +# revision -- barbican is rev 265 at 22.04 and 261 at 24.04, mysql-router 1154 vs 1178 -- +# then refuses the resulting downgrade, so a partial deploy cannot be resumed. A model-level +# `default-base` does NOT fix it (tested). RULED 2026-07-31 (GA-R5, option D). Do not +# "simplify" these lines away: they are what makes this bundle survive a partial failure. variables: # ----- UCA pocket + Ceph source ---------------------------------------------- @@ -138,6 +147,7 @@ # moot at one unit). Vault HA backend (Raft vs etcd) is a Roosevelt rehearsal item. mysql-innodb-cluster: + base: ubuntu@22.04/stable charm: mysql-innodb-cluster channel: 8.0/stable num_units: 3 @@ -152,6 +162,7 @@ constraints: arch=amd64 rabbitmq-server: + base: ubuntu@22.04/stable charm: rabbitmq-server channel: 3.9/stable num_units: 1 @@ -165,6 +176,7 @@ constraints: arch=amd64 vault: + base: ubuntu@22.04/stable charm: vault # D-068 AMENDMENT (2026-07-05): reverted 1.16/stable -> 1.8/stable. The 1.16 line is a # fundamentally different charm (certs interface V0->V1, Raft-only storage, no upgrade @@ -193,6 +205,7 @@ constraints: arch=amd64 vault-mysql-router: + base: ubuntu@22.04/stable charm: mysql-router channel: 8.0/stable bindings: @@ -206,6 +219,7 @@ # ===================================================================== keystone: + base: ubuntu@22.04/stable # DOCFIX-071: the D-051/D-064 SCS Domain Manager policy ships WITH the deploy -- # a manual post-deploy attach-resource proved unreachable from the schedule. # Keep policies/overrides.zip in sync with domain-manager-policy.yaml @@ -237,6 +251,7 @@ constraints: arch=amd64 keystone-mysql-router: + base: ubuntu@22.04/stable charm: mysql-router channel: 8.0/stable bindings: @@ -250,6 +265,7 @@ # ===================================================================== glance: + base: ubuntu@22.04/stable charm: glance channel: 2024.1/stable num_units: 1 @@ -274,6 +290,7 @@ constraints: arch=amd64 glance-mysql-router: + base: ubuntu@22.04/stable charm: mysql-router channel: 8.0/stable bindings: @@ -283,6 +300,7 @@ shared-db: metal-internal glance-simplestreams-sync: + base: ubuntu@22.04/stable charm: glance-simplestreams-sync channel: 2024.1/stable num_units: 1 @@ -303,6 +321,7 @@ # ===================================================================== nova-cloud-controller: + base: ubuntu@22.04/stable charm: nova-cloud-controller channel: 2024.1/stable num_units: 1 @@ -334,6 +353,7 @@ constraints: arch=amd64 nova-compute: + base: ubuntu@22.04/stable charm: nova-compute channel: 2024.1/stable num_units: 2 @@ -364,6 +384,7 @@ constraints: arch=amd64 ncc-mysql-router: + base: ubuntu@22.04/stable charm: mysql-router channel: 8.0/stable bindings: @@ -373,6 +394,7 @@ shared-db: metal-internal placement: + base: ubuntu@22.04/stable charm: placement channel: 2024.1/stable num_units: 1 @@ -391,6 +413,7 @@ constraints: arch=amd64 placement-mysql-router: + base: ubuntu@22.04/stable charm: mysql-router channel: 8.0/stable bindings: @@ -404,6 +427,7 @@ # ===================================================================== neutron-api: + base: ubuntu@22.04/stable charm: neutron-api channel: 2024.1/stable num_units: 1 @@ -428,6 +452,7 @@ constraints: arch=amd64 neutron-api-mysql-router: + base: ubuntu@22.04/stable charm: mysql-router channel: 8.0/stable bindings: @@ -437,6 +462,7 @@ shared-db: metal-internal neutron-api-plugin-ovn: + base: ubuntu@22.04/stable charm: neutron-api-plugin-ovn channel: 2024.1/stable @@ -446,6 +472,7 @@ neutron-plugin: metal-internal ovsdb-cms: metal-internal ovn-central: + base: ubuntu@22.04/stable charm: ovn-central channel: 24.03/stable num_units: 3 @@ -483,6 +510,7 @@ # when absent, so it cannot tell you this is missing or that it is the wrong DC's set -- # deploy the base WITHOUT its per-DC machines overlay and you get silent dead egress. ovn-chassis: + base: ubuntu@22.04/stable charm: ovn-chassis channel: 24.03/stable options: @@ -496,6 +524,7 @@ ovsdb: metal-internal ovsdb-subordinate: metal-internal ovn-chassis-octavia: + base: ubuntu@22.04/stable charm: ovn-chassis channel: 24.03/stable bindings: @@ -506,6 +535,7 @@ ovsdb: metal-internal ovsdb-subordinate: metal-internal cinder: + base: ubuntu@22.04/stable charm: cinder channel: 2024.1/stable num_units: 1 @@ -532,6 +562,7 @@ constraints: arch=amd64 # owns the relation -- but the binding still provisions the NIC. cinder-mysql-router: + base: ubuntu@22.04/stable charm: mysql-router channel: 8.0/stable bindings: @@ -541,6 +572,7 @@ shared-db: metal-internal cinder-ceph: + base: ubuntu@22.04/stable charm: cinder-ceph channel: 2024.1/stable bindings: @@ -549,6 +581,7 @@ ceph-access: storage storage-backend: metal-internal ceph-mon: + base: ubuntu@22.04/stable charm: ceph-mon channel: squid/stable num_units: 3 @@ -576,6 +609,7 @@ # public net (no cluster binding needed). ceph-osd: + base: ubuntu@22.04/stable charm: ceph-osd channel: squid/stable num_units: 4 @@ -592,6 +626,7 @@ constraints: arch=amd64 tags=openstack ceph-radosgw: + base: ubuntu@22.04/stable charm: ceph-radosgw channel: squid/stable num_units: 1 @@ -618,6 +653,7 @@ # ===================================================================== openstack-dashboard: + base: ubuntu@22.04/stable charm: openstack-dashboard channel: 2024.1/stable num_units: 1 @@ -647,6 +683,7 @@ constraints: arch=amd64 dashboard-mysql-router: + base: ubuntu@22.04/stable charm: mysql-router channel: 8.0/stable bindings: @@ -662,6 +699,7 @@ # documented apache2/octavia-api masking bug -- see test deployment v3 handoff). octavia: + base: ubuntu@22.04/stable charm: octavia channel: 2024.1/stable num_units: 1 @@ -703,6 +741,7 @@ constraints: arch=amd64 # subset for the subordinate's data binding (subset rule). octavia-mysql-router: + base: ubuntu@22.04/stable charm: mysql-router channel: 8.0/stable bindings: @@ -712,6 +751,7 @@ shared-db: metal-internal octavia-dashboard: + base: ubuntu@22.04/stable charm: octavia-dashboard channel: 2024.1/stable @@ -720,6 +760,7 @@ certificates: metal-internal dashboard: metal-internal octavia-diskimage-retrofit: + base: ubuntu@22.04/stable charm: octavia-diskimage-retrofit channel: 2024.1/stable options: @@ -744,6 +785,7 @@ # to stop it visually implying the bindings above belong to Barbican. barbican: + base: ubuntu@22.04/stable charm: barbican channel: 2024.1/stable num_units: 1 @@ -764,6 +806,7 @@ constraints: arch=amd64 barbican-mysql-router: + base: ubuntu@22.04/stable charm: mysql-router channel: 8.0/stable bindings: @@ -773,6 +816,7 @@ shared-db: metal-internal barbican-vault: + base: ubuntu@22.04/stable charm: barbican-vault channel: 2024.1/stable bindings: @@ -800,6 +844,7 @@ # magnum itself. magnum: + base: ubuntu@22.04/stable charm: magnum channel: 2024.1/stable num_units: 1 @@ -820,6 +865,7 @@ constraints: arch=amd64 magnum-mysql-router: + base: ubuntu@22.04/stable charm: mysql-router channel: 8.0/stable bindings: @@ -829,6 +875,7 @@ shared-db: metal-internal magnum-dashboard: + base: ubuntu@22.04/stable charm: magnum-dashboard channel: 2024.1/stable bindings: @@ -857,6 +904,7 @@ # NOT YET DEPLOYED to any live model -- prep-only repo change, 2026-07-10. # ===================================================================== designate: + base: ubuntu@22.04/stable charm: designate channel: 2024.1/stable num_units: 1 @@ -876,6 +924,7 @@ constraints: arch=amd64 designate-mysql-router: + base: ubuntu@22.04/stable charm: mysql-router channel: 8.0/stable bindings: @@ -885,6 +934,7 @@ shared-db: metal-internal designate-bind: + base: ubuntu@22.04/stable charm: designate-bind channel: 2024.1/stable num_units: 1 @@ -924,6 +974,7 @@ # NOT YET DEPLOYED to any live model -- prep-only repo change, 2026-07-10. # ===================================================================== cinder-backup: + base: ubuntu@22.04/stable charm: cinder-backup channel: 2024.1/stable bindings: @@ -931,6 +982,7 @@ ceph: storage ceph-rbd-mirror: + base: ubuntu@22.04/stable charm: ceph-rbd-mirror channel: squid/stable num_units: 1 @@ -955,22 +1007,23 @@ # ceph-local/ceph-remote (replication) + nrpe-external-master bindings (D-108/D-052) and # injecting a phantom `dashboard` binding (a charm with no such endpoint) -> juju deploy # validation reject. Confirmed via yaml.safe_load; committee-20260724-track2-bundle-render. - keystone-hacluster: {charm: hacluster, channel: 2.4/stable, options: {cluster_count: 1}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}} - glance-hacluster: {charm: hacluster, channel: 2.4/stable, options: {cluster_count: 1}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}} - neutron-api-hacluster: {charm: hacluster, channel: 2.4/stable, options: {cluster_count: 1}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}} - nova-cloud-controller-hacluster: {charm: hacluster, channel: 2.4/stable, options: {cluster_count: 1}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}} - placement-hacluster: {charm: hacluster, channel: 2.4/stable, options: {cluster_count: 1}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}} - openstack-dashboard-hacluster: {charm: hacluster, channel: 2.4/stable, options: {cluster_count: 1}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}} - cinder-hacluster: {charm: hacluster, channel: 2.4/stable, options: {cluster_count: 1}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}} - octavia-hacluster: {charm: hacluster, channel: 2.4/stable, options: {cluster_count: 1}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}} - barbican-hacluster: {charm: hacluster, channel: 2.4/stable, options: {cluster_count: 1}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}} - magnum-hacluster: {charm: hacluster, channel: 2.4/stable, options: {cluster_count: 1}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}} - ceph-radosgw-hacluster: {charm: hacluster, channel: 2.4/stable, options: {cluster_count: 1}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}} # B1 -- un-deferred - designate-hacluster: {charm: hacluster, channel: 2.4/stable, options: {cluster_count: 1}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}} # D-106 -- reactivated 2026-07-10, supersedes D-019 deferral + keystone-hacluster: {base: ubuntu@22.04/stable, charm: hacluster, channel: 2.4/stable, options: {cluster_count: 1}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}} + glance-hacluster: {base: ubuntu@22.04/stable, charm: hacluster, channel: 2.4/stable, options: {cluster_count: 1}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}} + neutron-api-hacluster: {base: ubuntu@22.04/stable, charm: hacluster, channel: 2.4/stable, options: {cluster_count: 1}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}} + nova-cloud-controller-hacluster: {base: ubuntu@22.04/stable, charm: hacluster, channel: 2.4/stable, options: {cluster_count: 1}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}} + placement-hacluster: {base: ubuntu@22.04/stable, charm: hacluster, channel: 2.4/stable, options: {cluster_count: 1}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}} + openstack-dashboard-hacluster: {base: ubuntu@22.04/stable, charm: hacluster, channel: 2.4/stable, options: {cluster_count: 1}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}} + cinder-hacluster: {base: ubuntu@22.04/stable, charm: hacluster, channel: 2.4/stable, options: {cluster_count: 1}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}} + octavia-hacluster: {base: ubuntu@22.04/stable, charm: hacluster, channel: 2.4/stable, options: {cluster_count: 1}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}} + barbican-hacluster: {base: ubuntu@22.04/stable, charm: hacluster, channel: 2.4/stable, options: {cluster_count: 1}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}} + magnum-hacluster: {base: ubuntu@22.04/stable, charm: hacluster, channel: 2.4/stable, options: {cluster_count: 1}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}} + ceph-radosgw-hacluster: {base: ubuntu@22.04/stable, charm: hacluster, channel: 2.4/stable, options: {cluster_count: 1}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}} # B1 -- un-deferred + designate-hacluster: {base: ubuntu@22.04/stable, charm: hacluster, channel: 2.4/stable, options: {cluster_count: 1}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}} # D-106 -- reactivated 2026-07-10, supersedes D-019 deferral # vault-hacluster: { charm: hacluster, channel: 2.4/stable } # C1: vault single-unit on mysql; HA at Roosevelt # memcached: nova-cloud-controller token/cell caching (BUNDLEFIX-004) memcached: + base: ubuntu@22.04/stable charm: memcached channel: latest/stable num_units: 1 diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index 190fb58..3fb2187 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -1249,6 +1249,36 @@ it. **The re-runnability 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.** + **BUILT 2026-07-31, AND THE FIX IS PROVEN AGAINST THE REAL POPULATED MODEL, NOT INFERRED.** + All **56** applications in `bundle.yaml` now carry an explicit `base: ubuntu@22.04/stable`. + **THE EARLIER ONE-APP TEST WAS AN INFERENCE AND IS NOW A MEASUREMENT:** the fully-based + bundle was staged as a throwaway on the rack and `--dry-run` against the LIVE 23-app model + -- the same command that errored on `barbican` 265-vs-261 -- **exits 0**. **SUBORDINATES ARE + IN SCOPE, measured not assumed:** `mysql-router` is rev **1154** at 22.04 and **1178** at + 24.04, so exempting subordinates would have left the trap open; `hacluster` happens to share + rev 166 across both bases, which is not a property to depend on. + **TWO SILENT UNDER-MATCHES CAUGHT BY CROSS-CHECKING THE EDIT AGAINST THE PARSED FILE.** + (i) A first pass keyed on the block form `^ :$` inserted **44** of 56 -- the twelve + `-hacluster` applications are single-line FLOW MAPPINGS (` keystone-hacluster: {charm: ...}`) + and matched nothing. Caught because the inserter asserts its app list equals what + `yaml.safe_load` sees, and refuses otherwise. The final edit asserts the ARTIFACT too: every + app carries the base, and the parsed structures with `base` stripped are IDENTICAL to the + original, so nothing else moved. (ii) **`overlays/dc-ha-scaleup.yaml` DEFINES an application + that exists nowhere in `bundle.yaml` -- `vault-hacluster` -- and it had no base.** That + overlay is a LATER deploy step, so the gap would have re-opened the trap after the deploy + succeeded. **It was 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` `prefer-ipv6` miss + earlier this session. Fixed. + **GATED, SO THE 56 LINES CANNOT BE SILENTLY SIMPLIFIED AWAY: `provider-bundle-check` + INVARIANT 12 IS NEW** -- every application carries an explicit `base` equal to the bundle's + OWN `default-base` (keyed on that value, not a literal, so a future base change moves one + line). A bundle with NO `default-base` REFUSES rather than passing vacuously. **Five cases, + each mutation-proven individually:** T49 stripping every base (a byte-identical PASS + before), T50 a single app, T51 a base disagreeing with `default-base`, T52 a SUBORDINATE + (not exempted), T53 the refusal. Neutering the `nobase` predicate kills T49/T50/T52; + neutering `wrongbase` kills T51; neutering the refuse branch kills T53. Harness 50 -> + **55/55**; gauntlet ALL GREEN (93); repo-lint 0 fail; `provider-bundle-check` on the dc0 + deploy input PASS. **RUNBOOK DEFECT FOUND IN PASSING, LOGGED NOT FIXED (DOCFIX material):** `runbooks/dc-dc-phase4-juju-bundle-per-dc.md:553-557` gives the dc0 deploy WITHOUT `overlays/vr1-dc0-machines.yaml`, while the dc1 block three lines below includes its diff --git a/docs/changelog-20260731-prefer-ipv6-research.md b/docs/changelog-20260731-prefer-ipv6-research.md index 70f1b3a..bf02b56 100644 --- a/docs/changelog-20260731-prefer-ipv6-research.md +++ b/docs/changelog-20260731-prefer-ipv6-research.md @@ -371,3 +371,48 @@ `default-base` is deployable exactly once -- is skill material at stage close. **Revert.** A ruling is a record; withdrawing it needs a new GA-R5 exchange. + +## Item 12 -- BUILD option D's first half: explicit base on all 56, gated by invariant 12 + +**`bundle.yaml`** -- all 56 applications now carry `base: ubuntu@22.04/stable`, plus a +note at the `default-base` line recording WHY the redundancy is deliberate and telling a +future reader not to simplify it away. + +**Proven against the real model, not inferred.** The earlier one-app test showed only +that the error MOVED 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 rev 1154 at 22.04 and 1178 at +24.04, so exempting subordinates would have left the trap open. `hacluster` shares rev +166 across both bases, which 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` applications are single-line FLOW MAPPINGS and matched nothing. The + inserter asserts its app list equals what `yaml.safe_load` sees and refuses otherwise, + which is what caught it. The final edit also asserts the ARTIFACT: every app carries + the base, and the parsed structures with `base` stripped are IDENTICAL to the + original. +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` `prefer-ipv6` miss in item 7. Fixed. + +**`provider-bundle-check` invariant 12 (NEW).** Every application carries an explicit +`base` equal to the bundle's OWN `default-base` -- keyed on that value, not a literal, so +a future base change moves one line. A bundle with no `default-base` REFUSES rather than +passing vacuously. + +**Five cases, each mutation-proven individually** (`tests/provider-bundle-check` 50 -> +55/55): T49 stripping every base -- a byte-identical PASS before this landed; T50 a +single app; T51 a base disagreeing with `default-base`; T52 a SUBORDINATE, not exempted; +T53 the refusal. Neutering `nobase` kills T49/T50/T52; neutering `wrongbase` kills T51; +neutering the refuse branch kills T53. Source restored byte-identical after each. + +Gauntlet ALL GREEN (93); repo-lint 0 fail. + +**Revert.** `git revert` this commit -- bundle, overlay, gate and harness must move +together, since the gate fails on a bundle without the base lines. diff --git a/overlays/dc-ha-scaleup.yaml b/overlays/dc-ha-scaleup.yaml index c99a0af..190591a 100644 --- a/overlays/dc-ha-scaleup.yaml +++ b/overlays/dc-ha-scaleup.yaml @@ -178,8 +178,14 @@ # ---- vault-hacluster: RE-DECLARED (item C above). Removed by BUNDLEFIX-002 # when vault was de-HA'd; D-121 (v-a) brings it back. Shape matches the - # other hacluster subs in bundle.yaml, with cluster_count: 3 (real HA). ---- - vault-hacluster: {charm: hacluster, channel: 2.4/stable, options: {cluster_count: 3}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}} + # other hacluster subs in bundle.yaml, with cluster_count: 3 (real HA). + # It carries an explicit `base` for the same reason all 56 applications in + # bundle.yaml do (RULED 2026-07-31, GA-R5 option D; provider-bundle-check + # invariant 12): this is the ONLY application any overlay INTRODUCES, so + # without it this later deploy step re-opens the once-only-deployable trap + # that invariant 12 exists to close. Found by that invariant on its first + # run, in a file the fix was not looking at. ---- + vault-hacluster: {base: ubuntu@22.04/stable, charm: hacluster, channel: 2.4/stable, options: {cluster_count: 3}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}} # ---- cluster_count: 1 -> 3 for the 12 active hacluster subs (item B above). # Overlay apply deep-merges options per-key -- the SAME mechanism diff --git a/scripts/provider-bundle-check.py b/scripts/provider-bundle-check.py index e2f57c0..1607faf 100644 --- a/scripts/provider-bundle-check.py +++ b/scripts/provider-bundle-check.py @@ -73,6 +73,19 @@ RESIDUAL LIMIT, stated plainly: a gutted overlay is only caught because --dc DECLARES the intended DC. Validating a dc1 input while calling it dc0 is outside what any bundle-static check can see. + 12. EVERY APPLICATION CARRIES AN EXPLICIT `base:`, EQUAL TO `default-base` (RULED + 2026-07-31, GA-R5 option D). MEASURED, not reasoned -- capture + docs/audit/stage5-dc0-deploy-attempt2-20260731.txt: a bundle whose applications rely + SOLELY on `default-base` is deployable exactly ONCE. On a RE-RUN, for an application + that ALREADY EXISTS, juju resolves the charm WITHOUT that default and lands on the + ubuntu@24.04 revision (barbican 265 vs 261; mysql-router 1154 vs 1178), then refuses + the downgrade -- so a partially-failed deploy cannot be resumed at all. A model-level + `default-base` does NOT fix it (set, read back, re-run identical). The 56 redundant + lines ARE the fix, and this invariant exists so they cannot be silently "simplified" + away: without it, deleting them is a byte-identical PASS and the trap returns at the + next partial failure, at any DC. Subordinates are included deliberately -- + mysql-router differs across bases, and hacluster happening to share a revision today + is not a property to depend on. It is the single deploy-gate: it REPLACED the retired scripts/d057-bundle-check.py (D-060) and now also scripts/review-bundle.py (DOCFIX-070). FAIL -> exit 1. ASCII-only output. @@ -583,6 +596,37 @@ # tag together, role_sep goes False, and the whole placement block -- these # checks included -- self-skipped behind an [ok]. A bundle with NO machines # block still skips (zero iterations), which is the VR0 shape. + + # ---- invariant 12: every application carries an explicit base == default-base ---- + # RULED 2026-07-31 (GA-R5 option D). Without this, deleting the 56 `base:` lines is a + # byte-identical PASS and the once-only-deployable trap returns at the next partial + # failure. Keyed on the bundle's OWN default-base rather than a literal, so a future + # base change moves one line and this still holds. REFUSES if default-base is absent: + # there would be nothing to compare against, and "could not look" is not "nothing + # there". + want_base = doc.get("default-base") + if not want_base: + fails.append("bundle declares no top-level default-base -- invariant 12 cannot be " + "evaluated, and an application base that matches nothing is not a pass") + else: + nobase = sorted(n for n in apps if not ((apps[n] or {}).get("base"))) + wrongbase = sorted(n for n in apps + if (apps[n] or {}).get("base") + and (apps[n] or {}).get("base") != want_base) + if nobase: + fails.append("%d application(s) carry NO explicit base -- a bundle relying on " + "default-base alone is deployable exactly ONCE and cannot be " + "resumed after a partial failure (2026-07-31 attempt 2): %s" + % (len(nobase), ", ".join(nobase))) + if wrongbase: + fails.append("%d application(s) carry a base that is not the bundle's " + "default-base %r: %s" + % (len(wrongbase), want_base, + ", ".join("%s=%r" % (n, apps[n]["base"]) for n in wrongbase))) + if not nobase and not wrongbase: + print(" [ok] all %d application(s) carry an explicit base == default-base " + "%r (re-runnable after a partial deploy)" % (len(apps), want_base)) + if machines: want_tag = DC_MACHINE_TAGS[args.dc] dc_tags = {} diff --git a/tests/provider-bundle-check/run-tests.sh b/tests/provider-bundle-check/run-tests.sh index 042d911..6d58d69 100644 --- a/tests/provider-bundle-check/run-tests.sh +++ b/tests/provider-bundle-check/run-tests.sh @@ -474,6 +474,43 @@ run 0 'PASS: Pattern A' \ "T48 prefer-ipv6 on a NON-VIP app whose charm DOES declare it PASSES" "$TMP/t48.yaml" +# ---- invariant 12: every application carries an explicit base == default-base ---- +# RULED 2026-07-31 (GA-R5 option D). MEASURED: a bundle relying on default-base alone is +# deployable exactly ONCE -- on a re-run juju resolves an EXISTING app's charm without +# that default, lands on the ubuntu@24.04 revision (barbican 265 vs 261, mysql-router +# 1154 vs 1178) and refuses the downgrade, so a partial deploy cannot be resumed. The 56 +# redundant `base:` lines ARE the fix; these cases stop them being simplified away. + +# T49 the whole point: strip every base and the gate must FAIL. Before invariant 12 this +# was a byte-identical PASS. +mutate t49.yaml 'for a in b["applications"].values(): a.pop("base", None)' +run 1 'application\(s\) carry NO explicit base' \ + "T49 stripping every base FAILS (was a byte-identical PASS)" "$TMP/t49.yaml" + +# T50 ONE app losing it is enough -- juju aborts the whole bundle on the first mismatch, +# so a partial strip is not a partial problem. +mutate t50.yaml 'b["applications"]["vault"].pop("base", None)' +run 1 'application\(s\) carry NO explicit base' \ + "T50 a SINGLE app missing its base FAILS" "$TMP/t50.yaml" + +# T51 a base that disagrees with default-base is its own defect: it deploys a charm for +# the wrong series rather than failing loudly. +mutate t51.yaml 'b["applications"]["keystone"]["base"]="ubuntu@24.04/stable"' +run 1 'not the bundle.s default-base' \ + "T51 an app base disagreeing with default-base FAILS" "$TMP/t51.yaml" + +# T52 subordinates are IN SCOPE. mysql-router differs across bases (1154 vs 1178) and +# hacluster sharing a revision today is not a property to depend on. +mutate t52.yaml 'b["applications"]["keystone-hacluster"].pop("base", None)' +run 1 'keystone-hacluster' \ + "T52 a SUBORDINATE missing its base FAILS (not exempted)" "$TMP/t52.yaml" + +# T53 no default-base to compare against REFUSES rather than passing vacuously -- +# "could not look" is never "nothing there". +mutate t53.yaml 'b.pop("default-base", None)' +run 1 'declares no top-level default-base' \ + "T53 a bundle with no default-base REFUSES (does not pass vacuously)" "$TMP/t53.yaml" + echo echo "RESULT: PASS=$PASS FAIL=$FAIL" [[ "$FAIL" -eq 0 ]] && { echo "ALL PASS"; exit 0; } || exit 1