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.
The two bugs this catches
Section titled “The two bugs this catches”1. Firewalla SDK stale-presence
Section titled “1. Firewalla SDK stale-presence”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.
2. Duplicate-file import shadowing
Section titled “2. Duplicate-file import shadowing”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.
What it checks, every 5 minutes
Section titled “What it checks, every 5 minutes”| Check | Signal | Threshold |
|---|---|---|
| presence drift | arp -an MAC-count vs Firewalla /hosts online-count | Warn when ARP ≥ 15 and Firewalla < ARP/3 |
| file drift | Same basename, different SHA256, in ≥2 top-level sanctum subdirs, outside known-noise zones | Any 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/.
Where the signal lives
Section titled “Where the signal lives”| Artefact | Path |
|---|---|
| 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 pipe | POST http://127.0.0.1:4077/notify (Force Flow, 30-min cooldown) |
Exit codes:
| Code | Meaning |
|---|---|
0 | Clean: zero findings |
1 | One or more drift findings — state file carries specifics |
2 | Sentinel itself broken (dependency missing, parse error) — investigate first, this isn’t a drift report, it’s a broken detector |
Reading the state
Section titled “Reading the state”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 stalestale-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]When drift fires — what to do
Section titled “When drift fires — what to do”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:
launchctl kickstart -k gui/$(id -u)/com.sanctum.firewallaIf 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.
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.pylaunchctl kickstart -k gui/$(id -u)/com.sanctum.force-flowfile 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.
Regression gate
Section titled “Regression gate”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.
Why Windu
Section titled “Why Windu”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.”
Rollback
Section titled “Rollback”One command, fully reversible:
launchctl unload -w ~/Library/LaunchAgents/com.sanctum.drift-sentinel.plistrm ~/Library/LaunchAgents/com.sanctum.drift-sentinel.plistrm ~/.sanctum/scripts/drift-sentinel.shrm ~/.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).