Skip to content

Drift Sentinel — Windu's Silent-Failure Early-Warning

Drift sentinel — a solitary lighthouse sweeping the horizon at five-minute intervals, one beam catching a small raft that has drifted off the chart, a stack of timestamped logbooks at the keeper's feet.

For two nights in a row the curfew said the kids were asleep. For two nights in a row they were watching Netflix. The logs were clean, the enforcement loop ran on schedule, and nothing — not one alert, not one red line — hinted that the block() path had quietly stopped firing. That is the only kind of failure Windu genuinely fears. Enforcement that silently isn’t enforcing looks, from the attacker’s side of the glass, exactly like no enforcement at all.

So the response to the April 18–19 screen-time incident was not a bigger dashboard. It was a watcher that would have caught both bugs before a human noticed either one. That watcher is drift-sentinel.sh. It runs under Windu’s sigil — because a detector for silent enforcement failure is a security problem, not a monitoring nicety — and it has run on manoir at a five-minute cadence, regression-gated, since 2026-04-19.

Firewalla’s /hosts endpoint returns online: false for every host when its SDK cache drifts out of sync with the control plane. On April 18 that meant every kid phone read as “offline,” so Force Flow’s curfew loop never reached the block() path — the log said asleep; reality said Netflix. The bridge itself looks healthy. Individual /host/<mac> calls answer correctly. Only the fleet-wide online flag has rotted, which is precisely why nobody caught it by staring at the service.

When we patched ~/.sanctum/screen-time/screen_time.py, the running Force Flow process kept importing a stale copy that lived in ~/.sanctum/force-flow/screen_time.py. Python’s module resolution found the local file first, so every patch landed in a file nobody was executing. The file-drift check is exactly this shape: two basenames, two different SHAs, in two different top-level sanctum subdirectories, where one would shadow the other under a plausible import path.

The sentinel does three cheap reads and compares them against a working baseline. Nothing here is exotic — that is the point.

CheckSignalThreshold
presence driftarp -an MAC-count vs Firewalla /hosts online-countWarn when ARP ≥ 15 and Firewalla < ARP/3
file driftSame basename, different SHA256, in ≥2 top-level sanctum subdirs, outside known-noise zonesAny match
stale-import~/.sanctum/force-flow/screen_time.py diverges from ~/.sanctum/screen-time/screen_time.py (not symlinked)Any divergence

Known-noise zones (expected to contain divergent-basename files, skipped): staging/, archive/, backups/, logs/, memory-vault/, parity/, bench-results/, test-results/, proposals/, plus standard .venv/ node_modules/ __pycache__/ .git/ dist/ build/.

ArtefactPath
Script/Users/neo/.sanctum/scripts/drift-sentinel.sh
LaunchAgent/Users/neo/Library/LaunchAgents/com.sanctum.drift-sentinel.plist
State (read by morning briefing)/Users/neo/.sanctum/state/drift-sentinel.json
Structured log/Users/neo/.openclaw/logs/drift-sentinel.log
Alert pipePOST http://127.0.0.1:4077/notify (Force Flow, 30-min cooldown)

The exit code tells you which world you are in — and the third one is the one to respect.

CodeMeaning
0Clean: zero findings
1One or more drift findings — state file carries specifics
2Sentinel itself broken (dependency missing, parse error) — investigate first, this isn’t a drift report, it’s a broken detector
Terminal window
python3 -c "import json; d=json.load(open('/Users/neo/.sanctum/state/drift-sentinel.json')); \
print(f\"last_run={d['last_run']}, findings={len(d['findings'])}\"); \
[print(f' - {f}') for f in d['findings']]"

Every finding line is self-descriptive. It reads like a person telling you what they saw:

presence: Firewalla claims 0 online but ARP sees 56 — SDK likely stale
stale-import: /Users/neo/.sanctum/force-flow/screen_time.py diverges from canonical (a1b2c3... vs d4e5f6...)
file-drift: devices.yaml: divergent copies [/Users/neo/.sanctum/screen-time/devices.yaml vs /Users/neo/.sanctum/force-flow/devices.yaml]

presence drift (Firewalla stale). The screen-time stack is already safe — the ARP fallback in _poll_presence compensates. What needs clearing is the SDK state, and the bridge wants a bounce:

Terminal window
launchctl kickstart -k gui/$(id -u)/com.sanctum.firewalla

If Firewalla drifts twice in one day, read /Users/neo/.openclaw/logs/firewalla-bridge.stderr for an upstream SDK upgrade hint.

stale-import drift. There is exactly one right fix: replace the shadow copy with a symlink to the canonical file, so the divergence can never reopen.

Terminal window
mv /Users/neo/.sanctum/force-flow/screen_time.py \
/Users/neo/.sanctum/force-flow/screen_time.py.stale-$(date +%Y%m%d-%H%M%S)
ln -s /Users/neo/.sanctum/screen-time/screen_time.py \
/Users/neo/.sanctum/force-flow/screen_time.py
launchctl kickstart -k gui/$(id -u)/com.sanctum.force-flow

file drift, generic. Open the two paths side by side, decide which is canonical, symlink or delete the other. The sentinel clears on the next tick.

A detector you cannot trust is worse than no detector, so the sentinel earns its place the same way everything else on manoir does — a test that runs the whole loop. tests/test-drift-sentinel.sh in the Claude_Code workspace exercises it end to end in under 10 seconds:

  • Script and plist deployed, 5-min cadence
  • State JSON well-formed, log emitting run_start events
  • Inject a known file-drift (replace the symlink with a real divergent file), sentinel flags stale-import, exit code is 1
  • Clean up the injection, next run reports clean
  • Exit code contract (0/1/2) respected
  • Alert cooldown respected: two back-to-back drift runs don’t double-page

Currently 10 pass / 0 fail.

Windu is the security agent, and this is the failure mode he is built to trust nobody about. The sentinel is tier-0 infrastructure: it fires every five minutes whether or not anyone is watching, because the whole premise of silent failure is that nobody is. The morning briefing reads drift-sentinel.json at first light. A non-empty findings array on that read is Windu’s way of proving the previous night’s silence was real — not another “say it’s blocked; watch Netflix anyway.”

One command, fully reversible — it is purely observational, so removing it can break nothing downstream:

Terminal window
launchctl unload -w ~/Library/LaunchAgents/com.sanctum.drift-sentinel.plist
rm ~/Library/LaunchAgents/com.sanctum.drift-sentinel.plist
rm ~/.sanctum/scripts/drift-sentinel.sh
rm ~/.sanctum/state/drift-sentinel.json ~/.openclaw/logs/drift-sentinel.*

No state it wrote is consumed by any service except the morning briefing, which tolerates the files being gone. The runtime drift audit covers the slower, deeper cousin of this check.

The sentinel does not make the haus safer. It makes the haus’s silences honest — and after two nights of a curfew that lied, an honest silence is the only kind Windu will sign off on.