Stock moves, payments, order events and gift-card entries are all rows. The admin writes that change what the shop sells and what it charges are not: admin.productCreate, productUpdate, variantCreate, variantUpdate, discountCreate, discountToggle, taxSave and taxRemove write the new value and leave nothing behind. A price can change between a shopper seeing it and paying it, and today nobody can say who changed it or when.
The direction asks for a money trail that is complete. This is the half of it that is not about an order.
Done looks like
[ ] One table, e.g. admin_change (id, at, actor_id, actor_name, subject, subject_id, field, before, after), written inside the same transaction as the change it records.
[ ] Every write in src/admin.ts records one row per field that actually changed; a form submitted with nothing changed writes nothing.
[ ] The actor is ctx.who and nothing else — never a form field, never a header the caller wrote.
[ ] /admin/changes lists the most recent 100, newest first, with the subject linked where there is a page for it; it answers in milliseconds on a table with 10,000 rows (an index on at).
[ ] Values are escaped on the way out and capped on the way in; a 4,000-character blurb does not become a 4,000-character audit row.
[ ] The terminal gets the same view — a tab beside stock, orders and the outbox, gated for an admin the way the rest is.
[ ] Tests: an admin edits a price and the row says the old and the new value with the admin's forge id; a no-op save writes nothing; a non-admin cannot reach /admin/changes.
Files I expect to change:src/db.ts (the table), a small src/changes.ts for the writer and the reader, src/admin.ts (the call sites), src/pages/admin.ts, src/tty.ts, src/server.test.ts.
Risks: #12 adds actor columns to the five existing ledgers — the same ctx.who plumbing, different tables. Whoever takes both should do this one second and reuse the actor shape #12 settles on. Do not log the value of anything secret: a gift-card code is not a field to record before and after.
Stock moves, payments, order events and gift-card entries are all rows. The admin writes that change what the shop sells and what it charges are not:
admin.productCreate,productUpdate,variantCreate,variantUpdate,discountCreate,discountToggle,taxSaveandtaxRemovewrite the new value and leave nothing behind. A price can change between a shopper seeing it and paying it, and today nobody can say who changed it or when.The direction asks for a money trail that is complete. This is the half of it that is not about an order.
Done looks like
admin_change (id, at, actor_id, actor_name, subject, subject_id, field, before, after), written inside the same transaction as the change it records.src/admin.tsrecords one row per field that actually changed; a form submitted with nothing changed writes nothing.ctx.whoand nothing else — never a form field, never a header the caller wrote./admin/changeslists the most recent 100, newest first, with the subject linked where there is a page for it; it answers in milliseconds on a table with 10,000 rows (an index onat)./admin/changes.Files I expect to change:
src/db.ts(the table), a smallsrc/changes.tsfor the writer and the reader,src/admin.ts(the call sites),src/pages/admin.ts,src/tty.ts,src/server.test.ts.Risks: #12 adds actor columns to the five existing ledgers — the same
ctx.whoplumbing, different tables. Whoever takes both should do this one second and reuse the actor shape #12 settles on. Do not log the value of anything secret: a gift-card code is not a field to record before and after.Suggested: ledger.