TenantAtlas/specs/154-finding-risk-acceptance/data-model.md
2026-03-20 02:05:50 +01:00

5.5 KiB

Data Model: Finding Risk Acceptance Lifecycle

1. FindingException

  • Purpose: Tenant-owned governance aggregate that represents the current accepted-risk exception state for one finding.
  • Ownership: Tenant-owned (workspace_id + tenant_id NOT NULL).
  • Fields:
    • id
    • workspace_id
    • tenant_id
    • finding_id
    • status enum: pending, active, expiring, expired, rejected, revoked, superseded
    • requested_by_user_id
    • owner_user_id
    • approved_by_user_id nullable
    • current_decision_id nullable
    • request_reason text
    • approval_reason text nullable
    • rejection_reason text nullable
    • revocation_reason text nullable
    • requested_at
    • approved_at nullable
    • rejected_at nullable
    • revoked_at nullable
    • effective_from nullable
    • expires_at nullable
    • review_due_at nullable
    • evidence_summary JSONB nullable
    • current_validity_state enum: valid, expiring, expired, revoked, rejected, missing_support
    • created_at, updated_at
  • Relationships:
    • belongs to Finding
    • belongs to Tenant
    • belongs to Workspace
    • belongs to requester User
    • belongs to owner User
    • belongs to approver User
    • has many FindingExceptionDecision
    • has many FindingExceptionEvidenceReference
  • Validation / invariants:
    • workspace_id, tenant_id, and finding_id are always required.
    • finding_id must reference a finding in the same workspace and tenant.
    • At most one current valid active exception may govern one finding at a time.
    • approved_by_user_id must differ from requested_by_user_id in v1.
    • expires_at must be after effective_from when both are present.

2. FindingExceptionDecision

  • Purpose: Append-only historical record of every exception lifecycle decision.
  • Ownership: Tenant-owned (workspace_id + tenant_id NOT NULL).
  • Fields:
    • id
    • workspace_id
    • tenant_id
    • finding_exception_id
    • decision_type enum: requested, approved, rejected, renewal_requested, renewed, revoked
    • actor_user_id
    • reason text nullable
    • effective_from nullable
    • expires_at nullable
    • metadata JSONB nullable
    • decided_at
    • created_at, updated_at
  • Relationships:
    • belongs to FindingException
    • belongs to actor User
  • Validation / invariants:
    • Decision rows are append-only after creation.
    • Decision type must be compatible with the parent exception's lifecycle state.
    • Renewal decisions must not erase prior approval or rejection records.

3. FindingExceptionEvidenceReference

  • Purpose: Structured pointer to evidence used to justify or review the exception.
  • Ownership: Tenant-owned (workspace_id + tenant_id NOT NULL).
  • Fields:
    • id
    • workspace_id
    • tenant_id
    • finding_exception_id
    • source_type string
    • source_id string nullable
    • source_fingerprint string nullable
    • label string
    • summary_payload JSONB nullable
    • measured_at nullable
    • created_at, updated_at
  • Relationships:
    • belongs to FindingException
  • Validation / invariants:
    • References must stay intelligible even if the live source artifact later expires or is removed from active views.
    • summary_payload is bounded, sanitized, and not a raw payload dump.

4. Finding Risk Governance Projection

  • Purpose: Derived truth used by finding detail, tenant exception lists, canonical queues, and downstream evidence/reporting consumers.
  • Derived from:
    • Finding.status
    • FindingException.status
    • exception validity window (effective_from, expires_at)
    • current exception evidence support state
  • Values:
    • ungoverned
    • pending_exception
    • valid_exception
    • expiring_exception
    • expired_exception
    • revoked_exception
    • rejected_exception
    • risk_accepted_without_valid_exception
  • Invariant:
    • Downstream consumers must use this projection, not finding status alone, when determining whether accepted risk is currently governed.

State Transitions

FindingException

  • pending -> active on approval
  • pending -> rejected on rejection
  • active -> expiring when within reminder threshold
  • active|expiring -> expired when expires_at passes
  • active|expiring -> revoked on explicit revoke
  • active|expiring|expired -> superseded when a renewal produces a newer governing decision under the same aggregate semantics

FindingExceptionDecision

  • requested always occurs first
  • approved or rejected resolves a pending request
  • renewal_requested may occur from active, expiring, or expired
  • renewed extends a current or lapsed exception through a new decision
  • revoked ends current validity explicitly

Indexing and Query Needs

  • Composite indexes on (workspace_id, tenant_id, status) for tenant register filtering.
  • Composite indexes on (workspace_id, status, review_due_at) for canonical queue and expiring views.
  • Unique partial index to prevent more than one current valid active exception per finding.
  • Composite index on (finding_id, tenant_id) for finding-detail resolution.

Relationship to Existing Domain Records

  • Finding remains the system of record for the detected issue and status workflow.
  • FindingWorkflowService remains the only allowed path for changing finding status.
  • AuditLog remains the immutable historical event stream for every lifecycle mutation.
  • EvidenceSnapshot and related artifacts remain separate systems of record referenced by exception evidence links.