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

Every ledger row says who did it, and /admin/audit is the whole money trail on one page #12

shipped production is serving it opened this has it
opened this

Every stock movement, payment, refund and order event names the actor who caused it, and one admin page reads the five ledgers as a single timeline.

The rows are already there — stock_entry, payment, order_event, gift_card_entry, discount_redemption — and they say what happened and why. What they do not say is who: a refund and a stock correction are anonymous. With identity (#9) and a buyer on the order (#10) that last column can be filled, and the money trail is complete rather than nearly complete.

Done looks like

  • [ ] stock_entry, payment, order_event, gift_card_entry and discount_redemption gain actor_id, actor_name and actor_role, added through addColumn with defaults that tell the truth about the rows already written ('' / unknown).
  • [ ] The writers take the actor as a required argument — note(), addStock, takeStock, setStock, the payment and refund writes, the gift card entries — so the compiler finds every caller and none can forget.
  • [ ] The actor is whoever it really was: the buyer for a purchase, the admin for an admin action, stripe for a webhook, system for a seed or a boot-time job, and the terminal's account for a terminal action.
  • [ ] GET /admin/audit is one timeline across those five tables, newest first: when, who, what, which order, how many cents. It filters by order number and pages with ?before=. Admin only, server-rendered, and it answers in milliseconds on a database with thousands of rows — index what it orders by rather than scanning.
  • [ ] Each row links to the order it belongs to, and /admin/orders/:number shows the actor on every event and payment it already lists.
  • [ ] The page reconciles: charges less refunds equals what the order says was paid, shown as a figure the reader can check rather than a claim.
  • [ ] The terminal gains the same timeline on a key, alongside stock, orders and the outbox, as AGENTS.md asks.
  • [ ] Tests: an admin refund writes a payment row naming that admin; a Stripe webhook charge names stripe; a shopper's order names the buyer; a terminal stock move names the terminal's account; and the audit page shows them in order. No arithmetic test changes value.
  • [ ] README's section on what is real gains a line about the audit trail and who can read it.

Files I expect to change

src/db.ts, src/inventory.ts, src/orders.ts, src/giftcards.ts, src/discounts.ts, src/admin.ts, src/shop.ts, src/routes.ts, src/pages/admin.ts, src/tty.ts, src/server.test.ts, src/units.test.ts, README.md.

Risks

  • Threading an actor through every writer touches most of the money code at once. Change no arithmetic while doing it: the totals tests in src/units.test.ts must pass unchanged, and if one needs editing, that is a bug in the change.
  • The actor column must not become a second place that says who bought an order. order.buyer_id from #10 stays the one answer to that; the actor says who caused the row.
  • A timeline over five tables is easy to write as five scans. Keep it to a bounded query per table with a limit, merged in memory, or a single UNION ALL with an index behind each ordering.
  • Nothing private leaks onto the audit page that is not already on the order page: no gift card codes, no full gateway secrets.

Blocked by: #9, #10.

Whose brief fits: ledger — this is the auditor's view it builds for. Sentinel should check the page for what it exposes.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs