| 2026-07-27 |

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

Stage 4 close-out set: GA-R2 consolidation, skill sweep, snapshot, GA-R7 review
...
Every substantive Stage 4 item is closed or split to gate row G17. This is the GA-R6 close-out
set; only the operator-gated merge to `main` remains.
GA-R2 CONSOLIDATION: the 7 stage changelogs archived to docs/archive/changelogs/ (top-level
docs/ 25 -> 18, 113 archived total) and the stage record written at
docs/archive/stage-records/vr1-stage4-record.md. It records what the stage actually delivered
(READY handoff, not deployed nodes), the three things a future session should read first, and
the false-green pattern this stage kept surfacing. 12 stale `docs/changelog-*` paths in live
surfaces rewritten to the archive -- including several already dangling from the G12 close, so
every changelog reference in CURRENT-STATE / session-ledger / security-ledger now resolves.
SKILL SWEEP: three new INVARIANTS folded in --
- per-DC ARTIFACT DELIVERY is a per-DC STRATEGY (dc0 full mirror / dc1 caching proxy, D-135
amendments); checking dc1 as a mirror fails BY DESIGN. Corollary, learned the hard way: a
per-DC utility service OWNS ITS OWN network prerequisites -- never let one strategy's
teardown be able to break the other's.
- Stage 4 hands off READY nodes, NOT deployed ones (DOCFIX-200). Any surface saying "nodes
Deployed" as a Stage-4 exit is a defect; four were found and fixed. Consequence: nothing
node-side is probeable in Stage 4, which is why G17 exists.
- "A CHECKER THAT CANNOT FAIL IS NOT A GATE", with the working rules that came out of two
measured false greens: assert on CONTENT not existence; an unrecognised state REFUSES;
"could not look" is never "nothing there"; ENUMERATE what exists rather than only auditing
what is declared, because a register cannot report the absence of a row it does not have;
and when a test asserts a literal finding string, REPLACE the assertion, never delete it.
Plus two routing rows (per-DC artifact checks; D-137 as the credential-lifecycle authority).
SNAPSHOT REGENERATED: .claude/skills/openstack-cloud-ops-consolidated-20260727.md (1589 lines,
ASCII + LF byte-verified, all 7 sections). The superseded 20260725 snapshot is REMOVED rather
than kept alongside -- a stale snapshot being uploaded is the exact failure
docs/audit/skill-divergence-20260725.md records (a failed clone + a mis-shaped handoff), and it
is a derived artifact regenerable from any commit.
GA-R7 MEMORY REVIEW: NO new memory written -- everything durable from this stage graduated to
the skill and the repo, which is the correct GA-R7 outcome, not an omission. Both existing
entries verified against the repo (the finding-class baseline they cite does exist) and extended
with the two reasoning traps this stage produced: the register cannot see a credential it has no
ROW for, so enumerate what exists; and diagnose a denied call by checking whether an existing
rule failed to MATCH before blaming the classifier (the measured 2026-07-26 root cause). Also
marked the ask-vs-allow precedence claim UNVERIFIED so no future session relies on it.
Noted, not actioned (hard rule 1): T24's finding-class baseline covers TIER 1 only, so tier-2/3
classes have no baselined red state and a future false green there would not be caught.
Gauntlet ALL GREEN (81), repo-lint 0-fail.
Revert: git revert this commit; the changelog moves are renames and the snapshot is derived.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

permission allowlist: 13 read-only entries; ssh/scp/virsh and creds-matrix DELIBERATELY excluded
...
Transcript scan of the 44 most-recent session JSONLs across both project dirs, counting Bash
command+subcommand pairs and splitting each candidate into LOCAL vs nested-inside-ssh (a
command that only ever runs inside `ssh host '...'` gains nothing from its own rule -- the
outer ssh is what the permission layer matches).
ADDED to permissions.allow only, 31 -> 44 entries. ask/deny/env/hooks untouched, nothing
removed or reordered, JSON re-validated and de-duplicated:
bash scripts/creds-audit.sh * ; bash scripts/dc-dc-ceph-disk-budget.sh * ;
bash scripts/site-baseleg.sh check* ; tofu validate* (+ -chdir variant) ;
tofu state list* (+ -chdir variant) ; tofu state show* ; tofu providers schema* ;
tofu version* ; ip route* ; systemctl is-active *
The substantive half of this change is what was NOT added:
- ssh (693 calls), scp (47), virsh (234) -- the three biggest prompt sources, left alone.
They are this cloud's MUTATION TRANSPORT; CLAUDE.md hard rule 3 makes the ask rules the
enforcement mechanism for per-mutation approval and says not to work around them, and the
`Bash(ssh *)` disposition is an OPEN operator item queued at the 2026-07-23 close.
Allowlisting any ssh shape would bypass the gate and pre-empt an unresolved decision.
THIS COMMIT DOES NOT DISCHARGE THAT QUEUED PRUNE -- it is still open.
- python3 scripts/creds-matrix.py (76 local runs, the largest genuine miss) -- skipped
because `Bash(*creds-matrix.py *--privileged*)` is an ask rule added 2026-07-26 right after
the auto-mode classifier incident, and every prefix pattern wide enough to cover the
observed `... 2>&1 | tail` forms would ALSO match --privileged. Allow-vs-ask precedence was
not verified, and a rule that might silently downgrade an operator-installed gate is the
class of change hard rule 3 forbids. Needs the precedence answer, then exact forms.
- tofu plan (54) -- read-only in effect, but CURRENT-STATE section 10 says in terms "Do NOT
re-run `tofu plan` casually against live state". The prompt is deliberate friction.
- python3 -c/heredocs (324) and shells: arbitrary code execution. curl (114): mutation vector
via -X/-d. tofu fmt (26): rewrites files. tofu init (10): mutates .terraform/. nft list
(10): normally needs sudo, which correctly stays ask.
Flagged not fixed: six existing allow entries are redundant with Claude Code's built-in
auto-allow (git status/log, grep, ls, jq, cat scripts/*), and `Bash(git pull*)` sits in allow
though it is a fetch-and-merge. Removing entries was out of scope.
Folded into today's session changelog as item 6 (GA-R2/D1, ONE changelog per session) with a
per-item revert. repo-lint 0-fail; no executable surface changed, so the gauntlet result from
this session's earlier commit (ALL GREEN, 81) still stands.
Revert: drop the 13 appended lines from permissions.allow -- a contiguous deletion.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|
| 2026-07-26 |
permissions: close the ssh 'virsh quoting gap (twin of the sudo gap)
...
Bash(ssh * virsh *) had the same defect as the sudo rule: it needs a literal
space before `virsh`, so a quoted remote command ssh <host> 'virsh ...'
matched no rule and fell through to the classifier. Added the single- and
double-quoted variants.
Verified against real command shapes: quoted, double-quoted and bare forms
all resolve to an ask rule; ssh <host> 'sudo virsh ...' resolves via the sudo
rule; an unrelated ssh <host> 'ls -la' still matches nothing, so the widening
did not become a catch-all. All rules are `ask`, never `allow`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

D-137 tier 2: privileged sweep completed; permission gap fixed; 6 inferred filenames corrected
...
The block was a PATTERN GAP, not a policy conflict. Bash(ssh * sudo *) was
already in the project ask list, but it needs a literal space before `sudo`
and the command was ssh <host> 'sudo ...' -- the quote meant no rule matched,
so it fell through to the classifier. Added the quoted variants plus a
targeted rule for the privileged invocation; all `ask`, never `allow`.
FLAGGED NOT FIXED (hard rule 1): Bash(ssh * virsh *) has the identical gap.
MEASURED (docs/audit/d137-tier2-privileged-20260726.txt): the region secrets
dir holds exactly admin.apikey/admin.pass/db.pass/lxd-trust.pass at 0600 --
precisely the SEC-020(i) carve-out list; netbox holds admin.pass/api.token/
secret_key at 0600; the SEC-022 shadow stores are confirmed and now audited.
ZERO undeclared files at any declared location: SEC-020 and SEC-022 are
accounted for.
OWNED: the first matrix draft carried SIX filenames I authored plausibly
instead of measuring -- a hard-rule-2 miss. The checker's undeclared-detection
caught every one on first contact with real hosts. Corrected to measured
values, real locations declared, and five colocated non-secret artifacts
declared so the check stays meaningful. Matrix 72 -> 77 rows.
9 findings remain, all true: SEC-021 (3 declaration + 2 on-disk), the ruling-5
conflation, and 3 S5 asymmetries -- dc0's snap power key is the generic
id_ed25519 while dc1's is the dedicated id_dc1_power, the SEC-012/SEC-016
per-DC-isolation divergence made visible.
Harness 35/35, gauntlet ALL GREEN (80), repo-lint 0-fail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|
| 2026-07-25 |
skill: stage-close sweep keeps the authoritative dir current + regenerates the Chat snapshot
...
SKILL.md stage-close rule now states the authoritative, versioned skill is the
.claude/skills/openstack-cloud-ops/ dir (SKILL.md + references), kept current at each
stage-close sweep, and the dated single-file Chat-upload snapshot is REGENERATED from it
then (a derived artifact, never the source; the stale Chat mount is a known failure mode).
Regenerated openstack-cloud-ops-consolidated-20260725.md from the updated skill so the two
stay in sync. ASCII/LF verified; repo-lint 0 fail.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUjEb7onHpdqFHUnio6iVw
|
settings: allowlist read-only whole-host budget calculator
...
From the /fewer-permission-prompts pass: adds Bash(python3
scripts/dc-dc-whole-host-budget.py*) -- a read-only capacity calculator, sibling of
the already-allowed provider-bundle-check.py. Edit was operator-approved earlier this
session; committing now for a clean tree.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUjEb7onHpdqFHUnio6iVw
|
skill: consolidated openstack-cloud-ops snapshot for Chat/project upload
...
The Chat-side skill mount is stale (130 lines vs the repo SKILL.md 244 + 6
references; docs/audit/skill-divergence-20260725.md) -- it caused a failed clone
and a wrongly-shaped first handoff-pack draft this session. Chat's skill surface is
single-file, so this flattens SKILL.md + all references/ into ONE uploadable
document the operator can drop into the Chat project to replace the stale mount.
Point-in-time snapshot at HEAD 951ba9c; the authoritative copy stays
.claude/skills/openstack-cloud-ops/. Regenerate from the skill dir on change.
ASCII + LF byte-verified. Not loaded as a skill (loose file, not a skill dir).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUjEb7onHpdqFHUnio6iVw
|
| 2026-07-24 |

caveman: disable on live host + SEC-017 + fail-closed compress guardrails
...
Council-reviewed follow-through on the caveman plugin (operator accepted the
recommendations). Three gated deliverables:
1. DISABLED on the live host: enabledPlugins."caveman@caveman"=false at user
(~/.claude, uncommitted) + committed repo scope; env CAVEMAN_DEFAULT_MODE=off
retained; marketplace kept for deliberate re-enable. Stops the per-turn
third-party hook execution (which ran regardless of mode=off) at next
session start.
2. SEC-017 opened (docs/security-ledger.md): supply-chain re-verify obligation
pinning SHA 0d95a81 + self-remeasured baseline hashes; register = OPS (no
D-number per GA-R3; precedent D-129). Open SEC 12->13, reconciled at Stage 4
close (not an L10 trigger; CURRENT-STATE untouched).
3. Guardrails: repo-lint L11 FAILs on any *.original.md (caveman-compress
residue), opt-out marker + harness T42-T44 (45/45 PASS); .claude/settings.json
deny on Bash(*python3 -m scripts*)+Bash(*caveman-compress*); CLAUDE.md
"Tooling guardrails" prohibiting the compressor against any repo-tracked
prose. Rejected the proposed plugin-hash-baseline-in-repo-lint (layering
violation; drift belongs in the SEC-017 re-verify duty).
Harness-config + governance only; no cloud mutation. repo-lint 0 fail;
gauntlet ALL GREEN (77). Changelog: docs/changelog-20260724-caveman-disable.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUjEb7onHpdqFHUnio6iVw
|
| 2026-07-23 |
caveman plugin: off-by-default opt-in guard (repo + user CAVEMAN_DEFAULT_MODE=off)
...
Installed the third-party caveman Claude Code plugin (user scope) per operator
direction, then guarded it off-by-default so it never auto-arms while operating
the cloud. Hooks inspected read-only first: purely behavioral, no network /
credential access. Repo .claude/settings.json carries env CAVEMAN_DEFAULT_MODE=off
(durable, shared); the same guard was added to ~/.claude/settings.json (out of
repo, not committed) for off-by-default across all repos. /caveman still
activates on demand for future hardened-workflow deployment testing.
Harness-config only: no cloud mutation, no status change (CURRENT-STATE
untouched), repo-lint 0 fail. Changelog: docs/changelog-20260723-caveman-optin-guard.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUjEb7onHpdqFHUnio6iVw
|
Session permissions: allow ledger-scan + opentofu-validate (read-only; gates untouched)
...
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUjEb7onHpdqFHUnio6iVw
|
G12 dc1 close-out: consolidation + skill sweep + CURRENT-STATE (G12 CLOSED)
...
- GA-R2 consolidation: 3 dc1 session changelogs (07-21 authoring, 07-22 build,
07-23 edge) archived to docs/archive/changelogs/; Stage 3 record gains a
"G12 CLOSED 2026-07-23" section + table rows
- CURRENT-STATE: G12 gate CLOSED, section-1 stage narrative updated
- skill sweep: per-DC dedicated MAAS->libvirt power key (SEC-012/SEC-016)
added as a standing DC-standup invariant
- GA-R7 memory review: clean (no changes)
Close-out set complete; ready for operator-gated merge to main.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUjEb7onHpdqFHUnio6iVw
|
| 2026-07-21 |
STAGE 3 CLOSE (operator-ruled): GA-R2 consolidation + skill sweep; gauntlet 76 ALL GREEN
...
- Four execution-session changelogs -> docs/archive/changelogs/;
vr1-stage3-record.md finalized (close summary + session table)
- CURRENT-STATE stage line CLOSED (vr1-dc0 scope; dc1 = HELD G12);
workflow Stage-3 State row + runbooks/README corrected
- Skill sweep: D-131 forwarder invariant + MAAS pod traps + merge
precedent folded; no stale facts found
- Close-out evidence: gauntlet 76 ALL GREEN, repo-lint 0 fail, GA-R7
memory review done (changelog items 11, 17)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013XRtm5sDQgUsZyTJk3k7dz
|
Incident docs: 2 appendix-A entries, platform-traps MAC-regen corollary, LP draft
...
Closes the remaining documentation queue from the 2026-07-21
commissioning incident. Appendix-A gains the MAC-drift and
agent-resolver symptoms (verbatim, with checks + recorded fixes);
platform-traps 1e gains the unpinned-MAC regeneration corollary + index
row; LP draft ready for the operator to file (sanitization warning
included). Still queued: stale pod cleanup at stage close.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STHXiHfoxHqq8fGRVvb66G
|
| 2026-07-20 |
Permissions: targeted ask rules for scp / pipe-to-ssh / ssh-virsh (classifier walls -> gates, per 2026-07-19 ruling)
...
Deploy-window surfaces that hard-denied and stalled work: scp staging to
the dc0 rack, cat|ssh file staging, nested virsh over ssh. Per the ruled
convention these become ASK (approval prompts, Remote-Control friendly),
never allow -- hard rule 3 mutation gating is unchanged, and read-only
ssh probes stay frictionless.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KiUu1oqt76tWvV4vEC3NAr
|
| 2026-07-19 |
Batch 5: skill sweep -- GA-R1..R7 implemented in openstack-cloud-ops (single commit)
...
New GA-rulings section reconciled against the RATIFIED text (draft
skill-additions cross-checked; the one draft-only item, the
reconciliation circuit-breaker, kept and marked as discipline). Stale
facts demoted to CURRENT-STATE delegation: the header's "current phase =
VR0 testcloud" and the deploy loop's "opentofu UNVALIDATED / no tofu
binary has ever run" (false since 2026-07-13). Session bookends + stage
close rewritten per GA-R4/GA-R6; change-delivery per GA-R2/D1; routing
rows re-pointed. Read-through checklist committed:
docs/audit/skill-sweep-checklist-20260719.md (every ratified rule ->
disposition). G3 cell updated same commit. repo-lint 0 fail.
Revert: git revert this commit (restores the pre-sweep skill).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZbUeRE8weySLSGY1odrCi
|

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

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

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

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

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

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