# Data Model: Evidence Domain Foundation ## 1. EvidenceSnapshot - **Purpose**: Immutable, tenant-scoped evidence package captured at a specific point in time and reused by downstream consumers. - **Ownership**: Tenant-owned (`workspace_id` + `tenant_id` NOT NULL) - **Fields**: - `id` - `workspace_id` - `tenant_id` - `operation_run_id` nullable - `initiated_by_user_id` nullable - `fingerprint` nullable, 64-char SHA-256 - `previous_fingerprint` nullable - `status` enum: `queued`, `generating`, `active`, `superseded`, `expired`, `failed` - `completeness_state` enum: `complete`, `partial`, `missing`, `stale` - `summary` JSONB - `generated_at` timestampTz nullable - `expires_at` timestampTz nullable - `created_at`, `updated_at` - **Relationships**: - belongs to `Workspace` - belongs to `Tenant` - belongs to `OperationRun` - belongs to initiator `User` - has many `EvidenceSnapshotItem` - **Validation / invariants**: - `workspace_id` and `tenant_id` required on every row - successful snapshots are immutable in content after `status` becomes `active` - one active snapshot per tenant evidence scope in v1 - identical fingerprint for the same tenant scope reuses the existing non-expired snapshot ## 2. EvidenceSnapshotItem - **Purpose**: One curated evidence dimension inside an evidence snapshot. - **Ownership**: Tenant-owned (`workspace_id` + `tenant_id` NOT NULL) - **Fields**: - `id` - `evidence_snapshot_id` - `workspace_id` - `tenant_id` - `dimension_key` string - `state` enum: `complete`, `partial`, `missing`, `stale` - `required` boolean - `source_kind` string - `source_record_type` nullable string - `source_record_id` nullable string - `source_fingerprint` nullable string - `measured_at` timestampTz nullable - `freshness_at` timestampTz nullable - `summary_payload` JSONB - `sort_order` integer default 0 - `created_at`, `updated_at` - **Relationships**: - belongs to `EvidenceSnapshot` - **Validation / invariants**: - unique per snapshot: `(evidence_snapshot_id, dimension_key)` - `workspace_id`/`tenant_id` must match the parent snapshot - `summary_payload` stores curated summary/reference data only; no secrets, tokens, or raw Graph dumps ## 3. Evidence dimensions in first rollout - `findings_summary` - `permission_posture` - `entra_admin_roles` - `baseline_drift_posture` - `operations_summary` Each dimension stores: - inclusion state (`complete|partial|missing|stale`) - source provenance (record type, identifier, fingerprint) - freshness timestamp - dimension-specific summary payload ## 4. Derived completeness rules - **Snapshot overall completeness precedence**: - `missing` if any required dimension is missing - `stale` if no required dimensions are missing and at least one required dimension is stale - `partial` if no required dimensions are missing or stale and at least one required dimension is partial - `complete` only when all required dimensions are complete ## 5. Lifecycle transitions ### EvidenceSnapshot status - `queued -> generating` - `generating -> active` - `generating -> failed` - `active -> superseded` - `active -> expired` - `superseded -> expired` No transition may mutate snapshot items after `active` is reached. ## 6. Downstream resolution contract ### EvidenceResolutionRequest - **Purpose**: Explicit request from a downstream consumer for a tenant evidence package. - **Fields**: - `workspace_id` - `tenant_id` - `snapshot_id` nullable explicit selection - `required_dimensions` array - `allow_stale` boolean default `false` ### EvidenceResolutionResult - **Outcomes**: - `resolved` with snapshot id and eligible dimensions - `missing_snapshot` - `snapshot_ineligible` with missing/stale dimension reasons