TenantAtlas/specs/159-baseline-snapshot-truth/research.md
ahmido 8426741068 feat: add baseline snapshot truth guards (#189)
## Summary
- add explicit BaselineSnapshot lifecycle truth with conservative backfill and a shared truth resolver
- block baseline compare from building, incomplete, or superseded snapshots and align workspace/tenant UI truth surfaces with effective snapshot state
- surface artifact truth separately from operation outcome across baseline profile, snapshot, compare, and operation run pages

## Testing
- integrated browser smoke test on the active feature surfaces
- `vendor/bin/sail artisan test --compact tests/Feature/Filament/BaselineSnapshotTruthSurfaceTest.php tests/Feature/Filament/BaselineProfileCompareStartSurfaceTest.php`
- targeted baseline lifecycle and compare guard coverage added in Pest
- `vendor/bin/sail bin pint --dirty --format agent`

## Notes
- Livewire v4 compliance preserved
- no panel provider registration changes were needed; Laravel 12 providers remain in `bootstrap/providers.php`
- global search remains disabled for the affected baseline resources by design
- destructive actions remain confirmation-gated; capture and compare actions keep their existing authorization and confirmation behavior
- no new panel assets were added; existing deploy flow for `filament:assets` is unchanged

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #189
2026-03-23 11:32:00 +00:00

6.7 KiB
Raw Blame History

Research: BaselineSnapshot Artifact Truth & Downstream Consumption Guards

Decision 1: Model BaselineSnapshot with a single persisted lifecycle enum and derived historical truth in V1

  • Decision: Add a first-class BaselineSnapshot lifecycle state with building, complete, and incomplete, and derive historical superseded truth in presentation/resolution instead of persisting it as a lifecycle mutation.
  • Rationale: The spec is intentionally narrow to BaselineSnapshot, but the constitution requires snapshots to remain immutable. A persisted three-state lifecycle is enough to separate artifact truth from run truth, while derived historical status lets the UI communicate that a snapshot is no longer current without mutating immutable artifacts.
  • Alternatives considered:
    • Separate lifecycle and historical-status enums. Rejected for V1 because it adds more schema and UI complexity than the spec requires.
    • Persisted superseded lifecycle mutation. Rejected because it conflicts with the constitution rule that snapshots and backups remain immutable.
    • Generic polymorphic artifact-state framework. Rejected because the spec explicitly limits scope to BaselineSnapshot.

Decision 2: Keep staged item persistence and add explicit finalization instead of rewriting capture into one large transaction

  • Decision: Preserve the existing deduplicated, chunked item-persistence strategy in CaptureBaselineSnapshotJob, but make snapshot creation begin in building and add an explicit finalization checkpoint before the snapshot can become complete.
  • Rationale: The current job already deduplicates item payloads and inserts items in chunks. Replacing that with one large transaction would be a broader persistence rewrite with higher regression risk. The core integrity defect is not the existence of staged persistence; it is the lack of explicit artifact finalization and unusable-state marking when staged persistence fails.
  • Alternatives considered:
    • Wrap snapshot row plus all item writes in one transaction. Rejected because it is a larger behavioral change, can increase lock duration, and is not necessary to satisfy the artifact-truth invariant.
    • Delete snapshot rows on failure. Rejected because the spec allows retaining incomplete artifacts for diagnostics and auditability.

Decision 3: Treat active_snapshot_id as a cached pointer to the latest complete snapshot only

  • Decision: Keep baseline_profiles.active_snapshot_id, but tighten its semantics so it may point only to a complete snapshot. Effective baseline truth resolves from complete snapshots, not from the latest attempt.
  • Rationale: Existing services, stats objects, and Filament resources already use active_snapshot_id heavily. Replacing it wholesale would create unnecessary churn. Tightening its meaning and adding a shared effective-snapshot resolver keeps compatibility while enforcing the new truth rule.
  • Alternatives considered:
    • Remove active_snapshot_id and resolve current truth only by querying snapshots. Rejected because it would require broader UI/service refactoring and lose a useful current-pointer optimization.
    • Leave active_snapshot_id semantics unchanged and only add compare-time checks. Rejected because profile truth and UI would still silently advance to incomplete snapshots.

Decision 4: Use a centralized consumability/effective-truth service or helper for capture, compare, and UI

  • Decision: Introduce one authoritative domain path for determining whether a snapshot is consumable and which snapshot is the effective current baseline for a profile.
  • Rationale: The current code assumes snapshot validity in multiple places: capture promotion, compare start, compare execution, stats, and UI. Duplicated state checks would drift. A central resolver keeps the rule enforceable and testable.
  • Alternatives considered:
    • Inline status === complete checks in each service/page. Rejected because that duplicates rules and invites inconsistent handling of superseded or legacy snapshots.
    • Put all logic only on the Eloquent model. Rejected because effective-truth resolution involves profile-level fallback rules, legacy handling, and operator-safe reason codes that fit better in a domain service/helper.

Decision 5: Backfill legacy snapshots conservatively using proof, not assumption

  • Decision: Backfill historical snapshots as complete only when completion can be proven from persisted item counts, summary_jsonb, and producing-run context where available. Mark ambiguous rows incomplete.
  • Rationale: The defect being fixed is silent trust in partial artifacts. Blindly backfilling historical rows as complete would reintroduce the same governance-integrity problem under a new label. Conservative backfill is aligned with the spec and with the platforms fail-safe posture.
  • Alternatives considered:
    • Mark every legacy snapshot complete. Rejected because it makes historical ambiguity look trustworthy.
    • Mark every legacy snapshot incomplete. Rejected because it would unnecessarily invalidate proven-good history and create avoidable recapture churn.

Decision 6: Completion proof must allow valid empty captures only when emptiness is explicitly proven

  • Decision: Allow a snapshot to become complete with zero items only when the capture completed cleanly and the scope/result proves there were zero subjects to capture. Otherwise, zero items are not enough to infer completeness.
  • Rationale: Existing tests allow empty captures for zero-subject scopes, and a zero-subject baseline can still be a valid outcome. The important distinction is between intentionally empty and ambiguously incomplete.
  • Alternatives considered:
    • Require at least one item for every complete snapshot. Rejected because it would make legitimate empty captures impossible.
    • Treat any zero-item snapshot as complete. Rejected because it would misclassify failures that occurred before meaningful assembly.

Decision 7: Reuse the existing artifact-truth and badge infrastructure for presentation

  • Decision: Extend the existing ArtifactTruthPresenter and badge registration patterns for BaselineSnapshot lifecycle and usability rather than creating a new snapshot-specific presentation system.
  • Rationale: The repo already centralizes artifact truth across EvidenceSnapshot, TenantReview, ReviewPack, and OperationRun. Reusing that system keeps state labels, colors, and operator explanations consistent and satisfies BADGE-001.
  • Alternatives considered:
    • Add ad-hoc state rendering in each Filament resource/page. Rejected because it would violate badge centralization and drift across surfaces.
    • Introduce a separate baseline-only presenter. Rejected because the existing artifact-truth envelope already models the required dimensions.