| 2026-07-10 |

DOCFIX-173: full-project sweep -- script logic bugs + security hardening
...
Four confirmed logic bugs (unguarded pipelines/function calls under
set -e+pipefail), all independently reproduced in isolation before being
trusted -- including catching that a first proposed fix for one of them
was itself wrong: `|| true` after a failing pipeline under pipefail
silently discards PIPESTATUS, which would have made phase-06-capi-stack's
own gate check never fire again. The correct `if pipeline; then :; fi`
idiom was verified via reproduction before committing.
- carve-host-interfaces.sh: emit()'s unguarded `return 1` killed apply-mode
runs on the first MAAS error, before the FATAL-accumulator summary
could print.
- phase-06-capi-stack.sh: run_step's PIPESTATUS read was dead code.
- juju-spaces-check.sh, osd-blank-check.sh: one unguarded display-only
pipe each, safely `|| true`-able.
- ledger-scan.sh: DOCFIX/BUNDLEFIX next-free regex used a fixed {3} not
{3,} -- the same bug class already fixed once for the D-series, never
mirrored here. Dormant, not yet reachable, but a real landmine.
- dc-dc-rbd-mirror.sh: hardened the bootstrap token's default path
(random suffix + immediate chmod 600, was a predictable /tmp filename
with no permission hardening).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|
DOCFIX-172: full-project sweep -- bundle.yaml fixes
...
magnum-dashboard had NO bindings: block at all -- a real, pre-existing bug
(confirmed via git log, present since the D-052 explicit-bindings commit,
not a regression from tonight's DOCFIX-167 work) violating this bundle's
own stated "explicit per-application blocks" discipline. Fixed with a
minimal block confirmed against the real charm-magnum-dashboard metadata
(subordinate, only a real `dashboard` endpoint -- not a copy of
octavia-dashboard's fuller shape). Also relocated two section-header
comments that visually (not functionally) misattributed to the wrong app,
and corrected a stale "11 API charms" HA-chain count to 12. 56 apps/108
relations unchanged; provider-bundle-check.py PASS throughout.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|
DOCFIX-171: full-project sweep -- DC-DC runbook consistency fixes
...
Stage 1's calculator scripts (DOCFIX-162) were never wired into its own
Step 3 (still showed manual arithmetic) -- fixed to actually invoke them.
Stage 5's Sequence-list still tagged the IPv6 overlay [NOT YET WRITTEN]
after the Step 6 body was already updated -- fixed to match. Stage 6's
delivery checklist claimed its steps call the new Ceph replication
scripts when the step bodies still only showed raw commands -- added real
"PREFER scripts/dc-dc-..." pointers to each affected step. Also closed out
two DOCFIX numbers (164, 167) whose ledger entries never got their real
number written into the closing line during the earlier integration pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|
DOCFIX-170: full-project sweep -- VR0 runbook + OpenTofu doc fixes
...
Part of an 8-dimension parallel sweep for errors/architectural problems
across the whole repo, requested after DOCFIX-169. phase-08-workload-
cluster-acceptance.md had the same cascading-staleness class as DOCFIX-169
(Designate framing never updated after DOCFIX-167's bundle.yaml edit) --
fixed with a nuanced correction distinguishing "charms now active/idle"
from "D-011.8's real acceptance criterion still needs Stage 7's overlay."
Also fixed a stale opentofu/modules/base-image/outputs.tf comment
referencing nonexistent cloudinit-vm variables. Session ledger updated
with the full 8-dimension sweep summary and two orphaned DOCFIX-number
closures (164, 167) caught as integration misses.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|
DOCFIX-169: fix stale bundle.yaml expectations in phase-01-bundle-deploy.md
...
DOCFIX-167 (Designate + D-108 DR mechanism reactivation) edited the same
shared bundle.yaml this existing, live-cloud-relevant VR0 runbook deploys,
but didn't update this runbook's own documented expectations. Found during
a full-project sweep: "expected plan: 50 apps, 97 relations" was stale
(real count 56/108); "NO designate (D-019)" GATE text was flatly wrong.
Fixed both live-gate references; left the historical 2026-06-03 as-built
record unchanged (append-only audit trail). Flagged, rather than decided,
a real architectural tension: this bundle now serves both a plain VR0
redeploy and DC1's buildout, so any future phase-01 run gets the new
charms regardless of intent.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|

DOCFIX-168: proposed netem parameters + ULA/GUA generation guidance (gaps #4(d)+11); tracker sync
...
docs/dc-dc-netem-and-ula-gua-proposal.md: a concrete, reasoned netem
parameter proposal (1ms/0.2ms jitter/0.01% loss/uncapped rate) for operator
ratification, plus the exact safe command (openssl rand -hex 5, RFC
4193-compliant) for the operator to generate the real org ULA /48
themselves. Deliberately does not generate the GUA carve or DC2's supernet
-- both need real ARIN/NetBox coordination this session has no authority
or visibility to perform.
Also finalizes docs/dc-dc-deployment-workflow.md and docs/session-ledger.md
across all seven gap-closure deliveries tonight (DOCFIX-162 through 168):
tooling gap register items #4(d), #5, #6, #7, #11, #12, #13, #14, #15, #16
updated to their real closed/partial state, new gap #17 added, harness-debt
item #10 corrected. Full test gauntlet re-run clean: same pre-existing
environment-gap failures as every earlier check tonight (jq missing on
this workstation), zero regressions across all seven new script families.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|

DOCFIX-167: bundle.yaml -- Designate reactivation + D-108 DR mechanism (gaps #6+14)
...
Adds designate/designate-bind/designate-mysql-router/designate-hacluster
(D-106, supersedes D-019) and cinder-backup/ceph-rbd-mirror (D-108) to
bundle.yaml -- the same file this repo's jumphost uses against the live
VR0/DC0 rehearsal cloud, edited with corresponding care. Every new charm's
relation/binding shape was checked against its real metadata.yaml, not
guessed: caught and fixed three mistakes this way during self-review
(designate's real endpoint is dnsaas, not public; cinder-backup is a
subordinate with no amqp relation; ceph-rbd-mirror needs ceph-local/
ceph-remote, not a single invented binding key). Corrected ceph-mon's
rbd-mirror binding from storage to replication. Documents, rather than
invents, that ceph-remote (the peer DC's ceph-mon) needs a cross-model
relation or the manual CLI flow -- D-104's per-DC-independent-controller
design means it can't be a same-bundle relation line. Deliberately does
NOT reactivate os-public-hostname in this shared bundle (pushed to a
proposed per-DC overlay instead). Bundle parses clean (56 apps, 108
relations); provider-bundle-check.py PASS, all invariants unaffected.
Not applied to any live model.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|
DOCFIX-166: $DC parameterization for reenroll-hosts/carve-host-interfaces/phase-00-maas-standup (gap #15, CLI half)
...
Each script gained an opt-in $DC env var, calling lib_net_select_dc/
lib_hosts_select_dc immediately after sourcing (unset $DC is byte-for-byte
unchanged VR0/DC0 behavior -- verified directly, no fixtures needed since
the selector calls sit before any MAAS call). DC=dc1 bash
scripts/phase-00-maas-standup.sh is now a real, correct, gated call
(D-101 makes dc1's target identical to dc0's); DC=dc1/dc2 for the other
two scripts still fails loud at the host selector until real per-DC host
data exists. New/extended test cases across three harnesses. Six of gap
#15's seven sub-findings remain open -- said plainly, not overclaimed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|
DOCFIX-165: Ceph replication tooling scripts, radosgw multisite + rbd-mirror (gap #5)
...
Extracts the Stage 6 runbook's manual command sequences into real,
parameterized scripts: scripts/dc-dc-radosgw-multisite.sh (master-init/
join-readonly/enable-two-way), scripts/dc-dc-rbd-mirror.sh
(bootstrap-primary/bootstrap-secondary --direction), scripts/dc-dc-dr-drill.sh
(failover/failback). The single most safety-critical property in the whole
drill -- demote the current primary before promoting the recovering side
during failback -- is hard-coded as fixed call order, not left to
documentation. Dry-run by default; --apply required for real execution,
plus a typed confirmation for the drill script. 19/19, 19/19, 23/23 tests.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|

DOCFIX-164: D-101 IPv6 family-matrix overlay, researched against real charm source (gap #13)
...
overlays/dc-dc-ipv6-family-matrix.yaml + docs/dc-dc-ipv6-charm-research.md.
Fetched real charm config/source (not memory): prefer-ipv6 is a real,
shared charms.openstack option, confirmed genuinely additive dual-stack
(not either/or) via charm-nova-cloud-controller's actual "Dual Stack VIPs"
commit; ceph-mon's own prefer-ipv6 is a straight switch, correct for the
ULA-only planes; OVN has no such option at all (confirmed by reading its
full config.yaml) and needs no overlay entry; vault's cert code has no
v4/v6 distinction, confirming D-109's SAN requirement is code-supported.
Found and deliberately did not paper over a real open risk: Octavia's
lb-mgmt-net has two real, still-referenced upstream IPv6 bug reports.
Excluded from the overlay pending an explicit operator decision.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|
DOCFIX-163: Office1-local network module + OPNsense edge ownership (gaps #12+16)
...
New opentofu/modules/office1-network (dc-planes-shaped, one shared network
instead of six), instantiated for real in main.tf -- deliberately not a
reused host bridge, since that would live outside OpenTofu's D-103
inventory and break the virsh net-list --all completeness check used
throughout the runbooks. Decided gap #16: Office1 gets its own OPNsense
edge (a fourth site), owned by Stage 2's runbook (new Step 4b). Found a
deeper cross-site gap while wiring the edge's WAN side (no per-site
ISP-uplink network exists for ANY site yet, DC1/DC2 included) -- logged as
new gap #17 rather than defaulted to a mesh-link, which would contradict
D-100's management-only ruling for those legs.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|
DOCFIX-162: MTU/geneve + Ceph disk-budget calculator scripts (gap #7)
...
scripts/dc-dc-mtu-geneve-budget.sh reproduces D-101's own worked example
exactly (1500-56=1444) and adds an IPv6-minimum-link-MTU sanity floor.
scripts/dc-dc-ceph-disk-budget.sh computes size=3-vs-size=2 fit against a
measured total disk, requiring --backup-overhead-fraction explicitly (no
invented default) and naming size=2 as an operator-logged decision, never
silently applied. 19/19 and 16/16 tests.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|
DOCFIX-161: final consolidation pass -- one more gap found, stale refs fixed
...
Last delivery of tonight's autonomous session. A final full read-through
of the deployment workflow tracker for cross-stage consistency found one
more real gap: no stage explicitly owns creating Office1's own OPNsense
edge VM, despite the topology being explicit that the design is per-site
(three sites, three edges). Logged as gap #16. Also fixed two stale
cross-references left over from earlier in the evening. Companion
Artifact redeployed with all seven runbooks reflected as written, the
gap register current, and the corrected 40/40 test count. Final full
gauntlet re-run: same 23 pre-existing environment-gap failures as every
earlier check tonight, zero regressions across the entire session.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|
DOCFIX-160: adversarial review pass finds and fixes 3 real issues
...
Ran a fresh-eyes review subagent across tonight's full deliverable set
(lib-net.sh/lib-hosts.sh selectors, the NetBox DC-DC pipeline, all seven
Stage 1-7 runbooks). Found and fixed:
- A real Python control-flow bug in tests/dc-dc-prefixes-import/
test_logic.py: a `return` inside `try` made the `else` clause
unreachable, silently leaving 3 happy-path assertions uncounted.
37/37 -> 40/40 after the fix (independently reproduced before trusting
the finding).
- A self-contradictory pair of comments in dc-dc-prefixes-import.py about
/19-vs-/22 subnet arithmetic (no functional bug, wrong prose).
- Stale self-referential text in the Stage 4 runbook, written before
Stage 3 existed and never reconciled once it did.
Corrected the 40/40 count in the workflow tracker and session ledger.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|

DOCFIX-157/158/159: Stage 4/6/7 runbooks -- MAAS deploy, DR drill, Designate/COS/Magnum
...
Completes all seven Stage 1-7 DC-DC runbooks (tooling gap register item #9
CLOSED):
- Stage 4 (MAAS enlist/commission/deploy, per DC): adapts phase-00's
validated single-rack sequence per DC. Mandates the $DC selector
convention (DOCFIX-151) and honestly documents that lib_hosts_select_dc
will keep failing until this stage's own enrollment populates real host
data. Names 7 real multi-rack gaps rather than working around them.
- Stage 6 (DR wiring and failover drill): implements Section 8's
failover/failback skeleton, one-way-then-two-way per D-108. Found three
real bundle.yaml gaps (missing cinder-backup/ceph-rbd-mirror charms,
wrong-plane rbd-mirror binding) and makes fixing them the runbook's own
first gated step. Includes a negative-control drill before the real one.
- Stage 7 (Designate, COS, Magnum/CAPI): quotes the bundle's actual
current "NO designate" text before describing the D-106 reversal;
identifies the reversal also affects the bundle's separate
os-public-hostname posture; leaves COS's mechanism as an operator
decision rather than inventing one.
Four new tooling-gap-register items (#12-15) added for cross-cutting
findings this runbook-authoring pass surfaced. None of the seven runbooks
have been executed against real infrastructure yet -- prep-only session.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|

DOCFIX-154/155/156: Stage 2/3/5 runbooks -- Office1 standup, DC substrate, per-DC bundle
...
Three DC-DC runbooks authored in parallel this evening:
- Stage 2 (Office1 headend standup): MAAS-region/NetBox/GitBucket/Tailscale,
each VM-creation step forking an explicit OpenTofu-blocked vs manual-debt
path rather than picking one silently. Flags a real gap: no Office1-local
network is modeled in opentofu/.
- Stage 3 (OpenTofu builds each DC substrate): DC1-first, DC2 hard-gated on
NetBox literals. Walks every open decision (node sizing, MAAS zone/pool,
config.xml tokens, netem params) and stops rather than inventing values.
- Stage 5 (Juju controller + bundle, per DC): describes adapting the
existing phase-01..08 loop to run twice, not duplicating it. Mandates the
$DC selector convention (DOCFIX-151) before every adapted command block.
Surfaces the undesigned IPv6 family-matrix overlay as blocking work.
All three: not yet executed (prep-only session, no live infrastructure
reachable tonight); repo-lint 0 fail/1 documented warn.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|
DOCFIX-153: Stage 1 runbook -- Phase 0 vcloud host preparation
...
Command-level runbook for turning the bare vcloud host into a VR1 substrate:
measure CPU/RAM/disk/nested-KVM/MTU, enable nested KVM, prepare storage pool
paths, install/confirm OpenTofu, write tfvars from measured values, init/
validate/plan/apply the DC1 planes + Office1 pool + mesh links, verify
against the Phase-0 gate. Flags a real open question (unconditional MAAS
provider block possibly demanding credentials before Stage 2 stands up
MAAS) rather than silently working around it. Not yet executed -- this is
the hand-off artifact for tomorrow morning's switch to the vcloud host.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|
DOCFIX-152: NetBox multi-DC/dual-stack import pipeline (gap #3 mechanism)
...
Adds netbox/dc-dc-prefixes-import.py, extending the v1 single-site IPv4-only
NetBox import to VR1's two-DC dual-stack model (D-101). DC1's v4 planes are
hardcoded (D-101: inherited from DC0 unchanged, this is decision text, not
an inference); DC2's v4 supernet, the org ULA /48, and the per-DC GUA carve
are all required env vars with no defaults, failing loud rather than
inventing any of them. 37/37 tests against a fake in-memory NetBox client
(no live NetBox reachable this session). Closes the tooling half of gap #3;
the real literals still need operator/NetBox assignment.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|
DOCFIX-151: $DC selector convention for lib-net.sh/lib-hosts.sh (gap #1 closed)
...
Adds lib_net_select_dc() (dc0/dc1 no-op per D-101 inherited-layout ruling,
dc2 fails loud pending NetBox) and lib_hosts_select_dc() (dc0 no-op, dc1+dc2
both fail loud -- no per-DC host enrollment exists yet). Backward compatible;
21/21 new harness tests green; full gauntlet unaffected (A/B via git stash).
Unblocks the Stage 5 runbook's "high reuse" claim in the DC-DC deployment
workflow.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|

DOCFIX-150: Stage 0 ratification -- D-100..D-110 ADOPTED
...
Operator ruled live on the buildout design's 6 redline items and on each of
D-100 through D-110 individually.
Redline rulings: Ceph size=3 by default (size=2 only as an explicit,
logged Phase-0 fallback); tc netem same-metro lean confirmed; D-102 folds
into D-101, D-109 stays standalone; metal-admin GAINS a ULA leg (reverses
the design's own stated lean -- a real ruling, not a rubber-stamp); COS
per-DC-only confirmed; mirror sync topology independent-per-DC confirmed.
All 11 decisions flipped PROPOSED -> ADOPTED in docs/design-decisions.md.
D-102's body is preserved under a MERGED-INTO-D-101 status line per this
repo's append-only discipline, not deleted. D-101 itself amended: the
metal-admin ULA ruling folded into its family matrix, D-102's tenant/MTU
content folded in as a new section. docs/dc-dc-buildout-design.md Section
10 rewritten from an open list to a ruling record; the stray "Section
D-102" cross-reference corrected to D-101.
Verified, not just asserted: ledger-scan.sh's PROPOSED/OPEN list no longer
contains any of D-100..D-110 -- only D-068/D-071 remain, pre-existing and
unrelated to this gate. Stage 0 of docs/dc-dc-deployment-workflow.md is
CLEARED; workflow doc + visual tracker updated to match.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|

DOCFIX-149: OPNsense config.xml template + tested renderer
...
Real design work, researched before drafting: fetched OPNsense's own actual
shipped config.xml.sample and its Route.xml static-routes model before
writing anything.
opentofu/templates/opnsense-config.xml.tmpl: {{TOKEN}}-parameterized (reuses
this repo's existing clientdocs convention), covering interfaces/gateway/
staticroutes/DNS/NTP and D-107-shaped firewall rules (default-deny WAN
egress except NTP + the per-DC mirror's upstream sync). The real sample
fetch directly confirmed last turn's audit finding with an actual example:
it ships literal placeholder device names inside each interface's own <if>
block, proving LAN/WAN role assignment really is that explicit per-block
mapping, not declaration order.
scripts/opnsense-render-config.sh: the renderer. Needs no external tool
(unlike every other opnsense-* script this session), so it's tested
END-TO-END, not just guard clauses -- 8/8, including well-formed-XML
validation. The harness caught a real bug before it shipped: the token
HOSTNAME collides with bash's own built-in $HOSTNAME variable (unset
doesn't actually clear it), silently passing a test that should have
failed. Renamed to OPNSENSE_HOSTNAME throughout.
opentofu/templates/README.md's token legend marks exactly which values are
real (NTP pool default, D-106 naming) vs. pending Stage 0 ratification
(D-100/D-101/D-107) vs. only measurable on a real boot (vtnetN assignment)
vs. a security requirement (root password hash must be freshly generated,
never the stock sample's own shipped default).
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
|

DOCFIX-147: opentofu/modules/maas-vm-host + modules/netem-link
...
Registers the vcloud host with MAAS as a virsh VM host, and applies tc netem
WAN-simulation parameters -- the two remaining unbuilt mechanisms from the
OpenTofu scope, researched before drafting per instruction.
maas-vm-host uses canonical/maas's maas_vm_host (register the chassis so
MAAS discovers already-existing VMs), deliberately not maas_vm_host_machine
(composes new VMs itself -- takes cores/memory/disks as inputs). Read both
schemas and cross-checked against D-103's explicit "MAAS does NOT compose
new ones" before picking one.
netem-link uses terraform_data (OpenTofu's own recommended replacement for
null_resource) with an SSH-wrapped local-exec provisioner -- confirmed
local-exec runs on the machine invoking tofu apply (Office1 per D-103), not
the vcloud host where the bridges live, so a bare local command would do
nothing useful.
Neither module instantiated: needs a real MAAS zone/pool, the vcloud host's
SSH target, and real netem parameters, none of which exist yet.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|

DOCFIX-146: opnsense-edge module + nano-image/config-ISO prep scripts
...
Implements the mechanism identified in today's OPNsense deployment research
(cloud-init confirmed unreliable on FreeBSD; the real native mechanism is
OPNsense's Configuration Importer, which scans an attached volume for
/conf/config.xml and gained ISO9660 support specifically for VM/cloud
automation).
scripts/opnsense-prep-image.sh: downloads the nano image, decompresses,
converts raw->qcow2, resizes -- work create.content.url almost certainly
can't do itself (a plain fetch, no decompression). No hardcoded mirror URL;
OPNSENSE_MIRROR_BASE is required explicitly.
scripts/opnsense-build-config-iso.sh: builds the ISO9660 image containing
/conf/config.xml that the Configuration Importer picks up.
opentofu/modules/opnsense-edge: wires both together, mechanically identical
to modules/cloudinit-vm's cdrom-attach shape but with no libvirt_cloudinit_disk
resource (wrong format for this). Explicit lan_network_name/wan_network_name
variables rather than an ordered list, since OPNsense's interface-1-is-LAN
convention makes list-ordering a real place for a silent mistake to hide. No
PXE boot-order attribute used -- this VM boots from its own disk, not the
network, so node-vm's flagged-unverified boot field doesn't apply here.
Both new scripts' harnesses (4/4, 3/3) exercise REAL missing-tool guards --
qemu-img/genisoimage/xorriso are all genuinely absent from this environment.
Two things flagged, not presented as fact: whether the Importer's ISO9660
behavior holds on a real boot, and whether interface list-order reliably
maps to LAN/WAN NIC enumeration for this guest. config_iso_path has no
default -- real config.xml content per site is still undesigned. Not
instantiated in root main.tf.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|

DOCFIX-145 + OPNsense deployment research
...
opentofu/modules/base-image + modules/cloudinit-vm: the cloud-init/pre-built-
image VM pattern for Office1's own service VMs (MAAS-region, NetBox,
GitBucket), verified against examples/alpine_cloudinit.tf. The base image's
.path is threaded across the module boundary as a real attribute reference
rather than reconstructed from pool+name strings. user_data/meta_data/
network_config are required inputs with no default -- the real Office1 VM
configs aren't designed yet, and a plausible DHCP default would silently
fail anyway since these planes carry no libvirt DHCP.
OPNsense research (no DOCFIX -- research delivery, not code): checked
OPNsense's own docs/forum/GitHub. Confirmed cloud-init is genuinely
unreliable on OPNsense (FreeBSD; OPNsense's own forum consensus), closing
the open question cloudinit-vm's build left. Found the real mechanism: the
Configuration Importer scans an attached volume for /conf/config.xml in a
2-3s boot window; ISO9660 support was added to it specifically for VM/cloud
automation -- mechanically identical to cloudinit-vm's cdrom-attach shape,
different payload. Use the nano image for KVM, not the installer images.
Fully sourced in opentofu/README.md. Not built yet, itemized: a decompress/
convert preprocessing script, an ISO9660 config.xml-seed builder, and the
real config.xml content.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|

DOCFIX-144: opentofu/modules/node-vm + fix real syntax bug in DOCFIX-142
...
Built the D-103 node-VM pattern (blank disk, PXE-boot, MAAS-managed) for
Stage 3 -- this time from the provider's own real example .tf files
(examples/domain_with_network.tf, examples/alpine_cloudinit.tf), not
doc-summarization alone, since that confidence gap is exactly why the domain
module was deferred last time.
That surfaced a real bug in the already-pushed dc-planes/mesh-link/
dc-storage-pool modules (14d7382): they used classic HCL block syntax
(domain { ... }) where this provider's current schema needs attribute-style
objects (domain = { ... }) -- confirmed once real examples showed the actual
convention. All three fixed; opentofu/README.md's schema notes now state this
as a provider-wide assumption for any field not personally confirmed
otherwise.
One inference flagged, not presented as fact: the per-device boot-order
attribute's exact internal shape ({ order = N }) -- the field's existence is
confirmed, its shape is not. Not instantiated in root main.tf: node
count/sizing is a pending Phase-0 decision, not invented here. Still
deferred: the cloud-init/base-image VM pattern (Office1 services + OPNsense),
DC2 planes, netem application.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|

DOCFIX-143: repo_lint.py no longer relies on git to clean up its own CRLF
...
The --record-clientdocs-sweep/--record-guide-skill-coupling writers used
write_text(), which translates \n to the platform newline (CRLF on Windows)
before writing. .gitattributes' eol=lf silently cleaned this up at every
`git add`, so no CRLF ever reached a commit, but the script was depending on
git to fix its own sloppy output -- flagged during the DOCFIX-141 commit.
Both writers now build the string, .encode("ascii") it, and write_bytes() it
directly. ascii (not utf-8) deliberately: this repo's own rule is ASCII-only,
so a future non-ASCII path/hash now raises loudly here instead of writing
content L1 would fail on anyway.
Verified live: re-ran --record-clientdocs-sweep, byte-inspected the receipt
(0 CR, was 41/41 CR/LF before). tests/repo-lint harness 34/34 unchanged.
repo-lint 0 fail / 1 documented warn.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|
Fold OPNsense's four separable sub-gaps into the tooling gap register
...
Gap #4 ("OPNsense automation + netem") was too coarse -- broke it into what
it actually is: (a) network substrate DONE (modules/mesh-link), (b) the edge
VM/image BLOCKED on the deferred domain module with no image source chosen,
(c) OPNsense's own routing/firewall config NOT STARTED with no mechanism
picked (config.xml is untouched by the libvirt provider), (d) tc netem WAN
simulation is a separate OS-level mechanism with no Terraform-native
equivalent, blocked on an unruled D-100 parameter decision before it can even
be scripted. Same breakdown mirrored into opentofu/README.md's deferred-items
list and the companion visual tracker.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|

DOCFIX-142: opentofu/ network + storage-pool scaffold (VR1 IaC, gap #2)
...
First OpenTofu content in the repo. Network/pool layer only: modules/dc-planes
(the six per-DC planes, D-052/D-100), modules/mesh-link (the D-100 dark-fiber
triangle), modules/dc-storage-pool, wired for DC1 (inherits DC0's CIDRs per
D-101) and Office1. DC2 deliberately not wired -- its CIDRs aren't assigned
yet (D-101 open sub-item).
Provider schema (dmacvicar/libvirt v0.9.8) verified against its actual current
docs this session, not memory -- it has diverged materially from older common
examples (network isolation is a nested forward.mode, not top-level mode;
libvirt_domain restructured to ~40 args mirroring raw libvirt XML). Node-VM/
volume resources, OPNsense config, and tc netem application are deliberately
deferred: that schema is too large/unfamiliar to author safely without a real
`tofu providers schema` pass on a connected machine -- see opentofu/README.md
for the full account and the concrete next step.
Added scripts/opentofu-validate.sh (fmt/init/validate wrapper) + a harness
testing what's actually exercisable without a tofu binary (none is available
anywhere this repo has been worked in this session -- logged, not hidden).
Extended repo_lint.py's L1 check to cover .tf files (uncovered before since
none existed). repo-lint 0 fail / 1 documented warn; both harnesses green.
Workflow doc + its companion visual tracker updated to match.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|
Add DC-DC deployment workflow overview + audited tooling gap register
...
Followable stage-by-stage checklist for the VR1 buildout (Stage 0 decision
ratification through Stage 7 Designate/COS/CAPI), cross-referenced to
docs/dc-dc-buildout-design.md and D-100..D-110. Includes an 11-item tooling
gap register audited directly against the repo (scripts/, tests/, netbox/,
runbooks/) rather than inferred from the design doc -- notably that
lib-net.sh/lib-hosts.sh are not actually DC-agnostic yet (single-deployment
hardcoded literals), which the Stage 5 reuse claim depends on, and that
OpenTofu has zero files in the repo today.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QrcJx8TUar7pYAvpGJw57A
|