feat(inventory): one service inventory drives planner, allow-lists, and seams
rank 3. shared/services.yml is the single source of truth for every deployable service (hosts, unit dirs, units, path claims, tests, scrape/ dns/probe facts, aliases, stacks); scripts/service-map.py is its only reader (hosts/services/allows/plan/seams/owner/units/stack/get/render/ check). deploy-plan.py becomes a thin CLI wrapper; both --service allow-lists (deploy.sh, deploy-remote.sh) and the changed-test mapper are the inventory. Selection semantics: host scoping (a host's tree selects only its own services), the quadlet netns/network coupling graph resolved across directories (gluetun's riders live in their own dirs), stacks, and canonical restart order. Against the pinned old-planner fixture: 921 raw deltas reduced to 17, all intentional (anubis now maps; cross-host paths select nothing; the retired qwen-sharp template plans nothing and the live froggeric template plans frank-archon; dankserver/romm/romm.container, missed by the old filename rule, plans the romm trio). The fixture is regenerated as the new pin. Integrity gates: schema, unit claims, quadlet-file ownership, path-glob liveness, Pi-hole hosts / Prometheus jobs / DNS / homepage probes drift. tests/service_inventory.bats pins all of it plus the plan fixture and the deploy-plan CLI. Allow-list grep seams in hermes_webui/observability_quadlet become behavioral allows calls.
This commit is contained in:
+57
-190
@@ -62,234 +62,101 @@ declare -a TESTS=()
|
||||
add_test() {
|
||||
local test="$1"
|
||||
local existing
|
||||
MAPPED=1
|
||||
for existing in "${TESTS[@]:-}"; do
|
||||
[ "$existing" != "$test" ] || return 0
|
||||
done
|
||||
TESTS+=("$test")
|
||||
}
|
||||
|
||||
# The path->test mapping has two layers:
|
||||
# 1. cross-cutting seams that no single service owns (compose validity,
|
||||
# env consistency, the deployer itself, docs, syntax);
|
||||
# 2. the service inventory (shared/services.yml via service-map.py
|
||||
# "seams"): every service the path selects on ANY host contributes its
|
||||
# declared tests. Service seams are the services' own — the former
|
||||
# shotgun fallbacks (every dankclaw quadlet unit ran the open-webui,
|
||||
# pihole, and searxng seams) are retired; the inventory names each
|
||||
# service's real seams, and adding a service is a data edit.
|
||||
for path in "${CHANGED[@]}"; do
|
||||
MAPPED=0
|
||||
case "$path" in
|
||||
tests/*.bats)
|
||||
add_test "$path"
|
||||
continue
|
||||
;;
|
||||
tests/helpers.bash|tests/run.sh)
|
||||
die "$path is shared test infrastructure; run ./tests/run.sh explicitly"
|
||||
;;
|
||||
.githooks/*)
|
||||
add_test tests/changed_tests.bats
|
||||
;;
|
||||
dankserver/compose.yaml|dankclaw/compose.yaml)
|
||||
add_test tests/compose_validity.bats
|
||||
add_test tests/env_consistency.bats
|
||||
add_test tests/deployment_scoping.bats
|
||||
;;
|
||||
dankserver/Caddyfile|dankclaw/Caddyfile)
|
||||
add_test tests/caddyfile.bats
|
||||
add_test tests/deployment_scoping.bats
|
||||
;;
|
||||
tests/e2e/*)
|
||||
add_test tests/e2e_scaffold.bats
|
||||
continue
|
||||
;;
|
||||
.githooks/*)
|
||||
add_test tests/changed_tests.bats
|
||||
continue
|
||||
;;
|
||||
# Archive material and host-level facts: a path under any
|
||||
# .AGENTTRASH/ directory is retired-by-policy (the planner plans
|
||||
# nothing from it), udev rules are applied by manual sudo install,
|
||||
# reports are frozen artifacts, and ignore-lists change nothing at
|
||||
# runtime. They select no focused test; without this case they would
|
||||
# trip the mapping gate below.
|
||||
.AGENTTRASH/*|*/.AGENTTRASH/*|dankclaw/udev/*|report/*|.gitignore|.env.example)
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
|
||||
# Cross-cutting seams.
|
||||
case "$path" in
|
||||
dankserver/.env.example|dankclaw/.env.example)
|
||||
add_test tests/env_consistency.bats
|
||||
;;
|
||||
shared/homepage/*)
|
||||
add_test tests/homepage_caddy.bats
|
||||
add_test tests/homepage_config.bats
|
||||
add_test tests/homepage_deploy.bats
|
||||
add_test tests/homepage_pages.bats
|
||||
add_test tests/homepage_checker.bats
|
||||
add_test tests/deployment_scoping.bats
|
||||
;;
|
||||
shared/searxng/*)
|
||||
add_test tests/searxng_limiter.bats
|
||||
add_test tests/searxng_quadlet.bats
|
||||
add_test tests/deployment_scoping.bats
|
||||
;;
|
||||
# The five observability containers left compose for Quadlet units on
|
||||
# 2026-09-11. Their seam pins boot persistence and the loopback-only
|
||||
# publish posture that keeps them off every host interface. This case
|
||||
# precedes the dankclaw/*/quadlet/*.container fallback below, which
|
||||
# would otherwise swallow it into unrelated seams.
|
||||
dankclaw/observability/*)
|
||||
add_test tests/observability_quadlet.bats
|
||||
dankserver/Caddyfile|dankclaw/Caddyfile)
|
||||
add_test tests/caddyfile.bats
|
||||
add_test tests/deployment_scoping.bats
|
||||
add_test tests/docs_completeness.bats
|
||||
;;
|
||||
# Caddy left compose.yaml on 2026-08-26 for a quadlet; caddyfile.bats
|
||||
# now asserts against the quadlet itself. Route every change under
|
||||
# dankclaw/caddy/ to both seams (this block must precede the
|
||||
# dankclaw/*.container glob, which would otherwise swallow the unit).
|
||||
dankclaw/caddy/*)
|
||||
add_test tests/caddy_quadlet.bats
|
||||
add_test tests/caddyfile.bats
|
||||
add_test tests/docs_completeness.bats
|
||||
add_test tests/deployment_scoping.bats
|
||||
;;
|
||||
dankclaw/*.container|dankclaw/*.network)
|
||||
add_test tests/open_webui_quadlet.bats
|
||||
add_test tests/pihole.bats
|
||||
add_test tests/searxng_quadlet.bats
|
||||
add_test tests/deployment_scoping.bats
|
||||
;;
|
||||
# Anubis is a quadlet + Caddyfile pair: the chain (public listener ->
|
||||
# anubis:8923 -> internal :8016 listener) spans both, so any change
|
||||
# under the service dir must re-run the caddyfile seam too. This case
|
||||
# precedes the dankclaw/*/quadlet/*.container fallback below, which
|
||||
# would otherwise swallow it into unrelated seams.
|
||||
dankclaw/anubis/*)
|
||||
add_test tests/anubis.bats
|
||||
add_test tests/caddyfile.bats
|
||||
add_test tests/homepage_caddy.bats
|
||||
add_test tests/deployment_scoping.bats
|
||||
;;
|
||||
# frank-dragoon is a rootless quadlet again since 2026-09-16 (the
|
||||
# halogen-flash-server restore). Its unit carries the fleet's GPU and
|
||||
# podman posture, so it gets its own seam rather than the generic
|
||||
# fallback below.
|
||||
dankclaw/frank-dragoon/*)
|
||||
add_test tests/frank_dragoon_unit.bats
|
||||
add_test tests/deployment_scoping.bats
|
||||
add_test tests/docs_completeness.bats
|
||||
;;
|
||||
# Quadlets live under dankclaw/<svc>/quadlet/ (reorganized out of the
|
||||
# top-level dankclaw/*.container globs above). Map the whole subtree so
|
||||
# changes to the pihole, open-webui, searxng, and gitea quadlets hit
|
||||
# their seam tests. The pihole-specific block must come first: shell
|
||||
# case matches the first pattern, and dankclaw/pihole/quadlet/*.container
|
||||
# also satisfies the dankclaw/*/quadlet/*.container fallback.
|
||||
dankclaw/pihole/*)
|
||||
add_test tests/pihole.bats
|
||||
add_test tests/docs_completeness.bats
|
||||
add_test tests/deployment_scoping.bats
|
||||
;;
|
||||
dankclaw/*/quadlet/*.container|dankclaw/*/quadlet/*.network)
|
||||
add_test tests/open_webui_quadlet.bats
|
||||
add_test tests/pihole.bats
|
||||
add_test tests/searxng_quadlet.bats
|
||||
add_test tests/docs_completeness.bats
|
||||
add_test tests/deployment_scoping.bats
|
||||
;;
|
||||
scripts/deploy.sh|scripts/deploy-remote.sh|scripts/deploy-plan.py|scripts/deploy-*-quadlet.sh)
|
||||
add_test tests/deployment_flow.bats
|
||||
add_test tests/deployment_scoping.bats
|
||||
add_test tests/scripts_syntax.bats
|
||||
;;
|
||||
scripts/service-map.py|shared/services.yml)
|
||||
add_test tests/service_inventory.bats
|
||||
add_test tests/deployment_scoping.bats
|
||||
add_test tests/scripts_syntax.bats
|
||||
;;
|
||||
scripts/lib/*.sh)
|
||||
add_test tests/llm_gates.bats
|
||||
add_test tests/scripts_syntax.bats
|
||||
;;
|
||||
serverAppConfigs-docs/*|README.md|AGENTS.md)
|
||||
add_test tests/docs_completeness.bats
|
||||
add_test tests/deployment_scoping.bats
|
||||
;;
|
||||
scripts/mount-entropy.sh)
|
||||
add_test tests/mount_entropy.bats
|
||||
;;
|
||||
# Archive material: a path under any .AGENTTRASH/ directory is
|
||||
# retired-by-policy (deploy-plan.py plans nothing from it), so it
|
||||
# selects no focused test. It still needs its own case: without one
|
||||
# the wrapper refuses retirement commits, whose trash moves show up
|
||||
# as additions, with "no focused-test mapping".
|
||||
.AGENTTRASH/*|*/.AGENTTRASH/*)
|
||||
;;
|
||||
# frank-archon is llama.cpp (the unsloth b11030-mix prebuilt) on the
|
||||
# two R9700s as a ROOT unit — the 2026-09-26 swap that replaced the
|
||||
# Lucebox DS4.1 quadlet on :7770. Its env, unit and serve script all
|
||||
# live in scripts/, which is why this case has to precede the generic
|
||||
# scripts/*.service and scripts/* fallbacks below.
|
||||
scripts/frank-archon.*|scripts/serve-frank-archon.sh)
|
||||
add_test tests/frank_archon_unit.bats
|
||||
add_test tests/llm_units.bats
|
||||
add_test tests/deployment_scoping.bats
|
||||
add_test tests/scripts_syntax.bats
|
||||
;;
|
||||
# frank-dragoon's chat template lives in scripts/ (moved there on
|
||||
# 2026-09-26 when its old home was retired): a template edit changes
|
||||
# what the live endpoint renders, so it selects the large unit seam.
|
||||
scripts/qwen-sharp-v22.4.0.jinja)
|
||||
add_test tests/frank_dragoon_unit.bats
|
||||
;;
|
||||
# frank-zealot is llama.cpp on the 3090 as a ROOT unit: a native CUDA
|
||||
# binary, so no generated quadlet is involved. Its env, unit and serve
|
||||
# script all live in scripts/, which is why this case has to precede
|
||||
# the generic scripts/*.service and scripts/* fallbacks below.
|
||||
scripts/frank-zealot.*|scripts/serve-frank-zealot.sh)
|
||||
add_test tests/frank_zealot_unit.bats
|
||||
add_test tests/llm_units.bats
|
||||
add_test tests/deployment_scoping.bats
|
||||
old/scripts/*)
|
||||
add_test tests/scripts_syntax.bats
|
||||
;;
|
||||
scripts/*.service)
|
||||
add_test tests/llm_units.bats
|
||||
add_test tests/scripts_syntax.bats
|
||||
add_test tests/deployment_scoping.bats
|
||||
;;
|
||||
scripts/*)
|
||||
add_test tests/scripts_syntax.bats
|
||||
;;
|
||||
serverAppConfigs-docs/*)
|
||||
add_test tests/docs_completeness.bats
|
||||
add_test tests/deployment_scoping.bats
|
||||
;;
|
||||
dankclaw/hermes/*)
|
||||
add_test tests/docs_completeness.bats
|
||||
;;
|
||||
README.md|AGENTS.md)
|
||||
# Project guidance changes can affect the documented deployment
|
||||
# contract; validate the docs vault and scoped deployment seams.
|
||||
add_test tests/docs_completeness.bats
|
||||
add_test tests/deployment_scoping.bats
|
||||
;;
|
||||
dankclaw/comfyui/*)
|
||||
add_test tests/docs_completeness.bats
|
||||
add_test tests/deployment_scoping.bats
|
||||
;;
|
||||
# loki and alloy images are built from this repo's Containerfiles; the
|
||||
# loki one carries the healthcheck command (see its comment), so both
|
||||
# seams read it.
|
||||
dankclaw/loki/*|dankclaw/alloy/*)
|
||||
add_test tests/observability_quadlet.bats
|
||||
add_test tests/caddyfile.bats
|
||||
add_test tests/deployment_scoping.bats
|
||||
;;
|
||||
dankclaw/udev/*)
|
||||
# Host-level udev rules are applied by manual sudo install + udevadm
|
||||
# trigger, not dispatched as a service; nothing here is bats-testable.
|
||||
;;
|
||||
.AGENTTRASH/*)
|
||||
# Trash moves are not testable; skip.
|
||||
;;
|
||||
.gitignore|.env.example)
|
||||
# Ignore/ignore-list changes change nothing at runtime; not testable.
|
||||
;;
|
||||
old/scripts/*|old/scripts/**)
|
||||
add_test tests/scripts_syntax.bats
|
||||
;;
|
||||
# Cutover-era dankserver quadlet units (romm trio, jellyfin, calibre-web,
|
||||
# copyparty, plex, tautulli): the seam checks boot-persistence +
|
||||
# v1-generator contract; deploy-flow seams guard the deployer routing.
|
||||
dankserver/romm/*|dankserver/jellyfin/*|dankserver/calibre-web/*|dankserver/copyparty/*|dankserver/plex/*|dankserver/tautulli/*)
|
||||
add_test tests/dankserver_quadlet.bats
|
||||
add_test tests/deployment_flow.bats
|
||||
add_test tests/deployment_scoping.bats
|
||||
;;
|
||||
# FrankBook-Pro units live inside the podman machine VM; the seam
|
||||
# pins boot persistence, the gluetun netns invariant, and the
|
||||
# plan-locked volume layout. Deployer changes route here too.
|
||||
frankbook-pro/*)
|
||||
add_test tests/frankbook_quadlet.bats
|
||||
add_test tests/deployment_flow.bats
|
||||
add_test tests/deployment_scoping.bats
|
||||
;;
|
||||
# Seerr unit on dankclaw: boot persistence, loopback-only port
|
||||
# posture, and the deployer routing that installs it.
|
||||
dankclaw/seerr/*)
|
||||
add_test tests/seerr_quadlet.bats
|
||||
add_test tests/deployment_flow.bats
|
||||
add_test tests/deployment_scoping.bats
|
||||
;;
|
||||
report/*)
|
||||
# Frozen audit reports are point-in-time artifacts; not testable.
|
||||
;;
|
||||
*)
|
||||
die "no focused-test mapping for '$path'; add one before committing"
|
||||
;;
|
||||
esac
|
||||
|
||||
# The inventory's declared seams for every service this path selects.
|
||||
while IFS= read -r seam; do
|
||||
[ -n "$seam" ] && add_test "$seam"
|
||||
done < <(python3 "$REPO_ROOT/scripts/service-map.py" seams -- "$path" 2>/dev/null || true)
|
||||
|
||||
# Mapping gate: a path that selected no test at all — no service seam,
|
||||
# no cross-cutting rule — is an unmapped area of the repo; refuse to
|
||||
# commit it silently (the old catch-all `die`).
|
||||
if [ "$MAPPED" -eq 0 ]; then
|
||||
die "no focused-test mapping for '$path'; map it in the inventory (shared/services.yml) before committing"
|
||||
fi
|
||||
done
|
||||
|
||||
# Mapped-but-untestable paths (udev rules, trash moves) legitimately produce an
|
||||
|
||||
Reference in New Issue
Block a user