Muin is in private beta.Watch the public release announcement —talk to us.
Falaah Falaah AI

Compliance Incidents

Report, investigate, and resolve compliance incidents with INC numbering, severity-tiered SLA banners, a 72-hour GDPR breach gate, and AI post-mortems.

The Incidents module handles the compliance side of incident response — security events, data-handling breaches, control failures, and anything else that needs a post-mortem and an audit record. It’s not a replacement for your engineering on-call pager; it’s where the compliance-relevant facts, timelines, communications, and corrective actions are captured once the technical fire is out.

Every incident gets a tenant-scoped INC-YYYY-NNN number, an SLA banner keyed to severity, a separate 72-hour banner for GDPR breach-notification when the category and severity warrant it, and a workflow that walks it through investigation to closure.

The Incident Lifecycle

Reported → Investigating → Contained → Resolved → Closed
                ↓                          ↓
           False Positive             Post-mortem fanout

State transitions are restricted by ALLOWED_TRANSITIONS in IncidentService — you can’t skip from reported straight to resolved, and closed + false_positive are terminal. Attempting an invalid transition returns 409 InvalidStateError with the allowed next states in the response body.

Automatic Numbering

Incident numbers follow INC-YYYY-NNN where YYYY is the reporting year and NNN is a 3-digit sequence. The sequence is tenant-scoped per year (each tenant starts at 001 on Jan 1) and is assigned atomically using a dedicated counter table (incident_number_sequence) via INSERT ... ON CONFLICT DO UPDATE RETURNING. Fifty concurrent POSTs all get unique numbers — no race conditions, no duplicates.

Known issue (DI-003): The database-level uniqueness on incident_number is currently global (not tenant-scoped), which means tenant B’s first report can occasionally collide with tenant A’s INC-YYYY-001 if the sequence generator runs before the unique constraint is dropped. This is tracked for a standalone migration plan. End users won’t hit it in normal practice; dev-test fixtures may.


Reporting an Incident

On /compliance/incidents, click Report Incident. Fill in:

FieldPurpose
TitleShort descriptor
DescriptionFull narrative — what happened, what was noticed first
Severitycritical / high / medium / low — drives SLA
Categorysecurity_breach, data_loss, privacy_violation, control_failure, policy_violation, regulatory, other
Detected atWhen you first noticed it (not when you file this form)
Affected systemsList of system names — these become the allowed-citation list for the AI post-mortem later

On save, Muin:

  1. Generates incident_number atomically
  2. Records reported_at = now()
  3. Computes and stores sla_due_date based on severity (see table below)
  4. Creates a HumanTask for the incident owner
  5. Fires a notification to the compliance owner

SLA by Severity (IS15 banner)

SeverityInternal response SLA
Critical4 hours
High24 hours
Medium72 hours
Low168 hours (7 days)

Definitions: SLA_HOURS_BY_SEVERITY in incident_service.py. The SLA deadline is stored on the incident at create time, so the frontend banner and the worker escalation both read the same field.

The banner shows a countdown. When it crosses into the last 25% of the window, it turns amber. When overdue, it turns red and the compliance owner gets a notification.

The GDPR 72h Breach-Notification Gate (separate)

The internal SLA above is not the GDPR 72-hour breach-notification window. For incidents with category=security_breach or category=privacy_violation involving personal data, GDPR Article 33 requires notifying the supervisory authority within 72 hours of awareness.

A separate banner appears for incidents that match the breach criteria. This one counts from reported_at regardless of severity — the 72-hour clock is statutory, not tuned per-tenant. If the incident genuinely involves a personal-data breach, the compliance owner must either:

  • File the breach notification (and record the filing date on the incident)
  • Explicitly dismiss the banner with a justification (e.g. “no personal data involved after investigation”) — stored for audit

Dismissal requires the compliance owner’s approval and is logged with timestamp, user, and justification text.


Incident Detail (IS14 — Tabbed View)

Click an incident row to open its detail drawer. The detail view is organized into tabs:

TabContents
OverviewTitle, description, status, severity, assigned owner, reported_at, sla_due_date, linked controls
TimelineChronological event log — status changes, comms sent, actions taken, evidence attached
PeopleReporter, current owner, escalation chain, affected users (when applicable)
CommunicationsEmails / Slack / notifications sent about this incident, fetched via the IncidentCommunicationsLog component (Plan 272 JG-7)
ActionsCorrective and preventive actions — who owns each, target date, status
Post-MortemRoot cause, lessons learned, recommended updates — authored directly or via AI draft

Status Transitions and Who Does What

TransitionWho fires itWhat happens
reportedinvestigatingIncident owner / compliance ownerInvestigation tab becomes active
reportedfalse_positiveCompliance ownerTerminal — logged but closed with no post-mortem
investigatingcontainedIncident owner”When was it contained” date captured
investigatingfalse_positiveCompliance ownerSame as above
containedresolvedIncident ownerTriggers post-mortem fanout
resolvedclosedCompliance ownerTerminal — audit-archived

When an incident transitions to resolved, Muin automatically:

  1. Creates a HumanTask(category=INCIDENT_POSTMORTEM) for the incident owner
  2. Starts a compliance.incident.post_mortem_review workflow instance
  3. Fires notifications to the compliance owner + linked framework control owners

AI Post-Mortem Draft (Section 8.7)

On a resolved or closed incident, the Draft Post-Mortem button invokes Bedrock Claude with:

  • Incident description, status, severity, category
  • The affected systems list (these become the only allowed citation list)
  • The timeline entries
  • Any linked controls’ names and descriptions

The AI returns a structured draft with four prose fields:

  • Root cause hypothesis
  • Lessons learned
  • Recommended updates
  • Timeline summary

Plus a cited_affected_systems array and a summary_source badge (llm or fallback).

The Citation Fence

The backend enforces an exact-match citation fence on affected_systems. If the LLM cites a system name that isn’t in the incident’s affected_systems array — case-sensitive exact match — the whole draft is rejected and the rule-based fallback fires instead. This prevents hallucinated “we believe the outage impacted the Foo service” when Foo was never part of the incident.

The fence also rejects:

  • Empty prose fields (must have meaningful content)
  • Prose fields over 2,000 characters (to prevent runaway costs)
  • Missing required keys (if the LLM returns an incomplete schema)

Rate Limits and Caching

  • Per-tenant limit: 10 drafts per hour
  • 24-hour Redis cache keyed on (tenant, incident, hash(description + status + systems + timeline)) — so edits to any of those fields bust cache and cost a fresh LLM call, but repeated clicks on the same state serve cached output

The Apply Flow

After review, click Apply. The four prose fields map to the incident’s editable fields via the existing drawer onUpdate mutation:

  • root_cause_hypothesisroot_cause
  • lessons_learnedlessons_learned
  • recommended_updatescorrective_actions

The incident owner can edit any of these before or after Apply — the draft is a starting point, not a committed record.


SOC 2 CC7.3–CC7.5 Mapping

The Incidents module maps to the SOC 2 CC7 Security Incident criteria:

  • CC7.3 — The entity evaluates security events to determine whether they could or have resulted in a failure. Muin’s incident creation + severity assessment covers this.
  • CC7.4 — The entity responds to identified security incidents by executing a defined response program. The status lifecycle + SLA + response workflow covers this.
  • CC7.5 — The entity identifies, develops, and implements activities to recover from incidents. The containedresolved transition + corrective actions + post-mortem workflow covers this.

Evidence requirements for these controls include the incident log, the SLA compliance percentage, and the post-mortem documents. Muin’s incident exports are built to serve these audit evidence asks directly.


Communications Log (JG-7)

The Communications tab aggregates every outbound communication about this incident — emails to the compliance owner, Slack notifications to security owners, customer breach notices, regulator filings. Each entry shows:

  • Channel (email, Slack, phone — logged by the team)
  • Timestamp
  • Sender + recipient
  • Subject / summary
  • Attached documents (if any)

This log is a critical audit artifact — when the regulator asks “when did you notify affected customers,” the timestamp on the customer-notice row is the answer.


FAQs

How is SLA computed?

At create time, the service reads the severity and looks up the tier in SLA_HOURS_BY_SEVERITY (critical=4h, high=24h, medium=72h, low=168h). sla_due_date = reported_at + hours(tier) is stored on the incident row. The banner countdown reads that field — the computation isn’t redone every page load. If the severity is later edited, the SLA updates accordingly.

When do I need to notify the DPA?

Under GDPR Article 33, a controller must notify the supervisory authority within 72 hours of becoming aware of a personal-data breach, unless the breach is unlikely to result in risk. The 72h banner surfaces on incidents flagged as security_breach or privacy_violation involving personal data. Muin doesn’t auto-file the notification — filing is a deliberate compliance-owner action — but the banner keeps the clock visible and requires an explicit dismiss-with-justification for incidents that don’t actually meet the notification criteria.

What if the post-mortem draft is wrong?

The draft is a starting point. The four prose fields are fully editable before and after Apply. If the AI output is hallucinated or off-base, discard it — the rule-based fallback can be invoked instead, or the incident owner can author from scratch. The citation fence should prevent the worst hallucinations (cited systems not in affected_systems), but the owner is always the editorial authority.

Can I transition an incident back to an earlier state?

Not via the normal status dropdown — the state machine forbids backwards transitions. If you truly need to, a compliance admin can edit the incident status directly (bypassing the transition validator), and the change is logged in the audit trail. Use this sparingly; it’s meant for correction of genuine mis-triage, not regular workflow.

What happens to incidents marked false-positive?

They’re terminal — moved out of the active investigation queue, excluded from SLA banners, excluded from post-mortem fanout. They stay in the incident list (with a grey badge) so the audit trail is complete and you can see that the triage decision was made. Moving an incident to false_positive requires compliance owner approval and a justification.