Compare commits

...
Author SHA1 Message Date
Frank c41384e03d fix(deploy): grafana copier provisions EVERY dashboard json, not a hand-listed subset
copy_grafana_provisioning named llm-fleet.json alone, so a new board in
shared/grafana/provisioning/dashboards/ silently never reached
/opt/grafana/provisioning. Measured 2026-10-21: the containers board was
merged, seam-tested, and documented as working since 2026-10-20, yet was
NEVER provisioned — its panel fixes were undeployable; the other four
boards existed on disk only because someone hand-copied them once (and
host-system.json there had gone stale against the repo). The copier now
globs the directory, and GRAFANA_PROV_ROOT lets the new bats case run
the copier for real into a sandbox (red against the old hand-listed
body, green against the glob). Vault: the copier law + the containers
incident on Observability Provisioning, the corrected dashboards claim
on Observability.

Note: a mktemp sandbox + trap RETURN races bats' trap machinery (the
dir vanishes mid-function); BATS_TEST_TMPDIR is the safe idiom.
2026-10-21 09:40:57 -07:00
dank 127621bdd7 Merge pull request 'docs(vault)+fix(grafana): audit tranche 1 and dashboard data-flow fixes' (#67) from fix/dragoon-context-200k into main 2026-10-21 09:28:43 -07:00
dank ccb805cf0b Merge pull request 'fix(frank-dragoon): mount the distro XRT at the third prefix the NPU child searches' (#66) from fix/dragoon-context-200k into main 2026-10-21 02:25:42 -07:00
dank 9eb53ef937 Merge pull request 'feat(frank-dragoon): 200,000-token harness window via the native 262,144 ceiling' (#65) from fix/dragoon-context-200k into main 2026-10-21 01:40:40 -07:00
4 changed files with 69 additions and 23 deletions
+17 -6
View File
@@ -321,14 +321,25 @@ copy_grafana_provisioning() {
# datasource would be invisible to review, lost with the data volume, and
# unknown to anyone reading the repository. The unit already mounts
# /opt/grafana/provisioning read-only, so this only has to populate it.
run_privileged mkdir -p /opt/grafana/provisioning/datasources \
/opt/grafana/provisioning/dashboards
# GRAFANA_PROV_ROOT is the test seam: point it at a sandbox and the whole
# copy runs unprivileged (the bats case executes this function for real).
local root="${GRAFANA_PROV_ROOT:-/opt/grafana}"
run_privileged mkdir -p "$root/provisioning/datasources" \
"$root/provisioning/dashboards"
run_privileged cp shared/grafana/provisioning/datasources/prometheus.yml \
/opt/grafana/provisioning/datasources/prometheus.yml
"$root/provisioning/datasources/prometheus.yml"
run_privileged cp shared/grafana/provisioning/dashboards/provider.yml \
/opt/grafana/provisioning/dashboards/provider.yml
run_privileged cp shared/grafana/provisioning/dashboards/llm-fleet.json \
/opt/grafana/provisioning/dashboards/llm-fleet.json
"$root/provisioning/dashboards/provider.yml"
# EVERY dashboard JSON rides, not a hand-listed subset: the fleet provider
# turns each file in the directory into a dashboard, so a copier that names
# files one by one silently drops new boards (measured 2026-10-21: the
# containers board existed in the repo since its commit and was NEVER
# provisioned — the panel-census browser check could not find it while the
# hand-placed older boards happened to be on disk). Same rule as the quadlet
# tier: the directory is the contract, the copy is generated state.
for dash in shared/grafana/provisioning/dashboards/*.json; do
run_privileged cp "$dash" "$root/provisioning/dashboards/$(basename "$dash")"
done
}
render_caddyfile() {
@@ -44,7 +44,7 @@ from `deploy_observability`:
- `install_signal_relay` → the user unit + iptables + the health wait on `192.168.0.143:18093/health` (`:243-274`)
- `copy_loki_config` → `/opt/loki/config/loki-config.yaml` (`:276-279`)
- `copy_alloy_config` → `/opt/alloy/config/config.alloy` (`:281-284`)
- `copy_grafana_provisioning` → `/opt/grafana/provisioning/` (`:286-300`)
- `copy_grafana_provisioning` → `/opt/grafana/provisioning/` — datasources, provider, and **every** `dashboards/*.json` (a glob, not a hand-listed subset; `GRAFANA_PROV_ROOT` is the test seam the bats case uses to run the copier into a sandbox)
`scripts/deploy-plan.py` maps `shared/prometheus/**` to `prometheus` (plus
`alertmanager` when the changed file is `alertmanager.yml`, `:251-256`)
@@ -82,11 +82,14 @@ reports 4 alerting + 2 recording. Rules reload with the prometheus service.
the backend (`http://prometheus:9090`), not loopback.
**Add a dashboard** — drop the JSON into
`shared/grafana/provisioning/dashboards/`. Provisioned dashboards live in
unified storage, not (only) the SQLite table — grep the API, not the DB, when
verifying (see the verify section of [[Fleet Audit and Posture]]). *(unverified
2026-10-18: the storage-backend claim needs an admin password to confirm
against `grafana.db` and the API side by side)*
`shared/grafana/provisioning/dashboards/`; the copier globs the directory, so
the next `--service grafana` deploy provisions it. This is a repaired law, not
a given: until 2026-10-21 the copier named `llm-fleet.json` alone, and the
`containers` board — merged, tested, documented as "working" — had **never
been provisioned**; the other boards existed on disk only because someone had
hand-copied them once. A repo JSON is not a live dashboard until the copier
carries it, and `deploy.sh` cannot see the gap because it only reports the
copy it performed.
## Verify through the APIs, not the filesystem
@@ -95,17 +98,22 @@ against `grafana.db` and the API side by side)*
curl -s http://127.0.0.1:9090/api/v1/rules | python3 -m json.tool | head
curl -s "http://127.0.0.1:9090/api/v1/targets?state=active" | \
python3 -c "import json,sys; [print(t['labels'].get('job'), t['health'], (t['lastError'] or '')[:60]) for t in json.load(sys.stdin)['data']['activeTargets']]"
# grafana: datasource + dashboard provisioned
curl -s -u admin:<pw> http://127.0.0.1:3002/api/datasources | python3 -m json.tool | grep -E '"name"|"url"'
# grafana 13: /api/health, /api/org, /login all 404 UNAUTHENTICATED, and there
# is NO local admin password (root .env sets no GF_SECURITY_ADMIN_*; the only
# account is the Authentik RP login). The proven surface is a real browser
# session: warm the Anubis challenge on the vhost root, then hit
# /login/generic_oauth with the e2e storage state (tests/e2e/.auth/qa-e2e-admin.json)
# — exactly the path specs/20-grafana-sso.spec.ts drives — and read the boards.
```
The two Prometheus queries were run on 2026-10-18 and are what every number on
this page comes from: 4 alerting + 2 recording rules, and `up` for
`prometheus`, `node-dankclaw`, `node-dankserver`, `grafana`, `loki`
`frank-zealot`, `frank-dragoon`, `frank-archon` with the four structural downs
listed in [[Prometheus]]. The Grafana query needs the local admin password and
was not run here; the filesystem side (`ls /opt/grafana/provisioning/*`) is
proof of installation only — see the next paragraph.
listed in [[Prometheus]]. The Grafana proof is the browser session above, run
2026-10-21: it found a provisioned-file gap the filesystem listing hid. The
filesystem side (`ls /opt/grafana/provisioning/*`) is proof of installation
only — see *Add a dashboard* for why a repo JSON is not a live board.
Health answers from a stale process and provisioned resources can hide in
unified storage — a file existing on disk is not proof the running server
@@ -109,12 +109,15 @@ verify it through the APIs.
* **Delivery: working.** Alertmanager's single `signal` receiver webhooks the
relay, and `journalctl --user -u signal-relay` records successful
`sent to +12062476336:` lines for every rule in the set.
* **Dashboards: working.** Provisioned datasources — Prometheus (uid
`prometheus`) and Loki (uid `loki`) — and the `llm-fleet`, `host-system`,
`containers`, `observability` and `logs-overview` dashboards, all
installed from `shared/grafana/provisioning/`. The Loki datasource is how
you read logs: Loki has no web UI of its own, so Grafana Explore is the
human surface (the guarded `loki.franksdank.cloud` vhost serves only the API).
* **Dashboards: working (after the 2026-10-21 copier fix).** Provisioned
datasources — Prometheus (uid `prometheus`) and Loki (uid `loki`) — and the
`llm-fleet`, `host-system`, `containers`, `observability` and
`logs-overview` boards. Until 2026-10-21 the copier carried only
`llm-fleet.json` by name and the `containers` board had never been
provisioned; the deployer now globs the directory. The Loki datasource is
how you read logs: Loki has no web UI of its own, so Grafana Explore is the
human surface (the guarded `loki.franksdank.cloud` vhost serves only the
API). Verify method and the copier law: [[Observability Provisioning]].
* **Per-container metrics: the `containers` dashboard (added 2026-10-20; live
after `deploy.sh <host> --service cadvisor`).** cadvisor is the only way to see
per-container CPU/memory here, and on this rootless-Podman host it works on one
+24
View File
@@ -644,3 +644,27 @@ assert not any('container_network' in e or 'container_fs_' in e for e in names),
[ "$status" -eq 0 ]
[ "$output" = "cadvisor" ]
}
@test "the grafana copier provisions EVERY dashboard json, not a hand-listed subset" {
# Behavioral, not a text grep: the copier is extracted and executed with a
# stubbed run_privileged against a sandbox. The fleet provider turns each
# file in its directory into a dashboard, so a file the copier skips is a
# dashboard that silently never exists (the containers board lived in the
# repo, never reached /opt, and its fixes were undeployable).
# BATS_TEST_TMPDIR: per-test, removed by bats itself; a mktemp dir with a
# RETURN trap races bats' own trap machinery and vanishes mid-function.
local sandbox="$BATS_TEST_TMPDIR/sandbox"
(
cd "$REPO_ROOT"
eval "$(sed -n '/^copy_grafana_provisioning()/,/^}/p' scripts/deploy-remote.sh)"
run_privileged() { "$@"; }
export GRAFANA_PROV_ROOT="$sandbox/opt/grafana"
copy_grafana_provisioning
)
local f
for f in "$REPO_ROOT"/shared/grafana/provisioning/dashboards/*.json; do
[ -f "$sandbox/opt/grafana/provisioning/dashboards/$(basename "$f")" ]
done
[ -f "$sandbox/opt/grafana/provisioning/dashboards/provider.yml" ]
[ -f "$sandbox/opt/grafana/provisioning/datasources/prometheus.yml" ]
}