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.

RungReachesMechanismConfirmed by
Networkanything with a MAC on the haus WiFiFirewalla pause / service-level DNS blocksFirewalla policy read-back
Mirrorplatform accounts that travel off the WiFi (iOS, Switch, Xbox/PS, Steam, Quest)platform-native parental controls, kept in step via the mirror pagesmanual re-check, on a cadence
AgentWindows PCsRust agent, server-authoritative clockagent 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.

TierPolicyWhy
1 — personal Apple devicesNot blocked. Apple Screen Time handles them, kept honest via the iOS mirror pagedoubling up with network blocks confuses the reporting a parent actually checks — leave Apple to Apple
2 — shared gaming consolesHard curfew at the earliest active child curfew, unblock at wakephone-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 neededthe 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:

Rulev4 behavior
Manual blockexpires at next wake by default — nothing blocks forever
Pauserequires a who and a duration; maximum is jusqu’au réveil (that child’s wake time, never past it)
Overridecapped at 120 minutes
Every holddisplays 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:

ProductBudget unit
Google Family Linkper-device
Nintendo parental controlsper-console
Firewalla, nativeno budgets at all
Haus-control v4per-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():

PriorityScheduleCurfewWakeSource
1 (highest)Study mode21:0007:00Operator opt-in, expires study_mode.until
2Quebec school holiday23:0009:00holidays.yaml
3Weekend (Fri/Sat night)23:0009:00Day-of-week check
4 (default)Weekday21:3007:00Default 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:

OffsetAction
-30 minSocial media blocked (TikTok, Instagram, Snapchat, Discord, Reddit)
-15 minGaming blocked (Steam, Epic, Roblox, Minecraft, Fortnite)
0 minEverything 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:

PresetDuration
Tâches ménagères15 min
Devoirs30 min
Lecture20 min
Exercice15 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:

LayerDefenseThe incident that bought it
1Persisted block state (SQLite)a 3 AM restart forgot the morning unblock
2SSH iptables fallbackthe box API returned “invalid policy” for every rule ID
3Daily idempotent safety-net unblocklost state, now caught within 30 seconds
4Closed-loop reconciler + conf-gen sentinel: tri-state policy read-back, fail-closed escalation after a 20-minute grace, fresh-MAC canary with an mtime guardthe 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.

InterfaceDetails
PWAhttp://force.home/pwa/ — iOS standalone, dark OLED theme, French UI
Home Assistantpackages/screen_time.yaml — REST commands, sensors, homework toggle sync
Siri shortcuts6 French invocations (“Mode devoirs”, “Prolonger 30 min”, “Couvre-feu maintenant”…) via Tailscale MagicDNS
Holocron panelScreenTimePanel.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.

MethodPathDescription
GET/screen/statusCurrent curfew state, active holds (owner + expiry), presence
POST/screen/overrideTemporary override (duration in minutes, cap 120)
POST/screen/blockManual block by MAC — expires at next wake by default
POST/screen/unblockManual unblock by MAC
GET/screen/reportUsage report (daily/weekly/custom range)
GET/screen/devicesAll known devices and their current state
POST/screen/assignAssign a device to a family member
POST/screen/reloadReload config from devices.yaml
GET/screen/scheduleCurrent effective schedule and next transition
POST/screen/homeworkActivate (action:start) or deactivate (action:stop) homework mode
GET/screen/homework/statusHomework mode state and remaining time
POST/screen/creditGrant earned credit (preset + optional note)
GET/screen/creditsCredit balance and history for today
POST/screen/credit/redeemRedeem accumulated credits
GET/screen/digestGenerate digest on demand (also sent weekly)
GET/screen/guestsList detected guest devices
POST/screen/guest/approveApprove a guest device
DELETE/screen/guest/removeRemove 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.

PropertyValue
Port4077 (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
DigestSunday 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.