From 3b7447289f7493c3e846adca85536db556004f06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20Jovanovi=C4=87?= Date: Thu, 24 Sep 2026 19:12:11 -0700 Subject: [PATCH] add todo list --- TODO.md | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 TODO.md diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..9ca2071 --- /dev/null +++ b/TODO.md @@ -0,0 +1,110 @@ +# TODO + +Ordered roughly by importance. Items near the top either caused real +failures already or block going live. + +## 1. Deletions never propagate — weekly full resync and prune + +`_since` returns records ST considers *updated*. A deleted user is not +returned at all, so their row stays in `people` forever. This has already +caused two failed runs: once with a deleted rep chosen as `reps[0]` (45 +identical 404s), once with a deleted member failing mid-run. + + started := time.Now().Unix() + users, _ := client.ListUsers(ctx, 0) // full pull, ~176 requests + store.UpsertPeople(users, eligible) + store.PruneStale(started) // DELETE FROM people WHERE synced_at < ? + store.SetLastSync(started) + +Guards: + +- Only prune after a `since=0` pass. A delta pass refreshes a handful of + rows and would delete everything else. +- Refuse to prune if it would remove more than a few percent of the table. + A bug that silently empties the cache takes out every reconcile until + someone notices. + +## 2. Delta sync at the start of each reconcile + +One request, usually returning nothing. Eliminates the whole class of +stale-cache confusion that came up four separate times during testing: +a new rep not yet cached, a rep whose flag was removed still being +chosen, a department change invisible to the reconciler. + +## 3. Small fixes, one commit + +- **`failed` in the reconcile summary.** Run 10 logged + `0 assigned, 0 skipped` after 45 failures. A wholly failed run is + currently indistinguishable from an empty one. +- **Verify the agent before the loop.** One `GetUser(agent)` at the top + of `reconcile`; abort with a clear message rather than failing once per + member. +- **Prune a member's cache row on a 404 from `AssignAgent`.** Reactive + and self-healing: a stale row fails once instead of on every future run + of that department. +- **Live `IsRep` check in the handler.** Chapter and department come from + a live `GetUser`, but nothing checks whether the person is still a rep. + Someone whose flag was removed can still trigger a full reconcile. +- **Add `sqlite` to the Dockerfile runtime stage.** Inspecting `skips`, + `runs`, or `sync_state` currently means a rebuild or a throwaway + container mounted on the volume. + +## 4. Before going live + +- **`VACUUM INTO` on a timer.** Volume snapshots of a live WAL database + are not safe to restore. The skip log is the only thing here that + cannot be regenerated. +- **Turn off `DRY_RUN`, redeploy.** +- **Backfill.** The webhook only fires when a rep is *newly* assigned the + role, so the existing 119 never trigger it. Run with `DRY_RUN` set + first and count the assignments in the log; expect roughly 2,300. + + sqlite3 worker.db "SELECT id FROM people WHERE is_rep = 1;" | while read id; do + curl -s -o /dev/null -X POST "https://HOST/webhook?id=$id&s=$SECRET" + sleep 1 + done + +## 5. Deferred design decisions + +- **Nightly sweep.** Without it, a rep who steps down keeps their + assignments and their replacement inherits nobody. Members added after + their department's rep signed up also stay unassigned until something + triggers that department again. Rep replacement was described as + frequent, so this matters more than the member-churn case. +- **Round-robin split for co-repped departments.** 14 departments have + two reps, covering 302 members, 9 of them at chapter 607. The second + rep currently gets nobody and sees an empty list with no error. + `RepsFor` already returns every rep sorted by ID, so this is a change + to how the agent is picked, not a restructuring. Blocked by the + fill-only rule, not by complexity. +- **Raise the fill-only consequence upstream.** The rule came from + outside the project. The concrete cost is now measurable: 14 reps at + three campuses will open the app to an empty list. +- **Schema migrations.** `CREATE TABLE IF NOT EXISTS` does not add + columns to an existing table. Changing the schema currently means + deleting the database and re-syncing, which will not be an option in + production. +- **License.** AGPLv3 if the goal is that other associations running a + modified copy must share changes; Apache-2.0 if the goal is that they + actually adopt it. Check first whether this is yours to license given + it is faculty association work. + +## 6. Open questions for ST support + +- Do a deleted or demoted rep's agent assignments stay active? This + decides whether departing reps permanently strand their members under + fill-only. Untested: the assignments observed as inactive were + deactivated by hand. +- `DELETE /agent_assignments/{id}` returns 404 on a row that + `GET /agent_assignments/{id}` returns fine. Either the path takes + something else or the handler is broken. + +## 7. README corrections + +- Work List rows are materialized by an ST job running on the half hour. + A new rep sees an empty list for up to 30 minutes after their + assignments are written. Confirmed: rows appeared exactly at :30. Worth + saying in whatever onboarding message reps get, or they will report it + as broken. +- Remove any claim that deleting a person deactivates their assignments. + That was done manually and the actual behaviour is unknown.