Context
docker-compose.yml's header comment block (lines 6-19) documents the full set of --profile flags an
operator can pass, under the banner "PROFILES — activate optional services by passing --profile <name>
(combine freely)":
--profile postgres, pgbouncer, qdrant, ollama, gpu, visual-review, rees, litestream, caddy,
observability, tailscale, runners
That list is stale. Grepping the file for every profiles: [...] a service actually declares turns up
two profiles that are real, wired, working services but are missing from the header list:
ams-observability — used by the ams-reporting-exporter service (docker-compose.yml ~line 850),
which redacts and exports the AMS (loopover-miner) attempt-log/prediction-ledger SQLite stores for the
"AMS Attempt Log" / "AMS Prediction Ledger" Grafana datasources
(grafana/provisioning/datasources/ams-ledgers.yml). This profile is real enough that it's documented
in .env.example (search LOOPOVER_AMS_REPORTING_EXPORT_INTERVAL_SECONDS), on the website
(docs.ams-observability-callout.tsx / docs.self-hosting-operations.tsx), and in
packages/loopover-miner/docs/observability.md — just not in the one place a self-hoster is most
likely to look first: the compose file's own profile banner.
backup — used by both the backup service and the backup-exporter service (~lines 1103, 1150),
which run scheduled pg_dump/SQLite-online-backup + Qdrant-snapshot backups and expose backup-freshness
metrics to Prometheus. This profile IS mentioned later in the file's inline comments (e.g. the Grafana
service's depends_on note) and in apps/loopover-ui/src/routes/docs.self-hosting-operations.tsx, but
is absent from the top-of-file banner that enumerates "the" profile list.
An operator skimming only the header (the documented entry point — "PROFILES — activate optional services
by passing --profile <name>") has no way to discover either profile exists without reading the entire
~1,170-line file or the separate website docs. This is exactly the kind of drift the repo already guards
against for other doc/runtime pairs (e.g. npm run selfhost:env-reference,
apps/loopover-ui/src/lib/selfhost-docs-audit.ts) — there is currently no automated check that the header
banner's profile list matches the profiles services actually declare.
Requirements
- Add
ams-observability and backup to the docker-compose.yml header's PROFILES comment block, each
with a one-line description matching the style of the existing entries (see the observability and
rees lines for tone/length).
- Add a regression test (e.g. a new
test/unit/docker-compose-profiles-header.test.ts, following the
existing pattern in test/unit/docker-compose-override-example.test.ts / test/unit/miner-docker-compose.test.ts
for reading/parsing this file) that:
- Parses every
profiles: [...] array declared anywhere in docker-compose.yml.
- Extracts every
--profile <name> token mentioned in the header comment block (lines before the
services: key).
- Asserts the two sets are equal, so a future profile addition/removal that forgets to update the
banner fails CI with a clear message instead of silently drifting again.
Deliverables
Test Coverage Requirements
This touches docker-compose.yml (not src/**, so it is outside coverage.include and Codecov's
patch-coverage gate does not apply to the YAML edit itself) plus a new test file under test/unit/**,
which IS measured. The new drift-guard test must cover both branches of the equality assertion (a
passing case against the real file, and at least one deliberately-broken fixture case — e.g. a profile
declared in profiles: but missing from the header, and vice versa — to prove the assertion actually
fails when the lists diverge, not just that it passes today).
Expected Outcome
An operator who reads only docker-compose.yml's header comment sees the complete, accurate list of
every --profile flag the file supports, including ams-observability and backup. A future PR that
adds or renames a profile without updating the header fails CI instead of shipping silent doc drift.
Links & Resources
docker-compose.yml (header lines 6-19; ams-reporting-exporter ~line 850; backup/backup-exporter
~lines 1103, 1150)
.env.example (LOOPOVER_AMS_REPORTING_EXPORT_INTERVAL_SECONDS, LOOPOVER_MINER_CONFIG_DIR)
packages/loopover-miner/docs/observability.md
apps/loopover-ui/src/routes/docs.self-hosting-operations.tsx
- Related prior art:
test/unit/docker-compose-override-example.test.ts,
test/unit/miner-docker-compose.test.ts, apps/loopover-ui/src/lib/selfhost-docs-audit.ts (the same
"doc must match runtime source of truth" pattern applied elsewhere in this repo)
Context
docker-compose.yml's header comment block (lines 6-19) documents the full set of--profileflags anoperator can pass, under the banner "PROFILES — activate optional services by passing
--profile <name>(combine freely)":
That list is stale. Grepping the file for every
profiles: [...]a service actually declares turns uptwo profiles that are real, wired, working services but are missing from the header list:
ams-observability— used by theams-reporting-exporterservice (docker-compose.yml~line 850),which redacts and exports the AMS (loopover-miner) attempt-log/prediction-ledger SQLite stores for the
"AMS Attempt Log" / "AMS Prediction Ledger" Grafana datasources
(
grafana/provisioning/datasources/ams-ledgers.yml). This profile is real enough that it's documentedin
.env.example(searchLOOPOVER_AMS_REPORTING_EXPORT_INTERVAL_SECONDS), on the website(
docs.ams-observability-callout.tsx/docs.self-hosting-operations.tsx), and inpackages/loopover-miner/docs/observability.md— just not in the one place a self-hoster is mostlikely to look first: the compose file's own profile banner.
backup— used by both thebackupservice and thebackup-exporterservice (~lines 1103, 1150),which run scheduled
pg_dump/SQLite-online-backup + Qdrant-snapshot backups and expose backup-freshnessmetrics to Prometheus. This profile IS mentioned later in the file's inline comments (e.g. the Grafana
service's
depends_onnote) and inapps/loopover-ui/src/routes/docs.self-hosting-operations.tsx, butis absent from the top-of-file banner that enumerates "the" profile list.
An operator skimming only the header (the documented entry point — "PROFILES — activate optional services
by passing
--profile <name>") has no way to discover either profile exists without reading the entire~1,170-line file or the separate website docs. This is exactly the kind of drift the repo already guards
against for other doc/runtime pairs (e.g.
npm run selfhost:env-reference,apps/loopover-ui/src/lib/selfhost-docs-audit.ts) — there is currently no automated check that the headerbanner's profile list matches the profiles services actually declare.
Requirements
ams-observabilityandbackupto thedocker-compose.ymlheader'sPROFILEScomment block, eachwith a one-line description matching the style of the existing entries (see the
observabilityandreeslines for tone/length).test/unit/docker-compose-profiles-header.test.ts, following theexisting pattern in
test/unit/docker-compose-override-example.test.ts/test/unit/miner-docker-compose.test.tsfor reading/parsing this file) that:
profiles: [...]array declared anywhere indocker-compose.yml.--profile <name>token mentioned in the header comment block (lines before theservices:key).banner fails CI with a clear message instead of silently drifting again.
Deliverables
docker-compose.ymlheader PROFILES list includesams-observabilityandbackup, each documentedin the same style as the existing entries.
profiles: [...]declarations always match.
Test Coverage Requirements
This touches
docker-compose.yml(notsrc/**, so it is outsidecoverage.includeand Codecov'spatch-coverage gate does not apply to the YAML edit itself) plus a new test file under
test/unit/**,which IS measured. The new drift-guard test must cover both branches of the equality assertion (a
passing case against the real file, and at least one deliberately-broken fixture case — e.g. a profile
declared in
profiles:but missing from the header, and vice versa — to prove the assertion actuallyfails when the lists diverge, not just that it passes today).
Expected Outcome
An operator who reads only
docker-compose.yml's header comment sees the complete, accurate list ofevery
--profileflag the file supports, includingams-observabilityandbackup. A future PR thatadds or renames a profile without updating the header fails CI instead of shipping silent doc drift.
Links & Resources
docker-compose.yml(header lines 6-19;ams-reporting-exporter~line 850;backup/backup-exporter~lines 1103, 1150)
.env.example(LOOPOVER_AMS_REPORTING_EXPORT_INTERVAL_SECONDS,LOOPOVER_MINER_CONFIG_DIR)packages/loopover-miner/docs/observability.mdapps/loopover-ui/src/routes/docs.self-hosting-operations.tsxtest/unit/docker-compose-override-example.test.ts,test/unit/miner-docker-compose.test.ts,apps/loopover-ui/src/lib/selfhost-docs-audit.ts(the same"doc must match runtime source of truth" pattern applied elsewhere in this repo)