Worker: W1.4 (Phase 1, container-layer-elimination pass). Date: 2026-08-09. Scope: READ-ONLY. This is the conceptual backbone for Phase 4's module-workflow design (SCOPE-AND-EXECUTION-PLAN.md W4.2). Inputs read in full: SCOPE-AND-EXECUTION-PLAN.md, pass0-admin-report.md (Option 1 CONFIRMED at the Phase-0 gate, Section 7a), plus live repo survey below. No inferred values -- every claim below cites path:line or a directory listing taken this session.
opentofu/modules/ (12 modules, opentofu/modules/*)Listing (find opentofu/modules -maxdepth 2 -type d, this session): base-image, cloudinit-vm, dc-planes, dc-storage-pool, maas-vm-host, mesh-link, netem-link, node-vm, office1-network, opnsense-edge, site-wan, wan-bridge.
Each module is already: (a) independently tofu validate-able (scripts/opentofu-validate.sh, opentofu/README.md:5-6,40-41 -- "validates EVERY module standalone"), (b) called from one or both of the two CURRENT roots -- the outer root opentofu/main.tf (vcloud provider) and the inner root opentofu/vr1-dc0-substrate/main.tf (qemu+ssh provider, D-123 Model B) -- and (c) already parameterized (no hardcoded DC identity inside a module body; the calling root supplies dc/CIDR/sizing values). This is a working IaC-module layer TODAY; the container-elim does not need to invent module mechanics, only re-home module CALLS from two roots into one and delete the modules that existed only to bridge the two-root split.
Outer-root calls (grep '^module "' opentofu/main.tf): vr1_dc0_storage, vr1_dc1_storage, office1_storage, office1_network, office1_opnsense, three mesh_* legs, ubuntu_noble_base, voffice1, netem_vr1_dc0_vr1_dc1, vr1_dc0_uplink, vr1_dc1_uplink, vvr1_dc0, vvr1_dc1 (opentofu/main.tf:35-537).
Inner-root calls (grep '^module "' opentofu/vr1-dc0-substrate/main.tf): inner_storage, vr1_dc0_planes, vr1_dc0_wan, vr1_dc0_opnsense, vr1_dc0_node (x12, D-121/R-3) (opentofu/vr1-dc0-substrate/main.tf:26-250).
The vvr1_dc0/vvr1_dc1 outer-root calls ARE the containment layer (pass0-admin-report.md Section 1.1); everything in the inner-root list is a module CALL that Option 1 re-homes, not a module BODY that changes shape (pass0-admin-report.md row 2: "module CALLS ... re-home into the outer/flat root -- bodies unchanged, provider changes").
runbooks/dc-dc-phaseN-*.md + docs/dc-dc-deployment-workflow.mddocs/dc-dc-deployment-workflow.md already defines an 8-stage sequence (Stage 0 decision ratification through Stage 7 Designate/COS/Magnum), each stage with a fixed schema -- Goal / Build / Gate / Owns (D-numbers) / Reuse-vs-new / Authoring status -- and each stage's authoring status POINTS AT one runbooks/dc-dc-phaseN-*.md file:
| Stage | Runbook | IaC layer it drives |
|---|---|---|
| 1 | dc-dc-phase0-vcloud-prep.md |
outer root: mesh/planes/pools/office1-network |
| 2 | dc-dc-phase1-office1-standup.md |
cloudinit-vm+base-image (voffice1), MAAS-composed LXD |
| 3 | dc-dc-phase2-tofu-dc-substrate.md |
outer vvr1_dcN + bootstrap gate + inner root (THE container layer) |
| 4 | dc-dc-phase3-maas-enlist-deploy.md |
no tofu; MAAS commission/deploy of already-created node VMs |
| 5 | dc-dc-phase4-juju-bundle-per-dc.md |
no tofu; Juju controller + bundle.yaml |
| 6 | dc-dc-phase5-dr-failover-drill.md |
netem-link mechanism + Ceph replication |
| 7 | dc-dc-phase6-designate-cos-magnum.md |
no tofu; Designate/COS/Magnum additive apps (D-106/D-105) |
Each runbook is already parameterized by $DC (lib_net_select_dc "$DC" / lib_hosts_select_dc "$DC", DOCFIX-151, docs/dc-dc-deployment-workflow.md:485-498) and each stage's close-out is gated by repo-lint + a harness sweep + a changelog + a branch-merge (docs/dc-dc-deployment-workflow.md:424-436, "Cross-cutting discipline"). This IS a procedure- module system already -- the container-elim pass does not need to invent the concept, only (a) rewrite Stage 3's content for the flat topology and (b) formalize the pattern the operator is asking for so it is named and reusable, not just an emergent convention.
Underneath the stage runbooks, two shared libraries act as the procedure layer's own "IaC modules": scripts/lib-net.sh and scripts/lib-hosts.sh -- every stage-4/5 script sources them for CIDR/host facts, keyed by the $DC selector (vr1-dc0/vr1-dc1, D-119). This is the existing parameterization mechanism the layer model below reuses rather than replaces.
scripts/opentofu-validate.sh (tests/opentofu-validate/), validates every module standalone plus both roots.tests/<script-name>/run-tests.sh (tool-index: "65 scripts with their own tests/<name>/ harness"; docs/tool-index.md:27).No module of either kind ships without its harness (CLAUDE.md "Delivery" rule); this is already the repo norm the layer model's design principles (Section 4) restate for the new modules Option 1 introduces, not a new rule.
Six layers, named to match the EXISTING Stage numbers where a layer is Stage-owned, so the layer model is a REFRAME of what's already built, not a parallel taxonomy. "IaC or procedure" marks whether the layer's artifact is an OpenTofu module/root or a runbook+script procedure. Verify layers (L5) are cross-cutting and re-invoked at every layer boundary, not a one-time final step.
| Layer | Name | Kind | Inputs | Outputs | Current artifacts | Stage owner |
|---|---|---|---|---|---|---|
| L0 | Host & inter-site substrate | IaC module | vcloud libvirt connection; site tokens (Office1/dc0/dc1); MTU plan (D-101) | mesh triangle (dark-fiber legs), per-site storage pools, office1-network |
modules/mesh-link, modules/dc-storage-pool, modules/office1-network, outer main.tf:35-163 |
Stage 1 |
| L1 | Site/edge nodes | IaC module | L0 outputs (a network + a pool); per-VM cloud-init or edge-image spec | Office1 headend VM (voffice1); per-site OPNsense edge VMs; (Option 1 NEW) the per-DC client VM |
modules/cloudinit-vm, modules/base-image, modules/opnsense-edge, modules/site-wan |
Stage 2 (Office1); Stage 3 (DC edges, currently split by the container layer -- Option 1 flattens into this same layer) |
| L2 | DC substrate: planes + node VMs | IaC module | L0 pool + L1 edge; $DC token; D-121/R-3 node counts/sizing; D-134 octet map |
six per-DC plane networks; 9+3 node-VM libvirt domains (role nodes + utility nodes) per DC | modules/dc-planes, modules/node-vm; TODAY split outer(vvr1_dcN)/inner(vr1_dc0_planes,vr1_dc0_nodex12) -- Option 1 COLLAPSES this to ONE root, one state |
Stage 3 |
| L3 | Enlist/commission procedure | Procedure module | L2 node VMs (MAC-pinned, discoverable); MAAS region reachable (L1's vr1-dcN-maas-01, D-132 addendum) |
READY, carved, tagged MAAS machines; per-DC artifact-mirror answering | runbooks/dc-dc-phase3-maas-enlist-deploy.md, scripts/dc-rack-net.sh, scripts/maas-node-power.sh, scripts/site-headend-install.sh (rack-role remainder), modules/maas-vm-host |
Stage 4 |
| L4 | Juju/OpenStack deploy procedure | Procedure module | L3's READY machines; $DC selector; bundle.yaml + per-DC overlays; Vault root |
a running independent OpenStack cloud per DC (controller + bundle + Vault) | runbooks/dc-dc-phase4-juju-bundle-per-dc.md, runbooks/phase-01..08-*.md (the VR0 template it runs twice), scripts/preflight.sh (entry gate) |
Stage 5 (+ Stage 6 DR, Stage 7 Designate/COS/Magnum as ADDITIVE sub-procedures riding the same L4 mechanism, per vr0-to-vr1-is-additive -- not separate layers) |
| L5 | Verify/gate | Procedure module (cross-cutting) | any layer's declared-done state | PASS/FAIL + a committed BOM at milestones | scripts/opentofu-validate.sh (L0-L2), scripts/cloud-assert.sh/--capture (L4), scripts/preflight.sh (L3->L4 boundary), scripts/geneve-encap-assert.sh (L2/L4 network correctness), scripts/repo-lint.sh (every layer's artifact hygiene) |
Invoked at every stage close, not owned by one stage |
Layer-boundary rule (why this ordering, not invented): each layer's INPUT is the layer below's OUTPUT only -- L1 does not reach into L3, L3 does not dial L0 directly. The one place this rule is currently VIOLATED is exactly the container layer: the inner root (part of L2) dials OUT to a cross-host qemu+ssh provider (opentofu/main.tf:28-29, "a libvirt provider cannot be configured from a resource created in the same apply") because L2's outer half (vvr1_dcN) had to exist as infrastructure BEFORE L2's inner half (planes/nodes) could be declared. Option 1 removes this violation structurally (Section 3) -- it is the single clearest justification, in layer-model terms, for why flattening also SIMPLIFIES the module system, not just the topology.
Option-1 change (from pass0-admin-report.md) |
Layer | What happens to the module/procedure |
|---|---|---|
| Flat single tofu root/state (no outer/inner split) | L2 | The two-root split collapses INTO L2: vvr1_dc0/vvr1_dc1 module calls (outer main.tf:410-623) are DELETED; the inner root's module calls (vr1_dc0_planes, vr1_dc0_node x12, vr1_dc0_opnsense) become direct calls in the single flat root, same module bodies, qemu+ssh provider replaced by the flat root's own qemu:///system (pass0-admin-report.md row 2). The BOOTSTRAP GATE script (site-headend-install.sh --host-nodes) that sat BETWEEN the two roots is eliminated as a step -- L2 becomes a single tofu apply, no cross-host provider dial. |
modules/wan-bridge deletion; DC edge WAN attaches directly to the outer uplink NAT |
L1/L2 boundary | modules/wan-bridge (existed only to fix the OBS-3 nesting-egress problem, D-125) is DELETED; the DC OPNsense edge (L1) attaches its WAN leg directly to vr1_dcN_uplink (already an L0/L1 artifact) -- no re-address, same /24 (pass0-admin-report.md row 3). |
| Per-DC client VM standup (D-138 role, Model-A shape) | L1 (new module instance) | A NEW small cloudinit-vm instantiation per DC (~4/8192/80, non-hypervisor, expose_nested_virt=false) -- the SAME L1 module type Office1's voffice1 and the DC edges already use, not a new module. Carries the D-138 client role + SEC-028/SEC-029 credential residencies (a procedure/config concern layered on top at L3/L4, not an IaC concern). |
| (a) cross-DC host-level isolation control | L5 (new artifact) | Confirmed at the Phase-0 gate (pass0-admin-report.md Section 7a) as a Phase-1 DESIGN ITEM: a NEW vcloud-level nftables isolation artifact + mechanical --check gate + its own SEC-NNN row -- structurally the SAME pattern as scripts/geneve-encap-assert.sh/cloud-assert.sh (an L5 verify module), one layer up from SEC-010's interface-scoped drop. This is a genuinely NEW L5 module, not a re-home of an existing one. |
Rack-controller remainder (MAAS --role rack) + D-131 forwarder + artifact-service (.4) placement |
L3 (open placement, not yet a module change) | Currently site-headend-install.sh --host-nodes bootstraps the rack role INSIDE vvr1-dcN; that call site becomes dead code for VR1 once the containment VM is gone. Where the rack role, dc-rack-net.sh's forwarder, and the mirror land (the new L1 client VM vs. vr1-dcN-maas-01 vs. retire-with-evidence) is UNRESOLVED -- pass0-admin-report.md Section 6 item 3, carried into Phase 1 explicitly as an open item, not resolved by this worker. |
MAAS region stays on vr1-dcN-maas-01 |
L2 (no change) | Already a flat L2 sibling node VM under D-132's addendum; flattening does not touch it. |
| Credential residencies (SEC-028/029) migrate to the client VM | L3/L4 (procedure, not IaC) | The register rows (vm-secret-locations, SEC-026 isolation control) re-point to the new L1 client VM; this is a data/procedure change at the L3-L4 boundary, not a module-body change. |
Teardown primitive (virsh destroy vvr1-dcN = site-down) |
L5/cross-layer | D-123's one-command site-down is LOST by flattening (no single containment domain to destroy); Option-1's design must re-earn it as a MODULE-SCOPED group-destroy (a tofu-state-scoped destroy -target set, or a scripted domain-group teardown) -- an L2-layer verify/teardown primitive, owed to Phase 4's module-workflow design (pass0-admin-report.md Section 6 item 7). |
D-143 stays a separate axis, threaded through every layer, not a layer of its own. The 10.12->10.13 re-IP touches L0's CIDR inputs, L2's node addressing, and L4's overlay files identically whether or not the container layer exists -- SCOPE-AND-EXECUTION-PLAN.md Section 7 requires the two changes stay distinguishable in the change-set, and the layer model above keeps that distinguishability structural: D-143 is a PARAMETER change at every layer's input; the container-elim is a LAYER-COUNT/BOUNDARY change at L1/L2/L5 specifically.
$DC selector convention (D-119, DOCFIX-151, lib_net_select_dc/lib_hosts_select_dc) already does this for the procedure layer; the IaC layer already does it structurally (no module body names a DC). Option 1's new artifacts (the client VM, the L5 isolation control) MUST take the same token rather than being written DC0-specific and copy-pasted for DC1 -- the exact anti-pattern dc-dc-deployment-workflow.md gap-register item 1 was created to close.opentofu-validate.sh's existing coverage (it's the same cloudinit-vm module, no new module body); the NEW L5 isolation control needs its own tests/<name>/run-tests.sh from day one, matching geneve-encap-assert.sh's and SEC-010's own precedent -- a mechanical --check, not a prose claim.tofu apply; L3/L4 procedure modules must stay re-run-safe (the existing MAAS "READY not deployed" handoff and the preflight.sh drift-detection gate already enforce this at the L3/L4 boundary -- D-140's own "OWED BEFORE THE REVIEW" note on P8 drift-detection value is the same principle one layer up).docs/design-decisions.md:7781-7822) is PINNED, not ruled, and governs whether the L4 Juju layer ITSELF becomes OpenTofu-managed -- sequenced strictly AFTER "dc0 deploys and is HARDENED ... that deployment method is TESTED." It does not currently govern how L0-L2's IaC modules are structured (that structure already exists and predates D-140). The layer model above is written so that IF D-140 is later adopted, L4 becomes an IaC-module layer too WITHOUT changing L0-L3's shape -- but that is a future trigger, not something this pass proposes now.dc-dc-deployment-workflow.md:11-14 Section-9 shim register) does NOT transfer; L1's client-VM pattern, L3's MAAS enlist procedure, and L4's Juju/bundle procedure ARE the D-138 Roosevelt bastion analog and the direct pre-Roosevelt bare-metal deliverable (pass0-admin-report.md Option-1 "For" bullet: "rehearsed early ... transfers to the pre-Roosevelt bare-metal test"). The layer model's job for Phase 4 is to keep these two classes visibly separate so the module-workflow design does not present shim-layer work as reusable.pass0-admin-report.md Section 6 item 3) -- unresolved; blocks writing the concrete L3 module change-set until ruled or proposed.--check gate shape, SEC-NNN number) is Phase-1/2 work, not yet started.