diff --git a/docs/decisions/007-network.md b/docs/decisions/007-network.md index f9c1d0e..0963685 100644 --- a/docs/decisions/007-network.md +++ b/docs/decisions/007-network.md @@ -59,7 +59,7 @@ ISP ### IP addressing -### VLAN 10 — mgmt (10.10.0.0/24) — no DHCP +#### VLAN 10 — mgmt (10.10.0.0/24) — no DHCP | Address | Host | |---|---| @@ -69,7 +69,7 @@ ISP | `10.10.0.201` | `pve1` | | `10.10.0.202` | `pve2` | -### VLAN 20 — srv (10.20.0.0/24) — no DHCP, all static +#### VLAN 20 — srv (10.20.0.0/24) — no DHCP, all static | Range | Purpose | |---|---| @@ -87,28 +87,28 @@ Assigned infrastructure addresses: | `10.20.0.12` | `proxy` | Reverse proxy | | `10.20.0.13` | `homeassistant` | Home Assistant (IoT controller) | -### VLAN 30 — lan (10.30.0.0/24) +#### VLAN 30 — lan (10.30.0.0/24) | Range | Purpose | |---|---| | `10.30.0.1` | OPNsense gateway | | `10.30.0.100`–`.249` | DHCP pool | -### VLAN 40 — iot (10.40.0.0/24) +#### VLAN 40 — iot (10.40.0.0/24) | Range | Purpose | |---|---| | `10.40.0.1` | OPNsense gateway | | `10.40.0.100`–`.249` | DHCP pool | -### VLAN 50 — guest (10.50.0.0/24) +#### VLAN 50 — guest (10.50.0.0/24) | Range | Purpose | |---|---| | `10.50.0.1` | OPNsense gateway | | `10.50.0.100`–`.249` | DHCP pool | -### VLAN 99 — vpn — retired +#### VLAN 99 — vpn — retired The OPNsense WireGuard VPN (`10.99.0.0/24`) is **replaced by the NetBird mesh** (ADR-016). Remote access for `ubongo`, `askari`, and road-warrior clients rides a @@ -117,7 +117,7 @@ NetBird self-hosted on `askari`. NetBird manages its own overlay addressing (default `100.64.0.0/10`); no boma VLAN/subnet is allocated for it, and `10.99.0.0/24` is freed. -### Corosync ring (172.16.0.0/24) — not on managed switch +#### Corosync ring (172.16.0.0/24) — not on managed switch | Address | Host | |---|---| diff --git a/docs/decisions/008-testing.md b/docs/decisions/008-testing.md index b6935e7..279e516 100644 --- a/docs/decisions/008-testing.md +++ b/docs/decisions/008-testing.md @@ -19,7 +19,7 @@ This document records the testing strategy, what each level covers, and — crit ### Three testing levels -### Level 1 — Molecule (per role, always required) +#### Level 1 — Molecule (per role, always required) Runs in Docker on the control node (`ubongo`) or in CI. Fast (~5 min per role). @@ -47,7 +47,7 @@ The idempotency step is non-negotiable. Every role must pass it cleanly. that: svc.stdout == "active" ``` -### Level 2 — Staging playbook (full stack, real VMs) +#### Level 2 — Staging playbook (full stack, real VMs) `make check PLAYBOOK=site` followed by `make deploy PLAYBOOK=site` on Terraform-provisioned staging VMs. Catches inter-role dependencies and ordering @@ -56,13 +56,13 @@ have already run and configured the firewall). Run before every merge to `main`. -### Level 3 — External smoke test from askari +#### Level 3 — External smoke test from askari Once `askari` is operational: scripted checks from outside the network confirming that public-facing services respond correctly. Catches firewall and reverse proxy configuration issues invisible to Ansible check mode. -### Level 4 — Service-UI acceptance (Claude-driven exploratory) +#### Level 4 — Service-UI acceptance (Claude-driven exploratory) A Claude-driven exploratory check of a service's **application UI**, run as `/verify-service ` on `ubongo` (ADR-017). Claude drives Chromium via the @@ -129,7 +129,7 @@ catches anything lint misses. ### What Molecule tests — and what it does not -### Tested in Molecule +#### Tested in Molecule | Capability | Notes | |---|---| @@ -145,7 +145,7 @@ catches anything lint misses. | auditd installation and configuration | Install and config file | | Idempotency of all of the above | Enforced by Molecule's idempotency step | -### Not tested in Molecule — explicit exceptions +#### Not tested in Molecule — explicit exceptions The following require a real kernel or real hardware and are validated only at Level 2 (staging) or Level 3 (external). This is a conscious, documented decision diff --git a/scripts/repo-scan.py b/scripts/repo-scan.py index 58b3594..95ca228 100644 --- a/scripts/repo-scan.py +++ b/scripts/repo-scan.py @@ -49,7 +49,7 @@ ADR_REQUIRED_SECTIONS = ("Status", "Context", "Decision", "Consequences") ADR_STATUS_LINE_RE = re.compile( r"^(Proposed \(\d{4}-\d{2}-\d{2}\)" r"|Accepted \(\d{4}-\d{2}-\d{2}\)" - r"|Superseded by ADR-\d{3}" + r"|Superseded by ADR-\d{3} \(\d{4}-\d{2}-\d{2}\)" r"|Deprecated \(\d{4}-\d{2}-\d{2}\))") @@ -136,8 +136,8 @@ def adr_structure_findings(adr_files): out.append({"check": "adr-structure", "severity": "medium", "path": rpath, "line": headings["Status"] + 1, "detail": "Status not parseable (want 'Proposed (YYYY-MM-DD)', " - "'Accepted (YYYY-MM-DD)', 'Superseded by ADR-NNN', or " - "'Deprecated (YYYY-MM-DD)'); " + "'Accepted (YYYY-MM-DD)', 'Superseded by ADR-NNN " + "(YYYY-MM-DD)', or 'Deprecated (YYYY-MM-DD)'); " f"got: {status_text[:60]!r}"}) return out