Skip to content

docs(adr): settle where stacks/lab/ runs and what it contains, and split #101 - #270

Merged
Gerrrt merged 1 commit into
mainfrom
gerrrt/adr-0007-lab-stack-saruman-3d8e44
Sep 4, 2026
Merged

docs(adr): settle where stacks/lab/ runs and what it contains, and split #101#270
Gerrrt merged 1 commit into
mainfrom
gerrrt/adr-0007-lab-stack-saruman-3d8e44

Conversation

@Gerrrt

@Gerrrt Gerrrt commented Sep 4, 2026

Copy link
Copy Markdown
Owner

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

The third premise — "the Makefile is already stack-parameterised, so the
tooling 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 under
stacks/lab/, reusing alloy/config.alloy unchanged — the two *_URL
variables 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.
Saruman is the one host in the estate that must not run Docker, and
scripts/deploy-agent.sh says why: ADR-0014 leans on the hypervisor's own
firewall and Docker rewrites iptables. It is the reason #88 shipped the native
package there rather than a container. So "Saruman hosts stacks/lab/" and
"Saruman does not run Docker" are both true, and putting compose on the
hypervisor honours the first by breaking the second. The stack goes in a guest,
single-homed on VLAN 30, on a static below .100 with a reservation.

What it contains — four services, not three. The same sentence says
config.alloy is reused with only the two *_URL variables changed, and that
file has two sinks: loki.write "grafana_loki" at line 386 and
prometheus.remote_write "metrics_service" at line 395. Name only Loki and the
second has nowhere to point but 10.0.99.20 — the one thing the Decision
forbids. 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.yaml has one creation_rule, and it matches all of secrets/. A
recipient 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=lab on the lab guest finds a
different recipient present and prints "Add this key as an additional
recipient by hand"
(scripts/bootstrap.sh:61-63), which is correct for a
second 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 the
lifecycle 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, and ci.yml:18. A
stacks/lab/ created before that work is a stack CI has never seen: compose
not config-checked, rules not promtool-tested, dashboards not checked
against 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:

#262 The guest on Saruman that the stack runs in
#263 Multi-stack validators and CI
#264 stacks/lab/ — Prometheus, Loki, Grafana, Alloy
#265 The Windows domain and its endpoints
#266 Wazuh — the heaviest component
#267 Velociraptor
#268 PBS — decide what it is for before installing it

Also fixes the README's ADR count, which said 14 against 18 on disk and would
have been five behind after this.

Verification

make validate passes — 2 skips, both the expected "not the deployment
checkout" ones. make lint and make check-docs clean; the ADR-number guard
from b71de98 accepts 0019.

Nothing was deployed and nothing on Saruman was touched: this is documents
and issues only.

Closes nothing. #101 stays open as the umbrella.

🤖 Generated with Claude Code

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>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 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.md to 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
Gerrrt merged commit c7071c0 into main Sep 4, 2026
4 checks passed
@Gerrrt
Gerrrt deleted the gerrrt/adr-0007-lab-stack-saruman-3d8e44 branch September 4, 2026 13:40
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants