diff --git a/docs/changelog-20260703-process-hardening.md b/docs/changelog-20260703-process-hardening.md index 15a6782..4ea61d2 100644 --- a/docs/changelog-20260703-process-hardening.md +++ b/docs/changelog-20260703-process-hardening.md @@ -265,3 +265,50 @@ argv-style invocations route through br-ex OVS + static; br-metal/.103/ +br-internal chain; raw data/storage/replication statics; enp11s0 idle), +missing-subnet and wrong-VID hard gates, all-SKIP idempotent re-run; forbids +br-prov-api/enp1s0.104/provider-vip forever. REVERT: git history (red harness). + +### 26. scripts/validate.sh -- placeholder TODO aligned to amended decisions +The D-011 runner's drafting list still targeted DROPPED features (Designate +resolution -- D-019; snapshot existence -- D-012, superseded by D-070) and +predated the manual-unseal ruling. List now points at the existing building +blocks (cloud-assert / tenant-acceptance / run-tests-all) and the genuinely +missing items, so the eventual implementation is built to the real spec. + +### 27. skills/openstack-cloud-ops/ -- unpacked skill SOURCE committed +The repo carried only the packaged .skill zip (opaque to grep/diff/repo-lint). +The unpacked SKILL.md + references/ (v1.2) now live beside it as the reviewable +source of truth; regenerate the .skill from this folder on any change (or drop +the zip -- operator's call). Divergence rule applies: repo source wins over any +installed copy. + +### Review attestations (Block 4 sweep) +Runbook->script contract cross-check: CLEAN (both automated hits verified +false -- reenroll --check and carve --apply exist under parser styles the +checker missed). Loop-efficiency scan: clean (flagged sites are watchers/polls +by design). Exit-code audit: clean (tenant-acceptance's 11-14 are a declared +per-phase contract). deploy-watch, maas-fabric-prune, juju-spaces-check, +osd-blank-check: read; disciplined; no findings. diff --git a/scripts/run-tests-all.sh b/scripts/run-tests-all.sh new file mode 100644 index 0000000..cfcfe97 --- /dev/null +++ b/scripts/run-tests-all.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# scripts/run-tests-all.sh [filter] +# +# The harness gauntlet: discovers and runs every tests/*/run-tests.sh, one +# summary line each, worst exit wins. This is the TOOLING gate -- run it after +# any script/harness change (the change-delivery loop) and before trusting the +# gate scripts in a deploy. Deliberately NOT folded into preflight.sh: +# preflight gates the DEPLOY TARGET, this gates the TOOLS -- different cadence, +# different failure meaning. +# +# [filter] runs only harnesses whose directory name contains the substring +# (e.g. `bash scripts/run-tests-all.sh phase-06`). +# Mutates NOTHING (harnesses confine themselves to mktemp dirs). +# Exit: 0 all green | 1 any harness failed | 2 no harnesses matched. +# ASCII + LF. +set -uo pipefail +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO="$(cd "$HERE/.." && pwd)" +FILTER="${1:-}" +FAILED=(); RAN=0 +shopt -s nullglob +for t in "$REPO"/tests/*/run-tests.sh; do + name="$(basename "$(dirname "$t")")" + [ -n "$FILTER" ] && case "$name" in *"$FILTER"*) ;; *) continue ;; esac + RAN=$((RAN+1)) + OUT="$(bash "$t" 2>&1)"; RC=$? + TAIL="$(printf '%s\n' "$OUT" | tail -1)" + printf ' %-38s %s\n' "$name" "$TAIL" + if [ "$RC" -ne 0 ]; then + FAILED+=("$name") + printf '%s\n' "$OUT" | grep -E '^\s*(FAIL|\[XX\]|MISS|LEAK|COUNT)' | head -4 | sed 's/^/ /' + fi +done +echo +if [ "$RAN" -eq 0 ]; then echo "GAUNTLET: no harnesses matched '${FILTER}'"; exit 2; fi +if [ "${#FAILED[@]}" -eq 0 ]; then + echo "GAUNTLET: ALL GREEN ($RAN harnesses)"; exit 0 +fi +echo "GAUNTLET: ${#FAILED[@]}/$RAN FAILED -- ${FAILED[*]}" +exit 1 diff --git a/scripts/validate.sh b/scripts/validate.sh index 646b6e6..ac9d7b2 100644 --- a/scripts/validate.sh +++ b/scripts/validate.sh @@ -18,15 +18,21 @@ result_pass() { echo "PASS: $*"; PASS=$((PASS+1)); } result_skip() { echo "SKIP: $*"; SKIP=$((SKIP+1)); } -# TODO during drafting: -# - all charms active/idle assertion (juju status --format=json | jq) -# - public API VIP reachability from jumphost (per service hostname) -# - public API VIP reachability from a test tenant VM (Option B verify) +# TODO during drafting (aligned 2026-07-03 to D-011 AS AMENDED -- the prior list +# targeted dropped features): +# Building blocks that ALREADY exist (wrap, do not reimplement): +# - scripts/cloud-assert.sh -- charms/behavioral sweep (D-011 items 1-2, 6-8) +# - scripts/tenant-acceptance.sh -- per-tenant kube + LB + isolation e2e +# - scripts/run-tests-all.sh -- tooling gauntlet (gate scripts trusted) +# Still to author here: +# - public API VIP reachability from jumphost AND from a test tenant VM (Option B) # - Octavia LB pattern test (create -> two members -> round-robin -> failover -> recovery) -# - Magnum CAPI cluster create end-to-end -# - Vault unseal/reseal pattern -# - Designate resolves API hostnames from tenant VM -# - Snapshot 1 + Snapshot 2 existence verified +# - Magnum CAPI cluster create end-to-end (fresh tenant, timed) +# - Vault MANUAL unseal-after-restart rehearsal by a SECOND person (D-011.6 as +# amended by phase-08 + D-069 -- auto-unseal is NOT a v1 item) +# DROPPED from the original list (do not implement): +# - Designate hostname resolution (DNS dropped for v1 -- D-019) +# - Snapshot 1/2 existence (D-070 superseded D-012: no snapshot restore path) echo "Placeholder validate.sh -- not yet implemented." diff --git a/tests/carve-host-interfaces/fix/if_done.json b/tests/carve-host-interfaces/fix/if_done.json index 26d50ef..97febe5 100644 --- a/tests/carve-host-interfaces/fix/if_done.json +++ b/tests/carve-host-interfaces/fix/if_done.json @@ -1,11 +1,166 @@ -[ {"name":"enp1s0","id":100,"vlan":{"id":5001},"links":[]}, - {"name":"enp1s0.104","id":104,"vlan":{"id":5004},"links":[]}, - {"name":"br-prov-api","id":300,"vlan":{"id":5004},"links":[{"id":906,"subnet":{"cidr":"10.12.8.0/22"}}]}, - {"name":"enp7s0","id":107,"vlan":{"id":5002},"links":[]}, - {"name":"br-metal","id":200,"vlan":{"id":5002},"links":[{"id":901,"subnet":{"cidr":"10.12.12.0/22"}}]}, - {"name":"br-metal.103","id":201,"vlan":{"id":5003},"links":[]}, - {"name":"br-internal","id":202,"vlan":{"id":5003},"links":[{"id":902,"subnet":{"cidr":"10.12.16.0/22"}}]}, - {"name":"enp8s0","id":108,"vlan":{"id":5005},"links":[{"id":903,"subnet":{"cidr":"10.12.20.0/22"}}]}, - {"name":"enp9s0","id":109,"vlan":{"id":5006},"links":[{"id":904,"subnet":{"cidr":"10.12.32.0/22"}}]}, - {"name":"enp10s0","id":110,"vlan":{"id":5007},"links":[{"id":905,"subnet":{"cidr":"10.12.36.0/22"}}]}, - {"name":"enp11s0","id":111,"vlan":{"id":5001},"links":[]} ] +[ + { + "name": "enp1s0", + "id": 100, + "type": "physical", + "vlan": { + "id": 5001, + "vid": 0, + "fabric": "fabric-prov" + }, + "links": [] + }, + { + "name": "br-ex", + "id": 200, + "type": "bridge", + "vlan": { + "id": 5001, + "vid": 0, + "fabric": "fabric-prov" + }, + "links": [ + { + "id": 910, + "subnet": { + "cidr": "10.12.4.0/22" + }, + "ip_address": "10.12.4.40", + "mode": "static" + } + ] + }, + { + "name": "enp7s0", + "id": 101, + "type": "physical", + "vlan": { + "id": 5002, + "vid": 0, + "fabric": "fabric-metal" + }, + "links": [] + }, + { + "name": "br-metal", + "id": 201, + "type": "bridge", + "vlan": { + "id": 5002, + "vid": 0, + "fabric": "fabric-metal" + }, + "links": [ + { + "id": 911, + "subnet": { + "cidr": "10.12.8.0/22" + }, + "ip_address": "10.12.8.40", + "mode": "static" + } + ] + }, + { + "name": "br-metal.103", + "id": 202, + "type": "vlan", + "vlan": { + "id": 5003, + "vid": 103, + "fabric": "fabric-metal" + }, + "links": [] + }, + { + "name": "br-internal", + "id": 203, + "type": "bridge", + "vlan": { + "id": 5003, + "vid": 103, + "fabric": "fabric-metal" + }, + "links": [ + { + "id": 912, + "subnet": { + "cidr": "10.12.12.0/22" + }, + "ip_address": "10.12.12.40", + "mode": "static" + } + ] + }, + { + "name": "enp8s0", + "id": 102, + "type": "physical", + "vlan": { + "id": 5004, + "vid": 0, + "fabric": "fabric-data" + }, + "links": [ + { + "id": 913, + "subnet": { + "cidr": "10.12.16.0/22" + }, + "ip_address": "10.12.16.40", + "mode": "static" + } + ] + }, + { + "name": "enp9s0", + "id": 103, + "type": "physical", + "vlan": { + "id": 5005, + "vid": 0, + "fabric": "fabric-stor" + }, + "links": [ + { + "id": 914, + "subnet": { + "cidr": "10.12.32.0/22" + }, + "ip_address": "10.12.32.40", + "mode": "static" + } + ] + }, + { + "name": "enp10s0", + "id": 104, + "type": "physical", + "vlan": { + "id": 5006, + "vid": 0, + "fabric": "fabric-repl" + }, + "links": [ + { + "id": 915, + "subnet": { + "cidr": "10.12.36.0/22" + }, + "ip_address": "10.12.36.40", + "mode": "static" + } + ] + }, + { + "name": "enp11s0", + "id": 105, + "type": "physical", + "vlan": { + "id": 5001, + "vid": 0, + "fabric": "fabric-prov" + }, + "links": [] + } +] diff --git a/tests/carve-host-interfaces/fix/if_fresh.json b/tests/carve-host-interfaces/fix/if_fresh.json index 6a36bb4..dd28c00 100644 --- a/tests/carve-host-interfaces/fix/if_fresh.json +++ b/tests/carve-host-interfaces/fix/if_fresh.json @@ -1,9 +1,86 @@ -[ {"name":"enp1s0","id":100,"vlan":{"id":5001},"links":[{"id":900,"subnet":{"cidr":"10.12.4.0/22"}}]}, - {"name":"enp7s0","id":107,"vlan":{"id":5002},"links":[]}, - {"name":"br-metal","id":200,"vlan":{"id":5002},"links":[{"id":901,"subnet":{"cidr":"10.12.12.0/22"}}]}, - {"name":"br-metal.103","id":201,"vlan":{"id":5003},"links":[]}, - {"name":"br-internal","id":202,"vlan":{"id":5003},"links":[{"id":902,"subnet":{"cidr":"10.12.16.0/22"}}]}, - {"name":"enp8s0","id":108,"vlan":{"id":5005},"links":[{"id":903,"subnet":{"cidr":"10.12.20.0/22"}}]}, - {"name":"enp9s0","id":109,"vlan":{"id":5006},"links":[{"id":904,"subnet":{"cidr":"10.12.32.0/22"}}]}, - {"name":"enp10s0","id":110,"vlan":{"id":5007},"links":[{"id":905,"subnet":{"cidr":"10.12.36.0/22"}}]}, - {"name":"enp11s0","id":111,"vlan":{"id":5001},"links":[]} ] +[ + { + "name": "enp1s0", + "id": 100, + "type": "physical", + "vlan": { + "id": 5001, + "vid": 0, + "fabric": "fabric-prov" + }, + "links": [ + { + "id": 900, + "subnet": { + "cidr": "10.12.4.0/22" + }, + "ip_address": null, + "mode": "dhcp" + } + ] + }, + { + "name": "enp7s0", + "id": 101, + "type": "physical", + "vlan": { + "id": 5002, + "vid": 0, + "fabric": "fabric-metal" + }, + "links": [ + { + "id": 901, + "subnet": { + "cidr": "10.12.8.0/22" + }, + "ip_address": "10.12.8.40", + "mode": "static" + } + ] + }, + { + "name": "enp8s0", + "id": 102, + "type": "physical", + "vlan": { + "id": 5004, + "vid": 0, + "fabric": "fabric-data" + }, + "links": [] + }, + { + "name": "enp9s0", + "id": 103, + "type": "physical", + "vlan": { + "id": 5005, + "vid": 0, + "fabric": "fabric-stor" + }, + "links": [] + }, + { + "name": "enp10s0", + "id": 104, + "type": "physical", + "vlan": { + "id": 5006, + "vid": 0, + "fabric": "fabric-repl" + }, + "links": [] + }, + { + "name": "enp11s0", + "id": 105, + "type": "physical", + "vlan": { + "id": 5001, + "vid": 0, + "fabric": "fabric-prov" + }, + "links": [] + } +] diff --git a/tests/carve-host-interfaces/fix/machine.json b/tests/carve-host-interfaces/fix/machine.json index 2b7b974..b8096dd 100644 --- a/tests/carve-host-interfaces/fix/machine.json +++ b/tests/carve-host-interfaces/fix/machine.json @@ -1 +1,3 @@ -{"status_name":"Ready"} +{ + "status_name": "Ready" +} diff --git a/tests/carve-host-interfaces/fix/machines.json b/tests/carve-host-interfaces/fix/machines.json index 34def38..3bd1a6c 100644 --- a/tests/carve-host-interfaces/fix/machines.json +++ b/tests/carve-host-interfaces/fix/machines.json @@ -1 +1,6 @@ -[{"hostname":"openstack0","system_id":"node-os0"}] +[ + { + "hostname": "openstack0", + "system_id": "abc100" + } +] diff --git a/tests/carve-host-interfaces/fix/sub_missing.json b/tests/carve-host-interfaces/fix/sub_missing.json new file mode 100644 index 0000000..23024b8 --- /dev/null +++ b/tests/carve-host-interfaces/fix/sub_missing.json @@ -0,0 +1,47 @@ +[ + { + "cidr": "10.12.4.0/22", + "id": 1, + "vlan": { + "id": 5001, + "vid": 0, + "fabric": "fabric-prov" + } + }, + { + "cidr": "10.12.8.0/22", + "id": 2, + "vlan": { + "id": 5002, + "vid": 0, + "fabric": "fabric-metal" + } + }, + { + "cidr": "10.12.12.0/22", + "id": 3, + "vlan": { + "id": 5003, + "vid": 103, + "fabric": "fabric-metal" + } + }, + { + "cidr": "10.12.32.0/22", + "id": 5, + "vlan": { + "id": 5005, + "vid": 0, + "fabric": "fabric-stor" + } + }, + { + "cidr": "10.12.36.0/22", + "id": 6, + "vlan": { + "id": 5006, + "vid": 0, + "fabric": "fabric-repl" + } + } +] diff --git a/tests/carve-host-interfaces/fix/sub_ok.json b/tests/carve-host-interfaces/fix/sub_ok.json index ccc8236..cc95e9e 100644 --- a/tests/carve-host-interfaces/fix/sub_ok.json +++ b/tests/carve-host-interfaces/fix/sub_ok.json @@ -1,7 +1,56 @@ -[ {"id":1,"cidr":"10.12.4.0/22","vlan":{"id":5001,"vid":0}}, - {"id":2,"cidr":"10.12.12.0/22","vlan":{"id":5002,"vid":0}}, - {"id":3,"cidr":"10.12.16.0/22","vlan":{"id":5003,"vid":103}}, - {"id":4,"cidr":"10.12.8.0/22","vlan":{"id":5004,"vid":104}}, - {"id":5,"cidr":"10.12.20.0/22","vlan":{"id":5005,"vid":0}}, - {"id":6,"cidr":"10.12.32.0/22","vlan":{"id":5006,"vid":0}}, - {"id":7,"cidr":"10.12.36.0/22","vlan":{"id":5007,"vid":0}} ] +[ + { + "cidr": "10.12.4.0/22", + "id": 1, + "vlan": { + "id": 5001, + "vid": 0, + "fabric": "fabric-prov" + } + }, + { + "cidr": "10.12.8.0/22", + "id": 2, + "vlan": { + "id": 5002, + "vid": 0, + "fabric": "fabric-metal" + } + }, + { + "cidr": "10.12.12.0/22", + "id": 3, + "vlan": { + "id": 5003, + "vid": 103, + "fabric": "fabric-metal" + } + }, + { + "cidr": "10.12.16.0/22", + "id": 4, + "vlan": { + "id": 5004, + "vid": 0, + "fabric": "fabric-data" + } + }, + { + "cidr": "10.12.32.0/22", + "id": 5, + "vlan": { + "id": 5005, + "vid": 0, + "fabric": "fabric-stor" + } + }, + { + "cidr": "10.12.36.0/22", + "id": 6, + "vlan": { + "id": 5006, + "vid": 0, + "fabric": "fabric-repl" + } + } +] diff --git a/tests/carve-host-interfaces/fix/sub_wrongvid.json b/tests/carve-host-interfaces/fix/sub_wrongvid.json index ed3dfb9..ce2f101 100644 --- a/tests/carve-host-interfaces/fix/sub_wrongvid.json +++ b/tests/carve-host-interfaces/fix/sub_wrongvid.json @@ -1,58 +1,56 @@ [ { - "id": 1, "cidr": "10.12.4.0/22", + "id": 1, "vlan": { "id": 5001, - "vid": 0 + "vid": 0, + "fabric": "fabric-prov" } }, { + "cidr": "10.12.8.0/22", "id": 2, - "cidr": "10.12.12.0/22", "vlan": { "id": 5002, - "vid": 0 + "vid": 0, + "fabric": "fabric-metal" } }, { + "cidr": "10.12.12.0/22", "id": 3, - "cidr": "10.12.16.0/22", "vlan": { "id": 5003, - "vid": 103 + "vid": 99, + "fabric": "fabric-metal" } }, { + "cidr": "10.12.16.0/22", "id": 4, - "cidr": "10.12.8.0/22", "vlan": { "id": 5004, - "vid": 241 + "vid": 0, + "fabric": "fabric-data" } }, { + "cidr": "10.12.32.0/22", "id": 5, - "cidr": "10.12.20.0/22", "vlan": { "id": 5005, - "vid": 0 + "vid": 0, + "fabric": "fabric-stor" } }, { + "cidr": "10.12.36.0/22", "id": 6, - "cidr": "10.12.32.0/22", "vlan": { "id": 5006, - "vid": 0 - } - }, - { - "id": 7, - "cidr": "10.12.36.0/22", - "vlan": { - "id": 5007, - "vid": 0 + "vid": 0, + "fabric": "fabric-repl" } } ] diff --git a/tests/carve-host-interfaces/make_fixtures.py b/tests/carve-host-interfaces/make_fixtures.py new file mode 100644 index 0000000..fcb9bac --- /dev/null +++ b/tests/carve-host-interfaces/make_fixtures.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +# tests/carve-host-interfaces/make_fixtures.py +# Emits fix/*.json for the carve-host-interfaces.sh harness -- Pattern A +# (D-060: br-ex OVS on enp1s0; br-metal/.103/br-internal; raw data/storage/ +# replication statics). Rewritten 2026-07-03 from the retired D-057 fixtures. +# ASCII + LF. +import json, os + +HERE = os.path.dirname(os.path.abspath(__file__)) +FIX = os.path.join(HERE, "fix") +os.makedirs(FIX, exist_ok=True) + +def w(name, obj): + with open(os.path.join(FIX, name), "w") as f: + json.dump(obj, f, indent=2); f.write("\n") + +def vl(vid, vid_id, fab): return {"id": vid_id, "vid": vid, "fabric": fab} + +SUBS = [ + {"cidr": "10.12.4.0/22", "id": 1, "vlan": vl(0, 5001, "fabric-prov")}, + {"cidr": "10.12.8.0/22", "id": 2, "vlan": vl(0, 5002, "fabric-metal")}, + {"cidr": "10.12.12.0/22", "id": 3, "vlan": vl(103, 5003, "fabric-metal")}, + {"cidr": "10.12.16.0/22", "id": 4, "vlan": vl(0, 5004, "fabric-data")}, + {"cidr": "10.12.32.0/22", "id": 5, "vlan": vl(0, 5005, "fabric-stor")}, + {"cidr": "10.12.36.0/22", "id": 6, "vlan": vl(0, 5006, "fabric-repl")}, +] +w("sub_ok.json", SUBS) +# metal-internal on the wrong VID -> hard gate +bad = [dict(s) for s in SUBS] +for s in bad: + if s["cidr"] == "10.12.12.0/22": s["vlan"] = vl(99, 5003, "fabric-metal") +w("sub_wrongvid.json", bad) +# a plane subnet missing entirely -> hard gate +w("sub_missing.json", [s for s in SUBS if s["cidr"] != "10.12.16.0/22"]) + +def nic(name, nid, vlan, links=None, typ="physical"): + return {"name": name, "id": nid, "type": typ, "vlan": vlan, + "links": links or []} + +def link(lid, cidr, ip=None, mode="dhcp"): + return {"id": lid, "subnet": {"cidr": cidr}, + "ip_address": ip, "mode": mode} + +# FRESH: post-commissioning tree -- L3 on the physical NICs, no bridges yet +w("if_fresh.json", [ + nic("enp1s0", 100, vl(0, 5001, "fabric-prov"), + [link(900, "10.12.4.0/22")]), # commissioning DHCP on the uplink + nic("enp7s0", 101, vl(0, 5002, "fabric-metal"), + [link(901, "10.12.8.0/22", "10.12.8.40", "static")]), # PXE link + nic("enp8s0", 102, vl(0, 5004, "fabric-data")), + nic("enp9s0", 103, vl(0, 5005, "fabric-stor")), + nic("enp10s0", 104, vl(0, 5006, "fabric-repl")), + nic("enp11s0", 105, vl(0, 5001, "fabric-prov")), # ex-lbaas, idle +]) + +# DONE: completed Pattern A -- re-run must be all-SKIP, zero WOULD +w("if_done.json", [ + nic("enp1s0", 100, vl(0, 5001, "fabric-prov")), + nic("br-ex", 200, vl(0, 5001, "fabric-prov"), + [link(910, "10.12.4.0/22", "10.12.4.40", "static")], typ="bridge"), + nic("enp7s0", 101, vl(0, 5002, "fabric-metal")), + nic("br-metal", 201, vl(0, 5002, "fabric-metal"), + [link(911, "10.12.8.0/22", "10.12.8.40", "static")], typ="bridge"), + nic("br-metal.103", 202, vl(103, 5003, "fabric-metal"), typ="vlan"), + nic("br-internal", 203, vl(103, 5003, "fabric-metal"), + [link(912, "10.12.12.0/22", "10.12.12.40", "static")], typ="bridge"), + nic("enp8s0", 102, vl(0, 5004, "fabric-data"), + [link(913, "10.12.16.0/22", "10.12.16.40", "static")]), + nic("enp9s0", 103, vl(0, 5005, "fabric-stor"), + [link(914, "10.12.32.0/22", "10.12.32.40", "static")]), + nic("enp10s0", 104, vl(0, 5006, "fabric-repl"), + [link(915, "10.12.36.0/22", "10.12.36.40", "static")]), + nic("enp11s0", 105, vl(0, 5001, "fabric-prov")), +]) + +w("machines.json", [{"hostname": "openstack0", "system_id": "abc100"}]) +w("machine.json", {"status_name": "Ready"}) diff --git a/tests/carve-host-interfaces/run-tests.sh b/tests/carve-host-interfaces/run-tests.sh index 34bfbfa..7c23a5e 100644 --- a/tests/carve-host-interfaces/run-tests.sh +++ b/tests/carve-host-interfaces/run-tests.sh @@ -1,13 +1,16 @@ #!/usr/bin/env bash -# Behavior regression for carve-host-interfaces.sh D-057 provider-vip split. -# Fake `maas` (read-only fixtures) + real jq. Drives the carve in DRY-RUN for openstack0 -# (octet .40) and asserts the emitted WOULD-actions (and absences). +# Behavior regression for carve-host-interfaces.sh -- Pattern A (D-060). +# Fake `maas` (read-only fixtures) + real jq. Drives the carve in DRY-RUN for +# openstack0 (octet .40) and asserts the emitted WOULD-actions and absences. +# Rewritten 2026-07-03 from the retired D-057 provider-vip expectations +# (harness must travel with its script's scheme). set -uo pipefail HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" CARVE="$(cd "$HERE/../../scripts" && pwd)/carve-host-interfaces.sh" BIN="$HERE/fakebin"; FIX="$HERE/fix" chmod +x "$BIN"/* 2>/dev/null || true # GitHub Desktop lands files mode 100644 command -v jq >/dev/null || { echo "FAIL: jq required"; exit 1; } +python3 "$HERE/make_fixtures.py" >/dev/null rc_all=0; OUT="$(mktemp)" run() { #