# Project Documentation Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Create a polished top-level `README.md`, a linear home user guide, a reference operator guide, and a split architectural reference (overview + conventions + 12 per-subsystem files) for Vigilar. **Architecture:** Approach 3 (Hybrid) from the spec — monolithic linear guides for users, split reference docs for contributors. No tooling added, no code changes, no image assets. Plain Markdown organized as if MkDocs-ready. **Tech Stack:** Markdown only. No build step. All claims must be grounded in the actual code under `/home/alee/Sources/vigilar/` at write-time. **Spec:** `docs/superpowers/specs/2026-04-05-project-documentation-design.md` — read it before starting. --- ## Universal rules for every task in this plan 1. **Do not guess.** Every file path, command, TOML key, MQTT topic, DB table, CLI subcommand, blueprint route, and function/file reference must be verified against the actual repo before writing. If you cannot verify something, omit it — or, for subsystem docs, write `"no publishers found at time of writing"` (or equivalent). 2. **Do not create code, scripts, unit files, or images.** This effort is docs-only. The only exception is creating the `docs/architecture/` and `docs/architecture/subsystems/` directories implicitly by writing files into them. 3. **Do not modify `docs/camera-hardware-guide.md` or anything under `docs/superpowers/`** other than this plan file. 4. **Commit after each task.** One commit per task unless a task says otherwise. 5. **Tone:** plain, direct, no marketing fluff, no emoji, no exclamation marks. Match the tone of `CLAUDE.md` and `scripts/backup.sh` comments. 6. **Line wrap:** keep prose at ~100 columns for readability in terminals and diffs. 7. **Relative links:** docs inside `docs/` link to each other with relative paths (e.g. `architecture/overview.md`). The top-level README links use `docs/...` paths. --- ## File Structure Files to be created in this plan (17 total): ``` README.md # new docs/home-user-guide.md # new docs/operator-guide.md # new docs/architecture/overview.md # new docs/architecture/conventions.md # new docs/architecture/subsystems/camera.md # new docs/architecture/subsystems/detection.md # new docs/architecture/subsystems/events.md # new docs/architecture/subsystems/alerts.md # new docs/architecture/subsystems/sensors.md # new docs/architecture/subsystems/ups.md # new docs/architecture/subsystems/storage.md # new docs/architecture/subsystems/highlights.md # new docs/architecture/subsystems/presence.md # new docs/architecture/subsystems/pets.md # new docs/architecture/subsystems/health.md # new docs/architecture/subsystems/web.md # new ``` Tasks are ordered so that later tasks can reference and link to earlier ones. --- ## Task 1: Architecture overview **Files:** - Create: `docs/architecture/overview.md` - [ ] **Step 1: Gather grounding facts by reading source** Read the following files and take notes. Do not write prose yet. ```bash ls vigilar/ ls vigilar/camera vigilar/events vigilar/alerts vigilar/storage vigilar/web/blueprints ``` Read these files in full: - `CLAUDE.md` - `config/vigilar.toml` - `vigilar/camera/worker.py` - `vigilar/camera/motion.py` - `vigilar/camera/recorder.py` - `vigilar/camera/ring_buffer.py` - `vigilar/events/processor.py` - `vigilar/events/rules.py` - `vigilar/storage/db.py` - `vigilar/storage/schema.py` - `vigilar/storage/encryption.py` - `vigilar/web/__init__.py` - `systemd/vigilar.service` - `systemd/vigilar-mosquitto.conf` From these, write down on scratchpad: - The exact MQTT topic format used (look for `mqtt.publish` / `client.publish` calls and topic string literals). - The actual table names in `vigilar/storage/schema.py`. - The file+function name where RTSP frames are captured. - The file+function name where motion is detected. - The file+function name where a recording is started. - The file+function name where an event row is written. - The file+function name where a push notification is sent. - Whether the process model is actually `multiprocessing.Process` (check `vigilar/cli/cmd_start.py` or wherever subsystems are spawned). If any of these cannot be found, note them as "unverified" and they will be omitted from the doc. - [ ] **Step 2: Create `docs/architecture/overview.md`** Write the file with these sections, in this order. Each section's content must reflect what you actually found in Step 1, not generic text. ```markdown # Vigilar Architecture Overview This document explains how Vigilar is put together for someone reading the codebase for the first time. It is short on purpose — per-subsystem details live under `subsystems/`. ## Design principles - **Offline-first.** No external calls in the critical path. Cloud integrations, if any, are opt-in and off the hot path. - **Subsystem isolation.** Each subsystem runs in its own process. A crash in one subsystem cannot take down another. - **Loose coupling via MQTT.** Subsystems do not call each other directly. They publish and subscribe to a local Mosquitto broker on `127.0.0.1:1883`. - **SQLite (WAL) is the single durable store.** Access goes through SQLAlchemy Core expressions, not ORM mapped classes. - **Adaptive cost.** Cameras idle at 2 FPS and jump to 30 FPS on motion, with a 5-second ring buffer so the moment leading up to the trigger is kept. - **Configuration is typed.** `config/vigilar.toml` is loaded and validated by Pydantic v2. Secrets are never inline — they are file paths under `/etc/vigilar/secrets/`. ## Process topology ``` ┌────────────────┐ │ vigilar start │ (supervisor) └───────┬────────┘ │ spawns ┌────────┬────────┬──────┼──────┬────────┬────────┐ ▼ ▼ ▼ ▼ ▼ ▼ ▼ camera sensors events ups alerts health web ↑ ↑ ↑ ↑ ↑ ↑ ↑ └─────────┴────────┴──────┴──────┴───────┴────────┘ MQTT (127.0.0.1:1883) ``` ## The MQTT bus - Broker: Mosquitto, bound to loopback only. Config: `systemd/vigilar-mosquitto.conf`. - Topic convention: `` — quote one or two real topics from the codebase. - Why MQTT rather than an in-process queue: crash isolation, introspection with `mosquitto_sub`, and the option to move subsystems to separate hosts later without changing the wire format. ## Data flow: from motion to phone notification Numbered sequence. Each step names the real file and function where the work happens. 1. `vigilar/camera/worker.py:` — RTSP capture at 2 FPS. 2. `vigilar/camera/motion.py:` — OpenCV MOG2 detects movement and publishes ``. 3. `vigilar/camera/recorder.py:` — flips to 30 FPS, flushes the ring buffer, begins writing a `.vge` file. 4. `vigilar/events/processor.py:` — inserts a row into `` and publishes ``. 5. `vigilar/highlights/...` — scores the event. 6. `vigilar/alerts/...` — sends a VAPID web-push notification. 7. Web UI — updates the timeline (mechanism: ). ## Storage layout - `vigilar.db` (SQLite, WAL mode). Tables: . - Recordings: `.vge` files, AES-256-GCM. Key at `/etc/vigilar/secrets/storage.key`. Losing the key means losing the recordings. - HLS: rolling segments under `[system] hls_dir` (default `/var/vigilar/hls`). - Backups: DB + `/etc/vigilar` tarball at `[system]`-adjacent path (see `scripts/backup.sh`). ## Configuration and secrets - `config/vigilar.toml` is the only configuration file the app reads. - It is validated by Pydantic v2 at startup — invalid config stops the process before any subsystem spawns. - Secrets never live in the TOML. They live as files under `/etc/vigilar/secrets/` and the TOML only references paths. - The arm PIN and admin password are hashed; comparisons are constant-time. ## The web tier - Flask with Blueprints, one per feature area (`vigilar/web/blueprints/*.py`). - Jinja2 templates, Bootstrap 5 dark theme. - Live view: `hls.js` grid for bandwidth efficiency, MJPEG single view for low latency. - PWA with VAPID push — no Firebase, no Google Cloud Messaging. ## What is NOT in the critical path - Remote access (`[remote]` section) — optional, bandwidth-shaped. - Email alerts (`[alerts.email]`) — optional. - Any cloud service — never. ## Where to go next - Conventions: `conventions.md` - Per-subsystem details: `subsystems/` ``` Replace every `` with the real value from Step 1. If a fact cannot be verified, delete the bullet or sentence entirely rather than guessing. - [ ] **Step 3: Verify all claims against source** For every file path referenced in the overview, run: ```bash ls ``` For every topic string quoted, run: ``` (use Grep tool, NOT bash grep) Grep for the topic string across vigilar/ — must return at least one hit. ``` For every table name quoted, confirm it appears in `vigilar/storage/schema.py`. Fix any that fail. - [ ] **Step 4: Commit** ```bash git add docs/architecture/overview.md git commit -m "docs: add architecture overview" ``` --- ## Task 2: Architecture conventions **Files:** - Create: `docs/architecture/conventions.md` - [ ] **Step 1: Re-read `CLAUDE.md` and `vigilar/constants.py`** ```bash ls vigilar/constants.py # confirm it exists ``` Read `vigilar/constants.py` in full and note the `StrEnum` style actually used. - [ ] **Step 2: Create `docs/architecture/conventions.md`** Target length ~400 words. Write the file: ```markdown # Coding Conventions These are the rules a contributor needs in their head before touching Vigilar. They are distilled from `CLAUDE.md` and from the patterns already in the codebase. ## Language and style - Python 3.11+. - Ruff for linting. Line length 100. - Type hints on all public functions. Internal helpers may omit them when obvious. - No docstrings unless the logic is non-obvious. Let short, well-named functions speak for themselves. ## String constants All string constants that are referenced in more than one place live in `vigilar/constants.py` as `StrEnum` members. This includes MQTT topics, event kinds, alert channels, and anything else that would otherwise be a magic string. If you are about to write `"motion_detected"` in two places, add it to the enum instead. ## Database access - SQLite in WAL mode. - SQLAlchemy Core expressions only — no mapped classes, no ORM session. - Schema lives in `vigilar/storage/schema.py`. Queries live in `vigilar/storage/queries.py`. - New tables go in `schema.py`. New query helpers go in `queries.py`. Do not scatter ad-hoc SQL across subsystem code. ## Processes and the MQTT bus - Each subsystem runs in its own `multiprocessing.Process`. - Subsystems communicate only through the local MQTT broker. They do not share memory or call each other's functions across process boundaries. - Topic naming: //`>. - If you are tempted to reach into another subsystem directly, publish a topic instead. ## Configuration - `config/vigilar.toml` is the only config file the app reads at runtime. - It is validated by Pydantic v2. Add new fields to the Pydantic models, not just to the TOML. - Secrets are file paths. Never put a key, password, or token directly in the TOML. ## Testing - `pytest` from the repo root. - Tests live under `tests/`. ## Committing - `ruff check vigilar/` must pass. - `pytest` must pass. - One logical change per commit. Commit often. ``` Replace `` with the actual topic convention observed. - [ ] **Step 3: Verify** ```bash ls vigilar/constants.py vigilar/storage/schema.py vigilar/storage/queries.py ``` All three must exist. If `queries.py` does not exist, remove the reference to it from the doc. - [ ] **Step 4: Commit** ```bash git add docs/architecture/conventions.md git commit -m "docs: add coding conventions reference" ``` --- ## Task 3: Subsystem doc template and the `camera` subsystem The next 12 tasks (3–14) all follow the same template. Task 3 is written in full. Tasks 4–14 reuse the same template verbatim and only change which subsystem is being documented. **Files:** - Create: `docs/architecture/subsystems/camera.md` - [ ] **Step 1: Enumerate the subsystem's files** ```bash ls vigilar/camera/ ``` Read every `.py` file in the directory. For each one, note in 1 sentence what it is responsible for. - [ ] **Step 2: Find MQTT topics** Use the Grep tool (NOT bash grep) to find what this subsystem publishes and subscribes to: ``` Grep pattern: publish\(|subscribe\( path: vigilar/camera output_mode: content ``` Write down every topic string literal you see, and whether it is being published or subscribed to. If a topic comes from `vigilar/constants.py`, resolve it. - [ ] **Step 3: Find DB tables touched** ``` Grep pattern: from vigilar.storage path: vigilar/camera output_mode: content Grep pattern: \.insert\(|\.select\(|\.update\(|\.delete\( path: vigilar/camera output_mode: content ``` Note any table names referenced. If none, the Database section will say "none". - [ ] **Step 4: Write the file** Create `docs/architecture/subsystems/camera.md` using this exact template. Fill every section from the evidence gathered in Steps 1–3. ```markdown # camera ## Purpose ## Key files - `vigilar/camera/worker.py` — - `vigilar/camera/manager.py` — - `vigilar/camera/motion.py` — - `vigilar/camera/recorder.py` — - `vigilar/camera/ring_buffer.py` — - `vigilar/camera/hls.py` — ## MQTT topics **Subscribes:** **Publishes:** ## Database tables
## Depends on - - ... or "none" ## Consumed by - - ... or "none" ## Notes ``` If you genuinely cannot find any MQTT publishers in this subsystem, write the literal text `"No MQTT publishers found at time of writing."` under **Publishes:**. Same for subscribes. **Do not invent topics.** - [ ] **Step 5: Verify** ```bash ls vigilar/camera/worker.py vigilar/camera/manager.py vigilar/camera/motion.py \ vigilar/camera/recorder.py vigilar/camera/ring_buffer.py vigilar/camera/hls.py ``` All must exist. If any do not, remove them from the Key Files list. - [ ] **Step 6: Commit** ```bash git add docs/architecture/subsystems/camera.md git commit -m "docs: add camera subsystem reference" ``` --- ## Task 4: `detection` subsystem Follow the exact same 6-step procedure as Task 3, but for the `detection` subsystem. **Files:** - Create: `docs/architecture/subsystems/detection.md` - [ ] **Step 1:** `ls vigilar/detection/` and read every `.py` file. - [ ] **Step 2:** Grep for `publish(` and `subscribe(` in `vigilar/detection`. - [ ] **Step 3:** Grep for DB access in `vigilar/detection`. - [ ] **Step 4:** Write `docs/architecture/subsystems/detection.md` using the template from Task 3. Fill from evidence. No guessing. - [ ] **Step 5:** Verify every file path in the Key Files list exists. - [ ] **Step 6:** Commit. ```bash git add docs/architecture/subsystems/detection.md git commit -m "docs: add detection subsystem reference" ``` --- ## Task 5: `events` subsystem Follow the Task 3 procedure for `events`. **Files:** - Create: `docs/architecture/subsystems/events.md` - [ ] **Step 1:** `ls vigilar/events/` and read every `.py` file. - [ ] **Step 2:** Grep for `publish(` and `subscribe(` in `vigilar/events`. - [ ] **Step 3:** Grep for DB access in `vigilar/events`. This subsystem almost certainly writes to an events table — name it. - [ ] **Step 4:** Write `docs/architecture/subsystems/events.md` using the template from Task 3. Fill from evidence. - [ ] **Step 5:** Verify Key Files paths. - [ ] **Step 6:** Commit. ```bash git add docs/architecture/subsystems/events.md git commit -m "docs: add events subsystem reference" ``` --- ## Task 6: `alerts` subsystem Follow the Task 3 procedure for `alerts`. **Files:** - Create: `docs/architecture/subsystems/alerts.md` - [ ] **Step 1:** `ls vigilar/alerts/` and read every `.py` file. Expect sub-channels for local (syslog/desktop), web push (VAPID), and email. - [ ] **Step 2:** Grep for `publish(` and `subscribe(` in `vigilar/alerts`. Alerts almost certainly subscribe to event topics. - [ ] **Step 3:** Grep for DB access in `vigilar/alerts`. - [ ] **Step 4:** Write the doc. In **Notes**, mention the VAPID / PWA push path if and only if you find the code for it (do not guess). - [ ] **Step 5:** Verify paths. - [ ] **Step 6:** Commit. ```bash git add docs/architecture/subsystems/alerts.md git commit -m "docs: add alerts subsystem reference" ``` --- ## Task 7: `sensors` subsystem Follow the Task 3 procedure for `sensors`. **Files:** - Create: `docs/architecture/subsystems/sensors.md` - [ ] **Step 1:** `ls vigilar/sensors/` and read every `.py` file. - [ ] **Step 2:** Grep for `publish(` / `subscribe(` in `vigilar/sensors`. Sensors likely integrate with Zigbee2MQTT — look for `zigbee2mqtt` topic prefixes too. - [ ] **Step 3:** Grep for DB access in `vigilar/sensors`. - [ ] **Step 4:** Write the doc. Mention Zigbee2MQTT in **Depends on** if the code actually subscribes to its topics. - [ ] **Step 5:** Verify paths. - [ ] **Step 6:** Commit. ```bash git add docs/architecture/subsystems/sensors.md git commit -m "docs: add sensors subsystem reference" ``` --- ## Task 8: `ups` subsystem Follow the Task 3 procedure for `ups`. **Files:** - Create: `docs/architecture/subsystems/ups.md` - [ ] **Step 1:** `ls vigilar/ups/` and read every `.py` file. - [ ] **Step 2:** Grep for `publish(` / `subscribe(` in `vigilar/ups`. - [ ] **Step 3:** Grep for DB access in `vigilar/ups`. - [ ] **Step 4:** Write the doc. In **Notes**, mention that this subsystem polls NUT (`127.0.0.1:3493` by default, from `[ups]` in the TOML) and triggers graceful shutdown on critical battery — but only if you can point to the code that does each of those things. - [ ] **Step 5:** Verify paths. - [ ] **Step 6:** Commit. ```bash git add docs/architecture/subsystems/ups.md git commit -m "docs: add ups subsystem reference" ``` --- ## Task 9: `storage` subsystem Follow the Task 3 procedure for `storage`. **Files:** - Create: `docs/architecture/subsystems/storage.md` - [ ] **Step 1:** `ls vigilar/storage/` and read `db.py`, `schema.py`, `encryption.py`, `queries.py`, `__init__.py`. - [ ] **Step 2:** Grep for `publish(` / `subscribe(` in `vigilar/storage` — this subsystem may have none, which is fine. - [ ] **Step 3:** This IS the DB layer, so the **Database tables** section should list every table defined in `schema.py`, with a one phrase description of each. - [ ] **Step 4:** Write the doc. In **Notes**, explain the `.vge` encryption format (AES-256-GCM) and where the key lives (`/etc/vigilar/secrets/storage.key`) — but only if `encryption.py` actually implements that. Quote the function name. - [ ] **Step 5:** Verify paths. - [ ] **Step 6:** Commit. ```bash git add docs/architecture/subsystems/storage.md git commit -m "docs: add storage subsystem reference" ``` --- ## Task 10: `highlights` subsystem Follow the Task 3 procedure for `highlights`. **Files:** - Create: `docs/architecture/subsystems/highlights.md` - [ ] **Step 1:** `ls vigilar/highlights/` and read every `.py` file. Expect scoring, reel assembly, and possibly timelapse code. - [ ] **Step 2:** Grep for `publish(` / `subscribe(`. - [ ] **Step 3:** Grep for DB access. - [ ] **Step 4:** Write the doc. Mention in **Notes** how scoring works if the code makes it obvious (e.g. FFmpeg clip assembly, event score thresholds). Do not invent scoring weights. - [ ] **Step 5:** Verify paths. - [ ] **Step 6:** Commit. ```bash git add docs/architecture/subsystems/highlights.md git commit -m "docs: add highlights subsystem reference" ``` --- ## Task 11: `presence` subsystem Follow the Task 3 procedure for `presence`. **Files:** - Create: `docs/architecture/subsystems/presence.md` - [ ] **Step 1:** `ls vigilar/presence/` and read every `.py` file. - [ ] **Step 2:** Grep for `publish(` / `subscribe(`. - [ ] **Step 3:** Grep for DB access. - [ ] **Step 4:** Write the doc. If you cannot figure out what this subsystem does from its code, say so honestly in **Purpose** — e.g. "Tracks resident presence state. Mechanism not documented at time of writing; see source for details." - [ ] **Step 5:** Verify paths. - [ ] **Step 6:** Commit. ```bash git add docs/architecture/subsystems/presence.md git commit -m "docs: add presence subsystem reference" ``` --- ## Task 12: `pets` subsystem Follow the Task 3 procedure for `pets`. **Files:** - Create: `docs/architecture/subsystems/pets.md` - [ ] **Step 1:** `ls vigilar/pets/` and read every `.py` file. - [ ] **Step 2:** Grep for `publish(` / `subscribe(`. - [ ] **Step 3:** Grep for DB access. - [ ] **Step 4:** Write the doc. - [ ] **Step 5:** Verify paths. - [ ] **Step 6:** Commit. ```bash git add docs/architecture/subsystems/pets.md git commit -m "docs: add pets subsystem reference" ``` --- ## Task 13: `health` subsystem Follow the Task 3 procedure for `health`. **Files:** - Create: `docs/architecture/subsystems/health.md` - [ ] **Step 1:** `ls vigilar/health/` — expected files: `digest.py`, `monitor.py`, `pruner.py`, `__init__.py`. Read all four. - [ ] **Step 2:** Grep for `publish(` / `subscribe(`. - [ ] **Step 3:** Grep for DB access. The pruner likely deletes old rows and old recordings — note what it prunes. - [ ] **Step 4:** Write the doc. In **Notes**, describe what the pruner enforces (e.g. `max_disk_usage_gb`, `free_space_floor_gb` from the `[storage]` TOML section) — but only if the code actually reads those settings. Quote the variable name. - [ ] **Step 5:** Verify paths. - [ ] **Step 6:** Commit. ```bash git add docs/architecture/subsystems/health.md git commit -m "docs: add health subsystem reference" ``` --- ## Task 14: `web` subsystem Follow the Task 3 procedure for `web`. This one is slightly different: the **Key files** list should enumerate every blueprint, and the **Purpose** should name the HTTP-facing surface. **Files:** - Create: `docs/architecture/subsystems/web.md` - [ ] **Step 1:** `ls vigilar/web/blueprints/` and read each blueprint's top-of-file routes. Also read `vigilar/web/__init__.py` to find the Flask app factory. - [ ] **Step 2:** Grep for `publish(` / `subscribe(` in `vigilar/web`. The web tier likely subscribes to event topics for live updates — verify. - [ ] **Step 3:** Grep for DB access. Blueprints likely use `vigilar.storage.queries`. - [ ] **Step 4:** Write `docs/architecture/subsystems/web.md`. Under **Key files**, list each blueprint from `vigilar/web/blueprints/` with a one-sentence description of what it serves: ``` - `vigilar/web/blueprints/cameras.py` — live view and camera management UI - `vigilar/web/blueprints/events.py` — event timeline - `vigilar/web/blueprints/recordings.py` — recording playback - `vigilar/web/blueprints/sensors.py` — sensor status - `vigilar/web/blueprints/system.py` — system health, settings, PIN - `vigilar/web/blueprints/kiosk.py` — wall display mode - `vigilar/web/blueprints/pets.py` — pet tracking UI - `vigilar/web/blueprints/visitors.py` — visitor tracking UI - `vigilar/web/blueprints/wildlife.py` — wildlife tracking UI ``` Adjust each description based on the actual routes you read. In **Notes**, mention: Jinja2 + Bootstrap 5 dark, `hls.js` for the grid, MJPEG for single-camera low-latency, and PWA + VAPID push — but only the items you can locate in the codebase. - [ ] **Step 5:** Verify paths. ```bash ls vigilar/web/blueprints/cameras.py vigilar/web/blueprints/events.py \ vigilar/web/blueprints/recordings.py vigilar/web/blueprints/sensors.py \ vigilar/web/blueprints/system.py vigilar/web/blueprints/kiosk.py \ vigilar/web/blueprints/pets.py vigilar/web/blueprints/visitors.py \ vigilar/web/blueprints/wildlife.py ``` - [ ] **Step 6:** Commit. ```bash git add docs/architecture/subsystems/web.md git commit -m "docs: add web subsystem reference" ``` --- ## Task 15: Home user guide **Files:** - Create: `docs/home-user-guide.md` - [ ] **Step 1: Re-read the grounding sources** ```bash ls scripts/install.sh scripts/gen_vapid_keys.sh scripts/gen_cert.sh \ scripts/backup.sh systemd/vigilar.service ``` Read `scripts/install.sh` in full so you can accurately describe in 3 bullets what it does (install system deps, create vigilar user, set up venv, install the package, generate VAPID keys, install the systemd unit — confirm each against the actual script). Read `config/vigilar.toml` and confirm the web port default is `49735` under `[web]`. If it has changed, use the actual value. - [ ] **Step 2: Create `docs/home-user-guide.md`** Write the file with exactly these sections, in this order. Keep the tone direct and practical. Target length 1500–2500 words. ```markdown # Vigilar Home User Guide This guide takes you from a bare mini PC to working cameras on your phone. It assumes you are comfortable with a Linux command line and can SSH into a machine on your network. If you are running Vigilar on a server you already administer, see the [Operator Guide](operator-guide.md) instead. ## What you will end up with - A small always-on box in your house that records from your IP cameras. - A web UI at `http://:49735` on your LAN. - Push notifications on your phone when motion is detected. - Optional nightly config + database backup to a NAS share. ``` [IP cameras] ──RTSP──▶ [mini PC running Vigilar] ──LAN──▶ [your phone/browser] │ └── optional nightly ──▶ [NAS] ``` ## What you need - A mini PC. x86_64, 4 GB RAM minimum, 128 GB SSD minimum. Any modern Intel NUC, Beelink, MinisForum, or similar will do. - A USB stick (8 GB or larger) for installing Linux. - One or more RTSP IP cameras on your LAN. For picks, see the [Camera Hardware Guide](camera-hardware-guide.md). - A phone for push notifications. - Optional: a NAS on your LAN for backups. ## Step 1 — Install Linux on the mini PC Install Debian 12 or Ubuntu Server 24.04 (or later) on the mini PC. The official installers walk you through it. During install: - Choose **OpenSSH server** when asked for tasks/components. - Set a hostname you will remember (e.g. `vigilar`). - Create a user for yourself. You will need `sudo`. After install, note the machine's LAN IP address (`ip addr` on the box) and make sure you can SSH in from your laptop. ## Step 2 — Install Vigilar SSH into the mini PC and run: ```bash git clone vigilar cd vigilar sudo ./scripts/install.sh ``` `install.sh` will: - Install system dependencies (ffmpeg, mosquitto, Python 3, NUT client). - Create a dedicated `vigilar` system user. - Set up a Python virtualenv at `/opt/vigilar/venv`. - Generate VAPID keys for web push notifications. - Install the systemd unit and Mosquitto broker config. Review the `[INFO]` output as it runs. Anything marked `[FAIL]` needs attention before you continue. ## Step 3 — Start it up ```bash sudo systemctl enable --now vigilar sudo systemctl status vigilar ``` You should see `active (running)`. If it is not, run `journalctl -u vigilar -e` and look for the first `ERROR` line. Open a browser on any device on the same LAN: ``` http://:49735 ``` The port is configurable under `[web] port` in `/etc/vigilar/vigilar.toml` if 49735 conflicts with something else you run. ## Step 4 — Set your PIN On the first visit you will be asked to set an arm/disarm PIN. Choose something you will remember. The PIN is stored as a hash — there is no recovery. If you lose it you will need to reset it from the command line with `vigilar config set-pin` on the mini PC. > **Screenshot placeholder:** `docs/images/first-run-pin.png` ## Step 5 — Add your first camera From the web UI, go to **Cameras → Add camera**. You will need: - A name for the camera (e.g. "Front door"). - The camera's RTSP URL. Format varies by vendor — see the [Camera Hardware Guide](camera-hardware-guide.md) for the common ones. - Username and password for the camera's RTSP stream (not the camera's web UI login, if they are different). Click **Test** to confirm Vigilar can pull a frame, then **Save**. The camera will appear in the live grid. It idles at 2 FPS to save CPU and jumps to 30 FPS when motion is detected. > **Screenshot placeholder:** `docs/images/add-camera.png` ## Step 6 — Phone push notifications Vigilar runs as a Progressive Web App. To get notifications on your phone: 1. Open `http://:49735` in your phone's browser on the same Wi-Fi. 2. Use your browser's "Add to Home Screen" option. 3. Open the installed app from your home screen. 4. When prompted, allow notifications. Under the hood, `install.sh` already generated the VAPID keys at `/etc/vigilar/secrets/vapid_private.pem`. You do not need to do anything else. ## Step 7 — Optional: NAS backup This step backs up your SQLite database and `/etc/vigilar` (config and secrets) to a NAS share once a day. It does **not** back up recordings — recordings stay on the mini PC's local disk. Recording backup is planned as a later improvement. ### Mount the NAS share If your NAS offers NFS: ```bash sudo apt-get install -y nfs-common sudo mkdir -p /mnt/nas/vigilar-backups echo "nas.lan:/volume1/vigilar-backups /mnt/nas/vigilar-backups nfs defaults,_netdev 0 0" | sudo tee -a /etc/fstab sudo mount -a ``` Replace `nas.lan:/volume1/vigilar-backups` with your NAS's actual export path. ### Point `backup.sh` at the mount and schedule it Create `/etc/systemd/system/vigilar-backup.service`: ```ini [Unit] Description=Vigilar nightly backup After=vigilar.service [Service] Type=oneshot Environment=VIGILAR_BACKUP_DIR=/mnt/nas/vigilar-backups Environment=VIGILAR_BACKUP_RETENTION_DAYS=30 ExecStart=/opt/vigilar/scripts/backup.sh ``` Create `/etc/systemd/system/vigilar-backup.timer`: ```ini [Unit] Description=Run vigilar backup nightly [Timer] OnCalendar=daily Persistent=true [Install] WantedBy=timers.target ``` Enable it: ```bash sudo systemctl daemon-reload sudo systemctl enable --now vigilar-backup.timer sudo systemctl list-timers | grep vigilar ``` The first run happens on the next daily rollover. To test it now: ```bash sudo systemctl start vigilar-backup.service ls -lh /mnt/nas/vigilar-backups/ ``` You should see a file like `vigilar-backup-20260405-030000.tar.gz`. ## Troubleshooting ### The web UI will not load - `sudo systemctl status vigilar` — is it running? - `journalctl -u vigilar -e` — what does the last error say? - Is port 49735 blocked by a firewall? `sudo ss -tlnp | grep 49735` should show the Vigilar process listening. ### A camera will not connect - Can you open the RTSP URL in VLC from your laptop? If not, it is a camera/network problem, not a Vigilar problem. - Double-check username/password — many cameras have a separate RTSP credential. - Some cameras need a specific stream path (e.g. `/stream1`). See the [Camera Hardware Guide](camera-hardware-guide.md). ### No push notifications on my phone - Did you open the PWA from your home screen, not from the browser tab? - Check the browser's notification permission for the site. - Safari on iOS only supports web push from installed PWAs, not from a tab. ### Service keeps restarting - `journalctl -u vigilar -n 100` will show the last 100 log lines. - A common cause is an invalid `vigilar.toml`. Run `vigilar config validate` to check. ### I forgot my PIN ```bash sudo -u vigilar /opt/vigilar/venv/bin/vigilar config set-pin ``` ### Motion detection is too sensitive See the `[camera]` and motion-related sections in the [Operator Guide](operator-guide.md) for tuning. ## Where to go next - [Operator Guide](operator-guide.md) — config reference, backups, upgrades, security hardening. - [Architecture Overview](architecture/overview.md) — how Vigilar is put together, if you are curious. ``` **Important:** every CLI command (`vigilar config set-pin`, `vigilar config validate`) must be verified against `vigilar/cli/` before you write it. If a command does not exist, remove that part of the doc or reword it around what does exist. - [ ] **Step 3: Verify CLI commands exist** Use Grep to check: ``` Grep pattern: set-pin path: vigilar/cli/ output_mode: content Grep pattern: set-password path: vigilar/cli/ output_mode: content Grep pattern: validate path: vigilar/cli/ output_mode: content ``` If `set-pin` is not found, the "I forgot my PIN" and Step 4 sections must be rewritten to match what actually exists. - [ ] **Step 4: Verify file paths referenced** ```bash ls scripts/install.sh scripts/backup.sh config/vigilar.toml ``` - [ ] **Step 5: Commit** ```bash git add docs/home-user-guide.md git commit -m "docs: add home user setup guide" ``` --- ## Task 16: Operator guide **Files:** - Create: `docs/operator-guide.md` - [ ] **Step 1: Gather grounding facts** Read, in full: - `config/vigilar.toml` — note every `[section]` and every key. - `vigilar/cli/main.py` — note every top-level Click command. - `vigilar/cli/cmd_config.py` — note every `config` subcommand. - `vigilar/cli/cmd_start.py` — note the `start` command's behavior. - `scripts/install.sh` - `scripts/backup.sh` - `scripts/gen_cert.sh` - `scripts/gen_vapid_keys.sh` - `scripts/setup_nut.sh` - `scripts/uninstall.sh` - `systemd/vigilar.service` - `systemd/vigilar-mosquitto.conf` For each TOML section, write a one-line purpose. For each CLI command, write a one-line purpose. - [ ] **Step 2: Find health endpoints** ``` Grep pattern: @.*route path: vigilar/web/blueprints/system.py output_mode: content ``` Also check `vigilar/health/` for any HTTP-facing code. Note any `/health`, `/status`, `/metrics`, etc. endpoints that actually exist. - [ ] **Step 3: Create `docs/operator-guide.md`** Write the file with these sections, in this order. Target length 2500–4000 words. Be concrete — every key gets a real description, every command gets a real usage line. ```markdown # Vigilar Operator Guide This guide is a reference for running Vigilar on a server you administer. It assumes comfort with Linux, systemd, and the CLI. For first-time home setup instead, see the [Home User Guide](home-user-guide.md). ## Layout on disk | Path | Purpose | |----------------------------------------|----------------------------------| | `/opt/vigilar/` | Source tree and Python venv. | | `/opt/vigilar/venv/bin/vigilar` | CLI entry point. | | `/etc/vigilar/vigilar.toml` | Main configuration file. | | `/etc/vigilar/certs/` | TLS cert and key (if enabled). | | `/etc/vigilar/secrets/storage.key` | AES-256 key for recordings. | | `/etc/vigilar/secrets/vapid_*.pem` | VAPID keys for web push. | | `/var/vigilar/data/vigilar.db` | SQLite database (WAL mode). | | `/var/vigilar/recordings/` | Encrypted .vge recording files. | | `/var/vigilar/hls/` | Live HLS segments (rolling). | | `/var/vigilar/backups/` | Default backup destination. | ## Installation `scripts/install.sh` is the blessed install path. It detects the package manager (apt or pacman), installs system dependencies, creates a `vigilar` system user, builds a virtualenv at `/opt/vigilar/venv`, installs the Python package, generates VAPID keys if missing, and drops the systemd unit and Mosquitto broker config into place. ### System dependencies On Debian/Ubuntu: `ffmpeg mosquitto python3 python3-venv python3-pip nut-client`. On Arch: `ffmpeg mosquitto python python-virtualenv nut`. ### Systemd unit `systemd/vigilar.service` runs `vigilar start` as the `vigilar` user. Mosquitto runs as a separate unit, configured by `systemd/vigilar-mosquitto.conf` to bind to loopback only. ## Configuration reference (`/etc/vigilar/vigilar.toml`) ### `[system]` - `name` (default: `"Vigilar Home Security"`) — display name shown in the web UI. Cosmetic. - `timezone` (default: `"America/New_York"`) — used for timestamps in logs and the UI. Set this to your local timezone. - `data_dir` (default: `"/var/vigilar/data"`) — SQLite database location. Change only if you are redirecting state to a different disk. - `recordings_dir` (default: `"/var/vigilar/recordings"`) — where encrypted recordings live. May be pointed at a larger disk. - `hls_dir` (default: `"/var/vigilar/hls"`) — live HLS segments. Consider tmpfs for reduced SSD wear. - `log_level` (default: `"INFO"`) — one of `DEBUG`, `INFO`, `WARNING`, `ERROR`. ### `[mqtt]` - `host` (default: `"127.0.0.1"`) - `port` (default: `1883`) Leave as-is unless you are running an external broker. ### `[web]` - `host` (default: `"0.0.0.0"`) — bind address. Leave as-is for LAN access; set to `127.0.0.1` to expose only via a reverse proxy. - `port` (default: `49735`) — HTTP port. - `tls_cert`, `tls_key` — PEM paths for HTTPS. Generate with `scripts/gen_cert.sh`. - `username` (default: `"admin"`) — admin username. - `password_hash` — set via `vigilar config set-password`. ## CLI reference The CLI is `vigilar`, installed at `/opt/vigilar/venv/bin/vigilar`. Run commands as the `vigilar` user: ```bash sudo -u vigilar /opt/vigilar/venv/bin/vigilar ``` ### `vigilar start` Usage: ```bash sudo -u vigilar /opt/vigilar/venv/bin/vigilar start ``` Normally you run this via systemd (`systemctl start vigilar`) rather than directly. ### `vigilar config ` ## Secrets and security ### `/etc/vigilar/secrets/` layout - `storage.key` — AES-256 key used to encrypt `.vge` recording files. **If you lose this file, every existing recording becomes unrecoverable.** Include it in your backups (`scripts/backup.sh` does this by default because it tars `/etc/vigilar`). - `vapid_private.pem`, `vapid_public.pem` — VAPID keys for web push notifications. Generated by `scripts/gen_vapid_keys.sh`. - Any other files in `secrets/` discovered at runtime. File permissions should be `0600 root:root` or `0600 vigilar:vigilar` for anything containing a key. `install.sh` sets these. ### Arm/disarm PIN ```bash sudo -u vigilar /opt/vigilar/venv/bin/vigilar config set-pin ``` PINs are stored as hashes. Comparisons are constant-time. ### Admin password ```bash sudo -u vigilar /opt/vigilar/venv/bin/vigilar config set-password ``` ### TLS ```bash sudo ./scripts/gen_cert.sh ``` Then set `[web] tls_cert` and `[web] tls_key` in the TOML and restart Vigilar. ### Firewall stance Vigilar is LAN-only by default — the Mosquitto broker binds to `127.0.0.1` and the web server binds to `0.0.0.0` on port 49735. If you need remote access, use the `[remote]` tunnel feature rather than exposing port 49735 to the internet. ## UPS / NUT integration `scripts/setup_nut.sh` configures a local NUT server for a USB-connected UPS. After running it, set `[ups] enabled = true` and adjust: - `nut_host`, `nut_port`, `ups_name` — NUT connection. - `poll_interval_s` — how often Vigilar polls the UPS. - `low_battery_threshold_pct` — triggers a warning alert. - `critical_runtime_threshold_s` — triggers a graceful shutdown. - `shutdown_delay_s` — seconds to wait before issuing `shutdown -h`. ## Backups `scripts/backup.sh` captures: - The SQLite database (via `sqlite3 .backup` for a consistent snapshot if `sqlite3` is installed, otherwise a raw file copy). - Any WAL/SHM sidecars next to the DB. - The entire `/etc/vigilar/` directory (config + certs + secrets). It does **not** back up recordings under `/var/vigilar/recordings/`. ### Environment variables - `VIGILAR_BACKUP_DIR` (default: `/var/vigilar/backups`) — destination. Set this to a NAS mount if you want off-box backups. - `VIGILAR_BACKUP_RETENTION_DAYS` (default: `30`) — older archives are pruned. ### Suggested systemd timer Create `/etc/systemd/system/vigilar-backup.service`: ```ini [Unit] Description=Vigilar nightly backup After=vigilar.service [Service] Type=oneshot Environment=VIGILAR_BACKUP_DIR=/var/vigilar/backups Environment=VIGILAR_BACKUP_RETENTION_DAYS=30 ExecStart=/opt/vigilar/scripts/backup.sh ``` Create `/etc/systemd/system/vigilar-backup.timer`: ```ini [Unit] Description=Run vigilar backup nightly [Timer] OnCalendar=daily Persistent=true [Install] WantedBy=timers.target ``` Enable: ```bash sudo systemctl daemon-reload sudo systemctl enable --now vigilar-backup.timer ``` ### Restore procedure ```bash sudo systemctl stop vigilar sudo tar -C / -xzpf /path/to/vigilar-backup-YYYYMMDD-HHMMSS.tar.gz sudo chown -R vigilar:vigilar /var/vigilar/data /etc/vigilar sudo systemctl start vigilar ``` ## Upgrades ```bash cd /opt/vigilar sudo -u vigilar git pull sudo -u vigilar /opt/vigilar/venv/bin/pip install -e . sudo systemctl restart vigilar ``` ### Rolling back ```bash sudo systemctl stop vigilar # Check out the previous tag or commit sudo -u vigilar git -C /opt/vigilar checkout sudo -u vigilar /opt/vigilar/venv/bin/pip install -e /opt/vigilar # Restore the pre-upgrade backup if schema changed sudo systemctl start vigilar ``` ## Logs and health ### Logs ```bash sudo journalctl -u vigilar -f # follow sudo journalctl -u vigilar -n 200 # last 200 lines sudo journalctl -u vigilar --since "1 hour ago" ``` Raise verbosity by setting `[system] log_level = "DEBUG"` in the TOML and restarting. ### Health endpoints - `GET /system/health` — JSON summary of subsystem status. - `GET /system/logs` — recent log entries from the web UI. If none exist, write: "There are no dedicated health HTTP endpoints at time of writing. Use `journalctl -u vigilar` and the system status page in the web UI." ## Remote access `[remote] enabled = true` turns on the tunnel-based remote viewing feature. It serves a downscaled HLS stream shaped to `upload_bandwidth_mbps` over a tunnel on `tunnel_ip`. This is **not** a cloud service — you are still the one running the tunnel endpoint. Key knobs: - `upload_bandwidth_mbps` — your upload ceiling. - `remote_hls_resolution`, `remote_hls_fps`, `remote_hls_bitrate_kbps` — shape the remote stream. - `max_remote_viewers` — concurrency cap. ## Troubleshooting ### Service is in a crash loop `journalctl -u vigilar -n 200` and look for the first `ERROR`. Common causes: - Invalid TOML — run `vigilar config validate` (if that command exists). - Missing secrets — `storage.key` or `vapid_private.pem` not readable by the `vigilar` user. - Port 49735 already in use by something else. ### Mosquitto broker will not start - `sudo systemctl status mosquitto` - Check `/etc/mosquitto/conf.d/` for a conflicting config file on the same port. - Run `mosquitto -v -c /etc/mosquitto/conf.d/vigilar.conf` by hand to see the error directly. ### A camera worker is thrashing - Check `journalctl -u vigilar | grep `. - Verify the RTSP URL with `ffprobe `. - Reduce expected FPS in the camera's entry if the stream is unstable. ### Disk is full / `free_space_floor_gb` triggered The health pruner is supposed to delete the oldest recordings when free space drops below `[storage] free_space_floor_gb`. If it is not keeping up: - Lower `[storage] max_disk_usage_gb`. - Move `recordings_dir` to a larger disk. - Check `journalctl -u vigilar | grep -i prun` for pruner errors. ### HLS grid is stalling - The mini PC's CPU may be saturated. Check `top`. - Drop the grid to fewer cameras or lower resolution. - Single-camera view uses MJPEG, which is cheaper to serve than HLS. ``` Every TOML key, every CLI command, every endpoint, every file path in this document must be backed by something you actually read in Step 1 or Step 2. Delete any bullet you cannot verify. - [ ] **Step 4: Verify CLI commands exist** ``` Grep pattern: @.*\.command path: vigilar/cli/ output_mode: content ``` Every `vigilar ` mentioned in the doc must be present here. Remove any that are not. - [ ] **Step 5: Verify TOML coverage** Every `[section]` in `config/vigilar.toml` must have a subsection in "Configuration reference". Every subsection in the doc must correspond to a real `[section]`. - [ ] **Step 6: Commit** ```bash git add docs/operator-guide.md git commit -m "docs: add operator guide" ``` --- ## Task 17: Top-level README **Files:** - Create: `README.md` - [ ] **Step 1: Confirm no existing README** ```bash ls README.md 2>/dev/null || echo "no existing README — good" ``` - [ ] **Step 2: Confirm all linked targets exist** Every relative link in this README will point to a file created in Tasks 1–16. Verify: ```bash ls docs/home-user-guide.md docs/operator-guide.md \ docs/architecture/overview.md docs/architecture/conventions.md \ docs/camera-hardware-guide.md ls docs/architecture/subsystems/*.md | wc -l # should be 12 ``` - [ ] **Step 3: Create `README.md`** ```markdown # Vigilar > DIY offline-first home security. Your cameras, your house, your data. Vigilar runs on a small mini PC in your home, records from your IP cameras, detects motion, and pushes notifications to your phone — with no cloud, no account, and no external service in the critical path. ![Vigilar web UI grid view](docs/images/grid.png) ## Why Vigilar - **Offline-first.** No cloud dependency. No external calls in the hot path. - **Multi-camera live view.** HLS grid for bandwidth efficiency, MJPEG single view for low-latency focus. - **On-device motion detection.** OpenCV MOG2 with a 5-second pre-motion ring buffer so you never miss the moment leading up to a trigger. - **Event timeline, highlight reels, timelapses.** Visitor, pet, and wildlife tracking as separate views. - **Phone push notifications.** PWA + VAPID web push. No Firebase, no Google Cloud Messaging. - **Encrypted recordings.** AES-256-GCM `.vge` files. Key stays on your box. - **UPS aware.** NUT integration, graceful shutdown on critical battery. - **Runs on a cheap mini PC.** 4 GB RAM, 128 GB SSD, any modern x86_64 box. ## Quick paths | I want to… | Start here | |----------------------------------------------|--------------------------------------------------------------| | Set it up at home on a mini PC | [Home User Guide](docs/home-user-guide.md) | | Run it as a self-hoster / sysadmin | [Operator Guide](docs/operator-guide.md) | | Understand how it works internally | [Architecture Overview](docs/architecture/overview.md) | | Pick cameras that work well with it | [Camera Hardware Guide](docs/camera-hardware-guide.md) | ## 60-second overview ``` [IP cameras] ──RTSP──▶ [mini PC running Vigilar] ──LAN──▶ [phone / browser] │ └── optional nightly ──▶ [NAS] ``` - **Language:** Python 3.11+ - **Web:** Flask + Bootstrap 5 dark theme + `hls.js` - **Storage:** SQLite (WAL) via SQLAlchemy Core - **Bus:** Local Mosquitto on `127.0.0.1:1883` - **Video:** OpenCV (capture + motion), FFmpeg subprocess (recording, HLS) ## Status **Alpha.** Works on the author's hardware. Expect rough edges and breaking changes. Not recommended for anyone who cannot read a stack trace. ## Installation (TL;DR) ```bash git clone vigilar cd vigilar sudo ./scripts/install.sh sudo systemctl enable --now vigilar ``` Then open `http://:49735` on your LAN. For the full walkthrough (OS install, camera setup, PIN, push notifications, NAS backup), see the [Home User Guide](docs/home-user-guide.md). For configuration, CLI, secrets, backups, and upgrades, see the [Operator Guide](docs/operator-guide.md). ## Documentation - **User guides** - [Home User Guide](docs/home-user-guide.md) — mini PC to working cameras, step by step. - [Operator Guide](docs/operator-guide.md) — configuration, CLI, backups, security, upgrades. - [Camera Hardware Guide](docs/camera-hardware-guide.md) — picking and wiring up cameras. - **Architecture** - [Overview](docs/architecture/overview.md) — process model, MQTT bus, data flow, storage. - [Conventions](docs/architecture/conventions.md) — coding rules for contributors. - [Subsystems](docs/architecture/subsystems/) — one short reference per subsystem: `camera`, `detection`, `events`, `alerts`, `sensors`, `ups`, `storage`, `highlights`, `presence`, `pets`, `health`, `web`. ## License Vigilar is distributed under the **GNU General Public License v3.0**. See `LICENSE` for the full text. (If the `LICENSE` file is not yet in the tree, it will be added in a follow-up.) ## Contributing Issues and pull requests are welcome. Before sending a PR: - Read `CLAUDE.md` for the project's code conventions. - Run `ruff check vigilar/` and `pytest` — both must pass. - Keep commits small and focused. ``` - [ ] **Step 4: Verify every link in the README resolves** ```bash ls README.md docs/home-user-guide.md docs/operator-guide.md \ docs/architecture/overview.md docs/architecture/conventions.md \ docs/architecture/subsystems docs/camera-hardware-guide.md CLAUDE.md ``` All must exist. If any do not, that means an earlier task was skipped — go back and do it. - [ ] **Step 5: Commit** ```bash git add README.md git commit -m "docs: add top-level README" ``` --- ## Task 18: Final verification pass **Files:** - Modify: any of the above, if issues are found - [ ] **Step 1: Link check** ``` Grep pattern: \]\( path: README.md output_mode: content Grep pattern: \]\( path: docs/ output_mode: content ``` For each relative link discovered, confirm the target exists with `ls `. Fix any broken links inline. - [ ] **Step 2: Command check** For every shell command in `docs/home-user-guide.md` and `docs/operator-guide.md`, confirm either: - the script exists (`ls scripts/.sh`), or - the `vigilar` subcommand exists (Grep in `vigilar/cli/`), or - it is a standard Linux command (`systemctl`, `journalctl`, `git`, `pip`, `ls`, etc.). Remove or rewrite anything that fails. - [ ] **Step 3: TOML coverage check** Open `config/vigilar.toml` and `docs/operator-guide.md` side by side. Every `[section]` in the TOML must appear in the operator guide's Configuration Reference. Every section in the guide must appear in the TOML. Fix any asymmetry. - [ ] **Step 4: Subsystem coverage check** ```bash ls vigilar/ ls docs/architecture/subsystems/ ``` Every subsystem directory under `vigilar/` that is listed in the spec (`camera`, `detection`, `events`, `alerts`, `sensors`, `ups`, `storage`, `highlights`, `presence`, `pets`, `health`, `web`) must have a matching `.md` file in `docs/architecture/subsystems/`. If a subsystem directory exists in `vigilar/` that is NOT in the list (because something was added to the codebase since the spec was written), note it but do not necessarily document it — flag it at the end. - [ ] **Step 5: Read-through** Read every new `.md` file top to bottom in this order: README, home-user-guide, operator-guide, architecture/overview, architecture/conventions, then each subsystem doc alphabetically. Check for: - Tone drift (the README should not shift register halfway through). - Terminology drift (if one doc says "motion event" and another says "movement trigger" for the same thing, unify). - Stale references (e.g. a doc saying "see the X section" where X was removed). Fix inline. - [ ] **Step 6: Final commit** If Steps 1–5 surfaced any fixes: ```bash git add -A git commit -m "docs: final verification pass fixes" ``` If no fixes were needed, skip the commit and report "no issues found in final verification pass" in the task output. --- ## Self-review notes (for the plan author) - **Spec coverage:** Each deliverable in §§3-9 of the spec maps to a task: README → Task 17, home-user → Task 15, operator → Task 16, overview → Task 1, conventions → Task 2, 12 subsystems → Tasks 3–14. Verification checklist from spec §10 → Task 18. Out-of-scope items from spec §11 are explicitly excluded in the "Universal rules". - **Placeholder scan:** The plan contains `` placeholders **inside task output templates**. These are **instructions to the executor** ("replace this with the value you found"), not deferred work in the plan itself. Each template placeholder is accompanied by a specific grep or file to read that tells the executor how to fill it. This is intentional — the alternative would be to pre-populate architecture facts in the plan without ever reading the code, which would make the plan itself guess. - **Type/signature consistency:** N/A — docs, not code. - **Order:** Task 1 (overview) is first so the MQTT topic convention is established once and can be referenced consistently by subsystem docs. README is last so every link it makes already exists.