docs(adr): settle where stacks/lab/ runs and what it contains, and split #101 - #270
Merged
Merged
Conversation
ADR-0007 gave `Saruman` the defended estate and said it "runs its own Loki, Grafana and Alloy under `stacks/lab/`, reusing `alloy/config.alloy` unchanged — the two `*_URL` variables are the only difference." #101 tracks that sentence. Two things it does not settle block the first line of work, and neither gets cheaper to answer once a directory exists. Where it runs. A compose stack is Docker, and `Saruman` is the one host that must not run Docker — ADR-0014 leans on the hypervisor's own firewall and Docker rewrites iptables, which is why #88 shipped the native .deb there. So "`Saruman` hosts stacks/lab/" and "`Saruman` does not run Docker" are both true. ADR-0019 puts the stack in a guest, single-homed on VLAN 30, static below .100 with a reservation. What it contains. The list is three services, and the same sentence says config.alloy is reused with only the two `*_URL` variables changed — but that file has two sinks, loki.write at 386 and prometheus.remote_write at 395. With no lab Prometheus the second points at 10.0.99.20, which is the one thing the Decision forbids. Four services, not three: the three-item list was an omission, not a choice. Two things found while writing it, both recorded as consequences rather than fixed here. `.sops.yaml` carries one creation_rule matching all of secrets/, so adding the lab guest as a recipient — which is exactly what bootstrap.sh tells you to do on a host with a different key — would let a lab host decrypt the estate's SNMP communities and Grafana admin password. And `STACK ?=` reaches the lifecycle targets only: validate.sh, the four Python checkers, check_loki_rules.sh, seed-validation-env.sh and ci.yml are all pinned to stacks/observability, so a second stack today is one CI has never seen. No Alertmanager inside the stack, which is the composition question and not an answer to #257 — that one asks whether the lab's liveness may cross to the estate even though its telemetry may not, and it predates this. #101 becomes an umbrella with seven issues behind it and moves out of "Decided but not built", whose premise is an ADR with nothing tracking it: #262 the guest, #263 the validators, #264 the stack, #265 the domain, #266 Wazuh, #267 Velociraptor, #268 PBS. The README's ADR count was four behind before this added a fifth. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
🟢 Approval recommended
The changes are documentation-only, internally consistent, and the updated ADR count matches the current docs/adr/ contents.
Pull request overview
This PR documents and clarifies the plan for ADR-0007’s “defended estate on Saruman” work by introducing ADR-0019 (guest-based lab stack with its own Prometheus), and updates the roadmap/ADR cross-references so #101 is treated as an umbrella with split-out prerequisites.
Changes:
- Add ADR-0019 to decide (a) where
stacks/lab/runs (guest, not hypervisor) and (b) that it includes Prometheus in addition to Loki/Grafana/Alloy. - Update
docs/roadmap.mdto move #101 out of “Decided but not built” and enumerate the split issues (#262–#268) and ordering. - Update README ADR count and add an ADR-0019 note/link in ADR-0007 without amending its original decision text.
File summaries
| File | Description |
|---|---|
| README.md | Updates the repository layout comment to reflect 19 ADRs. |
| docs/roadmap.md | Moves #101 to the active roadmap section with updated narrative and ordering; removes it from “Decided but not built”. |
| docs/adr/0019-run-the-lab-stack-in-a-guest-with-its-own-prometheus.md | Adds ADR-0019 capturing the guest placement + “four services” decision and consequences. |
| docs/adr/0007-defensive-estate-and-offensive-range.md | Adds a NOTE linking ADR-0019 as the resolution for the previously-unstated stacks/lab/ details. |
Review details
- Files reviewed: 4/4 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Gerrrt
added a commit
that referenced
this pull request
Sep 4, 2026
main is currently failing its own numbering guard: #98's "read device joins from the DHCP server" and #101's "run the lab stack in a guest" both merged as ADR-0019, twenty-three minutes apart, and check_adr_numbers reports the pair on any tree containing both. That blocks every open pull request, not this one. check_docs.py states the rule in the failure text — "renumber the one that landed second" — so this is arithmetic rather than a judgement call. Landing order on main's first-parent line, which is when a document became referenceable rather than when it was written: da0a071 06:33 #271 device joins stays ADR-0019 c7071c0 06:40 #270 lab stack becomes ADR-0020 (this branch, unmerged) becomes ADR-0021 The converge ADR yields to both, because merged work should not renumber to accommodate an open branch. Its number has now moved twice in one day, which is the cost of three ADRs being written against the same tip. Also fixes two link-text/target mismatches this branch introduced earlier: the 0019->0020 pass used a blanket `ADR-0019]` -> `ADR-0020]` substitution scoped to files mentioning converge, and roadmap.md and observability.md mention both, so two device-joins links were relabelled while still pointing at the right file. check_docs.py compares a filename to its own H1 and cannot see that, so both are now checked directly: every [ADR-NNNN](...NNNN-*.md) in the tree agrees with its target, and every ADR link resolves. The README conflict was main reflowing the runbook list against this branch adding `converge` to it; kept both. The adr/ count goes to 21. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Settles the shape of #101 and
breaks it up. No
stacks/lab/here — that is#264, and it is blocked behind
two things this PR names.
Two of #101's premises had already gone stale
docs/roadmap.mdcarried ADR-0007: build stacks/lab/ on Saruman — nothing of it exists #101 under Decided but not built, and Procure ifrit and build the playground — after the main network is finished #96sequences behind it ("ADR-0007: build stacks/lab/ on Saruman — nothing of it exists #101 first, because an attack VM pointed at an
uninstrumented estate teaches nothing").
Sarumanhas no Alloy agent yet." Deploy Alloy to Saruman and oracle #88 closed 2026-09-02: native.debvia
scripts/deploy-agent.sh, remote-writing the hypervisor's own telemetryover one unlogged pass.
The third premise — "the
Makefileis already stack-parameterised, so thetooling supports it" — is half true, and the half that is false is the one that
matters. See below.
What ADR-0019 decides
ADR-0007 said
Saruman"runs its own Loki, Grafana and Alloy understacks/lab/, reusingalloy/config.alloyunchanged — the two*_URLvariables are the only difference." Two things that sentence does not settle
block the first line of work.
Where it runs — a guest, not the hypervisor. A compose stack is Docker.
Sarumanis the one host in the estate that must not run Docker, andscripts/deploy-agent.shsays why: ADR-0014 leans on the hypervisor's ownfirewall and Docker rewrites iptables. It is the reason #88 shipped the native
package there rather than a container. So "
Sarumanhostsstacks/lab/" and"
Sarumandoes not run Docker" are both true, and putting compose on thehypervisor honours the first by breaking the second. The stack goes in a guest,
single-homed on VLAN 30, on a static below
.100with a reservation.What it contains — four services, not three. The same sentence says
config.alloyis reused with only the two*_URLvariables changed, and thatfile has two sinks:
loki.write "grafana_loki"at line 386 andprometheus.remote_write "metrics_service"at line 395. Name only Loki and thesecond has nowhere to point but
10.0.99.20— the one thing the Decisionforbids. The three-item list is an omission, not a choice.
Also decided: no Alertmanager inside the stack. That is the composition
question only, and explicitly not an answer to
#257, which predates this and
asks whether the lab's liveness may cross to the estate even though its
telemetry may not.
Two things found on the way, recorded rather than fixed
.sops.yamlhas onecreation_rule, and it matches all ofsecrets/. Arecipient added to it can decrypt every file there — the estate's SNMP
communities and Grafana admin password included. And that is the path the
tooling walks you down:
make secrets-init STACK=labon the lab guest finds adifferent recipient present and prints "Add this key as an additional
recipient by hand" (
scripts/bootstrap.sh:61-63), which is correct for asecond estate host and exactly wrong here. A lab host that can decrypt the
estate's credentials inverts the trust direction ADR-0007 exists to protect.
Every validator is pinned to
stacks/observability.STACK ?=reaches thelifecycle and secrets targets and stops —
validate.sh:17,check_docs.py:72,check_dashboards.py:37,check_compose_health.py:63,check_loki_rules.sh:28,seed-validation-env.sh:26, andci.yml:18. Astacks/lab/created before that work is a stack CI has never seen: composenot
config-checked, rules notpromtool-tested, dashboards not checkedagainst its own datasources, images pinned by nothing. That is #263, and it is
a prerequisite rather than something to find out afterwards.
The split
#101 moves out of Decided but not built — that section's premise is an
accepted ADR with nothing tracking it, and there are now seven issues behind
this one. In order:
Sarumanthat the stack runs instacks/lab/— Prometheus, Loki, Grafana, AlloyAlso fixes the README's ADR count, which said 14 against 18 on disk and would
have been five behind after this.
Verification
make validatepasses — 2 skips, both the expected "not the deploymentcheckout" ones.
make lintandmake check-docsclean; the ADR-number guardfrom b71de98 accepts 0019.
Nothing was deployed and nothing on
Sarumanwas touched: this is documentsand issues only.
Closes nothing. #101 stays open as the umbrella.
🤖 Generated with Claude Code