Build on Phases 1-8. Adds the project side of the frozen Hub contract
(central_ai_llm_master/docs/ask-contract.md) and the alert engine from
AGENTS.md §4 FR-12/FR-13, §8, §11 Phase 9.
- hub: internal/hub implements AskRequest/AskResponse/NotifyRequest and a
deterministic, read-only Answerer. It answers screen time today/yesterday,
what is blocked now, and device presence, scoped to one child or all kids.
Screen time is the foreground-app total per device-day; the domain total is
used only when a device reports no apps, so a report carrying both is never
double-counted.
- apprise: internal/apprise is a small Apprise API client (stored key or
stateless URL mode, optional bearer token, typed notifications).
- alert: internal/alert evaluates device state on an interval and raises one
Apprise alert per transition: a limit hit (a device becomes blocked) or a
device offline longer than the threshold. A never-seen device is never
alerted, and the first observation seeds state without replaying an old
limit. The alert is recorded before delivery, with its delivery outcome.
- store: migration 005 adds `alerts` (delivery outcome, audited) and
`alert_state` (per-device dedup). New usage reads from the telemetry detail
table so the daily answer can be checked independently.
- web: GET /capabilities, POST /ask (read-only) and POST /notify behind the
shared CONTROL_HUB_TOKEN, separate from the session and admin API tokens.
/notify accepts the contract's {from,level,message,person} and delivers it
through Apprise (the future tamper-report path, FR-17).
- cmd/control: start the alert engine (CONTROL_ALERTS_ENABLED,
CONTROL_ALERT_OFFLINE_AFTER, CONTROL_ALERT_INTERVAL) and add `alerts` and
`usage` subcommands for read-only inspection.
- deploy/verify-phase9.sh + `make verify-phase9` prove the definition of done;
version 0.7.0-phase9.
Measured on .13 (2026-10-13):
- GET /capabilities -> internet_use_control topics.
- POST /ask "how long have the kids been on screens today?" -> "The kids have
had 1h 22m of screen time today." (total_seconds=4925); the independent
count from the telemetry detail table is also 4925 (app=4925, domain=4800).
- A manual lock raised a delivered limit alert (id 4); an MQTT offline status
raised a delivered offline alert (id 5); POST /notify delivered (id 6). All
three report delivered=1 in `alerts`.
Tests: gofmt, go vet ./... and go test ./... pass in golang:1.25-alpine.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
92 lines
3.1 KiB
Makefile
92 lines
3.1 KiB
Makefile
IMAGE ?= webrestrict/control:0.7.0-phase9
|
|
BUILD_VERSION ?= 0.7.0-phase9
|
|
|
|
# Bounded docker helper for the Go toolchain. The container runs as the current
|
|
# user, so the repository and the module cache stay owned by you.
|
|
GO_IMAGE ?= golang:1.25-alpine
|
|
GO_RUN = timeout 600 docker run --rm --network host --user "$$(id -u):$$(id -g)" \
|
|
-e HOME=/src/.gocache -e GOMODCACHE=/src/.gocache/mod -e GOCACHE=/src/.gocache/build \
|
|
-v "$$PWD":/src -w /src $(GO_IMAGE)
|
|
|
|
# Scratch broker used by `make mqtt-verify` (see deploy/mosquitto/README.md).
|
|
MQTT_VERIFY_PORT ?= 18883
|
|
MQTT_IMAGE ?= eclipse-mosquitto:latest
|
|
|
|
.PHONY: help test vet build up down restart logs ps hash-password deps fmt \
|
|
mqtt-tls mqtt-verify mqtt-config verify-phase8
|
|
|
|
help:
|
|
@echo "make test run go test ./... in golang:1.25-alpine"
|
|
@echo "make vet run go vet ./... in golang:1.25-alpine"
|
|
@echo "make build build the container image ($(IMAGE))"
|
|
@echo "make up docker compose up -d --build"
|
|
@echo "make down docker compose down"
|
|
@echo "make logs follow container logs"
|
|
@echo "make ps docker compose ps"
|
|
@echo "make hash-password generate a bcrypt hash (PASSWORD=...)"
|
|
@echo "make deps go mod tidy + download (writes go.mod/go.sum)"
|
|
@echo "make mqtt-tls generate the lab CA and broker certificate"
|
|
@echo "make mqtt-config build the scratch broker config in deploy/mosquitto/build/"
|
|
@echo "make mqtt-verify prove TLS + credentials + topic ACLs on a scratch broker"
|
|
@echo "make verify-phase8 prove an API lock reaches a device in under a second"
|
|
@echo "make verify-phase9 prove the Hub answers and the Apprise alerts deliver"
|
|
|
|
test:
|
|
$(GO_RUN) go test ./...
|
|
|
|
vet:
|
|
$(GO_RUN) go vet ./...
|
|
|
|
deps:
|
|
$(GO_RUN) sh -c 'go mod tidy'
|
|
|
|
fmt:
|
|
$(GO_RUN) sh -c 'go fmt ./...'
|
|
|
|
build:
|
|
docker build --build-arg VERSION=$(BUILD_VERSION) -t $(IMAGE) .
|
|
|
|
up:
|
|
docker compose up -d --build
|
|
|
|
down:
|
|
docker compose down
|
|
|
|
restart:
|
|
docker compose restart control
|
|
|
|
logs:
|
|
docker compose logs -f control
|
|
|
|
ps:
|
|
docker compose ps
|
|
|
|
hash-password:
|
|
@test -n "$(PASSWORD)" || (echo "usage: make hash-password PASSWORD=secret"; exit 2)
|
|
docker run --rm --entrypoint /app/control $(IMAGE) hash-password '$(PASSWORD)'
|
|
|
|
# --- MQTT ---------------------------------------------------------------
|
|
# These targets never touch the shared Mosquitto. They build the material and
|
|
# prove it on a scratch broker on $(MQTT_VERIFY_PORT). See deploy/mosquitto/README.md.
|
|
|
|
mqtt-tls:
|
|
deploy/mosquitto/generate-tls.sh
|
|
|
|
mqtt-config: mqtt-tls
|
|
deploy/mosquitto/build-config.sh
|
|
|
|
mqtt-verify: mqtt-config
|
|
MQTT_VERIFY_PORT=$(MQTT_VERIFY_PORT) MQTT_IMAGE=$(MQTT_IMAGE) \
|
|
deploy/mosquitto/verify-isolation.sh
|
|
|
|
# Phase 8 definition of done: lock through the admin API, measured until a real
|
|
# broker subscriber receives the retained config. Never touches the 1883 plane.
|
|
verify-phase8:
|
|
deploy/verify-phase8.sh
|
|
|
|
# Phase 9 definition of done: /capabilities, /ask, the alert engine and /notify
|
|
# against the deployed service, with Apprise delivery checked in the alerts
|
|
# table. Never touches the 1883 plane.
|
|
verify-phase9:
|
|
deploy/verify-phase9.sh
|