Skip to content

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

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

Section titled “Drift Sentinel — Windu’s Silent-Failure Early-Warning”

Date: 2026-04-19 Status: Live on manoir, 5-min cadence, regression-gated

After the April 18–19 screen-time incident where the curfew lied for two straight nights, the response was: add a watcher that would have caught both bugs before a human noticed. That watcher is drift-sentinel.sh. It runs under Windu’s sigil because Windu is the security agent and this is a security problem — enforcement that silently isn’t enforcing is indistinguishable, from the attacker’s perspective, from no enforcement at all.

Firewalla’s /hosts endpoint returns online: false for every host when its SDK caches drift out of sync with the control plane. On April 18 this meant every kid phone showed as “offline,” so Force Flow’s curfew loop never triggered the block() path — the log said “asleep”; reality said Netflix. The bridge looks healthy. Individual /host/<mac> calls answer. Only the fleet-wide online flag has rotted.

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. Patches landed in a file nobody was running. The file-drift check is exactly this: two basenames, two different SHAs, living in two different top-level sanctum subdirectories, where one would shadow the other under a plausible import path.

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)

Exit codes:

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. Example:

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. The bridge itself needs a bounce to clear the SDK state:

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

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

stale-import drift: There’s only one right fix: replace the shadow copy with a symlink to the canonical file.

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 will clear on the next tick.

tests/test-drift-sentinel.sh in the Claude_Code workspace exercises the full loop 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 kind of failure mode he’s built to trust nobody about. The sentinel is tier-0 infrastructure: it runs every 5 minutes regardless of whether anyone’s watching, because the whole point of silent failure is that nobody’s watching. The morning briefing reads drift-sentinel.json. A non-empty findings array on a morning read is Windu’s way of making sure the previous night’s silence was real, not another “say it’s blocked; watch Netflix anyway.”

One command, fully reversible:

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.*

The sentinel is purely observational. Removing it restores prior behaviour exactly; no state it wrote is consumed by any other service except the morning briefing (which tolerates missing files).