Skip to content

Screen Time Enforcement — Closed-Loop Edition

Screen Time Enforcement — Closed-Loop Edition

Section titled “Screen Time Enforcement — Closed-Loop Edition”

Date: 2026-04-18 Status: Live on manoir, E2E-tested, regression-gated

A screen-time system that says “blocked” but streams Netflix is worse than one that admits it’s broken. This page documents what went wrong on April 16–18, 2026 and the closed-loop contract that now governs every enforcement action.

Force Flow’s _block_mac() called the Firewalla bridge and logged BLOCKED <mac> — curfew based on whether the response was truthy:

# Before
async def _block_mac(session, mac, reason="curfew"):
result = await _bridge_post(session, f"/host/{mac}/pause")
if result:
log.info(f"BLOCKED {mac}{reason}")
return result

The Firewalla SDK, when it got into a certain stale-policy state, would answer HTTP 200 with {"success": false, "errors": [{}]}. That’s a non-empty dict. if result: is truthy. BLOCKED got logged. The Firewalla did nothing.

The first night this happened (April 16), five of seven curfewed MACs stayed online. Log said they were blocked. They weren’t. Nobody noticed because everyone was asleep, which was what the curfew was supposed to guarantee.

The second night (April 17), same five MACs, same silent pass. The parents noticed because streaming was happening during “quiet hours.”

Debugging the incident surfaced a second trap that amplified the confusion:

Combined effect: while diagnosing live, the first pass of verification reported the wrong conclusion — said devices were blocked when they were passing. Which is how we ended up telling the parent ”🔴 tout est bloqué” when the opposite was true. (The parent, to their credit, asked “es-tu certain que ya plus rien qui passe?” and the cross-check caught it.)

Every write to the Firewalla now goes through a three-step contract. No exceptions.

async def _verify_acl(session, mac, expected):
await asyncio.sleep(1.0)
current = await _bridge_get(session, f"/host/{mac}")
acl = (current or {}).get("policy", {}).get("acl")
return acl is expected
async def _block_mac(session, mac, reason="curfew"):
result = await _bridge_post(session, f"/host/{mac}/pause")
# 1. Require explicit success, not truthy non-empty dict
if not result or not result.get("success"):
log.error(f"BLOCK FAILED {mac}{reason}: bridge returned {result}")
return None
# 2. Verify the side effect landed on the control plane
if not await _verify_acl(session, mac, expected=False):
log.warning(f"BLOCK DRIFT {mac}{reason}: bridge ok but acl stayed true; retrying once")
# 3. Retry once, then escalate
retry = await _bridge_post(session, f"/host/{mac}/pause")
if not retry or not retry.get("success") or not await _verify_acl(session, mac, expected=False):
log.error(f"BLOCK ESCALATION {mac}{reason}: retry did not stick; Firewalla drift")
return None
log.info(f"BLOCKED {mac}{reason}")
return result

Same shape for _unblock_mac — asserts acl == True after the call, retries on drift, escalates to UNBLOCK ESCALATION on failure.

Why “closed-loop” and not just “check the response”

Section titled “Why “closed-loop” and not just “check the response””

The bridge’s success field is a partial signal. The Firewalla SDK can accept the command, return success: true, and still not propagate the change to the enforcement tier (happened to us with the metallib-stalled SDK state). The only authoritative signal is re-reading the target’s policy on the Firewalla itself. If the observed state doesn’t match the intended state, the action didn’t happen, regardless of what any intermediary said.

This is Living Force principle 12: commands can’t lie either.

The new enforcement path emits exactly four terminal states per MAC, and nothing else can produce a BLOCKED line:

Log lineMeaningAction
BLOCKED <mac> — <reason>Success: bridge said success: true AND policy.acl == false confirmedNone — working
BLOCK FAILED <mac> — <reason>: bridge returned <dict>Bridge said failure explicitlyDispatch to Living Force queue; surface the bridge dict
BLOCK DRIFT <mac> — <reason>: bridge ok but acl stayed true; retrying onceBridge succeeded but state didn’t changeSelf-retry fires immediately
BLOCK ESCALATION <mac> — <reason>: retry did not stick; Firewalla driftRetry failed tooOperator attention — Firewalla SDK may need a bridge restart

Unblock has mirror lines (UNBLOCKED, UNBLOCK FAILED, UNBLOCK DRIFT, UNBLOCK ESCALATION).

tests/test-screen-time-enforcement.sh exercises the full closed-loop in under 30 seconds:

  • Endpoint + bridge token reachability
  • /screen/block → observe acl=False on the bridge
  • Log assertions: BLOCKED present, no FAIL/DRIFT/ESCALATION
  • /screen/unblock → observe acl=True restored
  • 12h auto-override cleanup (the override /screen/unblock creates would otherwise suppress the next curfew)
  • Nintendo’s standalone 22:30 schedule (moved out of shared_devices so it doesn’t inherit Albert’s 23:00 phone curfew)
  • God Mode parent-override path for both consoles
  • Self-cleaning final state

Currently 18 pass / 0 fail. Any regression to truthy-check silent-success will fail L3 (“no FAIL/DRIFT/ESCALATION”) loudly.

Parents can override both consoles (VRVANA PC + Nintendo Switch) from the Holocron dashboard. The button is styled after Doom’s IDDQD cheat, red palette, three duration presets:

ButtonEffect
+30 MINOverride vrvana_pc and nintendo_switch for 30 minutes
+60 MINSame, 60 minutes
+120 MINSame, 120 minutes (the backend max from override_max_minutes)
STOPClear both overrides immediately (minutes: 0)

Album’s phone curfew is unaffected — the phone keeps its 23:00 weekend bedtime so he can read or play chess while falling asleep. Only the consoles get bumped.

Layer by layer. Each step is reversible in one command.

Terminal window
# Revert the closed-loop code fix only (keep docs/tests)
ssh neo@100.0.0.25 'cp /Users/neo/.sanctum/screen-time/screen_time.py.bak-20260418-152105 \
/Users/neo/.sanctum/screen-time/screen_time.py && \
launchctl kickstart -k gui/$(id -u)/com.sanctum.force-flow'
# Full restore from the weekly snapshot
shasum -a 256 -c ~/Backups/sanctum-mini/sanctum-mini-20260418-*.tgz.sha256