TenantAtlas/specs/159-baseline-snapshot-truth/plan.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

22 KiB

Implementation Plan: 159 — BaselineSnapshot Artifact Truth & Downstream Consumption Guards

Branch: 159-baseline-snapshot-truth | Date: 2026-03-23 | Spec: specs/159-baseline-snapshot-truth/spec.md Input: Feature specification from specs/159-baseline-snapshot-truth/spec.md

Summary

Make BaselineSnapshot explicitly authoritative for its own usability by (1) adding a first-class lifecycle state and completion metadata to the artifact, (2) finalizing capture with building -> complete|incomplete semantics while deriving historical superseded presentation status without mutating immutable snapshot records, (3) centralizing consumability and effective-current-snapshot resolution so compare and profile truth only use complete snapshots, and (4) reusing the existing artifact-truth, badge, and Filament surface patterns so operators can distinguish run outcome from artifact usability.

Technical Context

Language/Version: PHP 8.4
Primary Dependencies: Laravel 12, Filament v5, Livewire v4
Storage: PostgreSQL (via Sail)
Testing: Pest v4 (PHPUnit 12)
Target Platform: Docker (Laravel Sail), deployed containerized on Dokploy
Project Type: Web application (Laravel)
Performance Goals: Preserve current chunked snapshot-item persistence characteristics and reject non-consumable snapshots before expensive compare/drift work
Constraints:

  • OperationRun lifecycle and outcomes remain service-owned via OperationRunService
  • Capture and compare keep the existing Ops-UX 3-surface feedback contract
  • Workspace/tenant isolation and 404/403 semantics remain unchanged
  • No new Microsoft Graph contracts or synchronous render-time remote calls
  • Legacy snapshots must be backfilled conservatively; ambiguous history must fail safe Scale/Scope: Workspace-owned baseline profiles may accumulate many snapshots and each snapshot may hold many items via chunked inserts; this feature touches capture jobs, compare services/jobs, artifact-truth presentation, Filament admin/tenant surfaces, migrations, and focused baseline test suites

Constitution Check

GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.

  • Inventory-first: PASS — Inventory remains the last-observed truth. BaselineSnapshot remains an explicit immutable capture artifact whose lifecycle becomes explicit without rewriting prior terminal states.
  • Read/write separation: PASS — this feature mutates TenantPilot-owned baseline records only; capture/compare start surfaces already require confirmation and auditability remains intact.
  • Graph contract path: PASS — no new Graph endpoints or contract-registry changes are introduced.
  • Deterministic capabilities: PASS — capability checks remain in existing registries/resolvers; no new raw capability strings.
  • Workspace + tenant isolation: PASS — BaselineProfile/BaselineSnapshot remain workspace-owned, compare execution remains tenant-owned, and current cross-plane rules remain unchanged.
  • Canonical-view scope: PASS — Monitoring run detail stays canonical-view scoped, defaults to active tenant context when present, and requires workspace plus referenced-tenant entitlement before revealing tenant-owned baseline runs.
  • Run observability (OperationRun): PASS — capture/compare continue using canonical queued jobs and OperationRun records.
  • Ops-UX 3-surface feedback: PASS — no additional toast/notification surfaces are introduced.
  • Ops-UX lifecycle: PASS — run transitions remain through OperationRunService; artifact lifecycle is added on BaselineSnapshot, not as a substitute for run lifecycle.
  • Ops-UX summary counts: PASS — artifact completeness moves to BaselineSnapshot fields; summary_counts remain numeric-only execution metrics.
  • Ops-UX guards: PASS — focused regression tests will cover that capture finalization and compare blocking do not regress service-owned run transitions.
  • Badge semantics (BADGE-001): PASS — lifecycle/usability presentation will extend the centralized badge/artifact-truth system instead of creating ad-hoc mappings.
  • UI naming (UI-NAMING-001): PASS — operator wording stays aligned to Capture baseline, Compare now, Building, Complete, Incomplete, Superseded, and Not usable for compare.
  • Operator surfaces (OPSURF-001): PASS — run outcome, snapshot lifecycle, snapshot usability, and compare readiness will be shown as distinct dimensions where relevant.
  • Filament UI Action Surface Contract: PASS — no new resource topology is introduced; existing immutable-snapshot exemptions remain valid.
  • Filament UX-001 layout: PASS — this feature changes labels, badges, and enablement logic on existing pages/resources rather than introducing new form layouts.
  • UI-STD-001: PASS — modified Baseline Profiles and Baseline Snapshots list surfaces will be reviewed against docs/product/standards/list-surface-review-checklist.md.

Project Structure

Documentation (this feature)

specs/159-baseline-snapshot-truth/
├── plan.md
├── research.md
├── data-model.md
├── quickstart.md
├── contracts/
│   └── openapi.yaml
└── checklists/
    └── requirements.md

Source Code (repository root)

app/
├── Filament/
│   ├── Pages/
│   │   └── BaselineCompareLanding.php
│   └── Resources/
│       ├── BaselineProfileResource.php
│       ├── BaselineSnapshotResource.php
│       ├── BaselineProfileResource/Pages/ViewBaselineProfile.php
│       └── BaselineSnapshotResource/Pages/ViewBaselineSnapshot.php
├── Jobs/
│   ├── CaptureBaselineSnapshotJob.php
│   └── CompareBaselineToTenantJob.php
├── Models/
│   ├── BaselineProfile.php
│   ├── BaselineSnapshot.php
│   ├── BaselineSnapshotItem.php
│   └── OperationRun.php
├── Services/
│   └── Baselines/
│       ├── BaselineCaptureService.php
│       ├── BaselineCompareService.php
│       ├── BaselineSnapshotIdentity.php
│       └── SnapshotRendering/
├── Support/
│   ├── Baselines/
│   ├── Badges/
│   └── Ui/GovernanceArtifactTruth/
database/
├── migrations/
└── factories/
tests/
├── Feature/Baselines/
├── Feature/Filament/
└── Unit/Badges/

Structure Decision: Web application (Laravel 12). All work stays within the existing baseline jobs/services/models, centralized support layers for badges and artifact truth, the existing Filament resources/pages, one schema migration, and focused Pest coverage.

Complexity Tracking

No constitution violations are required for this feature.

Phase 0 — Outline & Research (DONE)

Outputs:

  • specs/159-baseline-snapshot-truth/research.md

Key decisions captured:

  • Use a single BaselineSnapshot lifecycle enum in V1 (building, complete, incomplete) and derive historical superseded presentation status from effective-truth resolution instead of mutating immutable snapshots.
  • Keep the existing chunked snapshot-item persistence strategy, but add explicit finalization and completion proof rather than attempting a large transactional rewrite.
  • Treat baseline_profiles.active_snapshot_id as a cached pointer to the latest complete snapshot only; effective truth resolves from complete snapshots, not from the latest attempted snapshot.
  • Backfill legacy snapshots conservatively using persisted item counts, summary metadata, and producing-run context where available; ambiguous snapshots default to non-consumable.
  • Reuse the existing ArtifactTruthPresenter and badge domains rather than inventing a parallel UI truth system.

Phase 1 — Design & Contracts (DONE)

Outputs:

  • specs/159-baseline-snapshot-truth/data-model.md
  • specs/159-baseline-snapshot-truth/contracts/openapi.yaml
  • specs/159-baseline-snapshot-truth/quickstart.md

Design highlights:

  • BaselineSnapshot becomes the authoritative artifact-truth model with lifecycle state, completion timestamps, failure timestamps, and completion metadata sufficient to justify a complete transition.
  • active_snapshot_id remains the operator-facing current-snapshot pointer but only for complete snapshots; compare and UI consumers must use a shared effective-snapshot resolution helper.
  • Historical superseded status is derived in presentation and truth resolvers from the existence of a newer effective complete snapshot, not persisted as a mutable lifecycle transition.
  • Compare blocking happens both before enqueue and at job execution time so an explicit override or stale context cannot bypass the consumability rules.
  • Explicit compare overrides to older complete snapshots that are no longer the effective current truth are blocked in V1 and return a dedicated operator-safe reason.
  • Snapshot lifecycle display reuses centralized badge and artifact-truth presentation infrastructure so run outcome and artifact usability remain separate but consistent across list, detail, compare, and monitoring surfaces.

Phase 1 — Agent Context Update (REQUIRED)

Run:

  • .specify/scripts/bash/update-agent-context.sh copilot

Constitution Check — Post-Design Re-evaluation

  • PASS — Design keeps snapshot truth local to BaselineSnapshot and does not expand into a generic artifact framework.
  • PASS — No new Graph calls or contract-registry changes.
  • PASS — Existing run observability patterns remain intact and artifact state does not bypass OperationRunService.
  • PASS — Authorization, destructive confirmation, and operator-surface contracts remain within existing patterns.
  • PASS — Badge and artifact-truth semantics remain centralized.

Phase 2 — Implementation Plan

Step 1 — Schema and domain lifecycle semantics

Goal: implement FR-001 through FR-006, FR-015, and FR-021.

Changes:

  • Add BaselineSnapshot lifecycle/completion columns in a new migration:
    • lifecycle state
    • completed_at
    • failed_at
    • completion metadata JSON for integrity proof and failure reason
  • Add BaselineSnapshot status enum/support type and model helpers/scopes:
    • lifecycle label helpers
    • isConsumable()
    • transition helpers or a dedicated domain service
    • scopes for complete/current-eligible snapshots
  • Add centralized effective-snapshot and consumability resolution in the baselines domain layer.

Tests:

  • Add unit/domain tests for lifecycle-to-consumability rules.
  • Add badge/artifact-truth tests for the new snapshot lifecycle states.

Step 2 — Capture finalization and current-snapshot rollover

Goal: implement FR-002 through FR-005, FR-009 through FR-014, and the primary P1 regression path.

Changes:

  • Update capture flow so snapshot creation begins in building before item persistence.
  • Keep the existing deterministic deduplication/chunked insert strategy, but add explicit finalization after item persistence completes.
  • Only mark the snapshot complete after the completion proof passes:
    • persisted item count matches expected deduplicated item count
    • no unresolved assembly error occurred
    • finalization step completed successfully
  • Mark the snapshot incomplete when capture fails after row creation, including partial-item scenarios.
  • Persist completion-proof metadata and lifecycle context needed for later operator truth and legacy backfill decisions, including producing-run linkage and best-available incomplete reason data.
  • Advance active_snapshot_id only when the new snapshot is complete.
  • Derive the previously effective complete snapshot as historical or superseded in truth presentation only after the new snapshot becomes complete.

Tests:

  • Update tests/Feature/Baselines/BaselineCaptureTest.php for building -> complete semantics.
  • Add a partial-write failure regression test where a snapshot row exists but never becomes consumable.
  • Add retry/idempotency tests ensuring duplicate logical subjects do not produce a falsely complete snapshot.

Step 3 — Effective snapshot resolution and compare guards

Goal: implement FR-007 through FR-010 and FR-018.

Changes:

  • Update compare start service to resolve the effective snapshot from the latest complete snapshot when no explicit override is supplied.
  • Validate explicit snapshot overrides against the same consumability rules.
  • Re-check snapshot consumability inside CompareBaselineToTenantJob before any normal compare work starts.
  • Introduce clear reason codes/messages for:
    • no consumable snapshot available
    • snapshot still building
    • snapshot incomplete
    • snapshot no longer effective for current truth because a newer complete snapshot is active
  • Ensure compare stats and related widgets derive availability from effective consumable truth, not raw snapshot existence.

Tests:

  • Update tests/Feature/Baselines/BaselineComparePreconditionsTest.php for blocked building/incomplete snapshots.
  • Add tests covering fallback to the last complete snapshot when the latest attempt is incomplete.
  • Add execution-path tests proving the compare job refuses non-consumable snapshots even if queued context contains a snapshot id.
  • Add regression coverage proving modified compare and capture entry points preserve confirmation and 404 or 403 authorization behavior while lifecycle messaging changes.

Step 4 — UI semantic corrections and centralized presentation

Goal: implement FR-017, FR-019, and FR-020.

Changes:

  • Update ArtifactTruthPresenter and the relevant badge domain registrations for BaselineSnapshot lifecycle truth.
  • Update Baseline Profile detail and list surfaces to distinguish:
    • latest complete snapshot
    • latest attempted snapshot when different
    • compare availability and current baseline availability
  • Update Baseline Snapshot list/detail to show explicit lifecycle/usability semantics.
  • Update Baseline Compare landing and related widgets so compare unavailability explains the operator next step.
  • Update Monitoring/Operation Run detail presentation for baseline capture/compare so run outcome and produced snapshot lifecycle are clearly separate.

Tests:

  • Update or add Filament page/resource tests for operator-safe messaging and state display across baseline profile, baseline snapshot, and compare landing surfaces.
  • Add dedicated Monitoring run-detail truth-surface coverage so run outcome and artifact truth remain separately verifiable.
  • Extend artifact-truth presenter tests for historical/superseded presentation and incomplete snapshot rendering.

Step 5 — Conservative historical backfill

Goal: implement FR-016 without overstating legacy trust.

Changes:

  • Backfill existing snapshots as complete only when the first matching proof rule in the spec decision table succeeds:
    • count proof: summary_jsonb.total_items or equivalent expected-item metadata matches persisted item count
    • producing-run success proof: producing-run outcome proves successful finalization and expected-item and persisted-item counts reconcile
    • proven empty capture proof: producing-run outcome proves successful finalization for the recorded scope and zero items were expected and persisted
  • Classify contradictory, partial, or null evidence as incomplete with no tie-breaker that can elevate it to complete.
  • Leave operator guidance pointing to recapture when legacy truth is unavailable.

Tests:

  • Add migration/backfill tests for count-proof complete, proven-empty complete, and ambiguous legacy rows.

Step 6 — Focused regression and guard coverage

Goal: satisfy the spec test acceptance while keeping CI scope targeted.

Changes:

  • Update the most relevant existing baseline suites instead of creating broad duplicate coverage.
  • Add new focused tests in:
    • tests/Feature/Baselines/
    • tests/Feature/Filament/
    • tests/Unit/Badges/
  • Add focused lifecycle-auditability coverage proving producing-run linkage and best-available incomplete reasons remain queryable after complete and incomplete transitions, and that historical/superseded presentation is derived from effective-truth resolution.
  • Add a dedicated Ops-UX guard proving baseline capture/compare code does not bypass OperationRunService for status/outcome transitions and does not emit non-canonical terminal operation notifications.
  • Preserve existing guard tests for Filament action surfaces and Ops-UX patterns.

Tests:

  • Minimum focused run set:

    • baseline capture lifecycle tests
    • compare preconditions/guard tests
    • compare stats tests
    • artifact-truth/badge tests
    • affected Filament surface tests snapshot: $ref: '#/components/schemas/BaselineSnapshotTruth' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound'

    /workspaces/{workspaceId}/baseline-profiles/{profileId}/effective-snapshot: get: tags: [BaselineProfiles] summary: Resolve effective current baseline snapshot description: Returns the latest complete snapshot that is valid as current baseline truth for the profile. parameters: - $ref: '#/components/parameters/WorkspaceId' - $ref: '#/components/parameters/ProfileId' responses: '200': description: Effective current baseline truth resolved content: application/json: schema: type: object required: [profileId, effectiveSnapshot] properties: profileId: type: integer effectiveSnapshot: oneOf: - $ref: '#/components/schemas/BaselineSnapshotTruth' - type: 'null' latestAttemptedSnapshot: oneOf: - $ref: '#/components/schemas/BaselineSnapshotTruth' - type: 'null' compareAvailability: $ref: '#/components/schemas/CompareAvailability' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound'

    /workspaces/{workspaceId}/baseline-snapshots/{snapshotId}: get: tags: [BaselineSnapshots] summary: Read baseline snapshot truth parameters: - $ref: '#/components/parameters/WorkspaceId' - $ref: '#/components/parameters/SnapshotId' responses: '200': description: Snapshot truth returned content: application/json: schema: $ref: '#/components/schemas/BaselineSnapshotTruth' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound'

    /tenants/{tenantId}/baseline-compares: post: tags: [BaselineCompare] summary: Start baseline compare description: Starts compare only when the resolved or explicitly selected snapshot is consumable. parameters: - $ref: '#/components/parameters/TenantId' requestBody: required: false content: application/json: schema: type: object properties: baselineSnapshotId: type: integer nullable: true responses: '202': description: Compare accepted and queued content: application/json: schema: type: object required: [operationRunId, snapshot] properties: operationRunId: type: integer snapshot: $ref: '#/components/schemas/BaselineSnapshotTruth' '409': description: Compare blocked because no consumable snapshot is available content: application/json: schema: $ref: '#/components/schemas/CompareAvailability' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound'

components: parameters: WorkspaceId: name: workspaceId in: path required: true schema: type: integer ProfileId: name: profileId in: path required: true schema: type: integer SnapshotId: name: snapshotId in: path required: true schema: type: integer TenantId: name: tenantId in: path required: true schema: type: integer

responses: Forbidden: description: Member lacks the required capability NotFound: description: Workspace or tenant scope is not entitled, or the record is not visible in that scope

schemas: BaselineSnapshotTruth: type: object required: - id - workspaceId - baselineProfileId - lifecycleState - consumable - capturedAt properties: id: type: integer workspaceId: type: integer baselineProfileId: type: integer lifecycleState: type: string enum: [building, complete, incomplete, superseded] consumable: type: boolean capturedAt: type: string format: date-time completedAt: type: string format: date-time nullable: true failedAt: type: string format: date-time nullable: true supersededAt: type: string format: date-time nullable: true completionMeta: type: object additionalProperties: true nullable: true usabilityLabel: type: string examples: [Complete, Incomplete, Building, Superseded, Not usable for compare] reasonCode: type: string nullable: true reasonMessage: type: string nullable: true

CompareAvailability:
  type: object
  required:
    - allowed
  properties:
    allowed:
      type: boolean
    effectiveSnapshotId:
      type: integer
      nullable: true
    latestAttemptedSnapshotId:
      type: integer
      nullable: true
    reasonCode:
      type: string
      nullable: true
      enum:
        - no_consumable_snapshot
        - snapshot_building
        - snapshot_incomplete
        - snapshot_superseded
        - invalid_snapshot_selection
    reasonMessage:
      type: string
      nullable: true
    nextAction:
      type: string
      nullable: true
      examples:
        - Wait for capture completion
        - Re-run baseline capture
        - Review failed capture