## 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
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:
OperationRunlifecycle and outcomes remain service-owned viaOperationRunService- 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 andOperationRunrecords. - 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_countsremain 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, andNot 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 historicalsupersededpresentation 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_idas 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
ArtifactTruthPresenterand badge domains rather than inventing a parallel UI truth system.
Phase 1 — Design & Contracts (DONE)
Outputs:
specs/159-baseline-snapshot-truth/data-model.mdspecs/159-baseline-snapshot-truth/contracts/openapi.yamlspecs/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
completetransition. active_snapshot_idremains the operator-facing current-snapshot pointer but only for complete snapshots; compare and UI consumers must use a shared effective-snapshot resolution helper.- Historical
supersededstatus 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_atfailed_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
buildingbefore item persistence. - Keep the existing deterministic deduplication/chunked insert strategy, but add explicit finalization after item persistence completes.
- Only mark the snapshot
completeafter the completion proof passes:- persisted item count matches expected deduplicated item count
- no unresolved assembly error occurred
- finalization step completed successfully
- Mark the snapshot
incompletewhen 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_idonly 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.phpforbuilding -> completesemantics. - 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
CompareBaselineToTenantJobbefore 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.phpfor 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
ArtifactTruthPresenterand 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
completeonly when the first matching proof rule in the spec decision table succeeds:- count proof:
summary_jsonb.total_itemsor 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
- count proof:
- Classify contradictory, partial, or null evidence as
incompletewith no tie-breaker that can elevate it tocomplete. - 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
OperationRunServicefor 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