TenantAtlas/specs/075-verification-v1-5/research.md
ahmido 53dc89e6ef Spec 075: Verification Checklist Framework V1.5 (fingerprint + acknowledgements) (#93)
Implements Spec 075 (V1.5) on top of Spec 074.

Highlights
- Deterministic report fingerprint (sha256) + previous_report_id linkage
- Viewer change indicator: "No changes" vs "Changed" when previous exists
- Check acknowledgements (fail|warn|block) with capability-first auth, confirmation, and audit event
- Verify-step UX polish (issues-first, primary CTA)

Testing
- Focused Pest coverage for fingerprint, previous resolver, change indicator, acknowledgements, badge semantics, DB-only viewer guard.

Notes
- Viewing remains DB-only (no external calls while rendering).

Co-authored-by: Ahmed Darrazi <ahmeddarrazi@MacBookPro.fritz.box>
Reviewed-on: #93
2026-02-05 21:44:19 +00:00

120 lines
4.9 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: Verification Checklist Framework V1.5 (075)
**Date**: 2026-02-05
**Phase**: Phase 0 (Foundational Research)
**Status**: Complete
---
## Decisions
### D-075-001 — Canonical storage for report + metadata
**Decision**: Store the verification report (including `fingerprint` and `previous_report_id`) inside `operation_runs.context.verification_report` (JSONB), consistent with 074.
**Rationale**:
- Viewer surfaces must be DB-only at render time (constitution: Operations / Run Observability Standard).
- `OperationRun` is already the canonical operational record and stable viewer entry point.
- Adds supportability metadata without introducing a new top-level report table.
**Alternatives considered**:
- Dedicated `verification_reports` table: rejected for v1.5 to avoid new query/index surfaces; revisit if we need global querying across reports.
---
### D-075-002 — Report identity + “previous report” resolution
**Decision**: Resolve `previous_report_id` by querying the most recent earlier `OperationRun` whose **run identity** matches exactly (flow/type, tenant, workspace, provider connection where applicable).
**Rationale**:
- The existing `OperationRunService::ensureRunWithIdentity()` + `run_identity_hash` already defines the dedupe boundary.
- Matches the specs clarified rule: `provider_connection_id` must match exactly; `NULL` only matches `NULL`.
**Alternatives considered**:
- Match previous runs by only `tenant_id + workspace_id + type` and then filter in PHP: rejected due to ambiguity and risk of cross-connection mixing.
---
### D-075-003 — Report ID semantics
**Decision**: Treat the `OperationRun` ID as the report identifier in UX and contracts (`report_id == operation_run_id`).
**Rationale**:
- The report is attached to the run; the run is the stable, tenant-scoped canonical record.
- Avoids a second identifier for the same “verification execution artifact”.
**Alternatives considered**:
- Generate a separate report UUID inside the JSON: rejected as it adds indirection without benefits in v1.5.
---
### D-075-004 — Fingerprint algorithm
**Decision**: Use SHA-256 over a deterministic normalization of check outcomes:
- flatten checks
- sort by `check.key`
- contribute `key|status|blocking|reason_code|severity` where `severity` is always present (missing → empty)
Store as lowercase hex.
**Rationale**:
- Deterministic across environments.
- Treats severity-only changes as meaningful (per clarified requirement).
**Alternatives considered**:
- Hash the full report JSON: rejected (unstable ordering, non-semantic fields like timestamps).
---
### D-075-005 — Per-check acknowledgements persistence
**Decision**: Create a first-class table `verification_check_acknowledgements` keyed by `(operation_run_id, check_key)` with a unique constraint.
**Rationale**:
- Acknowledgements are governance metadata and must be queryable and auditable.
- Unique per report/check is enforced by the DB.
**Alternatives considered**:
- Store acknowledgements inside `operation_runs.context`: rejected as it complicates update semantics and auditability, and risks “report mutation” appearing like a changed verification outcome.
---
### D-075-006 — Capability naming reconciliation
**Decision**: Introduce a dedicated canonical capability `tenant_verification.acknowledge` in the capability registry and map it in the role → capability map.
**Rationale**:
- Keeps the feature spec requirement literal and avoids overloading “findings” semantics.
- Preserves the constitution rule that capabilities are centrally registered (no raw strings).
**Alternatives considered**:
- Reuse existing `tenant_findings.acknowledge`: rejected because this feature is specifically verification-report scoped, and we want the permission surface to remain explicit.
---
### D-075-007 — Audit action identifier + payload minimization
**Decision**: Add a stable audit action ID for acknowledgements (e.g. `verification.check_acknowledged`) and emit it on successful acknowledgement. Audit metadata is minimal and MUST NOT include `ack_reason`.
**Rationale**:
- Acknowledgement is a write mutation; constitution requires audit logging.
- Spec explicitly excludes `ack_reason` from audit payload; it remains only in the acknowledgement record.
**Alternatives considered**:
- Reuse `verification.completed`: rejected because it conflates verification execution with governance mutation.
---
### D-075-008 — Filament UI implementation constraints
**Decision**: Implement the “Verify step” UX changes in Filament v5 (Livewire v4) using:
- DB-only viewer helper (no external calls)
- centralized badge domains (BADGE-001)
- mutation via Filament `Action::make(...)->action(...)` with `->requiresConfirmation()`
**Rationale**:
- Aligns with Filament v5 patterns and constitution rules.
**Alternatives considered**:
- Publish/override Filament internal views: rejected; prefer render hooks + CSS hooks as needed.