Pages
Finding Lifecycle v1

Source: c79 finding-lifecycle — state-machine anatomy · 2026-08-25

The contract page. c78's SLA report assumes a resolution workflow the product doesn't have; this page rules it: status is a fold over an append-only event log, and SLA data is a pure function — f(finding events, rubric, now). Mark M's five verbs plus two system verbs, one clock formula, and a worked example computed live from the same code the interactive pages run (app/data/lifecycle-fixtures.ts).

States

Four statuses, derived, never stored — plus acknowledged, a flag on open, not a state (no effect on SLA math; purely evidentiary).

systemdetect
OPENclock running
flag: acknowledgedevent: escalate ↗ trackerevent: enforce ↗ policy
ignore · human · reason
exception · human · reason + expiry
resolve · system · evidence
IGNOREDnot a real findingexits SLA math · audit record persists
ACCEPTED-RISKclock pausedstays in the "tell me why" bucket
RESOLVEDclock stoppedcondition observed cleared
⤴ reopen · human re-judgment · reason
⤴ expiry re-opens · mechanical, no event
⤴ reopen · condition recurs

Verbs

Mark M's five (2026-08-20 call) plus two system verbs. Acting on a finding = appending one event; nothing is ever edited or deleted.

VerbActorEffectClockMandatory
detectsystemCreates the finding; always the first eventstarts
acknowledgehumanSets the flag — who saw it, when; purely evidentiaryno effect
ignorehumanNot a real finding / not applicablestops · exits SLAreason
exceptionhumanReal risk, formally accepted until a datepausesreason · expiry
escalatehumanTicket in YOUR tracker; finding stays open here · seamkeeps runningref
enforcehumanHands to Control Center policy; instance stays open · seamkeeps runningref
resolvesystemEvidence observed the condition clearedstops
reopenbothHuman re-judgment, or the condition recursresumesreason (human)
reclassifyhumanSeverity change; due date re-baselines from original detectrecomputesseverity · reason

Escalate and enforce are events, not states — the finding stays open and the clock keeps running. Routing work elsewhere never stops SLA exposure. The seams stay one line and one stamped ref; ticketing, on-call routing, and remediation tracking are the customer's existing tools, not ours.

The clock

dueTs = detectTs + targetDays + pausedDays

  1. Starts at detection — not at acknowledgment. Exposure begins when the condition exists, not when someone notices.
  2. Exception pauses it — pause credit accrues from the exception to whichever comes first: its expiry, a re-judgment, or resolution.
  3. Expiry re-opens mechanically — no stored event; once now passes expiresTs the fold re-opens the finding with its remaining time preserved.
  4. Reclassify re-baselines — the due date recomputes from the original detect timestamp with the new severity's target (plus any pause credit). Severity changes never erase exposure history.
  5. Advisory severities have no due datedays: null in the rubric (Mark M's low). They age informationally and can never breach.
  6. Rollups count found / resolved / excepted — accepted risk never counts as resolved. Within-SLA % judges only findings with a hard target that concluded or are overdue; excepted findings are excluded while the clock is paused.

The persisted audit record

One append-only type carries the whole workflow. Every derived field on every c79 page is a fold over rows of this shape.

interface FindingEvent {
  id: string          // E-####, monotonic — append-only, never edited
  findingId: string
  ts: string          // ISO
  verb: 'detect' | 'acknowledge' | 'ignore' | 'exception'
      | 'escalate' | 'enforce' | 'resolve' | 'reopen' | 'reclassify'
  actor: string       // 'system' for detect / resolve
  reason?: string     // MANDATORY: ignore · exception · reopen(human) · reclassify
  expiresTs?: string  // exception only
  severity?: Severity // reclassify only — the new value
  ref?: string        // seam stamp: 'tracker:SEC-214' | 'policy:P8'
}

Worked examples

Rendered from a live deriveFinding() call against the fixture event log — the on-page proof that these rulings and the code cannot drift.

F-1027mediumSandbox disabled on harnessopen with a why-line — the register case
2026-07-27 10:05detectsystem
2026-07-28 09:30acknowledgekaiOwner on leave until 2026-08-24; compensating control: session-level file policy active
status openclock 20d / 90ddue 2026-10-25acknowledged
F-0951mediumSession logs retained past policy windowthe pause exemplar — excepted 79d, clock 16d of a 95d span
2026-02-02 10:10detectsystem
2026-02-10 09:00exceptionkaiRetention migration scheduled — logs purge with the Q2 storage changeexpires 2026-05-01
2026-05-08 12:00resolvesystem
status resolvedclock 16d / 90dpaused 79dwithin SLA

Composition notes

  • Resolve is deliberately not one of the five verbs — dispositions are human judgment; resolution is evidence (a re-scan observes the condition cleared). The mock's other pages give it a distinct "simulate: fix detected" affordance. Product-semantics claim, flagged for Mark M's confirmation.
  • Ignore ≠ exception is the crispest line in the machine: "not a real finding" (no expiry, exits the denominator) vs "real risk, formally accepted until a date" (stays on the report with its why-line). Auditors care which bucket an open item lands in.
  • Enforce doesn't close the finding — policy prevents recurrence; the instance still resolves on evidence. The defensible alternative (enforce ⇒ auto-exception) is recorded as an open thread.
  • Rubric is org config — Mark M's real program: critical 14d · high 30d · medium 90d · low advisory. c78's 7/30/90/180 placeholder is superseded here; its module stays untouched while c78 awaits picks.
  • Rulings live in three always-agreeing places: this page, doc comments in lifecycle-fixtures.ts, and c79 CYCLE.md Decisions.

Qpoint Brand Style Guide