Public: anyone can read this; the crew can change it.

The delivery log an admin can read, understand and retry #55

open not picked up yet opened this
opened this

The second half of #7, which is cut down to sending. Once a decision fires a webhook and every attempt writes a delivery row, those rows need somewhere to be read: a webhook that quietly failed three times a week ago is worse than no webhook, because the group believes the thing on the other end knows.

What done looks like

  • [ ] listDeliveries(db, who, {proposalId?}) in src/actions.ts, admin only at the action (forbidden for a user, signin_required for anon) — the URL may name a private system, so this is not a public log.
  • [ ] /deliveries renders it for an admin: newest first, each row saying which proposal, when, which attempt, the status, the response code and the error in the words the sender recorded. A user or a guest gets the app's own 403, not a blank page.
  • [ ] A failed delivery is legible to somebody who did not write it: the row says what it means in a short sentence ("the endpoint answered 500 on all three attempts", "the endpoint did not answer within 5 seconds") rather than only a number.
  • [ ] Retry is one button per failed delivery, POST /api/deliveries/:id/retry, admin only, going through the same sender as the automatic attempts and writing a new delivery row rather than editing the old one — the log is append-only and the pull says so.
  • [ ] A retry of a delivery whose proposal has since been… nothing: a decision does not change, so a retry sends exactly the same body and the same signature it would have sent originally. Assert it in a test, because a body rebuilt from current rows is how signatures start disagreeing.
  • [ ] Retry is rate-limited by the bucket that already exists in src/http.ts — an admin holding the button down must not become an outbound flood.
  • [ ] /p/:id shows a decided proposal's delivery state in one line for an admin only ("delivered", "failed — see the log"), linking to /deliveries.
  • [ ] The delivery log is in the record export (#34's exportRecord) or explicitly excluded with a reason in the pull: it holds a URL that may be private, and that decision should be made deliberately rather than by default.
  • [ ] Tests: a user is refused at the page and the API; a retry writes a new row and sends the identical body and signature; a retry of a sent delivery is refused or is explicit that it will send again — pick one and say which in the README; the page escapes an error string containing <.
  • [ ] README.md says how to read the log and what retry does. bun test is green.

Files I expect to change: src/actions.ts, src/actions.test.ts, src/pages/deliveries.ts (new), src/pages/layout.ts, src/routes.ts, src/deliver.ts, src/server.test.ts, README.md.

Risks: an error string from the outside world is rendered on a page — escape it like any other untrusted text, and truncate it, because a remote endpoint can answer with a megabyte. Never render the signing secret or a header that carries it. The retry button is the app making an outbound request on a person's click: it goes through the same URL validation as the original send, since the stored setting may have been changed to something private since.

Fit: Hearth — the panel that says plainly what happened and offers the one thing to do about it. A person assigns; I do not.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs