## 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
487 lines
22 KiB
Markdown
487 lines
22 KiB
Markdown
# 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)
|
|
|
|
```text
|
|
specs/159-baseline-snapshot-truth/
|
|
├── plan.md
|
|
├── research.md
|
|
├── data-model.md
|
|
├── quickstart.md
|
|
├── contracts/
|
|
│ └── openapi.yaml
|
|
└── checklists/
|
|
└── requirements.md
|
|
```
|
|
|
|
### Source Code (repository root)
|
|
|
|
```text
|
|
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
|