Mitigation Audit Log
Append-only audit log of every create / update / delete that touches a DDS record — record envelope, version cuts, config edits (exclusions + justifications), and submission state transitions. Rows are immutable: there is no PUT, PATCH, or DELETE on this resource.
Each event lives at the granularity of a single user action:
- Plot-targeted events (
config.exclusion_added,config.exclusion_removed,config.justification_set,config.justification_removed) carrytarget_plot_id. Use them to reconstruct what changed for a given plot across the record's lifetime. - Global events (
record.create,record.update,version.create,version.task_*,config.replace,config.patch,version.submit_*) leavetarget_plot_idnull and stash the delta inpayload.
▶Reading override history off the finding events
Two event families now describe the same underlying finding row, because two writers reach it.
Verdict Overrides (PUT/DELETE /verdict-overrides) appends
config.finding_set / config.finding_removed, whose payload carries the
axis-generic shape: axis, subject_type, subject_id, narrative,
basis, verdict_override, document_attachment_id, provenance, plus the
operator note.
Bulk PATCH /config still appends the older
config.justification_set / config.justification_removed pair, with the
payload documented below. A reader of the log must expect both families — the
per-plot justification endpoints that used to emit this pair were removed in
EPO-1983, so on a current record it comes only from PATCH /config:
| Payload key | When set | What it carries |
|---|---|---|
text | always | The narrative the operator wrote. |
evidence_urls | always | The evidence URL array as resolved by the write. |
compliance_override | when present on the body, or when preserved from the existing row | "compliant" / "non_compliant" / null. Reading the latest event for a plot tells you the verdict that's currently pinned. |
note | always | The operator's reason from the request. |
source | always | "user" for operator-initiated writes. |
config.justification_removed carries only note + source —
the row (and any override) was deleted, so there's no "next"
state to record. The matching previous state is whatever the
most-recent config.justification_set event before it captured.
▶Endpoints
| Seq | Method | Path | Purpose |
|---|---|---|---|
| 1 | GET | /dds/:recordId/events | All events for the record, across every version, newest first |
| 2 | GET | /dds/:recordId/versions/:versionId/events | Cumulative history up to and including :versionId |
The version-pinned variant filters to events whose version's
version_number ≤ :versionId.version_number. Use it to inspect the
audit trail "as of" a given version, hiding events that landed on
later versions.
▶Filtering
| Query | Notes |
|---|---|
filter[action][eq] | Single action |
filter[action][in] | Comma-separated list — e.g. version.submitted,version.submit_failed |
filter[target_plot_id][eq] | Surgical: every event that touched the named plot |
filter[occurred_at][gte] / [lte] | ISO-8601 bounds |
▶Visibility
Same rule as the rest of the DDSv2 surface:
dds_record.organization_id ∈ ctx.effectiveOrgIds. Cross-org GETs
return 404.
Required scope:
ingestions:read