| 2026-07-18 |
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
|

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
|

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
|

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
|

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
|

Stage-3 review sweep C+D: Model B two-root reshape + SEC-010 hardening
...
Phase C (Model B reshape, D-123 operator ruling):
- outer root (opentofu/main.tf): vvr1-dc0 resized to whole-DC containment VM
(108 vCPU / 425984 MiB / 3221225472000 bytes; expose_nested_virt=true;
single Office1 transit leg); node/plane/wan/opnsense modules removed from
vcloud level (MOVED markers), storage-04 added per R-3.
- inner root (opentofu/vr1-dc0-substrate/): NEW second root, provider
qemu+ssh to vvr1-dc0; reuses ../modules/* verbatim to build 9 nodes +
6 planes + wan + opnsense inside the containment VM. dmacvicar/libvirt
0.9.8 pinned (.terraform.lock.hcl committed). Both roots tofu-validate.
- variables.tf: vvr1_dc0_vcpu/memory_mib/disk_bytes with derivation comment.
Phase D (SEC-010, R3-F02): site-headend-install.sh --host-nodes node-host
bootstrap installs qemu-kvm+libvirt, persists+verifies nested=1, writes a
transit FORWARD-drop (nftables inet sec010) enforcing DC-LOCAL under Model B.
Gate hardened (advisor catch): --check now verifies the keyed transit
interface EXISTS, else nftables oifname loads clean but matches nothing
(fail-open). Harness 49/49.
Records: D-103/107/114/121/122/123/124 amendments (append-only);
model-a-fallback-plan synced; ledgers + Phase-C changelog.
KNOWN GAP (OBS-3, operator-confirmed 2026-07-16): under Model B the inner
vr1-dc0-wan NAT is nested inside vvr1-dc0 (single transit NIC), severing the
simulated-ISP egress that Model A had at vcloud level. DC0/DC1 ARE meant to
have ISP connections -- egress path resolution is follow-up design work, not
resolved by this commit.
Present-only; not pushed. 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
|

Review sweep A+B + R-3 storage-04: coherent record, whole-host calculator, R-3-compliant Model A fallback
...
Phase A (record amendments) + Phase B (scripts/dc-dc-whole-host-budget.py + harness) of the
Stage-3 adversarial-review sweep, plus the R-3 storage-04 addition (Model A layout now
3+2+4 = 9 nodes/DC). This commit is the R-3-COMPLIANT Model A state and becomes the re-cut
`model-a-fallback` anchor (the prior tag at 87a7a8a was R-3-stale -- 3 storage -- flagged by
the Model B design cross-check). NO cloud mutation; nothing applied.
- design-decisions.md: D-103/D-107/D-114/D-115/D-121/D-122/D-123/D-124 amendments (rulings R-1..R-4).
- scripts/dc-dc-whole-host-budget.py + tests/ (13/13): replaces the lost optc-calc.py (R3-F06).
- opentofu/main.tf: locals.vr1_dc0_nodes += storage-04 (R-3); tofu validate Success.
- security-ledger SEC-011 closed; session-ledger rulings + sweep progress; model-a-fallback plan.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ck6xh3jWQi5b3Su8Dx1LEH
|
WIP: freeze surface for Stage-3 adversarial review -- SEC-010/011 + ledger
...
Plane-segregation review findings logged earlier this session, committed to
freeze the review surface (per the Stage-3 adversarial-review prompt, FIRST
ACTION). Commit is not push; nothing leaves the jumphost.
- security-ledger: SEC-010 (rack ip_forward/DC-local enforcement) + SEC-011
(node least-connectivity) as PRE-APPLY hardening rows.
- session-ledger: the 2026-07-16 plane-segregation review outcome + the
functional note (rack routes to an as-yet-unattached transit peer).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ck6xh3jWQi5b3Su8Dx1LEH
|

Stage 3 (vr1-dc0) prep batch: D-121..D-124 rulings + substrate/rack wiring, HA overlay, NetBox importers
...
Prepares the VR1 Stage-3 DC substrate so the live steps reduce to review-plan-and-apply.
Authoring + offline validation only -- NO cloud mutation; every tofu apply, NetBox --commit,
and rack install stays operator-gated. Four decisions ruled this batch, all recorded:
- D-121 (ADOPTED): make the decorative single-unit control plane REAL. 14 services scale 1->3
(D-009's mechanical scale-up pulled into VR1); node layout = Option C (3 control + 2 compute +
3 storage/DC, sizing set) after a whole-host resource validation (256 vCPU/1024 GiB/10 TiB);
vault sub-ruling = v-a (MySQL-backed, charm-verify gated). Placement-balance rule for Stage 5.
- D-122 (ADOPTED): VR1 site shape -- nested-per-site containment, dark fiber East-West only,
dedicated per-site L3 ISP uplink, 2-NIC fabric-routed DC edge (LAN=provider-public, WAN=uplink),
per-site MAAS. Edge NIC count baremetal-matched (nodes 6, edge 2).
- D-123 (ADOPTED Model A): DC nodes vcloud-level; site-down = destroy the vr1-dc0-* group; MAAS
region on Office1 + a rack controller per DC (vvr1-dc0).
- D-124 (ADOPTED Scheme A): the Office1-region<->DC-rack management overlay -- a transit link on
the office1<->dc0 mesh leg; rack sizing 4/8192/80.
Code (all validated, NOT applied):
- OpenTofu: new modules/site-wan (NAT /24 uplink, provider-schema-confirmed); main.tf Stage-3
section -- vr1_dc0_wan, the 2-NIC edge, 8 Option C node VMs (for_each), the vvr1-dc0 rack
(two legs, static IPs from new vr1_dc0_rack_* tfvars -- NetBox-assigned, not invented), netem
held, mesh-link network_name output; stale vr1_dc1_planes comment fixed. Step-9 maas-vm-host
deliberately deferred (DOCFIX-179: its maas provider would force every plan to demand creds).
tofu validate Success; opentofu-validate 11/11.
- NetBox: dc-edge-wan-import.py (58/58) + dc-rack-mgmt-import.py (96/96) -- register the DC-edge
/24s + the rack transit/IP, dry-by-default, whole-plan preflight, operator-supplied literals.
- overlays/dc-ha-scaleup.yaml: the D-121 14-service HA scale-up (Stage-5), placement tokens +
rabbitmq min-cluster-size + hacluster cluster_count + re-declared vault-hacluster.
- scripts/site-headend-install.sh: rack-only mode (enrolls a DC rack to Office1's region).
- runbooks/dc-dc-phase2-tofu-dc-substrate.md: fix-forward (D-119 naming, Steps 4-5 REST-API,
D-121/122 folded in, stale gate corrected).
docs/session-ledger.md + dc-dc-deployment-workflow.md reconciled; three changelogs added.
Gauntlet ALL GREEN; repo-lint 0-fail (1 documented legacy WARN).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ck6xh3jWQi5b3Su8Dx1LEH
|
| 2026-07-15 |

Stage 2 CLOSED+MERGED; open Stage 3 branch; standing merge-at-close discipline; drop stray temp/ files
...
Post-merge housekeeping after PR #1 (e468d2e) put VR1 Stage 1+2 on main.
- Workflow doc: the Stage 2 "branch merge point" note now records the merge as DONE
(PR #1, e468d2e) and GENERALIZES it into a standing rule in the Cross-cutting
discipline section -- "branch-per-stage + MERGE AT STAGE CLOSE": every stage
develops on its own branch and merging it to main (merge commit, operator-gated)
is part of that stage's definition-of-done, then the branch is retired. main is
trunk-of-record only for merged stages; the next stage branches off post-merge main.
- session-ledger: Stage 2 CLOSED+MERGED recorded; the PINNED merge ruling graduated
into the standing discipline; NEXT = Stage 3 on dc-dc-stage3-phase2-dc-substrate,
with D-119 selectors + creds-audit (SEC-009) in place.
- Removed temp/ (3 stray screenshot PNGs that rode onto main via 95f0634).
repo-lint 0 fail. No script/harness touched (gauntlet unaffected).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015WgVrZE2ryY9aXP7JiBuwj
|

C2 sub-task (ii): extend sandbox loop to ip-ranges/ip-addresses; load + verify D-120 bands
...
Closes the logged tooling gap and the D-120 Step 6 load, verified end to end. With
this, C2 is fully closed and Stage 2 close-out (C1..C5) is COMPLETE.
Tooling:
- prod-draft-dump.py: ENDPOINTS += ipam/ip-ranges, ipam/ip-addresses (ranges get a
synthetic start-end key). Verified live.
- sandbox-fidelity-check.py: KEY + EXPECTED_NEW cover the 3 D-120 ranges + 2 service
IPs; both-bounds gives endpoint/address correctness (pure delta -- the frozen draft
has none). Success banner now D-115/D-117/D-118/D-120. Harness +T11/T12 (12->14).
- netbox/d120-compose-bands.py: stdlib, UA-aware, SANDBOX_HOSTS-guarded, dry-by-default,
whole-plan preflight (parent 10.10.1.0/24 exists, endpoints in-parent, start<=end) so
it cannot half-write. Harness 16/16 pins the band values + guards.
Load (operator-approved --commit into office1-netbox, the VR1 apex; sandbox-local):
- created 3 ip-ranges (.2-.49 / .100-.200 / .201-.254) + 2 ip-addresses (.10 netbox,
.11 tailscale), 0 already present. Re-dump -> 3 ranges, 2 IPs. Fidelity check -> exit 0,
"EXACTLY the planned delta (D-115/D-117/D-118/D-120)."
D-120 Step 6 CLOSED (no apex write -- write-back stays deferred). Gauntlet ALL GREEN
(60); repo-lint 0 fail. Workflow C2 row + session-ledger updated: Stage 2 close-out
complete -> branch->main merge point reached (merge remains operator-gated).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015WgVrZE2ryY9aXP7JiBuwj
|

C2 sub-task (i): sandbox re-verified faithful; fidelity-checker scope false-positive fixed
...
Re-ran the HARDENED fidelity check against office1-netbox (the VR1 IPAM apex,
DOCFIX-195) -- read-only dump via the consolidated sandbox token + an SSH tunnel.
Result: exit 0, "upstream draft + EXACTLY the planned delta (D-115/D-117/D-118)."
C2 sub-task (i) is CLOSED; the sandbox is proven faithful.
The hardened checker false-failed on 10.10.0.0/16 and 172.30.0.0/16 being
unscoped -- but those two role-container /16s are unscoped BY DESIGN
(d115-office-carve.py), matching the 27 unscoped role containers already in the
upstream apex. A full cross-check of all 44 delta prefixes against their intended
scope was 44/44 (every DC prefix on the correct DC site; no wrong-target binding).
Fix (netbox/sandbox-fidelity-check.py): assert scope == INTENDED per delta prefix
(EXPECTED_PREFIX_SCOPE; DC half generated by _dc_prefix_scopes so the CIDR set and
scope map cannot drift). Strictly stronger than the old "has a scope?" test -- still
catches a dropped scope, and now catches a WRONG-TARGET binding (a DC0 prefix bound
to vr1-dc1, the D-117 near-miss). NOT fixed by exempting the two CIDRs.
Harness moved to dump format (scope_site), T1 sets each prefix's scope from the
checker's own map, +T9 (wrong-target fails) +T10 (legit-unscoped passes). 10->12.
Gauntlet ALL GREEN (58); repo-lint 0 fail.
SEC-009: the sandbox NetBox token had been MISSED by the consolidation (lived only
at /root/netbox-secrets/api.token on office1-netbox); now consolidated to
~/vr1-office1-creds/vr1-netbox-sandbox.env (0600), copied off the VM without
printing. Ledger addendum recorded.
No NetBox state changed -- dump and check are read-only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015WgVrZE2ryY9aXP7JiBuwj
|

DOCFIX-195: office1-netbox holds the VR1 IPAM apex role; apex write-back deferred; C2 redefined
...
Operator ruling 2026-07-15: office1-netbox (10.10.1.10) HOLDS THE IPAM APEX
ROLE during VR1 -- the authoritative NetBox every consuming system (OpenTofu,
MAAS, overlays, bundle) derives its values from. It is a working draft (edited
as the buildout proceeds), but it is the apex. netbox.baldurkeep.com takes NO
write during VR1; it is the eventual production apex, frozen as a read-only v1
reference draft. Merge-back is deferred to end-of-deployment (a maybe), NOT a
Stage 2 gate.
The repo encoded the opposite roles and defined close-out C2 (the last Stage 2
gate, and the branch->main merge trigger) as an operator-gated write UPSTREAM.
Under the ruling there is no apex write in Stage 2, so C2 is redefined:
office1-netbox holds the COMPLETE validated dataset -- carve + six-plane roles
+ both DCs + the D-120 compose-/24 child ranges + the two service IPs -- and it
is VERIFIED. C2 stays OPEN.
Finding surfaced while drafting (logged, not fixed): the D-120 objects are
ip-ranges/ip-addresses, object classes the sandbox loop does NOT cover
(prod-draft-dump.py ENDPOINTS and sandbox-fidelity-check.py KEY/EXPECTED_NEW
both stop at rirs/roles/regions/sites/aggregates/prefixes). So the hardened
fidelity check cannot verify the D-120 load; verifying it needs a tool
extension (+harness) or a recorded manual read-back. Confirmed no live system
config points at any NetBox URL -- documentation-only change.
Files: dc-dc-deployment-workflow.md (State + C2 row), dc-dc-netbox-buildout-scope.md
(sec 8.1 + 8.6), session-ledger.md (5 narrative spots + new deferred-item bullet;
machine-derived block re-seeded from the 2026-07-15 scan), design-decisions.md
(D-010 note + D-103 pointer), netbox/README.md (target banner), + changelog.
repo-lint 0 fail.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015WgVrZE2ryY9aXP7JiBuwj
|