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) carry target_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_*) leave target_plot_id null and stash the delta in payload.

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 keyWhen setWhat it carries
textalwaysThe narrative the operator wrote.
evidence_urlsalwaysThe evidence URL array as resolved by the write.
compliance_overridewhen 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.
notealwaysThe operator's reason from the request.
sourcealways"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

SeqMethodPathPurpose
1GET/dds/:recordId/eventsAll events for the record, across every version, newest first
2GET/dds/:recordId/versions/:versionId/eventsCumulative 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

QueryNotes
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