feat(ops-agent): drift-detect kijkt ook naar de geïnstalleerde build #127

Merged
janpeter merged 1 commit from chore/drift-detect-installed-build into main 2026-08-02 09:29:18 +02:00
Owner

Het gat

check-ops-agent-drift.sh vergeleek alleen /etc/ops-agent-config tegen de repo-baseline en keek nooit naar /opt/ops-agent. Daardoor bleef een twee weken oude agent-binary — zonder het complete, al gemergede control-room-subsysteem — onzichtbaar voor precies het mechanisme dat drift moet vangen. Dit is een detector, geen fix: hij vindt de volgende stale component, wat dat ook blijkt te zijn.

Mechanismekeuze: exacte vergelijking, geen mtime

setup.sh installeert met rsync -a --delete vanuit <repo>/ops-agent/, dus de geïnstalleerde tree draagt zijn eigen bronkopie mee. Een sha256-manifest per bestand is daarmee een echte vergelijking die retroactief werkt — ook op installs die van vóór elke vorm van stempelen dateren.

De voorgestelde mtime-heuristiek is bewust verworpen, met meting. Op 154:

bestand repo-mtime installed-mtime sha256
ops-agent/commands.yml.example Jul 18 02:06 Jul 17 16:34 identiek

Een git checkout herschrijft mtimes zonder de inhoud te raken. De heuristiek zou hier dus drift melden op een gezonde host — en een check die op een gezonde host afgaat wordt binnen een week gemute. Dan is hij erger dan geen check, want hij ziet eruit als dekking.

Vals-positief-analyse (verse install)

Gemeten, niet beredeneerd: na setup.sh is alles buiten src/ byte-identiek aan de repo, inclusief package-lock.json (npm ci + npm prune --omit=dev herschrijven die niet). De vier paden die by construction afwijken zijn expliciet uitgesloten:

pad waarom uitgesloten
node_modules/ niet ge-rsynct; npm ci/prune draaien in de installdir
dist/ niet ge-rsynct; ter plekke gebouwd door npx tsc
.git/ niet ge-rsynct
.install-provenance door setup.sh ná de rsync geschreven — meevergelijken zou een gegarandeerde vals-positief zijn

Op een vers geïnstalleerde agent is de uitkomst dus exit 0, zonder note. Bewezen in test 2 hieronder.

Laag (b): provenance-stamp

setup.sh schrijft nu /opt/ops-agent/.install-provenance met de git TREE-hash van ops-agent/. Content-adressed, dus stabiel over rebases en cherry-picks en veranderend enkel wanneer de agent-bron echt verandert — geen ruis van niet-gerelateerde commits.

Bewijs

# scenario uitkomst
1 live true positive op de echt-stale /opt/ops-agent DRIFT: 19 file(s) — 0 config, 19 build; hele control-room/ als in repo source, NOT installed
2 correcte install (rsync nagebootst + node_modules/dist-rommel + kloppende stamp) no drift, exit 0, geen note
3a één bestand in de install gewijzigd build:src/auth.ts — installed content differs from source
3b verweesd bestand in de install build:src/verweesd.ts — installed, not in repo source
3c stamp wijst naar andere tree build:provenance — installed tree deadbeefdead != source tree c13cc37f4f95
3d install vanaf dirty tree note, geen mismatch-melding
3e bron is geen git-checkout luide note, geen stilte
3f installdir ontbreekt install dir missing — agent never installed
3g regressie: 2-argument-aanroep zonder de nieuwe knop no drift, exit 0 — exact als voorheen

Twee bugs die het bewijzen zelf boven water bracht

  1. Beide scripts draaien als root tegen een repo die van ops-agent is → plain git weigert met detected dubious ownership, en de provenance-laag zou in productie nooit hebben gewerkt (test 3c faalde eerst stil). Opgelost door precies die ene repo te vertrouwen — niet safe.directory='*'.
  2. De slotalinea meldde "de live config is afgeweken, re-sync de baseline" ook wanneer 0 van de 19 rijen config waren. De samenvatting telt nu config en build apart en zegt bij build-drift dat een config-redeploy het níét verhelpt.

Ruis-discipline

  • Opt-in via OPS_AGENT_SRC_DIR. Ongezet ⇒ build-check volledig overgeslagen, zodat max2 (dat dit script deelt) niet gaat piepen tot iemand het daar bewust wiret.
  • Ontbrekende stamp is geen drift maar een note — anders alarmeert elke pre-stamp install elke nacht over iets dat geen redeploy kan verhelpen.
  • Kan de bron-tree niet bepaald worden, dan zegt hij dat luid: een provenance-laag die stil stopt met vergelijken is niet te onderscheiden van eentje die slaagt.

Scope

/etc/ops-agent en /opt/ops-agent zijn niet aangeraakt; de agent is niet gedeployd — de huidige staleness is het testfixture. Nog niet gearmd: zie de review-notitie over het wiren van de systemd-unit.

Testsuite

test/control-room-foundation.test.ts is rood (65 failed / 14 passed) — ook op onaangeraakte main, identieke aantallen. Pre-existing, niet door deze PR veroorzaakt.

## Het gat `check-ops-agent-drift.sh` vergeleek alleen `/etc/ops-agent`-**config** tegen de repo-baseline en keek nooit naar `/opt/ops-agent`. Daardoor bleef een twee weken oude agent-binary — zonder het complete, al gemergede `control-room`-subsysteem — onzichtbaar voor precies het mechanisme dat drift moet vangen. Dit is een **detector**, geen fix: hij vindt de volgende stale component, wat dat ook blijkt te zijn. ## Mechanismekeuze: exacte vergelijking, geen mtime `setup.sh` installeert met `rsync -a --delete` vanuit `<repo>/ops-agent/`, dus **de geïnstalleerde tree draagt zijn eigen bronkopie mee**. Een sha256-manifest per bestand is daarmee een echte vergelijking die retroactief werkt — ook op installs die van vóór elke vorm van stempelen dateren. De voorgestelde mtime-heuristiek is bewust verworpen, met meting. Op 154: | bestand | repo-mtime | installed-mtime | sha256 | |---|---|---|---| | `ops-agent/commands.yml.example` | Jul 18 02:06 | Jul 17 16:34 | **identiek** | Een `git checkout` herschrijft mtimes zonder de inhoud te raken. De heuristiek zou hier dus drift melden op een gezonde host — en een check die op een gezonde host afgaat wordt binnen een week gemute. Dan is hij erger dan geen check, want hij ziet eruit als dekking. ## Vals-positief-analyse (verse install) Gemeten, niet beredeneerd: na `setup.sh` is **alles buiten `src/` byte-identiek** aan de repo, inclusief `package-lock.json` (`npm ci` + `npm prune --omit=dev` herschrijven die niet). De vier paden die by construction afwijken zijn expliciet uitgesloten: | pad | waarom uitgesloten | |---|---| | `node_modules/` | niet ge-rsynct; `npm ci`/`prune` draaien in de installdir | | `dist/` | niet ge-rsynct; ter plekke gebouwd door `npx tsc` | | `.git/` | niet ge-rsynct | | `.install-provenance` | door setup.sh ná de rsync geschreven — meevergelijken zou een gegarandeerde vals-positief zijn | **Op een vers geïnstalleerde agent is de uitkomst dus exit 0, zonder note.** Bewezen in test 2 hieronder. ## Laag (b): provenance-stamp `setup.sh` schrijft nu `/opt/ops-agent/.install-provenance` met de git **TREE**-hash van `ops-agent/`. Content-adressed, dus stabiel over rebases en cherry-picks en veranderend enkel wanneer de agent-bron echt verandert — geen ruis van niet-gerelateerde commits. ## Bewijs | # | scenario | uitkomst | |---|---|---| | 1 | **live true positive** op de echt-stale `/opt/ops-agent` | `DRIFT: 19 file(s)` — 0 config, 19 build; hele `control-room/` als `in repo source, NOT installed` | | 2 | correcte install (rsync nagebootst + `node_modules`/`dist`-rommel + kloppende stamp) | `no drift`, exit 0, geen note | | 3a | één bestand in de install gewijzigd | `build:src/auth.ts — installed content differs from source` | | 3b | verweesd bestand in de install | `build:src/verweesd.ts — installed, not in repo source` | | 3c | stamp wijst naar andere tree | `build:provenance — installed tree deadbeefdead != source tree c13cc37f4f95` | | 3d | install vanaf dirty tree | note, geen mismatch-melding | | 3e | bron is geen git-checkout | **luide** note, geen stilte | | 3f | installdir ontbreekt | `install dir missing — agent never installed` | | 3g | **regressie**: 2-argument-aanroep zonder de nieuwe knop | `no drift`, exit 0 — exact als voorheen | ## Twee bugs die het bewijzen zelf boven water bracht 1. **Beide scripts draaien als root tegen een repo die van `ops-agent` is** → plain git weigert met `detected dubious ownership`, en de provenance-laag zou in productie **nooit** hebben gewerkt (test 3c faalde eerst stil). Opgelost door precies die ene repo te vertrouwen — niet `safe.directory='*'`. 2. De slotalinea meldde "de live config is afgeweken, re-sync de baseline" ook wanneer 0 van de 19 rijen config waren. De samenvatting telt nu config en build apart en zegt bij build-drift dat een config-redeploy het níét verhelpt. ## Ruis-discipline - Opt-in via `OPS_AGENT_SRC_DIR`. Ongezet ⇒ build-check volledig overgeslagen, zodat max2 (dat dit script deelt) niet gaat piepen tot iemand het daar bewust wiret. - Ontbrekende stamp is **geen** drift maar een note — anders alarmeert elke pre-stamp install elke nacht over iets dat geen redeploy kan verhelpen. - Kan de bron-tree niet bepaald worden, dan zegt hij dat **luid**: een provenance-laag die stil stopt met vergelijken is niet te onderscheiden van eentje die slaagt. ## Scope `/etc/ops-agent` en `/opt/ops-agent` zijn **niet** aangeraakt; de agent is niet gedeployd — de huidige staleness is het testfixture. **Nog niet gearmd**: zie de review-notitie over het wiren van de systemd-unit. ## Testsuite `test/control-room-foundation.test.ts` is rood (65 failed / 14 passed) — **ook op onaangeraakte `main`**, identieke aantallen. Pre-existing, niet door deze PR veroorzaakt.
feat(ops-agent): drift-detect kijkt ook naar de geinstalleerde build
Some checks failed
CI / Root app checks (pull_request) Failing after 6m49s
CI / Ops-agent checks (pull_request) Successful in 39s
CI / Deploy artifact checks (pull_request) Successful in 27s
CI / Docker image build (pull_request) Successful in 1m29s
515afd602d
De drift-check vergeleek alleen /etc/ops-agent-config tegen de repo-baseline en
keek nooit naar wat er draait. Daardoor bleef een twee weken oude
/opt/ops-agent — zonder het complete, al gemergede control-room-subsysteem —
onzichtbaar voor precies het mechanisme dat drift moet vangen.

Twee lagen, beide in deze commit:

(a) Retroactief, werkt nu meteen. setup.sh installeert met
    `rsync -a --delete` vanuit <repo>/ops-agent/, dus de geinstalleerde tree
    draagt zijn eigen bronkopie mee. Een exacte manifest-vergelijking
    (sha256 per bestand) is daardoor mogelijk op installs die van voor elke
    vorm van stempelen dateren. Geen mtime-heuristiek: gemeten op 154 heeft
    ops-agent/commands.yml.example een repo-mtime van Jul 18 tegen een
    installed-mtime van Jul 17 met een IDENTIEKE sha256 — een git checkout
    herschrijft mtimes zonder de inhoud te raken, dus die heuristiek meldt
    drift op een gezonde host. Een check die op een gezonde host afgaat wordt
    binnen een week gemute, en is dan erger dan geen check.

(b) Duurzaam: setup.sh stempelt de herkomst in /opt/ops-agent/.install-provenance
    (git TREE-hash van ops-agent/, dus content-adressed en ongevoelig voor
    rebases en niet-gerelateerde commits). Daarmee wordt het een exacte
    vergelijking in plaats van een afleiding.

Vals-positief-oppervlak op een verse install is nul: alles buiten src/ is
byte-identiek na setup.sh, inclusief package-lock.json (npm ci + prune
herschrijven die niet). De vier paden die by construction afwijken —
node_modules/, dist/, .git/ en de stamp zelf — zijn expliciet uitgesloten.

Verder:
- Opt-in via OPS_AGENT_SRC_DIR. Ongezet = de 2-argument-aanroep gedraagt zich
  exact als voorheen, zodat max2 (dat dit script deelt) niet gaat piepen.
- Beide scripts draaien als root tegen een repo die van ops-agent is; plain git
  weigert die met "dubious ownership". Opgelost door precies die ene repo te
  vertrouwen, niet safe.directory='*'.
- Ontbrekende stamp is bewust GEEN drift maar een note: anders alarmeert elke
  pre-stamp install elke nacht over iets dat geen redeploy kan verhelpen.
  Kan de bron-tree niet worden bepaald, dan zegt hij dat luid — een
  provenance-laag die stil stopt met vergelijken lijkt op eentje die slaagt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
s4m-codex-reviewer left a comment

Verdict: COMMENT

Geen gekoppeld plan gevonden — beoordeeld op codekwaliteit + product-standaarden.

Findings

  • mediumdeploy/ops-agent/check-ops-agent-drift.sh:127 — De nieuwe installed-build drift-check bevat meerdere niet-triviale shellpaden (find/sha256sum/comm/join, ontbrekende install-dir, provenance met/zonder git checkout), maar de PR-diff toont geen bijbehorende tests of fixture-validatie. Voor monitoring/deploy-code is het risico vooral false positives of stille false negatives op hosts; voeg bij voorkeur een shelltest/fixture toe voor minimaal: identieke source/install, ontbrekend bestand, gewijzigd bestand, extra install-bestand en .install-provenance met mismatch.

Geen blokkerende architectuur- of patroonafwijking gevonden in de getoonde diff. De aanpak is opt-in via OPS_AGENT_SRC_DIR, behoudt bestaand tweeargumenten-gedrag en vergelijkt content in plaats van mtimes, wat aansluit bij het doel om drift betrouwbaar te detecteren.

# Verdict: COMMENT Geen gekoppeld plan gevonden — beoordeeld op codekwaliteit + product-standaarden. ## Findings - **medium** — `deploy/ops-agent/check-ops-agent-drift.sh:127` — De nieuwe installed-build drift-check bevat meerdere niet-triviale shellpaden (`find`/`sha256sum`/`comm`/`join`, ontbrekende install-dir, provenance met/zonder git checkout), maar de PR-diff toont geen bijbehorende tests of fixture-validatie. Voor monitoring/deploy-code is het risico vooral false positives of stille false negatives op hosts; voeg bij voorkeur een shelltest/fixture toe voor minimaal: identieke source/install, ontbrekend bestand, gewijzigd bestand, extra install-bestand en `.install-provenance` met mismatch. Geen blokkerende architectuur- of patroonafwijking gevonden in de getoonde diff. De aanpak is opt-in via `OPS_AGENT_SRC_DIR`, behoudt bestaand tweeargumenten-gedrag en vergelijkt content in plaats van mtimes, wat aansluit bij het doel om drift betrouwbaar te detecteren.
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
janpeter/Ops-dashboard!127
No description provided.