diff --git a/README.md b/README.md index e30a9ef..e76f649 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,71 @@ # Compass / NHS -A school service for Harry and Finn: **Compass** for the daily schedule and -notices, **Teams** for homework and assignments. It stores what is due per child, -puts overdue work first, and tells the family before work is late. +A school service for Harry and Finn: **Compass** gives the daily schedule, the +homework and the notices. It stores what is due per child, puts overdue work +first, and tells the family before work is late. -> Build spec: [`AGENTS.md`](AGENTS.md). This repository is at **Phase 8** (the -> Hub API). See §11 of the spec for the phase order. +> **Teams (FR-15) is DEFERRED.** No Teams section is scanned. Compass answers +> what is due and overdue. This is a known gap, not a defect. + +> Build spec: [`AGENTS.md`](AGENTS.md). This repository is at **Phase 9** +> (documentation). See §11 of the spec for the phase order. + +## Status — Phase 9 + +Phase 9 publishes the project documentation across five places. + +| Layer | Artifact | Location | +|---|---|---| +| Technical README | This file | the Gitea repo | +| Architecture source | [`docs/architecture.mmd`](docs/architecture.mmd) | repo | +| Rendered diagram | [`docs/diagrams/architecture.png`](docs/diagrams/architecture.png) | repo | +| Interactive map | [`docs/architecture-map.html`](docs/architecture-map.html) | | +| Human note | Obsidian `010 inbox/` on `.27` | filed by the sorter | +| AI context | [`docs/outline/`](docs/outline/) | Outline collection *Compass / NHS (School)* | +| Tasks | [`docs/vikunja-tasks.json`](docs/vikunja-tasks.json) | Vikunja project id 21 | + +The daily trigger is **06:00** in `Australia/Melbourne` (n8n). The briefs go out +on **Email and WhatsApp** through Apprise on `.35:8210`: one brief per boy, plus +one combined brief to Jo and Sam. Important school events go to the Home +Assistant Local Calendar `calendar.school`. The Hub reads items through the +read-only `POST /ask` contract. + +The accepted scraper is **patchright** (headless Playwright), not the +pi-browser-harness: the harness cannot pass the Cloudflare managed challenge on +the Compass login POST. The Compass parent login required **no** two-factor code +during the Phase 2 proof. + +## Architecture + +```mermaid +flowchart LR + N8N["n8n on .13
06:00 daily trigger"] + SAM["Sam
browser dashboard"] + CADDY["Caddy on .35
school.lab.audasmedia.com.au"] + SVC["Compass / NHS service
Go + SQLite :8792"] + PATCH["patchright scraper
headless Playwright"] + COMPASS["Compass NHS
read-only login + pages"] + OMNI["OmniRoute
importance classification"] + HA["Home Assistant calendar
calendar.school"] + APPRISE["Apprise on .35:8210
Email + WhatsApp"] + BOYS["Harry and Finn
per-boy morning brief"] + PARENTS["Jo and Sam
combined morning brief"] + HUB["Hub :8788
read-only /ask"] + + N8N -->|"scripts/morning-brief.sh"| SVC + SAM -->|HTTPS| CADDY + CADDY --> SVC + SVC -->|scan| PATCH + PATCH -->|"login + fetch pages"| COMPASS + SVC -.->|"important only"| OMNI + SVC -->|"create event"| HA + SVC -->|briefs| APPRISE + APPRISE -->|"email + WhatsApp"| BOYS + APPRISE -->|"email + WhatsApp"| PARENTS + HUB -->|"Bearer /ask"| SVC +``` + +*Interactive map:* ## Status — Phase 8 @@ -126,7 +186,7 @@ Built and deployed on `.13`: - Its own container and port **8792**, behind Caddy at `school.lab.audasmedia.com.au`. -Not built yet (later phases): Teams (FR-15) and the Phase 9 documentation set. +Not built yet: Teams (FR-15). It is deferred on purpose. The service itself does **not** scan the school; n8n triggers the scan. ### Fail loudly (FR-12) @@ -155,6 +215,7 @@ internal/web/ HTTP handlers, templates, static assets deploy/ Caddy snippet, the n8n workflow, deployment notes, verify script scripts/ Compass fetchers, the parse and morning-brief wrappers, the calendar push environment.d/ env example (the real file is gitignored) +docs/ phase notes, the architecture source and map, the Outline set, Vikunja tasks ``` ## Run diff --git a/docs/archify/architecture.architecture.json b/docs/archify/architecture.architecture.json new file mode 100644 index 0000000..0f5d5a3 --- /dev/null +++ b/docs/archify/architecture.architecture.json @@ -0,0 +1,326 @@ +{ + "schema_version": 1, + "diagram_type": "architecture", + "meta": { + "title": "Compass / NHS school service", + "output": "architecture-map.html", + "quality_profile": "showcase", + "viewBox": [ + 1180, + 610 + ], + "views": [ + { + "id": "morning-run", + "label": "Morning run", + "focus": [ + "n8n", + "service", + "patchright", + "compass", + "apprise", + "family" + ], + "note": "n8n runs the scan and the sends at 06:00 every day." + }, + { + "id": "read-path", + "label": "Read path", + "focus": [ + "sam", + "caddy", + "service", + "hub" + ], + "note": "Sam and the Hub both read the same Go service over HTTP." + }, + { + "id": "writes", + "label": "Writes outside the school", + "focus": [ + "service", + "ha", + "apprise", + "omni" + ], + "note": "The calendar write and the briefs are the only writes." + } + ] + }, + "components": [ + { + "id": "n8n", + "type": "external", + "label": "n8n (.13)", + "sublabel": "06:00 daily trigger", + "pos": [ + 40, + 200 + ], + "size": [ + 150, + 60 + ] + }, + { + "id": "sam", + "type": "external", + "label": "Sam", + "sublabel": "browser dashboard", + "pos": [ + 40, + 430 + ], + "size": [ + 150, + 60 + ] + }, + { + "id": "caddy", + "type": "security", + "label": "Caddy (.35)", + "sublabel": "session login", + "pos": [ + 250, + 315 + ], + "size": [ + 150, + 60 + ], + "tag": "school.lab" + }, + { + "id": "service", + "type": "backend", + "label": "Compass / NHS service", + "sublabel": "Go + SQLite :8792", + "pos": [ + 470, + 310 + ], + "size": [ + 165, + 70 + ], + "tag": "source of truth" + }, + { + "id": "patchright", + "type": "backend", + "label": "patchright scraper", + "sublabel": "headless Playwright", + "pos": [ + 700, + 315 + ], + "size": [ + 150, + 60 + ] + }, + { + "id": "compass", + "type": "external", + "label": "Compass NHS", + "sublabel": "read-only login + pages", + "pos": [ + 920, + 315 + ], + "size": [ + 160, + 60 + ] + }, + { + "id": "omni", + "type": "cloud", + "label": "OmniRoute", + "sublabel": "importance only", + "pos": [ + 470, + 120 + ], + "size": [ + 160, + 60 + ] + }, + { + "id": "ha", + "type": "cloud", + "label": "Home Assistant", + "sublabel": "calendar.school", + "pos": [ + 700, + 120 + ], + "size": [ + 160, + 60 + ] + }, + { + "id": "hub", + "type": "external", + "label": "Hub", + "sublabel": "read-only /ask :8788", + "pos": [ + 250, + 500 + ], + "size": [ + 150, + 60 + ] + }, + { + "id": "apprise", + "type": "cloud", + "label": "Apprise (.35)", + "sublabel": "Email + WhatsApp :8210", + "pos": [ + 470, + 500 + ], + "size": [ + 160, + 60 + ] + }, + { + "id": "family", + "type": "external", + "label": "Family briefs", + "sublabel": "per-boy + combined", + "pos": [ + 790, + 500 + ], + "size": [ + 160, + 60 + ] + } + ], + "boundaries": [ + { + "kind": "region", + "label": "Home lab - Docker on .13, Caddy and Apprise on .35", + "wraps": [ + "caddy", + "service", + "patchright", + "omni", + "ha", + "apprise", + "hub" + ] + } + ], + "connections": [ + { + "id": "n8n-to-service", + "from": "n8n", + "to": "service", + "label": "scripts/morning-brief.sh", + "variant": "dashed" + }, + { + "id": "sam-to-caddy", + "from": "sam", + "to": "caddy", + "label": "HTTPS", + "variant": "emphasis", + "fromSide": "top", + "toSide": "bottom" + }, + { + "id": "caddy-to-service", + "from": "caddy", + "to": "service" + }, + { + "id": "service-to-patchright", + "from": "service", + "to": "patchright", + "label": "scan", + "labelDy": 30 + }, + { + "id": "patchright-to-compass", + "from": "patchright", + "to": "compass", + "label": "login + fetch" + }, + { + "id": "service-to-omni", + "from": "service", + "to": "omni", + "label": "importance", + "variant": "dashed", + "fromSide": "top", + "toSide": "bottom" + }, + { + "id": "service-to-ha", + "from": "service", + "to": "ha", + "label": "create event", + "fromSide": "right", + "toSide": "left" + }, + { + "id": "service-to-apprise", + "from": "service", + "to": "apprise", + "label": "briefs", + "fromSide": "bottom", + "toSide": "top" + }, + { + "id": "apprise-to-family", + "from": "apprise", + "to": "family", + "label": "email + WhatsApp" + }, + { + "id": "hub-to-service", + "from": "hub", + "to": "service", + "label": "Bearer /ask", + "variant": "dashed" + } + ], + "cards": [ + { + "dot": "cyan", + "title": "Compass / NHS service", + "items": [ + "Go and SQLite are the source of truth", + "Go stores items, scans and the send log", + "Serves the dashboard, the Hub API and the briefs" + ] + }, + { + "dot": "amber", + "title": "Read-only against the school", + "items": [ + "patchright logs in and fetches pages only", + "The Cloudflare login POST needs a real browser", + "No post, message or submit ever runs" + ] + }, + { + "dot": "rose", + "title": "Deferred: Teams (FR-15)", + "items": [ + "Teams homework is DEFERRED and not built", + "Compass answers what is due and overdue", + "No Teams section is scanned" + ] + } + ] +} \ No newline at end of file diff --git a/docs/architecture-map.html b/docs/architecture-map.html new file mode 100644 index 0000000..e174a08 --- /dev/null +++ b/docs/architecture-map.html @@ -0,0 +1,13698 @@ + + + + + + + Compass / NHS school service Diagram + + + + + + + + + + + +
+ +
+
+
+

Compass / NHS school service

+
+
+ + + + + + +
+ + Compass / NHS school service + A architecture diagram generated by Archify. + + + + + + + + + + + + + + + + + + + + + + + + + Home lab - Docker on .13, Caddy and Apprise on .35 + + + + + + + + + + + + + + + + n8n (.13) · 06:00 daily trigger · Architecture component + + + + n8n (.13) + 06:00 daily trigger + + + + Sam · browser dashboard · Architecture component + + + + Sam + browser dashboard + + + + Caddy (.35) · session login · Home lab - Docker on .13, Caddy and Apprise on .35 · school.lab + + + + Caddy (.35) + session login + school.lab + + + + Compass / NHS service · Go + SQLite :8792 · Home lab - Docker on .13, Caddy and Apprise on .35 · source of truth + + + + Compass / NHS service + Go + SQLite :8792 + source of truth + + + + patchright scraper · headless Playwright · Home lab - Docker on .13, Caddy and Apprise on .35 + + + + patchright scraper + headless Playwright + + + + Compass NHS · read-only login + pages · Architecture component + + + + Compass NHS + read-only login + pages + + + + OmniRoute · importance only · Home lab - Docker on .13, Caddy and Apprise on .35 + + + + OmniRoute + importance only + + + + Home Assistant · calendar.school · Home lab - Docker on .13, Caddy and Apprise on .35 + + + + Home Assistant + calendar.school + + + + Hub · read-only /ask :8788 · Home lab - Docker on .13, Caddy and Apprise on .35 + + + + Hub + read-only /ask :8788 + + + + Apprise (.35) · Email + WhatsApp :8210 · Home lab - Docker on .13, Caddy and Apprise on .35 + + + + Apprise (.35) + Email + WhatsApp :8210 + + + + Family briefs · per-boy + combined · Architecture component + + + + Family briefs + per-boy + combined + + + + + + scripts/morning-brief.sh + + + + HTTPS + + + + + scan + + + + login + fetch + + + + importance + + + + create event + + + + briefs + + + + email + WhatsApp + + + + Bearer /ask + + + + + +

+ + + + + + + + +
+ + +
+
+
+
+

Compass / NHS service

+
+
    +
  • • Go and SQLite are the source of truth
  • +
  • • Go stores items, scans and the send log
  • +
  • • Serves the dashboard, the Hub API and the briefs
  • +
+
+ +
+
+
+

Read-only against the school

+
+
    +
  • • patchright logs in and fetches pages only
  • +
  • • The Cloudflare login POST needs a real browser
  • +
  • • No post, message or submit ever runs
  • +
+
+ +
+
+
+

Deferred: Teams (FR-15)

+
+
    +
  • • Teams homework is DEFERRED and not built
  • +
  • • Compass answers what is due and overdue
  • +
  • • No Teams section is scanned
  • +
+
+
+ +
+ + + + diff --git a/docs/architecture.mmd b/docs/architecture.mmd new file mode 100644 index 0000000..ca47681 --- /dev/null +++ b/docs/architecture.mmd @@ -0,0 +1,30 @@ +flowchart LR + N8N["n8n on .13
06:00 daily trigger"] + SAM["Sam
browser dashboard"] + CADDY["Caddy on .35
school.lab.audasmedia.com.au"] + SVC["Compass / NHS service
Go + SQLite :8792"] + PATCH["patchright scraper
headless Playwright"] + COMPASS["Compass NHS
read-only login + pages"] + OMNI["OmniRoute
importance classification"] + HA["Home Assistant calendar
calendar.school"] + APPRISE["Apprise on .35:8210
Email + WhatsApp"] + BOYS["Harry and Finn
per-boy morning brief"] + PARENTS["Jo and Sam
combined morning brief"] + HUB["Hub :8788
read-only /ask"] + TEAMS["Teams homework
FR-15 DEFERRED"] + + N8N -->|"scripts/morning-brief.sh"| SVC + SAM -->|HTTPS| CADDY + CADDY --> SVC + SVC -->|scan| PATCH + PATCH -->|"login + fetch pages"| COMPASS + SVC -.->|"important only"| OMNI + SVC -->|"create event"| HA + SVC -->|briefs| APPRISE + APPRISE -->|"email + WhatsApp"| BOYS + APPRISE -->|"email + WhatsApp"| PARENTS + HUB -->|"Bearer /ask"| SVC + TEAMS -.->|"not built"| SVC + + click SVC "https://maps.lab.audasmedia.com.au/compass_teams_nhs/docs/" "View Archify map" + click COMPASS "https://maps.lab.audasmedia.com.au/compass_teams_nhs/docs/" "View Archify map" diff --git a/docs/diagrams/architecture.png b/docs/diagrams/architecture.png new file mode 100644 index 0000000..40c38e2 Binary files /dev/null and b/docs/diagrams/architecture.png differ diff --git a/docs/outline/00-index.md b/docs/outline/00-index.md new file mode 100644 index 0000000..8a31d14 --- /dev/null +++ b/docs/outline/00-index.md @@ -0,0 +1,43 @@ +# Compass NHS — AI Document Index + +> **Target**: AI Agents +> **Writing Style**: ASD-STE100 Simplified Technical English. + +## Document Catalog (Inject Specific Document) + +Read this index first. Inject the exact file you need. Do not search every file. + +| Document Title | Path | Scope | +| :--- | :--- | :--- | +| **System Overview** | `docs/outline/01-system-overview.md` | States the purpose, the main parts and the live services. | +| **Configuration and Environment** | `docs/outline/02-config-and-env.md` | Lists variables, paths, ports and secrets. | +| **Code Map** | `docs/outline/03-code-map.md` | Shows the folder layout, entry points and dependencies. | +| **Agent Runbook** | `docs/outline/04-agent-runbook.md` | Gives the commands to build, test, deploy and restart. | + +## System Summary + +This project reads the school systems for two children, Harry and Finn. It stores +what is due, finds overdue work, writes important events to the family calendar, +and sends morning briefs. + +The service is one Go binary with SQLite. It runs in its own container on `.13`, +port 8792, behind Caddy on `.35` at `school.lab.audasmedia.com.au`. + +**Teams (FR-15) is DEFERRED.** The project does not scan Teams. Compass answers +what is due and overdue. + +## Core Configuration + +- **Env File**: `environment.d/school-service.env` (gitignored) +- **Env Example**: `environment.d/school-service.env.example` +- **Config Code**: `internal/config/` +- **Active Ports**: `8792` (service), `8210` (Apprise on `.35`), `8788` (Hub) +- **Database**: SQLite in the Docker volume `compass-teams-nhs-data` + +## Agent Restart Steps + +1. Read the Document Catalog above. Inject only the required files. +2. Check the checkout: `git status`. +3. Read the recent history: `git log -n 5 --oneline`. +4. Run the checks: `make test` and `make verify-phase8`. +5. Read the current state: `curl -s http://127.0.0.1:8792/health`. diff --git a/docs/outline/01-system-overview.md b/docs/outline/01-system-overview.md new file mode 100644 index 0000000..9382295 --- /dev/null +++ b/docs/outline/01-system-overview.md @@ -0,0 +1,70 @@ +# Compass NHS — System Overview + +## Purpose + +The service reads the school systems for Harry and Finn. It answers three +questions: what is due, what is overdue, and what important events are coming. + +Sam sees both children on one page. The family receives the briefs before work is +late. Nobody logs into Compass each morning. + +## Users + +| User | Role | +| :--- | :--- | +| Sam | Owner. Uses the dashboard. | +| Jo, Harry, Finn | Read-only. Receive the briefs. | + +## Main Parts + +| Part | Function | +| :--- | :--- | +| Go service | Serves the dashboard and the Hub API. Holds the state. | +| SQLite store | Keeps children, items, scans, notifications and calendar pushes. | +| patchright scraper | Logs into Compass and fetches the pages. | +| n8n workflow | Runs the scan and the sends at 06:00 daily. | +| Apprise | Sends the briefs on Email and WhatsApp. | +| Home Assistant | Holds the calendar `calendar.school`. | +| Hub | Calls the read-only `/ask` endpoint. | + +## The Data Flow + +1. n8n triggers `scripts/morning-brief.sh` at 06:00. +2. The script scans Compass for each child and stores the raw pages. +3. The parser turns the pages into items: tasks, messages and events. +4. The classifier marks important events. OmniRoute helps only here. +5. The brief builder makes one brief per boy and one combined brief. +6. Apprise sends the briefs on Email and WhatsApp. +7. The calendar sync writes today's and future important events to Home Assistant. + +## Read-Only Rule + +The service never writes to the school. It logs in and fetches pages only. The +scraper never posts, messages or submits anything. + +## Fail Loudly + +A missing scan or a failed scan is never an empty day. The dashboard, the briefs +and `/ask` each state the real scan state. A failed scan says so in plain words. + +## Teams + +**Teams (FR-15) is DEFERRED.** The project does not scan Teams. This is a known +gap, not a defect. Compass gives the schedule, the tasks and the due dates. + +## Live Targets + +| System | Address | +| :--- | :--- | +| Dashboard | `https://school.lab.audasmedia.com.au` | +| Service container | `.13`, port 8792 | +| Health | `http://127.0.0.1:8792/health` | +| Apprise API | `.35`, port 8210 | +| Hub inbound | `http://192.168.20.13:8788/ask` | + +## Repository + +- Git: `ssh://git@gitea.lab.audasmedia.com.au:2222/sam/compass_teams_nhs.git` +- Branch: `main` +- Spec: `AGENTS.md` +- Interactive map: `https://maps.lab.audasmedia.com.au/compass_teams_nhs/docs/` diff --git a/docs/outline/02-config-and-env.md b/docs/outline/02-config-and-env.md new file mode 100644 index 0000000..c810f05 --- /dev/null +++ b/docs/outline/02-config-and-env.md @@ -0,0 +1,101 @@ +# Compass NHS — Configuration and Environment + +## Rules for Agents + +1. Never write a credential, token, cookie or session value into a document. +2. Keep every secret in `environment.d/school-service.env`. The file is gitignored. +3. Copy the shape from `environment.d/school-service.env.example`. Never copy a value. +4. Docker Compose interpolates `$VAR` inside `env_file`. Escape each bcrypt `$` as `$$`. + +## Paths + +| Item | Path | +| :--- | :--- | +| Real env file | `environment.d/school-service.env` | +| Env example | `environment.d/school-service.env.example` | +| Config code | `internal/config/` | +| SQLite schema | `internal/store/migrations/` | +| Service binary | `/app/school` inside the container | +| Database | `/data/school.db` inside the container | +| Docker volume | `compass-teams-nhs-data` | + +## Ports + +| Port | System | Host | +| :--- | :--- | :--- | +| 8792 | This service | `.13` | +| 8210 | Apprise API | `.35` | +| 8788 | Hub inbound API | `.13` | +| 8123 | Home Assistant | `.30` | +| 5678 | n8n | `.13` | + +Caddy on `.35` publishes `school.lab.audasmedia.com.au` and proxies to +`192.168.20.13:8792`. + +## Variables + +### Service + +| Variable | Meaning | +| :--- | :--- | +| `SCHOOL_ADDR` | Listen address. Default `:8792`. | +| `SCHOOL_DB_PATH` | SQLite path. Container value `/data/school.db`. | +| `SCHOOL_SESSION_SECRET` | Signs the session cookie. Secret. Minimum 16 characters. | +| `SCHOOL_ADMIN_USERNAME` | The single dashboard login. | +| `SCHOOL_ADMIN_PASSWORD_HASH` | Bcrypt hash. Prefer this over the plaintext. | +| `SCHOOL_ADMIN_PASSWORD` | Plaintext fallback. | +| `SCHOOL_COOKIE_SECURE` | `true` behind HTTPS. | +| `SCHOOL_SESSION_TTL` | Session life. Default `12h`. | +| `SCHOOL_TIMEZONE` | Family timezone for due dates. | +| `SCHOOL_LOG_LEVEL` | Log level. | +| `SCHOOL_APP_ENV` | Environment name. | + +### Home Assistant Calendar + +| Variable | Meaning | +| :--- | :--- | +| `SCHOOL_HA_URL` | Home Assistant base URL. | +| `SCHOOL_HA_TOKEN` | Long-lived access token. Secret. | +| `SCHOOL_HA_CALENDAR_ENTITY` | Calendar entity. Default `calendar.school`. | +| `SCHOOL_HA_INSECURE` | Skips TLS verification. Use only for the internal `.lab` URL. | +| `SCHOOL_HA_TIMEOUT` | Per-request timeout. Default `20s`. | + +### Apprise + +| Variable | Meaning | +| :--- | :--- | +| `SCHOOL_APPRISE_URL` | Apprise API base. Default `http://192.168.20.35:8210`. | +| `SCHOOL_APPRISE_KEY` | Stored configuration key. Secret. | +| `SCHOOL_APPRISE_TOKEN` | Optional bearer token. | +| `SCHOOL_APPRISE_TIMEOUT` | Per-request timeout. Default `15s`. | +| `SCHOOL_APPRISE_CHANNELS` | `email,whatsapp`. | +| `SCHOOL_APPRISE_TAGS` | Recipients as `Name=tag-base`. | +| `SCHOOL_APPRISE_PARENTS` | Who receives the combined brief. `Jo,Sam`. | +| `SCHOOL_NOTIFY_TOKEN` | Bearer token for `POST /notify/morning`. Secret. | + +### Hub API + +| Variable | Meaning | +| :--- | :--- | +| `SCHOOL_HUB_TOKEN` | Shared bearer token. Secret. Empty turns the Hub endpoints off with `503`. | + +The Hub holds the same token as `HUB_PROJECT_COMPASS_NHS_TOKEN`. The Hub reaches +this service through `HUB_PROJECTS=compass_nhs=http://192.168.20.13:8792`. + +## Secrets Outside This Repo + +| Secret | Location | +| :--- | :--- | +| Apprise destinations (address, number, SMTP) | Apprise stored configuration on `.35` | +| Hub token copy | `~/.config/environment.d/10-secrets.conf` on `.13` | +| Compass account and password | Paperclip secret or the env file | +| n8n trigger SSH key | `/home/sam/.ssh/id_ed25519_school_scan` | + +The Apprise destinations stay outside this repo on purpose. This service names a +tag such as `harry_email` only. + +## Environment Access for an Agent + +The service does not scan the school. n8n runs `scripts/morning-brief.sh` +through a restricted SSH key. The key has a forced command and `restrict`. It +cannot open a shell. diff --git a/docs/outline/03-code-map.md b/docs/outline/03-code-map.md new file mode 100644 index 0000000..6752983 --- /dev/null +++ b/docs/outline/03-code-map.md @@ -0,0 +1,82 @@ +# Compass NHS — Code Map + +## Module + +`gitea.lab.audasmedia.com.au/sam/compass_teams_nhs` + +Language: Go 1.25. Database: SQLite through `modernc.org/sqlite`. Passwords use +`golang.org/x/crypto/bcrypt`. There is no other direct dependency. + +## Entry Point + +`cmd/school/main.go` holds the entry point. The first argument selects a +subcommand. With no argument it starts the HTTP service. + +| Subcommand | Function | +| :--- | :--- | +| `run` (default) | Starts the HTTP service, migrates the schema and seeds the children. | +| `parse` | Turns stored raw pages into items. | +| `session` | Shows or clears the harness session per child. | +| `scan` | Fetches and parses one child or both. | +| `items` | Reads or deletes items. | +| `brief` | Builds a brief. | +| `notify` | Sends briefs and shows the send status. | +| `calendar` | Pushes events to Home Assistant. | +| `hash-password` | Prints a bcrypt hash. | +| `gen-secret` | Prints a fresh secret. | +| `version` | Prints the version. | + +## Folders + +| Folder | Function | +| :--- | :--- | +| `cmd/school/` | One file per subcommand plus `main.go`. | +| `internal/web/` | HTTP server, handlers, HTMX templates, static assets. Holds `hub_api.go`. | +| `internal/auth/` | Session login with signed cookies and CSRF. | +| `internal/store/` | SQLite open, migrations and queries. | +| `internal/store/migrations/` | `001_init`, `002_items_parse`, `003_calendar`, `004_notifications_send`. | +| `internal/parse/` | HTML to items. Readings for tasks, news, calendar and importance. | +| `internal/llm/` | OmniRoute client for importance classification only. | +| `internal/dashboard/` | Builds the per-child buckets. Classifies overdue and due-today. | +| `internal/brief/` | Builds and delivers the morning briefs. | +| `internal/apprise/` | Apprise API client. Uses tags and redacts errors. | +| `internal/calendar/` | Home Assistant client and the idempotent sync. | +| `internal/hub/` | The Hub contract: capabilities and read-only answers. | +| `internal/config/` | Reads the environment. | +| `deploy/` | Caddy snippet, n8n workflow, import and key scripts, verify scripts. | +| `scripts/` | Scrapers, parse and morning-brief wrappers, calendar push. | + +## The Read-Only Boundary + +`internal/hub` depends on a `Reader` interface. The interface has three read +methods and no write method. Test `TestHubAskReadsOnly` proves that `/ask` does +not change the `items`, `scans` or `notifications` row counts. + +## The Scraper + +`scripts/compass-fetch-patchright.mjs` logs into Compass with patchright. The +login POST meets a Cloudflare managed challenge. patchright, a headless +Playwright build, passes the challenge. The pi-browser-harness does not. + +The Compass parent login required no two-factor code during the Phase 2 proof. + +## Database Tables + +| Table | Holds | +| :--- | :--- | +| `children` | id, name, year, Compass id. | +| `items` | Child, type, subject, teacher, title, body, due date, URL. | +| `scans` | Start time, child, pages fetched, items found, error. | +| `notifications` | Recipient, scope, summary, channel, status, sent time. | +| `session` | Harness session state per child. | +| `calendar_pushes` | Calendar write attempts, result and error. | + +The raw Compass pages are not in the database. The fetcher writes them to +`var/raw///` on the host. Each folder holds the HTML, a meta +file per page, and a manifest with a SHA-256 and a byte count per page. + +## Tests + +Run `make test`. The suite uses standard Go tests. The tests cover the store, +the parser, the dashboard buckets, the brief, the Apprise client, the calendar +sync, the Hub API and the read-only rule. diff --git a/docs/outline/04-agent-runbook.md b/docs/outline/04-agent-runbook.md new file mode 100644 index 0000000..20e02f0 --- /dev/null +++ b/docs/outline/04-agent-runbook.md @@ -0,0 +1,98 @@ +# Compass NHS — Agent Runbook + +## First Checks + +Run these commands before any change. + +```bash +git -C /home/sam/paseo/projects/compass_teams_nhs status +git -C /home/sam/paseo/projects/compass_teams_nhs log -n 5 --oneline +curl -s http://127.0.0.1:8792/health +``` + +`/health` must return `"ok": true`. It also shows the scan state and +`hub_api`. A red scan state is real data, not a test failure. + +## Build and Test + +The host `.13` has no Go toolchain. Build and test inside `golang:1.25-alpine` +through `make`. Bound every Docker command. + +```bash +make test # go test ./... in golang:1.25-alpine +make vet # go vet ./... +make build # build the container image +``` + +## Deploy and Restart + +```bash +make up # docker compose up -d --build +make ps # container state +make logs # follow the service log +make restart # restart the service only +``` + +The container is `compass-teams-nhs`. The image tag is +`compass-teams-nhs/school:0.6.1-phase8`. + +## Operate + +```bash +make scan # fetch and parse both children +make scan SCAN_CHILD=Finn # one child +make parse CHILD=Harry RUN=latest # parse stored pages +make preview # print the briefs, send nothing +make notify # send the briefs from the last scan +make morning-brief # scan, then send (the n8n job) +make calendar CALENDAR_ARGS='push --dry-run' +make calendar CALENDAR_ARGS='status' +``` + +The send log: + +```bash +docker exec compass-teams-nhs /app/school notify status +``` + +## Verify + +```bash +make verify-phase1 # /health, the login, the Caddy route +make verify-phase8 # /capabilities, /ask, /notify auth +make verify-phase8 ARGS=--send # also deliver one real /notify message +``` + +## The n8n Schedule + +```bash +make n8n-key # dry run: print the key plan +make n8n-key APPLY=1 # create the restricted key +make n8n-import # dry run: print the import plan +make n8n-import APPLY=1 # import, publish and restart n8n +docker exec n8n n8n list:workflow --active=true +``` + +The workflow runs at 06:00 in `Australia/Melbourne`. The trigger key has a forced +command and `restrict`. It can run one command only. + +## Documentation + +```bash +# Publish the maps and diagrams to the LAN maps site. +~/.agents/skills/project-docs/scripts/publish-maps.sh \ + /home/sam/paseo/projects/compass_teams_nhs + +# Force one Obsidian sorter pass on .27. +ssh sam@192.168.20.27 'obsidian-sort watch --once' +``` + +## Rules + +1. **Read-only against the school.** Never post, message or submit. +2. **Fail loudly.** Never report "nothing due" when a scan failed. +3. **Never commit a secret.** Credentials, tokens and cookies stay in + `environment.d/school-service.env` or a Paperclip secret. +4. **Never write personal page content into a document.** +5. Use one commit and one push for a change. End the message with + `Co-Authored-By: Paperclip `. diff --git a/docs/vikunja-tasks.json b/docs/vikunja-tasks.json new file mode 100644 index 0000000..a917c1b --- /dev/null +++ b/docs/vikunja-tasks.json @@ -0,0 +1,19 @@ +{ + "title": "Compass / NHS (School) - Human Task Overview", + "description": "High-level task checklist for human review. The live board is the Vikunja project 'Compass / NHS (School)' (id 21). This file is the repo copy.", + "vikunja_project": { + "name": "Compass / NHS (School)", + "id": 21, + "url": "https://vikunja.lab.audasmedia.com.au/projects/21" + }, + "teams_status": "DEFERRED - FR-15 is not built. No Teams section is scanned.", + "tasks": [ + { "id": 196, "title": "Open the dashboard on a phone and confirm overdue work is first for Harry and Finn", "done": false }, + { "id": 197, "title": "Confirm the 06:00 n8n run sends the per-boy and combined briefs on Email and WhatsApp", "done": false }, + { "id": 198, "title": "Check the important school events appear in the Home Assistant calendar calendar.school", "done": false }, + { "id": 199, "title": "Ask the Hub 'what does Harry have on today at school?' and check the answer", "done": false }, + { "id": 200, "title": "Confirm a failed scan shows an error and never an empty day", "done": false }, + { "id": 201, "title": "Review the published docs: README, Obsidian note, Archify map and Outline collection", "done": false }, + { "id": 202, "title": "Decide the Teams scope (FR-15): build it, or close the project with Teams deferred", "done": false } + ] +}