Raxx · internal docs

internal · gated

ADR 0061 — Ticket-State-Aware Notification: Two-Path Model for Dim-3 Operator Reads

Status: Accepted
Date: 2026-05-09 UTC
Deciders: operator (Kristerpher), software-architect
Refs: customer-audit-unified/design.md §7, ADR-0060, project_workflow_uuid_tracing_decisions.md, docs/security/customer-audit-unified-threat-model.md §3.4, #1456


Context

When an operator reads a customer's Dimension 3 (operator_interaction) audit data, invariant I-8 requires proactive customer notification. The v1 design used a single nightly batch notification for all Dim-3 reads — regardless of whether the access occurred within an active support ticket or after ticket closure.

The security agent identified this as a gap (#1456, threat T-INS-1): a nightly batch SLA means a customer is not informed of an unauthorized admin read until up to 23:59 hours after the event. For an unauthorized post-resolution read, this is not proactive; it is retrospective.

The operator also identified that a single notification framing cannot serve both cases correctly. An in-ticket read ("our support team is working your ticket") requires a welcoming, trust-building tone. A post-resolution read ("someone accessed your account after your ticket was closed") requires a security-incident framing.

The discriminator for the correct path must be captured at write time, not derived later. This means the customer_audit_events table must store ticket_state_at_read — the FreeScout ticket status at the moment the operator accessed the data.


Decision

Two-path notification model, discriminated by ticket_state_at_read.

Path A — In-ticket read (welcoming)

Condition: ticket_state_at_read IN ('open', 'in_progress', 'pending')

Path B — Post-resolution / no-ticket read (security incident)

Condition: ticket_state_at_read IN ('resolved', 'closed', 'none'), or ticket_id IS NULL

Ticket-state acquisition

ticket_state_at_read is populated synchronously at write time from the freescout_ticket_cache table. The cache is maintained by FreeScout webhook events (POST /api/internal/freescout-webhook). If the cache is missing or expired, ticket_state_at_read = 'none' (fail-closed → Path B).

Superadmin path

Superadmin (raptor-audit-admin) reads always trigger Path B. Superadmins have no ticket_id linkage requirement; their access is unconditionally a security-significant event that the customer must know about.

Compliance auditor path

raptor-audit-compliance (SOC-2 auditor role) reads do NOT trigger either notification path. Auditor reads are aggregate and operational; customer notification would be inappropriate and would constitute noise that degrades the notification's meaning.

No opt-out

Customer notification has no opt-out (invariant I-12). The notification is a GDPR Art. 13/14 transparency obligation, not a preference. Customers may choose not to read the email; the right to receive it is non-waivable.


Consequences

Positive

Negative


Alternatives Considered

Single nightly batch notification (v1 design)

All Dim-3 reads notified in a nightly batch email. Simple, low infrastructure cost.

Rejected: not proactive for post-resolution reads. A customer who was browsed without authorization at 00:01 UTC is not informed until the following night. The invariant requires proactive notification; a 24-hour batch is retrospective.

Real-time per-action notification for all Dim-3 reads

Every Dim-3 read (including in-ticket support reads) fires an immediate notification.

Rejected: support reads within an active ticket create notification noise. A customer who opened a ticket receives a notification for each action a support agent takes — which may be dozens of reads during a complex investigation. This degrades the notification signal and may train customers to ignore the emails.

Ticket-close notification (notify when ticket closes, not when access occurs)

Aggregate all in-ticket reads into a single notification at ticket closure.

Considered but rejected for the post-resolution case: this model cannot produce a Path B notification at all, because the access that defines Path B occurs after the ticket is already closed. There is no ticket-close event to attach the notification to.

Opt-out for customers

Customers who find the notifications alarming can opt out.

Rejected: the notification is a GDPR transparency obligation, not a preference. An opt-out would allow customers to waive a privacy right — which is permissible under GDPR only if the waiver is freely given, specific, informed, and unambiguous. Given that the notification is also a security control (insider-threat detection), permitting opt-out would weaken the insider-threat signal. Security agent recommendation: no opt-out. Operator confirmed.