Add android/, a normal Gradle/Kotlin project with its own wrapper. It
blocks by interruption: an Accessibility Service watches the foreground
package and a full-screen overlay covers any listed app while the device
is locked (FR-16). No Device Owner, no factory reset, no managed profile.
The client subscribes to the retained restrict/<device>/config topic on
the 8883 TLS plane, keeps the last config on disk so a broker-less device
keeps its rules (FR-14), and reports tampering on its own status topic
(FR-17): a disabled service, a revoked overlay permission, or a
force-stop seen on the next start. The Control Service now parses the
tamper field and raises an Apprise alert, so it reaches Sam.
The block path and the tamper alert are proven on the headless pixel35
emulator on .27; see android/README.md and android/tools/emu-proof.sh.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Build on Phases 1–7. Adds the two override paths from AGENTS.md §4 FR-10/FR-11:
the shared device keycode and the versioned admin HTTP API the separate Kotlin
admin app will call.
- store: migration 004 adds state.grant_until and a settings table. New
overrides.go records every override in `overrides` and verifies the shared
keycode (bcrypt; one code for every device, §10 rule 3). ExpireLocks also
clears an expired grant, and NextScheduleBoundary includes the grant end so a
device re-locks on time.
- effective state: a grant unlocks a device inside a schedule window; a manual
lock clears a grant and a grant clears a manual lock. The retained config
payload gains grant_until (additive, v stays 1).
- web: /api/v1/admin/... (list, state, lock, unlock, grant) guarded by
CONTROL_ADMIN_API_TOKEN, separate from the session login, plus the on-device
keycode endpoint POST /api/v1/device/override with per-address throttling.
A state change is published retained before the API responds.
- cmd/control: `keycode` subcommand and start-up seeding from
CONTROL_DEVICE_KEYCODE_HASH.
- deploy/verify-phase8.sh + `make verify-phase8` prove the definition of done:
an API lock reaches a real broker subscriber in well under a second.
- Version 0.6.0-phase8 (compose, Makefile, docs).
Measured on .13: the lock reached the TLS broker subscriber in 7 ms (handler
1 ms, request to retained publish accepted).
Tests: go vet ./... and go test -count=1 ./... pass in golang:1.25-alpine.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- Weekly schedule editor per device: a Monday-Sunday grid of blocking
windows, including windows that cross midnight.
- Blocklist/allowlist editor per device: app (Android package / Windows
process) and site (hostname) entries, tagged by the device platform.
- Effective desired state: a manual lock, an active schedule window, or
neither. While blocked the allowlist is honoured (Spotify stays while
other apps are blocked). Published retained with reason schedule/manual.
- Boundary-aware republish loop so a window turns on and off on time,
and manual lock expiry is cleared promptly.
- CONTROL_TIMEZONE (default Australia/Sydney) with embedded tzdata, so
wall-clock windows are evaluated in the family zone, not the container.
- Every change is written to the append-only audit log (FR-15).
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- internal/mqtt: publish each device's desired state retained on
restrict/<device>/config; subscribe to restrict/+/status and
restrict/+/telemetry and write reports to SQLite; publish on every state
change, republish on connect and every 15 minutes from SQLite, so the broker
is repaired from the source of truth.
- store: presence (devices.online and last_seen, audited only on transitions),
telemetry upsert per (device, day, app, domain) via migration 002, allow and
block lists read into the config payload, device lookup by topic.
- config: MQTT settings. TLS plus per-device credentials are required; the
pre-TLS shared broker needs an explicit CONTROL_MQTT_INSECURE escape hatch.
- web: republish config after lock, unlock, expiry, create and edit; broker card
on the status partial.
- deploy/mosquitto: TLS listener, topic ACL, credential provisioning and a
verifier that proves the policy on a throwaway broker. Not applied to the
shared Mosquitto on .13 — that change needs Sam's approval.
- cmd/control: mqtt-clients prints the MQTT identity of every device.
- paho.mqtt.golang v1.5.1, which pulls golang.org/x/net and with it the
golang.org/x/crypto bump to v0.42.0.
Verified on .13: retained config delivered to a fresh subscriber; a client
killed without DISCONNECT drove the broker's retained will and the service
flipped the device offline; telemetry landed in SQLite; make mqtt-verify passed
13/13; the service connected over TLS with credentials and a CA.
Co-Authored-By: Paperclip <noreply@paperclip.ing>