Fetch and parse Harry and Finn with a stored Compass session per child and store every item against the right children.id. - cmd/school/session.go + internal/store/session.go: read and write the per-child harness session state in the SQLite `session` table. `show` prints metadata only; the state is never printed. - scripts/compass-fetch-patchright.mjs: reuse the stored session, refresh it after login, resolve the child's userId from the parent page by name, and keep the shared calendar out of the per-child run (it would leak the eldest's timetable into the younger child). - scripts/scan-compass.sh + make scan: scan one child or both; restore the session, fetch, persist the session, then parse. A failing child is recorded in `scans` with ok = 0 and the script exits non-zero (FR-12). - cmd/school/scan.go: `school scan record` writes a fetch failure to the scans table. cmd/school/items.go: `school items purge` removes rows that a shared page stored against the wrong child. - internal/store/store_test.go: prove items and sessions are separated by child. docs/phase-4-both-children-notes.md; README + image tag 0.3.0-phase4. Live evidence: Harry 70 tasks / 1 message / 35 events (6 overdue); Finn 42 tasks / 1 message / 0 events (2 overdue). Finn's subjects are Year 7, Harry's are Year 11. Both sessions stored. Co-Authored-By: Paperclip <noreply@paperclip.ing>
3.9 KiB
Phase 4 — both children, per-child sessions
Status: done. The Compass scan now fetches and parses Harry and Finn
with a stored session per child, and every item is stored against the right
children.id. See AUD-108.
What runs
# Both children, with the stored session per child.
make scan
# One child only (a scan can target one child).
make scan SCAN_CHILD=Finn
# Same thing without make.
scripts/scan-compass.sh
scripts/scan-compass.sh --child Harry --child Finn --classify none
scripts/scan-compass.sh runs the patchright fetcher on the host and the
school subcommands in a one-off container. It never restarts the service.
Per-child session (FR-2, §7 session table)
The Compass login is one parent account, but each child gets its own browser session state so a scan is independent and targetable.
| Step | Command | Notes |
|---|---|---|
| Restore | school session get --child NAME --out FILE |
Reads session.state; writes a 0600 file. No row means "log in fresh". |
| Fetch | node scripts/compass-fetch-patchright.mjs --child NAME --state FILE |
Loads the state; logs in only when Compass rejects it; saves the refreshed state. |
| Persist | school session put --child NAME --file FILE |
Upserts session.state for that child. |
| Inspect | school session show [--child NAME] [--json] |
Prints bytes, cookie and origin counts, and updated_at. Never the state. |
The session state is a secret. It lives in the database volume only. No command prints it, and the handoff file exists only inside the run's scratch directory.
What each run stores
One run directory per child under var/raw/<child>/<runId>/ (git-ignored):
| Page | Source | Becomes |
|---|---|---|
home |
parent landing page | message rows (news feed) |
learning-tasks-all |
POST /Services/LearningTasks.svc/GetAllLearningTasksByUserId |
task rows (authoritative per-child list) |
child-profile |
/Records/User.aspx?userId=N |
no grid; used only when it carries the legacy grid |
learning-tasks |
/Records/UserNew.aspx?userId=N#learningTasks |
task rows (legacy grid fallback) |
The per-child Compass userId is discovered from the parent landing page by
child name (Harrison → Harry, Finnegan → Finn) and recorded in the manifest.
Why the calendar is not fetched per child
The parent calendar page renders only the calendars that the parent has enabled. In this account only Harry's calendar is enabled; Finn's is hidden. Fetching that page inside Finn's run therefore stored Harry's Year 11 timetable against Finn — a leak between children.
Until per-child calendar attribution exists, the shared calendar stays out of the
per-child loop. Harry's events remain from Phase 3. A caller can still fetch it
explicitly with the fetcher's --pages option. The school items purge command
removes rows that a shared page stored against the wrong child.
Fail loudly (FR-12)
- A fetch failure records
scans.ok = 0with the message throughschool scan record --child NAME --ok=false --message TEXT. - A parse failure records its own failed scan inside
school parse. - The scan script finishes every child, prints each failure by name, and exits non-zero. A failed child is never shown as an empty day.
Evidence (2026-10-15, live)
scripts/scan-compass.sh --classify auto
| Child | tasks | messages | events | overdue (home-page recon.) |
|---|---|---|---|---|
| Harry | 70 | 1 | 35 | 6 |
| Finn | 42 | 1 | 0 | 2 |
Rows are separated by child_id. A check for items with no matching child
returns 0. Finn's task subjects are all Year 7 (7MA, 7DRA, 7DES); Harry's
are all Year 11 (11MGMA, 11HLEG, 11EENG).
Known gaps
children.compass_idis not populated yet. The manifest holds theuserIdper run; a later phase can copy it into thechildrentable.- Per-child calendar attribution is deferred (see above).
- Teams (FR-15) is a separate source and is not scanned here.