Amendment (2026-09-10): this issue is backend-only and shipped in #742. The payload it records was invisible on screen until #747/#748 (snapshot the product name) and #745/#749 (render it as a Details column). See this comment for what landed where and what this issue never claimed. Body below left as written.
Part of #719. Blocked by #720 (needs the list-price snapshot).
What
Put the actual prices into the sales-line audit events.
AddOrderItemHandler.cs:106 and UpdateOrderItemHandler.cs:30 already write
SalesOrder.AddItem / SalesOrder.UpdateItem audit events — but with no
payload. The trail today shows someone touched a line, never what price
they set. That is why the discounts that triggered #719 are not reconstructible
from stored data.
Scope
Carry list price, previous unit price and new unit price in the audit payload for
both actions. AuditWriter already resolves the actor (#500) and AuditEvents
already has a durable global ordering key (#508), so this one change makes "who
discounted what, in what order" answerable from data already being written.
Guard to read before writing the call site
AuditVocabularyCoverageTests accepts only a literal AuditActions.X — or a
ternary of two such references — as the action argument of an
IAuditWriter.WriteAsync call, and fails closed on everything else. The obvious
tidy-up here (forwarding both calls through one shared private helper) goes red
on a test you would never have opened. Keep the two call sites literal.
Acceptance
Note
Audit rows are not a reporting surface — the report in #719 reads the order
lines, not this. This slice exists so a specific past incident can be
investigated after the fact, which is the thing that was impossible in the case
that opened #719.
Part of #719. Blocked by #720 (needs the list-price snapshot).
What
Put the actual prices into the sales-line audit events.
AddOrderItemHandler.cs:106andUpdateOrderItemHandler.cs:30already writeSalesOrder.AddItem/SalesOrder.UpdateItemaudit events — but with nopayload. The trail today shows someone touched a line, never what price
they set. That is why the discounts that triggered #719 are not reconstructible
from stored data.
Scope
Carry list price, previous unit price and new unit price in the audit payload for
both actions.
AuditWriteralready resolves the actor (#500) andAuditEventsalready has a durable global ordering key (#508), so this one change makes "who
discounted what, in what order" answerable from data already being written.
Guard to read before writing the call site
AuditVocabularyCoverageTestsaccepts only a literalAuditActions.X— or aternary of two such references — as the action argument of an
IAuditWriter.WriteAsynccall, and fails closed on everything else. The obvioustidy-up here (forwarding both calls through one shared private helper) goes red
on a test you would never have opened. Keep the two call sites literal.
Acceptance
burst (fix(api): order same-instant audit events by a durable monotonic key, not a random Guid #508's
Sequencetiebreak).AuditVocabularyCoverageTestsstays green.Note
Audit rows are not a reporting surface — the report in #719 reads the order
lines, not this. This slice exists so a specific past incident can be
investigated after the fact, which is the thing that was impossible in the case
that opened #719.