@rackbops/plugin-tracker 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +92 -6
  2. package/dist/plugin.js +874 -176
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -7,8 +7,9 @@ on a rackbops-discord-bot instance. Plan of record: Rackbops/Tooling
7
7
  command, delivered by DM, answered by button, with history -- the first slice of the web area
8
8
  (#80): sign-in by one-time link, my tasks, a task's history, and settings -- renewals and
9
9
  the price tracker (#81, plan E6): two more types on the notify side, no model, nothing sent to
10
- city-hall -- and the web area's task editor (#80, slice 2): make, edit, pause, resume and delete
11
- your own tasks from the web, under the commands' own rules.
10
+ city-hall -- the web area's task editor (#80, slice 2): make, edit, pause, resume and delete
11
+ your own tasks from the web, under the commands' own rules -- and the admin view and forget-me (#80,
12
+ slice 3).
12
13
 
13
14
  ## Commands
14
15
 
@@ -65,6 +66,11 @@ served at a hashed path, cached for a year).
65
66
  | `/tasks/<id>/edit` | The owner's edit form (GET) and saving it (POST). |
66
67
  | `/tasks/<id>/pause`, `/resume`, `/delete` | POST only. Delete answers a confirmation first; only a second post carrying `confirm=yes` deletes. |
67
68
  | `/settings` | Preferred hour and time zone, checked as `/register` checks them. |
69
+ | `/forget` | Forget me (below): what it deletes (GET); a POST asks for the confirmation; a POST with `confirm=yes` and the word `forget` erases. Linked from Settings. |
70
+ | `/admin` | Admins only: everyone on the list, the decline blocks in force (each with Lift), and a form to allow a person by Discord id. |
71
+ | `/admin/tasks` | Admins only: every task in the store, with its owner, status and how many receive it, each linking to its page. |
72
+ | `/admin/people/<id>` | Admins only: one person -- Discord id, registration, zone and hour, delivery, task counts, their tasks -- with Make admin or Revoke admin, Resume delivery (when paused), and Remove from the tracker. |
73
+ | `/admin/allow`, `/admin/people/<id>/grant`, `/revoke`, `/resume-delivery`, `/forget`, `/admin/blocks/<id>/lift` | Admins only, POST only: the acts below. |
68
74
  | `/signin` | Where a request that is not signed in is sent: says to run `/web`. |
69
75
  | `/login?t=...` | The link `/web` gives. |
70
76
 
@@ -130,7 +136,86 @@ while the last confirmation is under 24 hours old, and are then signed out and t
130
136
  again. Concurrent requests share one lookup per person, and after a failed one that person is not
131
137
  looked up again for a minute, so an outage or rate limit does not pile up calls. A confirmation
132
138
  time in the future (a clock set back) counts as stale. Every lookup asks Discord (`force: true`),
133
- never discord.js's member cache, which with only the Guilds intent never learns that someone left. There is no way yet to take a person off the tracker (forget-me and the admin view, below).
139
+ never discord.js's member cache, which with only the Guilds intent never learns that someone left.
140
+
141
+ **The admin view** (slice 3, plan 5.8, 5.10). An admin is a person whose row in the tracker's store
142
+ has the admin flag -- the one admin definition; `/allow` checks the same flag. `TRACKER_ADMIN_DISCORD_IDS`
143
+ grants it at start; admins grant and revoke it here. The flag is read from the store on every
144
+ request, and again inside the write queue before any admin act, so an admin whose flag is revoked is
145
+ refused on their very next request. To a signed-in person who is not an admin, every `/admin` path --
146
+ any method -- answers the same 404, byte for byte, as an unknown page; a visitor who is not signed in
147
+ is sent to sign in, as on every other signed-in path. An admin sees every person, every task
148
+ and every task's page and history, read-only: an admin never edits, pauses or deletes another
149
+ person's task (the owner's acts answer them 404, as in slice 2). What an admin can do, each through
150
+ the function the command path runs:
151
+
152
+ - **Allow** a person by Discord id: `/allow`'s own `allowPerson`, with the same server-membership
153
+ check when `TRACKER_GUILD_ID` is set (one bounded lookup; no Discord client yet since a restart
154
+ refuses, as an unknown answer does). A form cannot tell a bot's id from a person's; a bot's row can
155
+ never `/register`.
156
+ - **Make admin / Revoke admin** (plan 5.8). The last admin can be neither revoked nor removed. An
157
+ admin who revokes themselves lands on My tasks. Each change is logged (`u1 made u2 an admin`).
158
+ An admin named in `TRACKER_ADMIN_DISCORD_IDS` is made an admin again at every start, so their page
159
+ offers neither Revoke nor Remove, and both are refused: take them out of the configuration first.
160
+ Such an admin may still forget themselves (if another admin is left); the pages say that the next
161
+ start makes them again, as a new person, unless the configuration no longer names them.
162
+ - **Resume delivery** of a person paused after failed DMs: `DeliveryHealth.resume`, what the
163
+ person's own next command does -- the count clears and the tasks nothing else holds go back on.
164
+ - **Lift** a decline block in force: docket's `liftBlock`, recorded as that admin's lift.
165
+ - **Remove from the tracker**: forget-me for that person (below), with the same confirmation.
166
+
167
+ Not here yet, though plan 5.10 lists them: grants, budgets and retry (they belong to the execute
168
+ lane, which is not built) and an admin pause of someone else's task.
169
+
170
+ **Forget me** (plan 5.8). A signed-in person erases themselves from `/forget` (web only: the plan
171
+ asks for no Discord command, so without `TRACKER_WEB_URL` the only path is an admin with the web
172
+ area). Two posts: the first asks, the second must carry `confirm=yes` and the word `forget`. Then,
173
+ in one SQLite transaction in the write queue, **deleted, not archived**:
174
+
175
+ - every task they own, archived ones too, with everything under it: runs, run events, replies
176
+ (anyone's), history, series, recipients, delivery claims and pauses;
177
+ - on everyone else's tasks: their recipient rows, their replies and answers, the history rows they
178
+ made or that name them (an invitation of them, a pause for them), the run events and delivery
179
+ claims of DMs to them, and their pause rows;
180
+ - every decline block they are either side of;
181
+ - their admission, delivery health, web sessions and unused sign-in links, and their person row.
182
+
183
+ What stays, and why: another person's task that they received stays that owner's; one paused only
184
+ because DMs to them failed goes back on. Another person's row keeps its own audit fact but not their
185
+ id -- `admitted_by` of someone they admitted, and `lifted_by` of a block they lifted, become
186
+ `forgotten` ("an admin since forgotten"; null already means "the configuration"). Text is matched
187
+ only in the forms the code writes a person's id into -- docket's consent rows (`u5`, `u5 24h`), the
188
+ tracker's `u5: ...` removals and `delivery to {u5} paused` pauses, docket's `u5 <message>` delivery
189
+ events, and the notifier's error phrases (`recipient u5 cannot be messaged`, ...) -- never as any
190
+ id-like word, so an address with `/u5/` in someone else's text is left alone. A history row or a
191
+ delivery event of theirs is deleted; the error of someone else's run keeps its row and has the id
192
+ replaced with `(forgotten)`. A pause for a recipient now names them by id and shows their current
193
+ name when read; a pause row written before 0.6.0 names the recipient's display name of the time,
194
+ and is deleted when it is on a task they were a recipient of, names their current name, and no
195
+ other recipient of that task has that name. A row written under an older name of theirs, or shared
196
+ with a same-named recipient, is kept: the one known gap. A reply other people wrote on the erased
197
+ person's own tasks is deleted with those tasks. Outside the store, nothing is touched: the DMs the
198
+ bot sent stay in the person's Discord DMs, and the host's log may hold their tracker id.
199
+
200
+ Bytes: the database runs with `secure_delete` on, so a deleted row is overwritten, and the first
201
+ start of 0.6.0 on an existing database runs one `VACUUM`, so pages freed before that are rewritten
202
+ too. That one-time VACUUM is recorded in a small `tracker_meta` table, not by a schema bump: the
203
+ schema stays at 3, so rolling back to 0.5.0 still opens the database. If the VACUUM cannot run (a
204
+ reader holds the file), the start goes on, a warning is logged, and the next start tries again.
205
+ Not a VACUUM per erasure: it rewrites the whole file under an
206
+ exclusive lock, and `secure_delete` already covers every later delete. After an erasure the
207
+ write-ahead log is checkpointed without waiting; when a reader keeps it busy, that is logged and
208
+ SQLite's next checkpoint copies it back.
209
+
210
+ Timing: forget-me first waits, outside the write queue, until no notify or poll tick is running (at
211
+ most 20 seconds; then it answers 503, try again, and deletes nothing), so a reminder DM to them
212
+ already in flight cannot be recorded after they are gone -- and no one else's command waits behind
213
+ it. Inside the queue it only checks that no tick started meanwhile (else the same 503). A DM sent
214
+ outside any tick -- an invitation's -- that fails after they are gone writes nothing: the failure
215
+ count is not kept for a person no longer in the store, and the withdrawn invitation is not
216
+ recorded. A `/price` whose page is read while its person is forgotten makes nothing. Their sessions
217
+ are among the rows, so they are signed out everywhere at once, and the browser's cookie is cleared. They can come back only as someone new: an admin
218
+ `/allow`s them again, and the tracker never reuses an id.
134
219
 
135
220
  **Choose the web origin's domain with care.** A host under the same parent domain as
136
221
  `TRACKER_WEB_URL` that you do not control can set a `__Secure-` cookie on the parent domain
@@ -151,6 +236,7 @@ The `__Host-` prefix, which would stop that, requires `Path=/` and so cannot be
151
236
  | People | `src/people.ts`, `src/admissions.ts` | Discord id, time zone, preferred hour, admin flag -- in the tracker's store, never usr; who admitted each person and when they registered. `TRACKER_ADMIN_DISCORD_IDS` only ever grants admin. |
152
237
  | Commands | `src/discord.ts`, `src/interactions.ts`, `src/discord-common.ts` (discord.js); `src/actions.ts`, `src/press.ts`, `src/history.ts`, `src/access.ts` | The discord.js files read options and render; the rest is Discord-free over the injected store, clock and notifier. Store writes are handled one at a time; Discord lookups and DMs run outside that queue. |
153
238
  | Health | `src/health.ts` | `GET /tracker/healthz` (through the bot's HTTP router): `200` `ok`/`starting`, `503` `inactive`/`stale`/`blocked`. |
239
+ | Admin, forget-me | `src/admin.ts`, `src/roster.ts`; `src/web/admin.ts`, `src/web/admin-pages.ts` | The admin acts and forget-me, Discord-free; the SQL docket's Store has no method for (listing people and blocks, erasing a person); the routes and pages. |
154
240
  | Web area | `src/web/` | `app.ts` authenticates and dispatches (pure over the injected store and clock), `routes.ts` names the paths; `pages.ts`, `editor-pages.ts` and `html.ts` render; `editor.ts` reads the editor's forms and calls the shared rules; `signin-link.ts` and `sessions.ts` hold the sign-in; `theme.ts` the stylesheet; `command.ts` is `/web`. |
155
241
  | Task rules | `src/reminders.ts`, `src/tracked.ts`, `src/price.ts`, `src/edit.ts`, `src/manage.ts`, `src/limits.ts` | What makes, edits, pauses, resumes and deletes a task, and the limits on it, Discord-free, for the commands and the web editor alike. |
156
242
 
@@ -166,9 +252,9 @@ The `__Host-` prefix, which would stop that, requires `Path=/` and so cannot be
166
252
 
167
253
  ## Not yet
168
254
 
169
- - The rest of the web area (E5): the admin view, lifting a decline block, forget-me (which would
170
- erase a deleted task), sharing a task from the web, and the JSON task API. Until forget-me and the admin view exist, nobody can be taken off the
171
- tracker, so a web session ends only by sign-out, expiry, or the membership re-check. Discord OAuth2 as a second sign-in method, if chosen (plan item 41).
255
+ - The rest of the web area (E5): sharing a task from the web, the JSON task API, and the admin
256
+ view's grants, budgets and retry (with the execute lane). Discord OAuth2 as a second sign-in
257
+ method, if chosen (plan item 41).
172
258
  - The city-hall Executor adapter (the execute lane).
173
259
  - Editing in Discord (the editor is on the web only), and changing a price tracker's page or `near` after it is made (make a new one); a free-form pattern for `price` (`near` is the safe subset: a user's regular expression run on a large page could hang the bot).
174
260
  - An optional Discord-role gate (plan 5.5), and showing unconfirmed deliveries to admins anywhere but the log.