Skip to content

Haus-Control

Haus-Control — a holographic parental control dashboard with glowing shield protecting sleeping orbs, network lines connecting to gaming consoles and TVs with teal status indicators

Every parental control app assumes you can install something on the device you want to control. Kids uninstall things. That assumption fails at the PS5, the Apple TV, the grandmother’s iPad, and whatever tablet a sleepover guest brought. Haus-control runs inside Force Flow on port 4077 and starts from the opposite premise: the network is the only authority that can’t be uninstalled. If a MAC address exists on the haus WiFi, Firewalla can pause it or block services on it.

v4 keeps that premise and adds what it was missing: an explicit ladder for the screens the network can’t fully see, holds that cannot outlive their author’s memory, and budgets that follow the child instead of the device.

Three enforcement tiers as concentric network rings — phone presence in the center drives blocking decisions for gaming devices and screens

Three rungs, chosen per screen by one rule: the cheapest layer that actually reaches it.

Rung Reaches Mechanism Confirmed by
Network anything with a MAC on the haus WiFi Firewalla pause / service-level DNS blocks Firewalla policy read-back
Mirror platform accounts that travel off the WiFi (iOS, Switch, Xbox/PS, Steam, Quest) platform-native parental controls, kept in step via the mirror pages manual re-check, on a cadence
Agent Windows PCs Rust agent, server-authoritative clock agent heartbeat

Network first, because it has no uninstall button. Mirrors where the device leaves the haus: each mirror page (Apple iOS, Nintendo Switch, Xbox & PlayStation, Steam, Meta Quest) walks the platform’s own steps under the platform’s own titles, and every one ends with the same stale-mirror paragraph — a mirror nobody re-checks stops being enforcement and becomes surveillance. The agent rung exists for exactly one platform, covered below.

Tier Policy Why
1 — personal Apple devices Not blocked. Apple Screen Time handles them, kept honest via the iOS mirror page doubling up with network blocks confuses the reporting a parent actually checks — leave Apple to Apple
2 — shared gaming consoles Hard curfew at the earliest active child curfew, unblock at wake phone-presence gating was tried and retired: a charging phone kept the PS5 blocked all night, airplane mode unblocked everything; presence now feeds the weekly digest, not enforcement
3 — screens (TVs, PCs) Hard curfew with service precision — per-screen schedules, mode: services where needed the HomeKit-hub Apple TV blocks streaming while HomeKit and Bell Fibe stay alive

The haus doesn’t stop being smart just because it’s bedtime.

v3 shipped a landmine: a manual Block Now was sticky. It survived engine restarts and quietly suppressed the morning wake-unblock; one child’s PC stayed dark for two days before anyone connected the dead machine to a button pressed the week before. v4 retires the class, not just the bug. Every hold is a (who, until) pair:

Rule v4 behavior
Manual block expires at next wake by default — nothing blocks forever
Pause requires a who and a duration; maximum is jusqu’au réveil (that child’s wake time, never past it)
Override capped at 120 minutes
Every hold displays its owner and its expiry, in the PWA and the Holocron panel alike

The most dangerous state in a parental-control system is the one nobody remembers creating.

New in v4: time budgets metered at the network, by category, summed per child across every device. Ninety minutes of YouTube is ninety minutes whether it arrived through the iPad, the TV, or the PC. This is the whitespace the whole market leaves open:

Product Budget unit
Google Family Link per-device
Nintendo parental controls per-console
Firewalla, native no budgets at all
Haus-control v4 per-child, across all devices

Budgets ship in shadow mode — log-only until a parent arms them, so the family sees a week of honest numbers before anything enforces. The industry meters the thing instead of the child because things are easier to count; the children worked out the difference years ago and simply rotate devices. Parent walkthrough: Budgets.

A block is not a block until the box confirms it. Every action closes the loop: haus-control reads the policy back from Firewalla, and on failure the UI shows red — never a fake green inferred from a 200 response. The doctrine was paid for: the closed-loop edition documents the nights the engine logged BLOCKED while an Apple TV streamed Netflix. A system that lies about doing its job is worse than none, because it also spends your credibility at breakfast.

Identity for that loop is the MAC, not the Firewalla display name. Renaming a screen (or a phone) only updates labels and aliases — it cannot drop a device out of curfew. The full identity ladder (rotation, pins, unknown-during-curfew, rename aliases) lives under Device Identity.

The one rung that needs software on the device is the platform that gives parents the least: a Windows gaming PC. The v4 agent is a small Rust binary with two commitments. A server-authoritative clock: the schedule’s time comes from manoir, not the machine, because rolling the local clock back is the oldest trick on the platform. And a 24-hour fail-safe: lose the server for a full day and the agent stands down rather than keep enforcing stale rules — a rule enforced past its author’s intent is the sticky-block bug in a trench coat. The local clock belongs to the child; bedtime does not.

Four schedule tiers, resolved in priority order by _get_effective_schedule():

Priority Schedule Curfew Wake Source
1 (highest) Study mode 21:00 07:00 Operator opt-in, expires study_mode.until
2 Quebec school holiday 23:00 09:00 holidays.yaml
3 Weekend (Fri/Sat night) 23:00 09:00 Day-of-week check
4 (default) Weekday 21:30 07:00 Default config

Study mode outranks holiday and weekend — a Friday night during finals is still a 9 PM Friday night — and expires on its own date. The holiday file is hand-maintained because Quebec publishes its school calendar as a PDF, exactly the kind of government decision this system was built to route around. Sunday is a school night; kids arguing it’s technically the weekend are technically correct and operationally irrelevant.

Curfew is a slope, not a cliff:

Offset Action
-30 min Social media blocked (TikTok, Instagram, Snapchat, Discord, Reddit)
-15 min Gaming blocked (Steam, Epic, Roblox, Minecraft, Fortnite)
0 min Everything blocked (full curfew)

Wind-down blocks are batch-cleaned at wake — no orphaned DNS rules. On QoS-capable boxes the last 30 minutes throttle to 1 Mbit: YouTube buffers into oblivion, iMessage still works, and the kid chooses to stop, which is the outcome worth having. The throttle lifts on override and ships off by default: throttling a child’s internet is a parent’s decision, not a vendor default.

Homework mode (POST /screen/homework) blocks gaming, social, and streaming — block-list-only, no allowlist to maintain — and self-deactivates after 2 hours. Guest devices are auto-discovered via ARP, get haus rules immediately, and expire after 24 hours. The weekly digest (Sunday 18:00, Signal + push) names names: average bedtime, overrides, credits, and a per-kid top-3 app line from the box’s app tracker.

Earned credits let a kid buy screen time with the real world:

Preset Duration
Tâches ménagères 15 min
Devoirs 30 min
Lecture 20 min
Exercice 15 min

Presets are French because the family is; homework pays more than chores because the incentives should. Credits cap at 90 minutes per day and don’t roll over. This is a parental control system, not a loyalty program.

Four layers, each bought with an incident:

Layer Defense The incident that bought it
1 Persisted block state (SQLite) a 3 AM restart forgot the morning unblock
2 SSH iptables fallback the box API returned “invalid policy” for every rule ID
3 Daily idempotent safety-net unblock lost state, now caught within 30 seconds
4 Closed-loop reconciler + conf-gen sentinel: tri-state policy read-back, fail-closed escalation after a 20-minute grace, fresh-MAC canary with an mtime guard the box wedged silently while the engine logged success

The full narrative — including the iCloud-Private-Address multi-MAC trap that made one partial failure cost 2.5 hours of Netflix — lives in the closed-loop edition and the vault runbook firewalla-confgen-wedge.md.

Interface Details
PWA http://force.home/pwa/ — iOS standalone, dark OLED theme, French UI
Home Assistant packages/screen_time.yaml — REST commands, sensors, homework toggle sync
Siri shortcuts 6 French invocations (“Mode devoirs”, “Prolonger 30 min”, “Couvre-feu maintenant”…) via Tailscale MagicDNS
Holocron panel ScreenTimePanel.tsx — countdown ring, holds with owner + expiry, quick actions

Shortcuts route to the MagicDNS name (http://manoir.local:4077 here), never a 100.0.0.x literal. A Siri shortcut that 404s at bedtime helps no one.

All endpoints on port 4077 under the /screen prefix.

Method Path Description
GET /screen/status Current curfew state, active holds (owner + expiry), presence
POST /screen/override Temporary override (duration in minutes, cap 120)
POST /screen/block Manual block by MAC — expires at next wake by default
POST /screen/unblock Manual unblock by MAC
GET /screen/report Usage report (daily/weekly/custom range)
GET /screen/devices All known devices and their current state
POST /screen/assign Assign a device to a family member
POST /screen/reload Reload config from devices.yaml
GET /screen/schedule Current effective schedule and next transition
POST /screen/homework Activate (action:start) or deactivate (action:stop) homework mode
GET /screen/homework/status Homework mode state and remaining time
POST /screen/credit Grant earned credit (preset + optional note)
GET /screen/credits Credit balance and history for today
POST /screen/credit/redeem Redeem accumulated credits
GET /screen/digest Generate digest on demand (also sent weekly)
GET /screen/guests List detected guest devices
POST /screen/guest/approve Approve a guest device
DELETE /screen/guest/remove Remove a guest device
GET /pwa/ PWA index and static assets

Everything is driven by ~/.sanctum/screen-time/devices.yaml — name-keyed maps, four-layer schedules, service slugs the bridge resolves to domains:

family:
kid1:
role: child
schedule:
study_mode: { enabled: true, curfew: "21:00", wake: "07:00", until: "2026-06-25" }
weekday: { curfew: "21:30", wake: "07:00" }
weekend: { curfew: "23:00", wake: "09:00" }
holiday: { curfew: "23:00", wake: "09:00" }
phone_mac: "FA:CE:DE:CA:CA:01"
winddown:
enabled: true
phases:
- { offset_minutes: -30, block_categories: [social] }
- { offset_minutes: -15, block_categories: [gaming] }
- { offset_minutes: 0, block_categories: [all] }
parent1:
role: parent
phone_mac: "FA:CE:DE:CA:CA:02"
shared_devices:
ps5: { name: "PS5", mac: "FA:CE:DE:CA:CA:10" }
screens:
first_floor_appletv:
name: "First Floor Apple TV"
mode: services # block streaming, keep HomeKit alive
blocked_services: [netflix, youtube, disney, crave, twitch]
schedule:
weekday: { curfew: "22:00", wake: "10:00" }
macs: ["FA:CE:DE:CA:CA:20", "FA:CE:DE:CA:CA:21"]
service_categories: # slugs, not domains — bridge maps them
social: [tiktok, instagram, snapchat, discord, reddit]
gaming: [steam, epicgames, roblox, minecraft, fortnite]
streaming: [netflix, youtube, disney, crave, twitch]
credits:
enabled: true
max_daily_minutes: 90
guest_rules:
enabled: true
auto_apply_curfew: true
curfew: "22:00"
wake: "07:00"
expire_hours: 24

The control token is not in this file — a config the whole family can read is the wrong place for the one secret that gates the API. Parent-facing debugging: Parents’ Guide troubleshooting. Operator-depth failure modes: the closed-loop edition.

Why Python, not Rust. Haus-control is a control plane, not a data plane: it wakes every 30 seconds to compare intent against the box and toggle a handful of rules. The sanctum-proxy is Rust because it handles thousands of requests per second; haus-control toggles a few rules a minute. The one Rust piece is the Windows agent, because it lives on hostile territory.

Why not Apple MDM. Invasive, platform-locked (no PS5, no PC), and one “Remove Profile” tap from irrelevant. Apple Screen Time on personal devices plus Firewalla at the network layer covers every device in the haus without installing anything the kids can find — except one Rust agent on one PC, which is exactly why it doesn’t trust the clock it sits next to.

Property Value
Port 4077 (shared with Force Flow)
Engine ~/.sanctum/force-flow/screen_time.py
Bridge ~/.openclaw/firewalla-bridge.js (port 1984)
Config ~/.sanctum/screen-time/devices.yaml
Holidays ~/.sanctum/screen-time/holidays.yaml
Database ~/.sanctum/screen-time/usage.db
PWA ~/.sanctum/screen-time/pwa/
HA Package ~/.openclaw/homeassistant/packages/screen_time.yaml
Holocron ~/Projects/the-holocron/src/renderer/components/ScreenTimePanel.tsx
Export ~/.sanctum/scripts/firewalla-export.sh / firewalla-import.sh
Digest Sunday 18:00 via Signal + push

That is the whole system: four files, one box, and a promise that every hold expires. The hard part was never the toggling of DNS rules — it was building something a twelve-year-old respects and a parent trusts, on the same WiFi, at the same 21:30. The network doesn’t negotiate. Bedtime, most nights, still tries.