TenantAtlas/specs/153-evidence-domain-foundation/research.md
ahmido a74ab12f04 feat: implement evidence domain foundation (#183)
## Summary
- add the Evidence Snapshot domain with immutable tenant-scoped snapshots, per-dimension items, queued generation, audit actions, badge mappings, and Filament list/detail surfaces
- add the workspace evidence overview, capability and policy wiring, Livewire update-path hardening, and review-pack integration through explicit evidence snapshot resolution
- add spec 153 artifacts, migrations, factories, and focused Pest coverage for evidence, review-pack reuse, authorization, action-surface regressions, and audit behavior

## Testing
- `vendor/bin/sail artisan test --compact --stop-on-failure`
- `CI=1 vendor/bin/sail artisan test --compact`
- `vendor/bin/sail bin pint --dirty --format agent`

## Notes
- branch: `153-evidence-domain-foundation`
- commit: `b7dfa279`
- spec: `specs/153-evidence-domain-foundation/`

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #183
2026-03-19 13:32:52 +00:00

5.6 KiB

Research: Evidence Domain Foundation

Decision 1: Model evidence as immutable snapshots plus per-dimension items

  • Decision: Create a dedicated evidence_snapshots root record and a child evidence_snapshot_items table keyed by evidence dimension.
  • Rationale: The candidate is a curation and completeness layer, not a stakeholder export. A normalized snapshot root plus dimension items supports immutable capture, completeness per dimension, tenant/workspace-safe filtering, and downstream reuse without forcing every consumer to parse a single monolithic blob.
  • Alternatives considered:
    • Reuse review_packs as the evidence domain: rejected because ReviewPack is export-centric, binary-file oriented, and already downstream-facing.
    • Store one large JSON blob per snapshot: rejected because it weakens queryability, completeness filtering, and per-dimension provenance.

Decision 2: Snapshot generation is DB-only queued work with a new OperationRun type

  • Decision: Generate evidence snapshots through a queued OperationRun such as tenant.evidence.snapshot.generate, created and updated exclusively through OperationRunService.
  • Rationale: Snapshot creation aggregates existing internal artifacts and can exceed synchronous request budgets. Existing patterns in ReviewPackService, GenerateReviewPackJob, ScanEntraAdminRolesJob, and OperationRunService already solve dedupe, active-run visibility, and canonical operator feedback.
  • Alternatives considered:
    • Inline synchronous snapshot creation: rejected because it would violate existing operations UX expectations and create unpredictable request latency.
    • DB-only action with no OperationRun: rejected because snapshot generation is operationally relevant and must remain observable and auditable.

Decision 3: Reuse existing internal artifacts as source references, not as copied raw payload dumps

  • Decision: Snapshot items store curated summaries plus source references and source fingerprints to StoredReport, Finding summaries, baseline/drift summaries, and recent OperationRun rollups.
  • Rationale: The foundation should preserve reproducible evidence composition while minimizing duplication. Existing artifacts already carry domain truth; the snapshot needs to record what was included, how fresh it was, and how complete it was.
  • Alternatives considered:
    • Deep-copy every source payload in full: rejected because it increases storage cost, duplicates secrets/redaction risks, and blurs the line between curated evidence and archival dump.
    • Store only foreign keys with no captured summary: rejected because snapshots would become unintelligible if source artifacts later changed or expired.

Decision 4: Dedupe identical evidence state by fingerprint, but supersede changed active snapshots

  • Decision: Use a deterministic fingerprint over the included evidence state. If the fingerprint matches an existing non-expired snapshot for the same tenant scope, reuse it. If the fingerprint changes, create a new snapshot and mark the previously active snapshot as superseded.
  • Rationale: This preserves immutability and prevents duplicate noise while still producing a historical chain when evidence actually changes. The pattern aligns with existing StoredReport and ReviewPack fingerprint behavior.
  • Alternatives considered:
    • Always create a new snapshot on every request: rejected because it creates operator noise and unnecessary storage churn.
    • Update the existing snapshot in place: rejected because it destroys the foundation's core immutability guarantee.

Decision 5: Downstream consumers must resolve explicit snapshots, not silently fall back to live data

  • Decision: Introduce a resolver contract that returns an explicit eligible snapshot or an explicit “no snapshot available” result. Downstream consumers in the first slice use this contract instead of reconstructing the same evidence set from raw live records.
  • Rationale: The foundation only creates real architectural leverage if downstream consumers depend on it intentionally. Silent fallback to live sources would hide evidence gaps and undermine reproducibility.
  • Alternatives considered:
    • Let downstream consumers fall back to live assembly when no snapshot exists: rejected because it preserves the current ad hoc architecture and weakens trust.
    • Force all consumers to always require a snapshot immediately: rejected because first-slice adoption needs staged rollout and explicit absence handling.

Decision 6: Use a tenant-context resource plus a workspace overview page for first-slice visibility

  • Decision: Provide a tenant-scoped evidence snapshot list/detail surface and a workspace-scoped overview that summarizes evidence completeness across authorized tenants.
  • Rationale: The spec requires both immutable capture and pre-report completeness visibility. Existing patterns in ReviewPackResource, TenantReviewPackCard, and the canonical AuditLog page show how tenant detail and workspace-wide review can coexist safely.
  • Implementation note: The implemented canonical overview lives at /admin/evidence/overview, while the tenant-context Filament resource uses the evidence slug directly rather than a nested /snapshots segment. Optional overview prefilters are carried only for entitled tenant ids.
  • Alternatives considered:
    • No UI until downstream consumers exist: rejected because completeness visibility is a first-class user story, not a later polish layer.
    • Tenant-only UI with no workspace overview: rejected because MSP/workspace operators need safe aggregate visibility before tenant-by-tenant drill-down.