docs(adr): fix 007/008 heading nesting; require date in Superseded status

Final-review polish: demote the sub-headings under the demoted 'IP addressing'
(007) and 'Three testing levels'/'What Molecule tests' (008) to #### so they
nest correctly instead of flattening to siblings. Tighten the adr-structure
Superseded pattern to require '(YYYY-MM-DD)' per ADR-023.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
sjat 2026-06-10 15:00:58 +02:00
parent 0df24909e3
commit d0a3307822
3 changed files with 16 additions and 16 deletions

View file

@ -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 |
|---|---|

View file

@ -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 <name>` 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

View file

@ -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