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

59 lines
6.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.