| 2026-07-27 |

Session close: GA-R4 bookend, skill sweep, ledger rotation, permission block removed
...
CLOSE SET, all executed and verified:
BOOKEND (GA-R4). The STAGE-5 GROUNDING AUDIT close bookend had already landed
while the operator was away, by design. They then returned and worked the queued
rulings, so this session's second entry is recorded as a POST-CLOSE ADDENDUM
rather than a second close -- the 2026-07-25/07-26 precedent, which states the
convention in terms: "The close bookend landed early by design; this addendum
records the work that followed it rather than re-opening the entry." Catching
that avoided leaving TWO close entries for one session. Addendum is 12 bullets
against the 15 cap.
LEDGER ROTATION (GA-R4 rule 3 / F1). The addendum took the live ledger to 321
lines against the 300 cap. One oldest-first pass left it at 307, still over, so a
SECOND pass was taken in the same close: both 2026-07-25 summaries (handoff-pack
execution, and MAAS admin-account recovery) moved VERBATIM to
docs/archive/session-ledger-rotated-20260727.md. Live ledger now 292 lines, five
entries, from the 2026-07-26 D-137 addendum onward.
SKILL SWEEP, done although NO STAGE CLOSED, on operator direction. Three new
invariants folded into SKILL.md, each earned this session:
- A FINDING IS AN OBSERVATION, NOT A CONCLUSION -- measure before putting it to
the operator. Four of the first six questions changed shape on measurement and
three were withdrawn or re-scoped.
- A CITATION IS AN EXISTENCE CLAIM; ONLY ITS CONTENT IS EVIDENCE -- both bugs
behind the v4-only lb-mgmt draft failed on reading and the conclusion inverted.
No gate reads prose, so nothing in a repo can catch this.
- RULED IS NOT BUILT -- D-134's bands and D-020's vault VIP were both properly
ruled and never implemented, and passed every gate for weeks.
Into references/script-authoring.md: the repo-lint L5 heading trap (a heading
LEADING with a D-number needs AMENDMENT or RESOLVED on the same line -- it bit
this session twice and was on no author-facing surface), the commit-gated-on-lint
pattern that then caught its second occurrence, and the space-aligned .tsv.
Into references/operating-discipline.md: the probe PATH determines the answer --
the office1 VMs read "unreachable" via voffice1 and NO-CLONE from vcloud, and only
the second is a finding.
Snapshot regenerated as openstack-cloud-ops-consolidated-20260727.md: 1695 lines,
ASCII/LF byte-verified, all five new items present. A stale snapshot is the
recorded failure mode of docs/audit/skill-divergence-20260725.md.
CHANGELOG extended with the full ruling table (14 ruled, 2 withdrawn, G18 opened)
and the sweep/skill sections, each carrying its revert.
PERMISSION BLOCK REMOVED as promised at grant time: allow 303 -> 248, ask 24 -> 7.
All 55 session-scoped allow rules deleted and the five original broad
`ssh voffice1 "maas admin <noun> *` ask rules restored in place of the 23
verb-scoped ones. JSON validated. The file is gitignored, so the scope doc's
section 6 and this changelog are its only durable record.
FINAL VERIFICATION: repo-lint 0 fail / 1 warn (the L1 legacy carve-out, expected);
gauntlet ALL GREEN (81 harnesses); ledger-scan reconciles at 21 open SEC rows and
next-free D 138 / DOCFIX 205 / BUNDLEFIX 053 -- UNCHANGED, correctly, because
fourteen rulings were recorded and nothing was remediated.
Revert: git revert this commit; the skill edits are additive, the snapshot is
derived, and the ledger rotation is reversible from the archive file.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

close-out durability sweep: 4 transcript-only items landed; dc1-is-not-a-mirror warning
...
Operator-requested sweep for content that existed only in the session transcript, before
clearing. Precedent: docs/audit/queued-findings-20260726.txt. Capture:
docs/audit/queued-findings-20260727.txt.
THE CONSEQUENTIAL ONE. scripts/dc-mirror.sh's dc1 site row carried NO warning that dc1 is no
longer a mirror site, so `dc-mirror.sh install dc1` would have SILENTLY REBUILT everything the
same-day teardown removed -- units, nginx vhost, sync helper, and an ENABLED daily debmirror
timer that would start a fresh ~950G pull on a rack whose ruled artifact path is the proxy. That
is precisely the command someone would reach for after treating a failing `check dc1` as a
regression. The row now states dc1 is proxy-only per the D-135 amendment, that `check dc1` FAILS
BY DESIGN, and that `install dc1` is a deliberate strategy change and never a repair. The row is
RETAINED so its measured values survive a legitimate rebuild.
QUEUED, not built (hard rule 1): a RUNTIME guard in do_install for a site whose ruled path is
not the mirror. A comment is strictly weaker than a guard, and prose-only prevention has already
failed twice on this project (DoD item 8 missed dc1; D-137's wiring sat as prose and missed BOTH
DC standups). Recording that the interim is an interim.
A WRONG CLAIM CORRECTED. docs/audit/stage4-mirror-gate-20260727.txt said `systemctl reset-failed`
would re-arm the paused dc1 debmirror. It cannot: reset-failed on the SERVICE does not start an
INACTIVE TIMER. The real vector was a REBOOT -- `enabled` starts the timer at boot and
`Persistent=yes` fires it IMMEDIATELY because its window had passed. Correction APPENDED rather
than edited in place: the file is dated evidence, so a wrong analytical claim is superseded
openly, not quietly rewritten.
THE GENERALISABLE LESSON, previously unrecorded, is now platform-traps section 5:
5a "stopped is not dormant across a reboot" -- enabled + inactive + EMPTY NextElapse looks
harmless and is not; plus reset-failed does not re-arm, and Result=signal/status=15 on a
oneshot means it was SIGTERM'd and never wrote a terminal state (so a stale RUNNING is a
corpse, not progress).
5b a `oneshot` with RemainAfterExit=yes does NOT undo its work on stop, so stopping it proves
NOTHING about dependents -- which is exactly why the dc1 teardown test deleted the live
address and route instead of stopping the mirror net unit.
Three rows added to the verbatim-error index. Note dc0-mirror-sync.timer is still enabled with
Persistent=yes, so 5a describes dc0 today -- benign while its syncs succeed, same shape.
HARD RULE 2. dc-cache-proxy.sh stated that the two net units coexist safely on one host as
FACT. It is REASONED (idempotent replace verbs, identical measured values), never MEASURED --
the proxy has only run on dc1, where the mirror unit was then removed, so nobody has run both.
Marked accordingly, with an instruction to verify rather than trust the sentence.
VERIFICATION: dangling-reference sweep over docs/, runbooks/, scripts/ and the skill -- every
path this session introduced or cited RESOLVES; all pre-existing dangles are legitimate
(deleted-as-history, not-yet-built, or the deliberately-absent octavia PKI overlay preflight P4
fails on). ledger-scan reconciled against the machine-derived block. Gauntlet ALL GREEN (81),
repo-lint 0-fail, dc-mirror 24/24, dc-cache-proxy 20/20.
Revert: text + comments only; the capture correction is an append.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

Stage 4 close-out set: GA-R2 consolidation, skill sweep, snapshot, GA-R7 review
...
Every substantive Stage 4 item is closed or split to gate row G17. This is the GA-R6 close-out
set; only the operator-gated merge to `main` remains.
GA-R2 CONSOLIDATION: the 7 stage changelogs archived to docs/archive/changelogs/ (top-level
docs/ 25 -> 18, 113 archived total) and the stage record written at
docs/archive/stage-records/vr1-stage4-record.md. It records what the stage actually delivered
(READY handoff, not deployed nodes), the three things a future session should read first, and
the false-green pattern this stage kept surfacing. 12 stale `docs/changelog-*` paths in live
surfaces rewritten to the archive -- including several already dangling from the G12 close, so
every changelog reference in CURRENT-STATE / session-ledger / security-ledger now resolves.
SKILL SWEEP: three new INVARIANTS folded in --
- per-DC ARTIFACT DELIVERY is a per-DC STRATEGY (dc0 full mirror / dc1 caching proxy, D-135
amendments); checking dc1 as a mirror fails BY DESIGN. Corollary, learned the hard way: a
per-DC utility service OWNS ITS OWN network prerequisites -- never let one strategy's
teardown be able to break the other's.
- Stage 4 hands off READY nodes, NOT deployed ones (DOCFIX-200). Any surface saying "nodes
Deployed" as a Stage-4 exit is a defect; four were found and fixed. Consequence: nothing
node-side is probeable in Stage 4, which is why G17 exists.
- "A CHECKER THAT CANNOT FAIL IS NOT A GATE", with the working rules that came out of two
measured false greens: assert on CONTENT not existence; an unrecognised state REFUSES;
"could not look" is never "nothing there"; ENUMERATE what exists rather than only auditing
what is declared, because a register cannot report the absence of a row it does not have;
and when a test asserts a literal finding string, REPLACE the assertion, never delete it.
Plus two routing rows (per-DC artifact checks; D-137 as the credential-lifecycle authority).
SNAPSHOT REGENERATED: .claude/skills/openstack-cloud-ops-consolidated-20260727.md (1589 lines,
ASCII + LF byte-verified, all 7 sections). The superseded 20260725 snapshot is REMOVED rather
than kept alongside -- a stale snapshot being uploaded is the exact failure
docs/audit/skill-divergence-20260725.md records (a failed clone + a mis-shaped handoff), and it
is a derived artifact regenerable from any commit.
GA-R7 MEMORY REVIEW: NO new memory written -- everything durable from this stage graduated to
the skill and the repo, which is the correct GA-R7 outcome, not an omission. Both existing
entries verified against the repo (the finding-class baseline they cite does exist) and extended
with the two reasoning traps this stage produced: the register cannot see a credential it has no
ROW for, so enumerate what exists; and diagnose a denied call by checking whether an existing
rule failed to MATCH before blaming the classifier (the measured 2026-07-26 root cause). Also
marked the ask-vs-allow precedence claim UNVERIFIED so no future session relies on it.
Noted, not actioned (hard rule 1): T24's finding-class baseline covers TIER 1 only, so tier-2/3
classes have no baselined red state and a future false green there would not be caught.
Gauntlet ALL GREEN (81), repo-lint 0-fail.
Revert: git revert this commit; the changelog moves are renames and the snapshot is derived.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|
| 2026-07-25 |
skill: stage-close sweep keeps the authoritative dir current + regenerates the Chat snapshot
...
SKILL.md stage-close rule now states the authoritative, versioned skill is the
.claude/skills/openstack-cloud-ops/ dir (SKILL.md + references), kept current at each
stage-close sweep, and the dated single-file Chat-upload snapshot is REGENERATED from it
then (a derived artifact, never the source; the stale Chat mount is a known failure mode).
Regenerated openstack-cloud-ops-consolidated-20260725.md from the updated skill so the two
stay in sync. ASCII/LF verified; repo-lint 0 fail.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUjEb7onHpdqFHUnio6iVw
|
| 2026-07-23 |
G12 dc1 close-out: consolidation + skill sweep + CURRENT-STATE (G12 CLOSED)
...
- GA-R2 consolidation: 3 dc1 session changelogs (07-21 authoring, 07-22 build,
07-23 edge) archived to docs/archive/changelogs/; Stage 3 record gains a
"G12 CLOSED 2026-07-23" section + table rows
- CURRENT-STATE: G12 gate CLOSED, section-1 stage narrative updated
- skill sweep: per-DC dedicated MAAS->libvirt power key (SEC-012/SEC-016)
added as a standing DC-standup invariant
- GA-R7 memory review: clean (no changes)
Close-out set complete; ready for operator-gated merge to main.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUjEb7onHpdqFHUnio6iVw
|
| 2026-07-21 |
STAGE 3 CLOSE (operator-ruled): GA-R2 consolidation + skill sweep; gauntlet 76 ALL GREEN
...
- Four execution-session changelogs -> docs/archive/changelogs/;
vr1-stage3-record.md finalized (close summary + session table)
- CURRENT-STATE stage line CLOSED (vr1-dc0 scope; dc1 = HELD G12);
workflow Stage-3 State row + runbooks/README corrected
- Skill sweep: D-131 forwarder invariant + MAAS pod traps + merge
precedent folded; no stale facts found
- Close-out evidence: gauntlet 76 ALL GREEN, repo-lint 0 fail, GA-R7
memory review done (changelog items 11, 17)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013XRtm5sDQgUsZyTJk3k7dz
|
Incident docs: 2 appendix-A entries, platform-traps MAC-regen corollary, LP draft
...
Closes the remaining documentation queue from the 2026-07-21
commissioning incident. Appendix-A gains the MAC-drift and
agent-resolver symptoms (verbatim, with checks + recorded fixes);
platform-traps 1e gains the unpinned-MAC regeneration corollary + index
row; LP draft ready for the operator to file (sanitization warning
included). Still queued: stale pod cleanup at stage close.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STHXiHfoxHqq8fGRVvb66G
|
| 2026-07-19 |
Batch 5: skill sweep -- GA-R1..R7 implemented in openstack-cloud-ops (single commit)
...
New GA-rulings section reconciled against the RATIFIED text (draft
skill-additions cross-checked; the one draft-only item, the
reconciliation circuit-breaker, kept and marked as discipline). Stale
facts demoted to CURRENT-STATE delegation: the header's "current phase =
VR0 testcloud" and the deploy loop's "opentofu UNVALIDATED / no tofu
binary has ever run" (false since 2026-07-13). Session bookends + stage
close rewritten per GA-R4/GA-R6; change-delivery per GA-R2/D1; routing
rows re-pointed. Read-through checklist committed:
docs/audit/skill-sweep-checklist-20260719.md (every ratified rule ->
disposition). G3 cell updated same commit. repo-lint 0 fail.
Revert: git revert this commit (restores the pre-sweep skill).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZbUeRE8weySLSGY1odrCi
|

Batch 4 C6: docs disposition -- 24 history docs archived, 16 retained, refs rewritten
...
DISPOSITION MANIFEST (archive, one-line justifications): clientdocs-
workflow-review (v1 tenant-docs era) / D-057-DECIDED-append +
D-057-REVIEW-ITEMS + README-D057-PACK + D-058-renumber (executed v1
decision packs) / D-068-openbao-assessment-DRAFT (supplementary; the
cited analysis retained) / DOCFIX-064-phase08-changelist + docfix-draft-
20260702 (executed/draft) / handoff-20260703 + handoff-20260705
(superseded handoffs) / incident-20260712-triplefault (trap routed to
platform-traps 1b/1c long ago) / netbox-vip-queue + phase-00-maas-
standup-notes + script-quality-findings + session-findings-2026-07-02 +
tenant-cidr-overlap-PLAN + v1-pre-deploy-fixes (v1-era executed/dated) /
repo-lint-nextfree-bug-FINDING (absorbed DOCFIX-105/107; repo_lint.py
pointer updated) / upstream-bug-draft-dashboard-tls (draft, dated) /
model-a-fallback-plan (D-123 Model B adopted; fallback preserved in
archive) / stage3-adversarial-review-20260716 (R3-F register,
dispositioned in 2.10) / dc-dc-replication-DR-seed (absorbed per
workflow) / dc-dc-ipv6-charm-research + dc-dc-netem-and-ula-gua-proposal
(stage 4+ research, refs updated). RETAINED 16 top-level, justified in
the session changelog. Live-surface refs to all moved files rewritten to
archive paths (18 files). G3 cell + ledger + changelog updated same
commit (the L10 fire on a Status-line path rewrite is satisfied by the
CURRENT-STATE touch -- the check worked as designed).
Revert: git revert this commit.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZbUeRE8weySLSGY1odrCi
|

Batch 4 C1: GA-R4 rule-6 ledger rotation -- 1187 -> 131 lines (GA-F13)
...
Bodies rotated VERBATIM to docs/archive/session-ledger-rotated-20260719.md
(Live state 2026-07-13, OPEN WORK VR1+VR0, NetBox write-path bugs, PINNED
rulings, standing lessons, state facts, project-completion, NetBox import
narrative). Routed-before-rotating (durable homes): juju format=line 3.6
trap -> appendix-A; guards-are-load-bearing -> operating-discipline; VR0
operational facts + admin/controller -> maas-as-built-reference.md
(lessons 1-6 verified already covered by platform-traps/L8). Live ledger
keeps header + machine-derived fence + two compact still-live blocks
(NetBox write-path bugs pointer, project-completion) + the bounded session
summaries. 131 < 300: the F1 cap is now ENFORCEABLE. Fences OK; ledger-
scan counters unchanged; repo-lint 0 fail.
Revert: git revert this commit (restores the 1187-line ledger).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZbUeRE8weySLSGY1odrCi
|

Batch 3 CLOSE: memory hygiene (GA-F05..F08 memory surfaces) + G1 review; GA-F16 registered
...
Per the recorded provenance rulings: review-after-each-step.md REMOVED
(GA-F06 -- one-time instruction, never standing) and project-posture-
closed-test.md REMOVED (GA-F07 -- "ruling" never ruled; posture NEUTRAL);
hostname -> vcloud in the Remote Control memory (GA-F05 last surface);
MEMORY.md index rebuilt (GA-F08). GA-R7 rule-6 skill line added to
SKILL.md bootstrap. First full G1 review (rules 1-4) found + fixed one
further violation: creds-folder-convention.md duplicated a repo-carried
convention and linked both removed files -- registered as GA-F16, reduced
to a pointer entry. Post-review: 3 memory files, zero violations, zero
stale refs (grep-verified). Pre-edit copies: docs/archive/memory-20260718/
(index copy ASCII-normalized, noted in the changelog). G3 cell updated
same commit (L10 satisfied).
Revert: restore files from docs/archive/memory-20260718/ (memory side);
git revert this commit (repo side).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZbUeRE8weySLSGY1odrCi
|
Batch 2 CLOSE: verification + adjudication recorded; last GA-F03 site fixed
...
Gauntlet ALL GREEN (70); repo-lint 0 fail / 1 legacy WARN (L10 at FAIL).
platform-traps:19 tofu pin (the fifth GA-F03 site) demoted; CURRENT-
STATE's known-stale-pins paragraph rotated (all sites fixed, memory copy
= Batch 3). G3 cell: Batches 0-2 CLOSED. Extractor adjudication + the
criterion-mechanics FINDING recorded in the session changelog (live-
surface vocab flags 0; residual groups history-class or mechanical;
literal ZERO-on-counted lands with Batch 4 + Batch 6.1 by construction).
Revert: git revert this commit (docs + one skill row).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZbUeRE8weySLSGY1odrCi
|

GA-F11/GA-F12: version rows + broken refs on live surfaces adjudicated (2.9)
...
Version groups (juju/lxd/maas/netbox): the single live-surface claim row
(skill environment.md:29 "Juju 3.6, MAAS 3.7.2") demoted to the two pin
authorities (appendix-B for VR0, CURRENT-STATE section 7 for VR1). The
remaining group members are extractor false positives (step numbers
"2.1"/"3.1", charm channel "2024.1", the LXD ">= 6.7" incompatibility
THRESHOLD) or history-class rows (register/ledger/changelogs -- Batch 4).
Broken path refs: 192 non-history rows adjudicated -- ALL false positive
(provider-repo doc paths in skill references, glob/range forms like
phase-00..08, slash-phrases like "netbox/tailscale", wrapped names) or
refs inside VR0-era history docs whose routing is Batch 4; zero real
broken refs on live operational surfaces (spot-checks in the session
changelog). Batch 2 item 2.9.
Revert: git revert this commit.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZbUeRE8weySLSGY1odrCi
|
GA-F04: skill OPNsense 26.1 rows repointed/requalified (live edge is 26.7)
...
platform-traps: pin-table row points at CURRENT-STATE section 7; section-4
header + fresh-image prose requalified as traps that ENTERED at 26.1 and
persist on later releases (all three traps re-verified applicable to 26.7
per the 2026-07-18 D-129 review). environment.md drops both its OPNsense
pin AND a discovered stale tofu-1.12.3 pin (GA-F03 class, same surface)
for the CURRENT-STATE pointer. Batch 2 item 2.3.
Revert: git revert this commit.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZbUeRE8weySLSGY1odrCi
|
GA-F05: jumphost name corrected to vcloud on always-loaded surfaces
...
CLAUDE.md:4 + SKILL.md:46 per the finding, plus a fourth surface
discovered in the same sweep: references/environment.md:43 (same defect
class, same measured fact -- hostname -> vcloud, env snapshot). The
memory-file instance is Batch 3's item per the plan. Batch 2 item 2.4.
Revert: git revert this commit.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZbUeRE8weySLSGY1odrCi
|
| 2026-07-17 |
Gap register #20: durable host->site reachability CLOSED (Office1); skill carries the per-DC discipline
...
- docs/dc-dc-deployment-workflow.md: new tooling-gap item 20 -- the durable
host->isolated-site base leg had no owning tool; CLOSED for Office1 via
scripts/site-baseleg.sh (installed+enabled 2026-07-17; boot-race unproven until
a reboot). Documents the DC generalization so the gap does not reopen per-DC.
- SKILL.md: operating-model invariant extended -- durable host->site reach is
standing per-SITE tooling (DCs included): at each DC standup MEASURE the reach
and, if the DC net is ISOLATED, add a MEASURED site-baseleg.sh leg row + install
as part of standup DoD (never an inferred DC IP; harness enforces it).
Doc-only follow-up to 1958063. Nothing merged to main (stage-close).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QMKdXCquMnh1jKH2sWXPMg
|

D-126 base-leg oneshot (site-baseleg.sh) + D-128 operating model; post-restart sweep
...
vcloud post-restart health sweep all-green (host on 6.8.0-136, patch+reboot done;
libvirt/pools/autostart clean; Office1 stack self-recovered end-to-end). Sweep
surfaced a recurring break: office1-local is an ISOLATED libvirt net, so the host's
base L3 leg (10.10.0.10/24 on the bridge + compose route 10.10.1.0/24 via 10.10.0.20)
lives nowhere in the XML and drops on every reboot -- the un-persisted foundation
D-126 assumed but never owned.
- NEW scripts/site-baseleg.sh + tests/site-baseleg/ (24/24): root Type=oneshot unit,
After=libvirtd (+virtnetworkd for modular hosts; ordering MEASURED here = monolithic
libvirtd), re-adds the leg at boot. Bridge DISCOVERED from the stable net name (never
a baked virbrN, PATTERN-1); apply idempotent + root-guarded; check = session-verify.
INSTALLED + enabled this session; boot-race UNPROVEN until an actual reboot.
- docs/design-decisions.md: D-126 amendment (base-leg durability) + NEW D-128 (operating
model -- Claude on the vcloud jumphost; Plane-1 substrate on vcloud / Plane-2 executes
on voffice1; workstation tailnet is the human path; D-107 untouched).
- Skill invariant + routing row (SKILL.md), Cross-cutting bullet (dc-dc-deployment-workflow.md),
session-ledger + changelog.
Gauntlet ALL GREEN (67 harnesses), repo-lint 0-fail. Nothing merged to main (stage-close).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QMKdXCquMnh1jKH2sWXPMg
|
| 2026-07-16 |

D-125: Model B per-DC ISP egress (bridge-in, single-NAT) -- resolves OBS-3 design gap
...
OBS-3: Model B nesting put vr1-dc0-wan (a libvirt NAT) inside vvr1-dc0, whose
only leg is the SEC-010 FORWARD-dropped transit -- so the per-DC simulated ISP
had no egress. Operator ruled bridge-in (single-NAT) over double-NAT.
- modules/wan-bridge (NEW): libvirt forward=bridge onto a pre-existing host
bridge (bridge={name}, schema measured from the provider, not guessed). No
subnet/NAT. Sibling to site-wan so every NAT user stays untouched. Covered by
opentofu-validate S3 (standalone PASS).
- OUTER root: vcloud ISP NAT vr1_dc0_uplink (the single NAT) + a 2nd IP-less
uplink NIC on vvr1-dc0 + the br-vr1-dc0-wan bridge declared in netplan (transit
stays NIC1 so SEC-010/--transit-if keep keying on it). New HELD var
vr1_dc0_uplink_cidr (no default; office1-netbox apex, hard rule 2).
- INNER root: vr1_dc0_wan site-wan(NAT) -> wan-bridge onto br-vr1-dc0-wan;
OPNsense WAN re-addresses into the vcloud ISP /24 (edge config, D-113, HELD).
- Bootstrap (site-headend-install.sh): --uplink-if/--wan-bridge; --check verifies
the bridge exists + uplink ENSLAVED (fail-open guard, same class as the transit
check); SEC-010 rationale corrected (bridge-in has no inner NAT) + br_netfilter
CONSTRAINT (keep the FORWARD-drop interface-scoped, NEVER global). Harness 49->54.
Records: D-125 (new); D-122 amendment (forward-pointer -- WAN realization
superseded, intent stands); SEC-010 (br_netfilter + hardened --check);
model-a-fallback B->A delta item 8 + revert; platform-traps 1g (bridge +
br_netfilter + nested-foreign-MAC trap).
NOT closed: the L2-NAT-of-foreign-MAC path is a DEPLOY-TIME gate (bridge-in
equivalent of the depth-4 boot gate); fallback = double-NAT. HELD: vr1_dc0_uplink_cidr
+ OPNsense WAN re-address. Both roots validate; gauntlet ALL GREEN (63); repo-lint
0 fail. Present-only; NOT pushed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ck6xh3jWQi5b3Su8Dx1LEH
|
| 2026-07-15 |
stage-close discipline: add the operating-skill sweep as part of definition-of-done
...
Per operator: the skill-update sweep must itself be part of every stage close, so it
is never skipped. At stage close (alongside the branch->main merge + branch retire),
sweep .claude/skills/openstack-cloud-ops/ for any new INVARIANT the stage established
and reconcile divergence from repo HEAD. Recorded in both the workflow doc's
Cross-cutting discipline and the skill's own VR1 deploy-loop stage-close note.
repo-lint 0 fail.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015WgVrZE2ryY9aXP7JiBuwj
|
skill: add the standing merge-at-close discipline + the DOCFIX-195 apex pointer
...
The operating skill delegates facts (stage status, IPs) to the repo by design, so
Stage-2-merged/D-120/etc. need no skill edit. But two things are the skill's own
domain and were missing:
- The new INVARIANT: branch-per-stage + MERGE AT STAGE CLOSE (merge commit,
operator-gated, retire the branch, next stage off post-merge main). Added to the
VR1 deploy loop as part of a stage's definition-of-done. Precedent: Stage 2 -> PR #1.
- A DOCFIX-195 pointer on the IPAM routing row: during VR1 the WORKING apex is
office1-netbox (netbox.baldurkeep.com is a read-only v1 reference, write-back
deferred). This exact ambiguity cost time early this session.
repo-lint 0 fail (L8 guide<->skill coupling pins clientdocs, not this skill -- unaffected).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015WgVrZE2ryY9aXP7JiBuwj
|
| 2026-07-14 |

D-117 (PROPOSED): VR1 site naming collision blocks the NetBox import
...
Measured live against the apex: prod NetBox binds 2602:f3e2:f02::/48 -> site
VR1 DC0, but dc-dc-prefixes-import.py maps --dc dc1 -> slug vr1-dc1 and treats
f02::/48 as dc1. The addressing agrees; the labels are off by one. Because that
importer has NO --commit (it WRITES BY DEFAULT), running it as documented against
a prod-seeded NetBox would silently bind DC1's prefixes to the other DC's site.
This is the known G5 gap, promoted to a decision because it blocks the import.
Options A/B/C recorded; A (rename the two skeletal apex sites) recommended, with
the slug-collision rename ORDER called out as a trap.
Also records the upstream-NetBox User-Agent WAF trap in platform-traps.md: the
same token that works from curl gets 403 from urllib/requests/pynetbox. It will
bite pynetbox and looks exactly like an auth failure.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013ZBxzAbmqwLW7jEmMG23Xw
|
| 2026-07-13 |

Office1 headend LIVE: MAAS 3.7.2 + LXD 5.21 on voffice1. D-114 PROVEN END TO END.
...
MAAS composed, PXE-booted and COMMISSIONED its first VM inside voffice1. The model works.
vcloud (L1 -- itself a KVM guest, measured)
+- voffice1 (L2) MAAS 3.7.2 region+rack + PostgreSQL 16.14 + LXD 5.21.5
+- office1-netbox (L3) LXD VIRTUAL-MACHINE, MAAS-composed, MAAS-PXE-booted,
leased 10.10.1.100 from MAAS, status READY, 2c/4096MiB detected
THE L3 PROOF IS NOW DEFINITIVE. Earlier today we could only say nested KVM was AVAILABLE
(/dev/kvm + svm present). Now a guest has actually BOOTED, PXE'd, COMMISSIONED and reported its
hardware three levels deep. D-114's Office1 gate is CLEARED. This reproduces VR0's `lxd` +
`tailscale` pattern exactly -- and the Juju-created LXD CONTAINERS remain invisible to MAAS, as in VR0.
THE DHCP SPLIT IS STRUCTURAL, NOT CONVENTIONAL:
10.10.0.0/24 (site LAN) -> Kea on the OPNsense edge MAAS dhcp_on=False
10.10.1.0/24 (lxdbr0) -> MAAS MAAS dhcp_on=True
Separate L2s; lxdbr0 is never bridged onto the site LAN, so they cannot see each other. The
installer asserts after configuring DHCP that MAAS owns NO other subnet, and hard-fails otherwise.
NEW: scripts/site-headend-install.sh + tests/site-headend-install/ (18/18 PASS). It is the sequence
we ACTUALLY EXECUTED, codified -- not written from docs -- and reusable verbatim for DC1/DC2. Its
--check mode was run against the live voffice1 and reports every item OK.
FOUR TRAPS, ALL HIT FOR REAL TODAY, ALL NOW ENCODED AND HARNESS-ASSERTED:
1. `lxd init --auto` FAILS on a MAAS host. It brings up lxdbr0 with LXD's own DHCP+DNS, which starts
a dnsmasq; dnsmasq binds the WILDCARD 0.0.0.0:53 and MAAS's bind9 already holds :53. The error
names the BRIDGE address ("failed to create listening socket for 10.10.1.1"), which sends you
hunting the wrong thing. `ipv4.dhcp=false` + `dns.mode=none` are NOT enough -- LXD still spawns
dnsmasq. The fix is raw.dnsmasq="port=0". Verified after: dnsmasq holds ZERO :53 and ZERO :67.
2. `lxc` READS CONFIG FROM STDIN. Piped to `bash -s` over ssh, the first unredirected `lxc` call
EATS THE REST OF THE SCRIPT as YAML and everything after it silently vanishes. Every lxc call now
ends in </dev/null. Same class as the OPNsense tcsh trap.
3. LXD snap track changes are ONE-WAY -- install DIRECTLY onto 5.21/stable, never via `latest`.
MAAS 3.6/3.7 is incompatible with LXD >= 6.7. (Bonus: core.trust_password exists in 5.21, gone in
6.x -- the pin also preserves the scriptable registration path.)
4. MAAS DHCP on the compose network ONLY. --compose-cidr is REQUIRED with no default so it can never
be picked by accident.
DISCIPLINE: every MAAS/LXD flag was read from the tool's own --help ON THE BOX before use (maas init,
createadmin, vm-host compose), not trusted from docs. All secrets (DB, admin, API key, LXD trust) are
generated on the target, stored 0600 under /root/maas-secrets/, and never printed into the session.
10.10.1.0/24 IS AN UNBLESSED ALLOCATION -- chosen deliberately (DC1=10.12.x, DC2=10.13.0.0/19, tenant
pool=10.20.0.0/16 per D-016; the obvious 10.20.x would have COLLIDED with tenant space), but NetBox
has not assigned it. Register once NetBox exists -- it is one of the VMs this headend composes.
Also: the ops skill is now tuned to this exact stack (new references/platform-traps.md with a
verbatim-error -> cause index; the OPNsense section that still routed sessions into the DELETED
config-ISO path is replaced). Upstream corroboration found for two of our own findings: the libvirt
provider's release notes document that an "in-place" domain update undefines and re-defines the
domain (our measured guest-bounce), and there is an upstream issue for the AppArmor denial.
Gauntlet ALL GREEN (53 harnesses). repo-lint 0 fail.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
| 2026-07-10 |
DOCFIX-177: make the skill and runbooks/README.md aware the VR1 DC-DC track exists
...
Found while the operator was heading to the vcloud host to begin real
execution: SKILL.md and runbooks/README.md (what its routing table points
to) had zero mentions of the entire VR1 DC-DC track, so a fresh session
would be routed to VR0's phase-01..08 runbooks with no indication today's
actual mission (dc-dc-phase0..6 + the new teardown runbook) exists.
Added a "VR1 DC-DC track" index to runbooks/README.md, VR1-specific rows
to SKILL.md's routing table and standard loops, and pointers in
references/environment.md and references/troubleshooting.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|

DOCFIX-148: OpenTofu module audit pass + provider-docs skill reference
...
Audited every "UNVERIFIED"/"inferred"/"assumed" marker across all 9 modules
built this session rather than leaving them as-is. Found and fixed a real
documentation error: opnsense-edge's comments claimed devices.interfaces
list order directly sets OPNsense's LAN/WAN role. Researching OPNsense's own
interface-assignment model showed this conflates two things -- list order
plausibly controls which vtnetN device number a NIC gets (libvirt's own
docs: auto-assigned PCI addresses "usually match" XML order for a simple
topology), but the actual LAN/WAN role is a separate, explicit mapping set
inside config.xml itself ("vtnet0=WAN" is a convention some guides choose,
not an enforced rule). Corrected main.tf's comments and both
lan_network_name/wan_network_name variable descriptions -- no config.xml
exists in this repo yet, so this fixes documentation accuracy for whoever
writes it next, not current behavior.
Confidence upgraded (not just re-hedged) on three other items: node-vm's
boot={order=N} shape (confirmed the provider 1:1-mirrors libvirt's own XML,
and libvirt's own docs confirm the single-attribute shape), create.content.url
accepting local paths (second independent source), and the genisoimage
flags (confirmed standard, cleanly separated from the still-open question of
whether OPNsense's Importer actually reads the result). Confirmed safe:
maas_vm_host's zone/pool computed-if-unset behavior.
Added .claude/skills/openstack-cloud-ops/references/opentofu-provider-docs.md:
indexes every provider/doc source used this session with exact URLs and
confirmed versions, plus the fetch methodology that actually worked
(Registry doc pages are JS-rendered, use GitHub instead; branch names vary,
check default_branch via the API; real example .tf files beat doc-summarized
prose for syntax questions). Wired into SKILL.md's routing table,
cross-referenced from opentofu/README.md in both directions without
duplicating content.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|
| 2026-07-09 |

DOCFIX-139..141: repo-rename sweep (ipv4->dc-dc) + repo-lint L9 self-reference guard
...
New repo openstack-caracal-dc-dc still had stale operational references to the
source repo it was seeded from (openstack-caracal-ipv4), plus, after the
mechanical fix, hardcoded references to its own new name -- both break on any
future fork/rename. Fixes:
- DOCFIX-139/140: repaired 8 stale clone-path/URL references across the skill
and 4 runbook RUN blocks; fixed a repo_lint.py Windows path-separator bug
that silently turned a documented WARN into a FAIL (rel comparison used
str() instead of as_posix()).
- DOCFIX-141: new repo-lint check L9 fails any script/RUN-block/skill file
that hardcodes the current repo's own directory name. Narrowed the
.claude exclusion in repo_lint.py to .claude/worktrees/ only (it was
blanket-hiding checked-in .claude/skills and .claude/hooks from all
lint checks). Established a $REPO session-variable convention
(runbooks/README.md Conventions) and converted the 8 hardcoded paths to
require it explicitly, fail loud if unset.
repo-lint: 0 fail / 1 documented legacy WARN. tests/repo-lint harness 34/34.
Full details + per-item revert instructions in the two changelogs.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|
| 2026-07-06 |
addendum 28: operator-away batch integrated -- DOCFIX-098..105 (H4 drift detector, offboard orphan sweep + E0 guard, handover pack, cloud-snapshot, tenant-acceptance harness, lint L7 sweep rule, ledger-scan 099-boundary fix)
...
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFdCdEuxynmM8Q6HichbHn
|
| 2026-07-05 |
Delete openstack-cloud-ops.skill
|
|
|
| 2026-07-04 |
|
| 2026-07-03 |
|