| 2026-07-18 |
Ledger: session-close bookend -- DC0 apply STOPPED, superseded by grounding audit charter
...
Bookend for the 2026-07-18 session. Records what was established (source-verified
autostart bounce path, the /tmp cloudinit root cause, the corrected 3-VM blast
radius), what remains open (D-130 mechanism ruling, verifications 7 and 8, the
doc-integrity list), and points at the full record in
docs/finding-20260718-voffice1-cloudinit-seed-replace.md (43b29d7).
Notes that this session is superseded by a grounding audit charter arriving via
git, to be read first by whoever picks this up.
Nothing applied, no module touched, no state written. Gate before any apply
stands: plan must read EXACTLY 5 add / 0 change / 6 destroy.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YArUfQQe6VasfTvzzb9D6P
|

Findings: post-reboot outer plan would BOUNCE Office1 headend + edge -- DC0 apply STOPPED
...
Read-only orientation + pre-apply re-verification. NOTHING applied, no module
touched, no state written. Records findings only, before any fix work.
WHAT WAS FOUND
Outer `tofu plan` post-reboot = 7 add / 2 change / 7 destroy, NOT the committed
5 add / 2 in-place / 6 destroy in docs/dc0-deploy-readiness.md. The entire delta
is `voffice1` (the live headend), not DC0 scope.
1. DOMINANT RISK -- the 2 "in-place" changes bounce both guests. VERIFIED FROM
PRIMARY SOURCE (dmacvicar/libvirt v0.9.8 internal/provider/domain_resource.go):
Update's only no-bounce path (L1090) requires
`domainConfigUnchanged && runningChanged && autostartUnchanged`. An
autostart-only diff has runningChanged=FALSE, so it falls through to
stopDomainIfRunning -> DomainUndefine -> DomainDefineXML -> DomainSetAutostart.
The guest is STOPPED. Applies to voffice1 AND office1-opnsense.
2. `-refresh-only` CANNOT fix it. Read is self-gating (L938):
`if isImport || (!state.Autostart.IsNull() && ...)`. State's Autostart is NULL,
so autostart is never read back. Matches empirical `grep -c autostart` = 0 on
both domains. (isImport DOES read it -- `tofu import` is a candidate
bounce-free reconcile path.)
3. Seed-volume replacement is STRUCTURAL, not drift. modules/cloudinit-vm chains
libvirt_cloudinit_disk.seed (stages the ISO in /tmp) -> libvirt_volume.seed
(create.content.url = that /tmp path). The 2026-07-17 host reboot cleared /tmp,
so the disk re-creates with a NEW random filename, so the url changes, so the
volume force-replaces -- while attached to the RUNNING domain as sda. The real
ISO is intact. Recurs after every host reboot. Warrants D-130 (next-free
confirmed 2026-07-18).
ALSO RECONCILED
- Bookend item (a) CLOSED (measured): vcloud host patched to 6.8.0-136 and rebooted
2026-07-17; apt clean; no reboot-required. D-127 autostart chain PROVEN by a real
unattended reboot -- both VMs + all 3 SSH targets recovered with zero manual
intervention; base-leg ok, NetBox 302, edge 0% loss. Item (b) STAYS OPEN (edge
guest still on 14.3-RELEASE-p16; a host reboot does not patch it).
- Stage-3 "HELD NetBox addressing" blocker in dc-dc-deployment-workflow.md is STALE.
Measured live against the apex: 172.31.0.0/30 (Transit, scope VR1 DC0), the
172.31.0.0/24 container, and 10.12.8.2/22 all PRESENT. Needs DOCFIX-196.
- Version drift: OpenTofu on the jumphost is v1.12.4; README.md:41 says v1.12.3.
Coincidental to the above -- the cause is /tmp, not the version bump.
TWO OF MY OWN CLAIMS CORRECTED IN-FLIGHT (both were inferences, per hard rule 2)
- Cited platform-traps.md 1e as the warrant for the autostart bounce. 1e was
measured on a MEMORY change and does NOT transfer to an autostart-only diff. The
conclusion held but the reasoning was wrong; replaced with the source evidence
above. 1e's operational rule ("0 to change is the only assurance") stands.
- Claimed the /tmp trap hits "all 9 DC0 node VMs and every containment VM". WRONG,
retracted. libvirt_cloudinit_disk appears in exactly ONE module (cloudinit-vm),
instantiated TWICE (main.tf:181 voffice1, main.tf:361 vvr1_dc0). The 9 nodes use
modules/node-vm = blank-disk PXE, NO cloud-init; edges use REST config (D-113(a2)).
Real exposure = voffice1 + vvr1-dc0 (+vvr1-dc1) = 3.
PRIOR-SESSION ROOT CAUSE
docs/changelog-20260717-d127-iac-autostart-and-guest-updates.md asserts "the 2
in-place collapse to 0 once the bookend sets those two autostart live" and "no
forces-replacement on any existing". Both are FALSE. The collapse prediction was
wrong WHEN WRITTEN, not made wrong by the reboot -- it inferred that `virsh
autostart` would be picked up by refresh, without reading the provider's Read path.
Correcting those assertions is queued as Class 1 doc-integrity work.
OPERATOR RULING (2026-07-18): do NOT proceed to DC0 leaving these behind. Fix
scripts/data/settings to 100% correct first; the post-teardown redeploy must be
error-free from these deliverables.
repo-lint: 0 fail (1 legacy WARN).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YArUfQQe6VasfTvzzb9D6P
|
Permission posture: ask-list mutation surfaces so they PROMPT (not classifier-deny)
...
Root cause (claude-code-guide, doc-cited): in auto mode, unmatched mutations
fall to the classifier and get HARD-DENIED (no approval prompt). ask/deny rules
are evaluated BEFORE the classifier, so ask-listing the mutation surfaces forces
an approve/deny/adjust prompt -- the intended gating.
.claude/settings.json (operator-applied; editing the permission file is itself
gated): + ask for *opnsense-api.sh POST, *opnsense-plugins.sh apply, *tofu
apply/destroy/import/state rm, *--commit, Edit(.claude/settings*.json); + allow
for the edge GET + opnsense-plugins.sh list. deny + guard hook unchanged.
Changelog + ledger note + carry-forward (verify prompts post-reload) added.
Revert: git revert this commit, or cp .claude/settings.json.bak over it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUBUyinU4sBAT4HV1b9vSz
|

D-129: build the OPNsense edge plugin profile-installer + sharpen pins
...
Autonomous processing of D-129 items that need no operator keyboard. Repo-only;
NOTHING applied to the cloud (live plugin install stays classifier-gated).
- NEW scripts/opnsense-plugins.sh + tests/opnsense-plugins/ (17/17): named-profile
installer over the D-113 firmware API. Installs ONE plugin at a time + polls
upgradestatus (the firmware-lock lesson), delegates auth to opnsense-api.sh,
warns os-qemu-guest-agent != a working agent, offline --dry-run. Closes the
"build the reusable installer" pin.
- D-129 + dc-dc-phase2 pin: installer recorded as BUILT (use it, not build it);
qga channel logged as an OPT-IN-var design (default OFF -> no office1/DC0-plan
side-effect; dmacvicar 0.9.8 mechanism-to-verify); os-git-backup operator ask.
- Gauntlet ALL GREEN (68), repo-lint 0-fail.
Revert: git rm the script+harness; revert the doc/runbook edits.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUBUyinU4sBAT4HV1b9vSz
|
Ledger: RETIRE boot-race carry-forward -- D-126 base-leg PROVEN 2026-07-18
...
Operator-forced test reboot (host boot 2026-07-17 23:38:41) exercised the
After=libvirtd+poll race. All four checks PASS; the unit journal logs
"added leg" (leg absent at boot -> unit created it), the strong proof. The
D-126 base-leg oneshot self-heals office1-local's dropped L3 leg unattended.
jessea123 added to systemd-journal group + relog so future sessions read
unit journals without sudo.
Revert: restore the prior carry-forward wording (informational only).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUBUyinU4sBAT4HV1b9vSz
|
D-129 verify: qga installed-but-non-functional; iperf lost to firmware lock
...
Reconcile records to the measured 2026-07-18 install verification (operator ran
the firmware POSTs via `!`; agent verified read-only). No cloud mutation here.
- os-qemu-guest-agent: package installed, NON-FUNCTIONAL -- daemon off + the
opnsense-edge module defines no org.qemu.guest_agent.0 libvirt channel
(guest-ping fails). Functional enablement = channel-in-module + service-enable
+ edge restart -> DEFERRED; new DC edges born with the channel.
- os-iperf: did NOT install -- back-to-back firmware POSTs collided on the
OPNsense firmware lock. Re-run pending; profile-installer must be 1-at-a-time
+ poll upgradestatus.
- D-129 Verification note + phase2 pin + as-built row + ledger reconciled.
Revert: git revert; nothing installed/applied by this commit.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUBUyinU4sBAT4HV1b9vSz
|

D-129 partial-rule: adopt VR1 edge plugin profile + pin the rest at deploy steps
...
Operator directive 2026-07-18 ("install the recommendations + pin the rest").
Doc/runbook-only; the LIVE install is operator-gated (auto-mode classifier
blocked the agent POST) and pending verification -- no cloud mutation here.
- D-129 PARTIALLY RULED (status line kept OPEN so ledger-scan still tracks the
4 sub-decisions: COS-edge scope, os-frr, os-tailscale, edge-NTP). VR1 profile
{os-qemu-guest-agent, os-iperf} adopted; pinned-at-steps mapping added.
- dc-dc-phase2 (DC-edge section): pin the VR1 profile at edge bring-up + build
the reusable opnsense-api profile-installer there.
- dc-dc-phase6 Step 10: pin os-node_exporter gated on the COS-edge scope call.
- vr1-office1-as-built: FreeBSD base 14.3->15.1 recorded; directed plugins noted.
- session-ledger + machine block synced.
Revert: git revert this commit; nothing was installed/applied on the cloud.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUBUyinU4sBAT4HV1b9vSz
|
Ledger+D-129: OPNsense 26.7 edge review + version drift + key-push record
...
Doc-only recording (nothing installed/applied on the live cloud):
- docs/opnsense-edge-addon-review-20260718.md (NEW): full 104-plugin catalog
review of office1-opnsense @ 26.7, sorted by minimize-delta-to-Roosevelt.
Findings: os-git-backup installed but UNCONFIGURED (no backup running).
- D-129 (PROPOSED): edge plugin/add-on base profile; operator rules buckets.
- vr1-office1-as-built.md: edge version drift 26.1.11 -> 26.7 recorded.
- session-ledger.md: 26.7 update + review note; operator personal key pushed
to the 3 Office1 Linux VMs + edge (GUI, durable); machine-block re-seeded
(next-free D=130, D-129 PROPOSED).
Revert: delete the review doc + D-129 entry; revert the as-built/ledger notes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUBUyinU4sBAT4HV1b9vSz
|
| 2026-07-17 |
Ledger: forced test-reboot marker for the base-leg boot-race proof
...
Operator is forcing a vcloud reboot to prove site-baseleg-office1.service
self-heals the base leg at boot (carry-forward #1). The rebooting session dies
with the host; this marker gives the post-reboot session the exact 4-step
verification (status=0 / journal 'added leg' / check ok / ssh voffice1 live) and
the retire-or-fix decision. Continuity lives in the committed ledger.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QMKdXCquMnh1jKH2sWXPMg
|
Ledger: mark 2026-07-17 base-leg/D-128 session CLOSED (two carry-forwards recorded)
...
Session deliverables closed + committed (1958063, 6f26b55): post-restart sweep,
site-baseleg.sh + D-126 amendment, D-128 operating model, gap register item 20,
skill per-DC discipline. Two honest carry-forwards kept OPEN, NOT marked done:
(1) boot-race proof (unit installed+enabled; unproven until an actual reboot);
(2) DC leg rows (deferred-until-measured per hard rule 2). Machine-derived block
re-seeded: next-free D = 129 (D-128 ADOPTED).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QMKdXCquMnh1jKH2sWXPMg
|
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
|
Ledger: record LXD service-VM autostart verification (2026-07-17)
...
Measured this session: office1-netbox + office1-tailscale (LXD VMs inside
voffice1) both already carry boot.autostart=true (confirms the D-127
guest-update note; no change made). Documents the autostart chain now proven
end to end -- vcloud->voffice1/edge via virsh autostart; voffice1->netbox/
tailscale via LXD boot.autostart -- so a host reboot self-recovers Office1.
Revert: `git revert <this>` (doc-only).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CsPuN3nSP1EMzkBhh8KJQt
|
dc0-readiness: add BOOKEND vcloud host patch+reboot block
...
Persists the exact gated sequence to close the last bookend item: apt
full-upgrade onto kernel 6.8.0-136 (pending in apt, not installed; running
-134) then reboot. Ordered patch-with-VMs-up -> quiesce -> reboot; STEP 4
ends the session. Records the preconditions measured 2026-07-17: both
foundational VMs autostart=enable, libvirt-guests default suspend (so
explicit virsh shutdown for the verified cold-boot path), no qemu/libvirt in
the pending set. Includes post-reboot verify feeding section D.
Revert: `git revert <this>` (doc-only).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CsPuN3nSP1EMzkBhh8KJQt
|

Ledger: re-seed machine-derived block + record 2026-07-17 edge cold boot
...
Re-seed (from `bash scripts/ledger-scan.sh`, 2026-07-17):
- next-free D 125 -> 128; add SEC-010 to OPEN security rows.
- correct stale "D-123 Model A" -> Model B (operator ruling R-1, supersedes);
fold D-124..127 as ADOPTED; retire the "Stage-4 addressing HELD" flag.
- drop the D-115 "known ledger-scan defect" note: scan now keys on the last
**Status:** line per `## D-NNN:` block, so D-115 (ADOPTED) no longer flags.
Session-scoped update (health sweep + edge cold boot):
- vcloud HOST never rebooted (uptime 8d); "bookend" only partially done --
voffice1 + tailscale rebooted to kernel -136; host + edge were not.
- office1-opnsense cold-booted + verified clean (2.0 GiB mem, pf 80, kea up,
LAN/egress 0% loss) -> D-127 gate met -> `virsh autostart` ENABLED.
- voffice1 autostart ALSO enabled -> both foundational VMs autostart per D-127.
- still pending: vcloud kernel -136 pending in apt (not installed) -> apt
full-upgrade THEN reboot; edge guest OS still on p16.
Revert: `git revert <this>` (doc-only; no code/tooling touched).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CsPuN3nSP1EMzkBhh8KJQt
|

D-127 IaC autostart matrix baked into deploy tooling + Office1 guest updates
...
Operator directive: bake the autoboot-vs-manual matrix into the IaC so DC0/DC1 get
correct autostart at creation, not by hand. Added an `autostart` bool var to modules
cloudinit-vm/opnsense-edge/node-vm (wired to libvirt_domain.autostart); set explicitly
per instance (shared modules => IaC is source of truth): voffice1=true,
office1-opnsense=true, vvr1-dc0=FALSE, vr1-dc0-opnsense=true, node-vm=FALSE.
HARD GATE verified: the two existing Office1 domains (voffice1, office1-opnsense) plan
as autostart false->true UPDATE-IN-PLACE, NOT replacement (replacement would destroy the
live host/edge). vvr1-dc0 is a new create with autostart=false baked in. Outer plan now
5 add / 2 in-place / 6 destroy; the 2 collapse to 0 once the bookend sets those two
autostart live pre-apply. tests/opentofu-validate T11-T15 pin the matrix against drift.
Standup runbook records the composed service VMs' LXD boot.autostart step. Confirmed
DC0/DC1 create NO LXD instances (inner root = node-vm + opnsense-edge + planes/pool/
wan-bridge) -- DC side is purely this VM matrix.
Office1 guest updates (agent sudo inside the guests): office1-netbox apt upgrade (no
reboot, netbox healthy); office1-tailscale full-upgrade -> kernel 6.8.0-136 + reboot
(back, re-advertising 10.10.0.0/22); voffice1 0-upgradable; LXD boot.autostart=true set
on both. vcloud + office1-opnsense stay in the operator's bookend.
Gauntlet ALL GREEN (66); opentofu-validate clean; repo-lint 0-fail.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ck6xh3jWQi5b3Su8Dx1LEH
|
Block B complete: d124-transit-seed tooling + full D-124 NetBox import
...
Operator chose "write the seeder". netbox/d124-transit-seed.py (NEW) + harness
(21/21, incl. a cross-file consistency check so the seeded role/container can't
drift from dc-rack-mgmt-import's required ROLE_SLUG/CONTAINER) -- mirrors
d115-office-carve (NB client, sandbox gate, WAF UA, dry-by-default). Seeds ONLY
the transit role + the 172.31.0.0/24 UNSCOPED container (mirrors edge's /16).
Ran on the apex (gated, dry-run-verified, token server-side, sandbox-only):
seed -> role transit (id=30) + 172.31.0.0/24 (id=137); then dc-rack-mgmt-import
placed 172.31.0.0/30 (transit, vr1-dc0) + 10.12.8.2/22 (rack, dns=vvr1-dc0),
matching the vr1_dc0_rack_* tfvars. Full D-124/edge-WAN IPAM now in the apex.
changelog/ledger/readiness updated (Blocks A+B DONE).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ck6xh3jWQi5b3Su8Dx1LEH
|

NetBox apex recovery + restart policy + edge-WAN import (Block A)
...
Found the IPAM apex (office1-netbox, netbox-docker) DOWN -- cleanly exited ~3h prior,
no auto-restart. Operator-approved: brought it up (docker compose up -d, HTTP 302) and
added restart: unless-stopped to all 5 services so it survives instance reboots (incl.
the bookend); override backed up on host, config-validated before apply.
Block A committed (gated, dry-run-verified): dc-edge-wan-import created 172.30.2.0/24
(vr1-dc0) + 172.30.3.0/24 (vr1-dc1), role edge. Token read server-side on the apex host
(never in agent context); sandbox-only, upstream untouched.
Block B (D-124 rack) BLOCKED: dc-rack-mgmt-import dies on 'role transit absent' -- the
transit role + 172.31.0.0/24 container have no seeder. Pending operator tooling decision
(decoupled from the deploy). as-built + ledger updated; changelog added.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ck6xh3jWQi5b3Su8Dx1LEH
|
DC0 prep COMPLETE (2026-07-17): lock deploy-ready state
...
Final gate re-verified: opentofu-validate clean, the four vr1_dc0_rack_* +
vr1_dc0_ssh_pubkey_path tfvars in place, creds-audit vr1-dc0 CLEAN, gauntlet ALL
GREEN (65), repo-lint 0-fail. netem clarified as deploy-time (Step 11, the module
isn't pre-instantiated -- no prep tfvar). NetBox import clarified as decoupled
hygiene (importer not on the apex host; no netbox provider in the tree).
Remaining is NOT prep: the bookend restart (vcloud + voffice1 + opnsense patched
and rebooted together -- voffice1 is load-bearing like vcloud, so it defers with
it, not mid-prep) as the boundary into the new session; then fresh-session re-verify
of the plan + logged shell + gated outer apply. Readiness doc + ledger marked
PREP COMPLETE.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ck6xh3jWQi5b3Su8Dx1LEH
|
| 2026-07-16 |
D-127: VR1 host-VM autostart policy (foundational autostart, DC containment manual)
...
Recorded before a vcloud OS-update/reboot window (open now: only Office1 VMs live,
pre-DC0). voffice1 autostarts; office1-opnsense autostarts AFTER one verified post-fix
cold boot (DOCFIX-188 mem-unit fix -- the triple-fault was a fixed config bug, not live
fragility); vvr1-dc0/dc1 containment stay MANUAL (gated depth-4/site-down bring-up).
Live via `virsh autostart`; IaC layer (module autostart arg) queued. ops-restart-
procedure.md pointer added. Ledger records the post-reboot TODO (verify clean startup,
THEN set autostart) since a host reboot ends the running agent session.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ck6xh3jWQi5b3Su8Dx1LEH
|

DC0 deploy-readiness handoff: per-env-key ruling implemented + netem ruled + prep checklist
...
Prep for a fresh session to run the Stage-3 Phase-2 DC0 build. Nothing applied;
the outer tofu apply remains the first (operator-gated) mutation.
Per-env-key (D-126, operator ruling 2026-07-16, option a): vvr1-dc0 gets its OWN
access key, not office1's. Minted ~/vr1-dc0-creds/vr1-dc0_svc_ed25519 (creds-audit
CLEAN); added variable vr1_dc0_ssh_pubkey_path; vvr1-dc0 cloud-init now installs the
dc0 pubkey (voffice1 keeps office1's key -- only the vvr1_dc0 block changed). Re-
validated + re-planned: outer plan UNCHANGED at 5 add / 0 change / 6 destroy (the
cloud-init edit is create-only content, no new destroys/replaces of live infra).
netem (D-100 gap #11): RULED = placeholder for the rehearsal; final values deferred.
docs/dc0-deploy-readiness.md (NEW): the full readiness state (outer plan verified
safe -- vvr1-dc0 416GiB/108vcpu, 6 empty-plane relocation, 980GiB RAM headroom),
remaining prep (pre-apply / mid-deploy / non-blocking), fresh-session bootstrap, and
the gated A-E deploy sequence. Ledger points at it.
Gauntlet ALL GREEN (65 harnesses incl. the 2 new access harnesses); repo-lint 0-fail;
opentofu-validate clean (both roots + 12 modules). The dc0 private key + local tfvars
stay gitignored/off-repo.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ck6xh3jWQi5b3Su8Dx1LEH
|

D-126: durable rootless agent access to VR1 site VMs (shell + forward) + DC creds prep
...
Establishes durable, rootless jumphost->site-VM access so the recurring
sudo-route friction stops (and generalizes to the DCs). Operator ruled Option A
(SSH via systemd --user / ssh_config ProxyJump), advisor-conferred.
Shell layer (FOUNDATION, EXERCISED): scripts/site-ssh-config.sh generates
~/.ssh/config.d/vr1-sites ProxyJump aliases (voffice1, office1-netbox,
office1-tailscale, office1-opnsense). All four aliases proven live from the
agent's session after the operator installed with the one-key office1 env key --
key read from IdentityFile, no creds path in any command (clears the guard hook).
Forward layer (convenience, reviewed-but-unexercised): scripts/site-forward.sh
holds a systemd --user SSH local-forward -> http://127.0.0.1:8000 for office1-netbox.
Secrets boundary: the key is operator-supplied via --key (a PATH, never read/baked);
generated fragments name that path and are written locally (not committed).
DC prep: per-env-key isolation (operator ruling) -- each DC gets a DEDICATED key in
its own ~/vr1-dc<N>-creds/ folder, minted at deploy. Folders pre-created (0700);
creds-manifests/vr1-dc{0,1}.manifest declare the keypairs. DC ssh aliases deferred
until their IPs are measured post-apply.
Also: D-124 dc0 rack tfvars written locally (opentofu/d124-rack.auto.tfvars,
gitignored) -- the independent `tofu plan` unblocker. vr1_dc1_rack_* NOT written
(no ruled dc1 addressing; variables absent) -- STOP flagged for an operator ruling.
Tests: site-forward 38/38, site-ssh-config 30/30; repo-lint 0-fail (1 legacy WARN).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ck6xh3jWQi5b3Su8Dx1LEH
|

D-124 addressing pinned: transit supernet 172.31.0.0/24 (operator-ratified); importer CONTAINER + harness
...
Operator ruled the three dc-rack-mgmt-import.py scoping flags + ratified the
values (2026-07-16): seed a dedicated `transit` role; CARVE a dedicated transit
supernet (not Cloud) = 172.31.0.0/24; scope vr1-dc0 ("mirror the edge"); transit
/30 172.31.0.0/30; rack IP 10.12.8.2.
- netbox/dc-rack-mgmt-import.py: CONTAINER "10.12.0.0/16" -> "172.31.0.0/24"
(+ header note, RULED scope flags, ratified usage example). Still takes
--transit-cidr/--rack-ip as required inputs; still fail-loud on missing/
collision container (the backstop for a block picked against a stale draft).
- tests/dc-rack-mgmt-import/{run-tests.sh,test_logic.py}: CONTAINER assertion +
every transit fixture rebased Cloud->172.31.0.x (shapes/host-bits preserved so
rejections still die for the right reason). 96/96 PASS.
- docs: D-124 AMENDMENT (carve + ratified addressing) + changelog-20260716-
d124-addressing-pin.md (the exact on-host --commit sequence) + session-ledger.
NOT DONE (cannot from here): the NetBox --commit. office1-netbox (10.10.1.10) is
UNREACHABLE from the vcloud jumphost (ping/TCP:8000/HTTP all fail, no SSH/tunnel)
and the apex token is a secret on that host -- the write is an on-host operator
step. NO apex mutation in this commit.
Gauntlet ALL GREEN (63); repo-lint 0 fail.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ck6xh3jWQi5b3Su8Dx1LEH
|
docs(D-125): tighten "already registered" -> "owned by importer, pending --commit"
...
The corrected D-125 note claimed 172.30.2.0/24 is "already registered by
dc-edge-wan-import.py". The frozen NetBox draft confirms it is NOT loaded (the
edge role + 172.30.0.0/16 container are absent -- d115-office-carve not yet
committed either). Corrected: the /24 is OWNED BY that importer and PENDING its
--commit, not already present. Avoids reintroducing a stale-as-fact claim while
fixing one.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ck6xh3jWQi5b3Su8Dx1LEH
|

D-125 correction: uplink /24 is the ruled 172.30.2.0/24, not a HELD tfvar (no new NetBox object)
...
The initial D-125 records wrongly modeled the vcloud ISP /24 as a NEW,
NetBox-assigned HELD value (`var.vr1_dc0_uplink_cidr`) with an OPNsense WAN
re-address. That is factually wrong: bridge-in is single-NAT, so there is
exactly ONE simulated-ISP /24 for vr1-dc0, and D-115 already ruled it
(172.30.2.0/24, role edge, site vr1-dc0). NetBox models the prefix by role+site
and does not care which libvirt host runs the NAT, so the /24's IPAM identity is
UNCHANGED from Model A; only the libvirt realization moved (a vcloud NAT bridged
through vvr1-dc0). OPNsense WAN stays .2. (Caught reconciling the "create netbox
objects" request against dc-edge-wan-import.py, which already registers this /24.)
- opentofu/main.tf: module vr1_dc0_uplink cidr = "172.30.2.0/24" inlined (ruled
literal, matching the inner root's site-wan pattern) instead of the var.
- opentofu/variables.tf: REMOVED variable vr1_dc0_uplink_cidr (replaced with a
note explaining why it is a ruled literal, not a tfvar).
- Records corrected everywhere the false claim propagated: D-125 body + D-122
amendment, changelog-phaseC2-D125, model-a-fallback (B->A delta item 8 +
revert), session-ledger, dc-dc-deployment-workflow, phase-2 runbook.
Net effect: D-125 adds NO new NetBox object and NO new HELD gate. The only
NetBox work left for the DC substrate is D-124's transit /30 + rack IP.
Both roots validate; gauntlet ALL GREEN (63); repo-lint 0 fail.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ck6xh3jWQi5b3Su8Dx1LEH
|
docs(session-ledger): record D-125 push + the post-Model-B doc drift-reconciliation batch
...
Standing-ledger update (deliverable discipline): logs D-125 committed d34ec94 +
pushed, and the three doc-only reconciliations (8496c2e workflow-doc, 87f9d75
opentofu/README, 788911c teardown-rollback) with the repo-wide drift-scan
finding (remaining deleted-file/old-count refs are changelogs/decision-records/
tombstones, not live drift; deeper prose + Stage-4/5 sections still lag).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ck6xh3jWQi5b3Su8Dx1LEH
|

docs(teardown-rollback): Model B reconciliation banner (two-root DC teardown)
...
runbooks/dc-dc-teardown-rollback.md was written 2026-07-10 for the SINGLE-ROOT
shape (OpenTofu creates DC node-VM domains directly on vcloud; maas-vm-host
registers the vcloud host). Under D-123 Model B that is operationally wrong for
the DCs and a mis-directed rollback is a hazard, so added a top-of-file
reconciliation banner (body not yet rewritten). Claims grep-verified:
- TWO roots / TWO state files: OUTER (opentofu/, vcloud) owns vvr1-dc0 + transit
+ the D-125 vcloud ISP uplink; INNER (opentofu/vr1-dc0-substrate/) owns the 9
node VMs + 6 planes + vr1-dc0-wan + edge + inner pool ON vvr1-dc0's libvirt.
- maas-vm-host now registers vvr1-dc0's INNER virsh -> Office1 region.
- Whole-DC site-down is ONE object: virsh destroy vvr1-dc0 (D-122/D-123).
- Teardown ORDER: inner root first (or virsh destroy vvr1-dc0), then outer;
D-061 "clean up MAAS record before destroying libvirt" still applies.
- Model B -> A revert is docs/model-a-fallback-plan.md, not this runbook.
Office1/mesh/pool steps unaffected. Doc-only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ck6xh3jWQi5b3Su8Dx1LEH
|

docs(opentofu/README): reconcile to Model B two-root + neutralize deleted config-ISO path
...
opentofu/README.md described a single-root pre-Model-B tree and, dangerously,
the DELETED OPNsense config.xml/config-ISO delivery as current. Added an
authoritative STATUS-RECONCILED banner (repo/ledger authoritative over the older
per-module prose) and inline SUPERSEDED/DELETED markers. Grep-verified:
- TWO ROOTS (D-123 Model B): outer opentofu/ + inner opentofu/vr1-dc0-substrate/,
bootstrap gate between; 9 node VMs (D-121/R-3); D-125 vcloud ISP uplink.
- Modules ADDED, missing from the Built list: site-wan (D-122), wan-bridge (D-125).
- INSTANTIATED (contradicts "not instantiated" prose): cloudinit-vm + base-image
(voffice1/vvr1-dc0/ubuntu_noble_base), node-vm (inner), dc-planes/site-wan/
wan-bridge/opnsense-edge (inner).
- DANGER neutralized: templates/opnsense-config.xml.tmpl,
scripts/opnsense-build-config-iso.sh, scripts/opnsense-render-config.sh are
DELETED (verified) -- a full config.xml push clobbers the live API-managed edge
(D-112/D-113(a2)). Banner + inline markers on the Built-list refs; deeper
2026-07-09 research/audit refs are framed historical by the banner.
- GitBucket out of scope (D-116) where listed.
Kept Stage-1 history (applied 2026-07-10, DOCFIX-179 provider mapping). Doc-only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ck6xh3jWQi5b3Su8Dx1LEH
|

docs: reconcile workflow-doc Stage-3 section against repo (post D-121..D-125)
...
The dc-dc-deployment-workflow.md Stage-3 (Phase 2) section + gap register had
drifted behind the rulings that landed through 2026-07-16. Repo wins; each
correction grep-verified against the tree (hard rule 2):
- Build/Gate/Owns rows: single-root -> D-123 Model B two-root
(outer->bootstrap->inner->maas-register); vvr1-dc0 IS the rack; D-125
bridge-in per-DC ISP egress; Owns += D-121/122/123/124/125.
- Authoring status: node sizing RULED (D-121/R-3 = 9/DC), config.xml retired
(D-113(a2) REST), both roots instantiated + validate.
- Replaced the stale "two blocking rulings landed 2026-07-15" note (D-121
"3 storage/16 VMs, 790 GiB, vault fork OPEN") with a current rulings summary:
9 nodes/DC (R-3), FIT 870/1024 (Model B), vault RULED (R-2), + D-123/124/125.
- Rack source no longer a missing-module gap: vvr1-dc0 stood up by
site-headend-install.sh --host-nodes.
- Gap register: D-113(a2) OPEN->DONE in both places (banner + line 838)
(template deleted, config_seed/config_iso_path retired). Stage-4
"node count/sizing undecided" -> RULED.
Bottom line now stated honestly: Stage-3 authoring complete; execution is
operator-gated (HELD NetBox addressing + deploy-time gates). Doc-only; no
code/behavior change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ck6xh3jWQi5b3Su8Dx1LEH
|

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
|