# Implementation Plan: Enforce Creation-Time Finding Invariants **Branch**: `255-enforce-finding-creation-invariants` | **Date**: 2026-04-29 | **Spec**: `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/255-enforce-finding-creation-invariants/spec.md` **Input**: Feature specification from `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/255-enforce-finding-creation-invariants/spec.md` **Note**: This plan is prep-only. It updates only spec-package artifacts for implementation readiness and does not change application code, runtime behavior, migrations, assets, or repo files outside this spec directory. ## Summary - Make lifecycle-ready finding creation and recurrence semantics explicit across the only three active finding writers currently persisting `Finding` records: baseline compare drift, Entra admin roles, and permission posture. - Keep the slice narrow and repo-grounded: reuse existing `Finding` fields, existing recurrence identities, existing `FindingWorkflowService::reopenBySystem()`, and existing `FindingSlaPolicy` behavior; do not add repair tooling, workflow states, migrations, or a broader findings framework. - Tighten validation where repo proof is already strongest: extend the three focused feature suites so they explicitly cover new creation, repeated observation, resolved-to-reopened behavior, and inline repair of incomplete lifecycle fields encountered on active write paths. ## Technical Context **Language/Version**: PHP 8.4, Laravel 12 **Primary Dependencies**: Filament v5, Livewire v4, Pest v4, existing `FindingWorkflowService`, `FindingSlaPolicy`, baseline compare job, Entra admin roles finding generator, and permission posture finding generator **Storage**: PostgreSQL existing `findings`, `operation_runs`, `stored_reports`, and `audit_logs` only; no new persistence or migration is planned **Testing**: Pest feature tests in the existing generator and compare suites **Validation Lanes**: fast-feedback, confidence **Target Platform**: Sail-backed Laravel web application with tenant `/admin/t/{tenant}` findings surfaces and existing background jobs or services that already generate findings **Project Type**: web **Performance Goals**: lifecycle invariants must be satisfied in the same write path that creates or refreshes the finding; no second-pass repair job, no extra operator step, and no widened query surface should be required **Constraints**: LEAN-001 replacement over compatibility shims; no new persistence; no new workflow states; no compare refresh or repair-tooling scope; preserve existing `404` vs `403` behavior; no new Filament assets, panel work, or provider registration changes **Scale/Scope**: 3 active finding writer families, 1 shared workflow service, 1 shared SLA policy, 1 existing `Finding` model, and 3 established feature-test families plus downstream findings surfaces as regression consumers only ## Likely Affected Repo Surfaces - Active write paths and their local recurrence or observation logic: - `apps/platform/app/Jobs/CompareBaselineToTenantJob.php` - `apps/platform/app/Services/EntraAdminRoles/EntraAdminRolesFindingGenerator.php` - `apps/platform/app/Services/PermissionPosture/PermissionPostureFindingGenerator.php` - Shared lifecycle and due-date seams already reused by those paths: - `apps/platform/app/Services/Findings/FindingWorkflowService.php` - `apps/platform/app/Services/Findings/FindingSlaPolicy.php` - `apps/platform/app/Models/Finding.php` - Downstream operator-facing regression consumers that should not need design changes but do rely on `due_at`, `reopened_at`, and canonical open-status truth: - `apps/platform/app/Filament/Resources/FindingResource.php` - `apps/platform/app/Filament/Resources/FindingResource/Pages/ListFindings.php` - `apps/platform/app/Filament/Pages/Findings/MyFindingsInbox.php` - `apps/platform/app/Filament/Pages/Findings/FindingsIntakeQueue.php` - Current focused proof surfaces that already cover part of the invariant and should remain the primary validation entry points: - `apps/platform/tests/Feature/Baselines/BaselineCompareFindingsTest.php` - `apps/platform/tests/Feature/EntraAdminRoles/EntraAdminRolesFindingGeneratorTest.php` - `apps/platform/tests/Feature/PermissionPosture/PermissionPostureFindingGeneratorTest.php` ## Domain / Model Fit - `Finding` remains the single tenant-owned source of truth with required `workspace_id` and `tenant_id` anchors. No new entity, table, compatibility projection, or lifecycle wrapper is introduced. - The slice does not change the canonical findings status set. `new`, `triaged`, `in_progress`, and `reopened` remain the active statuses; `resolved`, `closed`, and `risk_accepted` remain terminal statuses. - Lifecycle-ready creation in this feature means that the first persisted or inline-repaired record is already safe for existing downstream workflow use: canonical active status, `first_seen_at`, `last_seen_at`, `times_seen >= 1`, and existing SLA or `due_at` truth when the current severity policy requires them. - Recurrence identity stays family-owned and explicit rather than being normalized into a new shared engine: - baseline compare uses `recurrence_key` plus `fingerprint`, with `current_operation_run_id` preventing double counting for the same compare run - Entra admin roles uses its existing role-assignment and aggregate fingerprints - permission posture uses its existing missing-permission and error fingerprints - `OperationRun` and `StoredReport` remain contextual references only where current writers already use them. This slice does not introduce a new audit artifact or independent lifecycle store. ## UI / Filament & Livewire Fit - No operator-facing surface change is planned. Existing findings resource, inbox, and intake surfaces are regression consumers of better write-time truth, not redesign targets. - Filament remains v5 on Livewire v4.0+; no Livewire v3 behavior or API is in scope. - `FindingResource` already has a view page, so the hard global-search rule remains satisfied without new work. No new globally searchable resource is added. - No destructive action is introduced or changed. Any touched findings action surface must keep current server-side authorization and existing `->requiresConfirmation()` behavior where destructive-like actions already exist. - No panel/provider work is planned. If provider registration ever became relevant later, Laravel 12 and Filament v5 still require panel providers under `apps/platform/bootstrap/providers.php`, not `bootstrap/app.php`. - No asset change is planned. Deployment keeps the existing `cd apps/platform && php artisan filament:assets` expectation unchanged only for already-registered assets. ## RBAC / Policy Fit - This slice should not add a new capability, new role mapping, or new policy branch. User-triggered actions that lead to in-scope finding writes keep their current authorization semantics. - Tenant membership and workspace membership remain the isolation boundary: non-members stay `404`, in-scope members missing the current capability stay `403`, and no new write bypass is introduced for background or queued generation. - If implementation appears to require a new capability or policy relaxation just to enforce lifecycle invariants, that is a stop condition and should be split rather than absorbed. ## Audit / Logging Fit - `FindingWorkflowService::reopenBySystem()` remains the authoritative reopen path because it already owns reopened state mutation, audit context, and alert notification dispatch. - No new `AuditActionId`, no new operation type, and no new completion notification path should be introduced. - The feature should preserve existing `current_operation_run_id` and `StoredReport` correlation meaning where current writers already set them. Creation-time hardening must not create a second audit or run-tracking dialect. ## Data / Migration / Constraint Fit - No migration, no historical data backfill, no deploy hook, and no repair command are planned. - Under LEAN-001, stale local data or incomplete fixtures should be handled by fixture replacement or inline repair on active write paths, not by compatibility shims. - A database-level constraint discussion is allowed only as an explicit follow-up or stop condition if planning or implementation proves that application-level write-path enforcement cannot satisfy the invariant safely. It must not be silently folded into this slice. - If due-date initialization for already-open findings would require recomputing correct existing data instead of filling missing lifecycle fields only, stop and split rather than broadening this feature into a data repair rollout. ## UI / Surface Guardrail Plan - **Guardrail scope**: no operator-facing surface change - **Native vs custom classification summary**: N/A - existing native Filament findings surfaces remain regression consumers only - **Shared-family relevance**: none; no new notification, header action, dashboard, or evidence viewer family is added - **State layers in scope**: none - **Audience modes in scope**: N/A - **Decision/diagnostic/raw hierarchy plan**: N/A - **Raw/support gating plan**: N/A - **One-primary-action / duplicate-truth control**: existing findings workflow actions remain unchanged; tighter write-time truth prevents partial lifecycle data from competing with the existing canonical action flow - **Handling modes by drift class or surface**: N/A - **Repository-signal treatment**: review-mandatory for downstream regression only - **Special surface test profiles**: standard-native-filament regression only - **Required tests or manual smoke**: functional-core, state-contract - **Exception path and spread control**: none - **Active feature PR close-out entry**: Guardrail ## Shared Pattern & System Fit - **Cross-cutting feature marker**: no - **Systems touched**: N/A for shared operator interaction families; domain reuse stays within existing findings lifecycle services only - **Shared abstractions reused**: existing `FindingWorkflowService` and `FindingSlaPolicy` only - **New abstraction introduced? why?**: none by default; if a shared write-time normalizer is later proposed, it must be a narrow findings-domain replacement for duplicated inline repair across all three concrete writers, not a new registry or framework - **Why the existing abstraction was sufficient or insufficient**: `reopenBySystem()` is already sufficient for terminal-to-reopened transitions. The current planning gap is open-record lifecycle repair, which is still duplicated and partially covered across the three writers. - **Bounded deviation / spread control**: none; keep any repair logic either local to each writer or in one bounded findings-domain helper only if it replaces real duplication immediately ## OperationRun UX Impact - **Touches OperationRun start/completion/link UX?**: no - **Central contract reused**: N/A - **Delegated UX behaviors**: N/A - **Surface-owned behavior kept local**: existing baseline compare and other generation flows keep their current start and completion UX unchanged - **Queued DB-notification policy**: N/A - **Terminal notification path**: N/A - **Exception path**: none ## Provider Boundary & Portability Fit - **Shared provider/platform boundary touched?**: no - **Provider-owned seams**: N/A - **Platform-core seams**: existing tenant-owned findings truth only - **Neutral platform terms / contracts preserved**: existing `Finding` lifecycle and tenant/workspace ownership vocabulary remain unchanged - **Retained provider-specific semantics and why**: provider-specific recurrence evidence stays inside the existing writer families that already own it - **Bounded extraction or follow-up path**: N/A ## Constitution Check *GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* - LEAN-001: PASS - the slice is explicitly app-code hardening only; no compatibility shim, legacy alias, fallback reader, or migration path is planned. - TEST-GOV-001: PASS - proof stays in the narrowest existing feature suites, with no browser lane and no new heavy-governance family. - RBAC-UX: PASS - no new capability or policy branch is introduced; non-members remain `404`, members lacking the current capability remain `403`, and system generation stays tenant-scoped. - PERSIST-001: PASS - no new persisted truth, table, artifact, or projection is introduced. - STATE-001: PASS - no new state, reason-code family, or lifecycle branch is added; current findings states remain authoritative. - PROP-001 / ABSTR-001: PASS - the narrowest plan is to align the three concrete write paths and reuse the existing reopen service. Any helper beyond that is a stop-and-justify decision, not a default. - XCUT-001 / UI-SEM-001: PASS - no new operator interaction family or presentation framework is introduced. - Filament v5 / Livewire v4 compliance: PASS - existing findings surfaces stay on native Filament v5 with Livewire v4.0+; no legacy API mixing is planned. - Global-search hard rule: PASS - `FindingResource` already has a view page, and no new searchable resource is added. - Panel/provider registration: PASS - no panel/provider work is planned; if needed later, Filament v5 on Laravel 12 still uses `apps/platform/bootstrap/providers.php`. - Destructive confirmation standard: PASS - no new destructive action is added; existing destructive-like actions remain outside this slice. - Asset strategy: PASS - no new panel or shared asset registration is planned; existing deploy behavior for `filament:assets` remains unchanged. - Auditability and tenant isolation: PASS - reopen semantics remain on the current audited service path, and every in-scope write remains bound to tenant and workspace context. ## Test Governance Check - **Test purpose / classification by changed surface**: Feature for writer-level creation-time lifecycle readiness, shared recurrence/workflow-service behavior, and narrow downstream consumer plus trigger-authorization continuity checks; no new unit, browser, or heavy-governance family is planned - **Affected validation lanes**: fast-feedback, confidence - **Why this lane mix is the narrowest sufficient proof**: the feature risk lives in domain write behavior already exercised through the existing compare and generator suites, but FR-255-005, FR-255-006, FR-255-009, and FR-255-011 also require bounded proof of shared recurrence/workflow behavior and unchanged consumer/auth continuity. Focused feature coverage is still sufficient because the adjacent checks stay limited to existing findings and trigger-authorization suites. - **Narrowest proving command(s)**: - `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Baselines/BaselineCompareFindingsTest.php` - `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/EntraAdminRoles/EntraAdminRolesFindingGeneratorTest.php` - `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/PermissionPosture/PermissionPostureFindingGeneratorTest.php` - `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Findings/FindingRecurrenceTest.php tests/Feature/Findings/FindingAutomationWorkflowTest.php tests/Feature/Findings/FindingWorkflowServiceTest.php` - `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Findings/MyWorkInboxTest.php tests/Feature/Findings/FindingsIntakeQueueTest.php tests/Feature/Rbac/BaselineCompareMatrixAuthorizationTest.php tests/Feature/EntraAdminRoles/AdminRolesSummaryWidgetTest.php` - **Fixture / helper / factory / seed / context cost risks**: low to moderate but bounded; reuse existing tenant, operation-run, snapshot, generator, and trigger-surface fixtures. Avoid a new umbrella findings harness unless repeated setup clearly becomes the bottleneck. - **Expensive defaults or shared helper growth introduced?**: no; the plan explicitly avoids a new generic invariant framework or new default-heavy helper layer. - **Heavy-family additions, promotions, or visibility changes**: none - **Surface-class relief / special coverage rule**: standard-native relief; no browser smoke is required because no operator-facing interaction changes are planned - **Closing validation and reviewer handoff**: rerun the three writer suites plus the bounded recurrence/workflow and consumer/auth suites, confirm each family now proves missing-field inline repair in addition to existing create/idempotence/reopen behavior, and verify that no migration, no policy branch, and no new UI action was introduced while hardening write paths. - **Budget / baseline / trend follow-up**: none expected beyond routine feature-test maintenance - **Review-stop questions**: did implementation widen into a repair tool, migration, DB constraint rollout, or generic invariant framework; did it silently reset already-valid due dates; did it leave one writer family with only partial invariant proof - **Escalation path**: reject-or-split - **Active feature PR close-out entry**: Guardrail - **Why no dedicated follow-up spec is needed**: routine lifecycle-hardening proof belongs in this feature unless a database-level constraint or a broader findings lifecycle redesign is proven necessary later ## Rollout & Risk Controls - Rollout is code-only and bounded. No migration, queue worker sequencing, asset build, or provider registration step is expected. - Recommended implementation order is: 1. confirm the shared invariant vocabulary and stop conditions against the three active writers only 2. harden baseline compare first because it already carries the strictest observation-boundary rule through `current_operation_run_id` 3. align permission posture and Entra admin roles creation and refresh logic around the same lifecycle-ready contract while preserving their family-specific recurrence rules 4. extract a shared normalizer only if the concrete code shows immediate duplication across all three paths and the helper replaces duplication instead of adding a new abstraction layer 5. extend focused regression tests and verify downstream findings surfaces do not require design changes - Stop conditions for task execution: - another shipped finding writer is discovered outside the three confirmed paths - the invariant cannot be enforced safely without a migration or DB constraint - the only available code shape is a new generic registry, strategy system, or lifecycle framework - user-facing findings workflow affordances would need to change to compensate for missing write-time truth ## Project Structure ### Documentation (this feature) ```text specs/255-enforce-finding-creation-invariants/ ├── plan.md ├── research.md ├── data-model.md ├── quickstart.md ├── checklists/ │ └── requirements.md ├── contracts/ │ └── finding-creation-invariants.contract.yaml └── tasks.md ``` ### Source Code (repository root) ```text apps/platform/ ├── app/ │ ├── Jobs/ │ │ └── CompareBaselineToTenantJob.php │ ├── Models/ │ │ └── Finding.php │ ├── Services/ │ │ ├── EntraAdminRoles/ │ │ │ └── EntraAdminRolesFindingGenerator.php │ │ ├── Findings/ │ │ │ ├── FindingSlaPolicy.php │ │ │ └── FindingWorkflowService.php │ │ └── PermissionPosture/ │ │ └── PermissionPostureFindingGenerator.php │ └── Filament/ │ ├── Pages/Findings/ │ │ ├── FindingsIntakeQueue.php │ │ └── MyFindingsInbox.php │ └── Resources/ │ ├── FindingResource.php │ └── FindingResource/ │ └── Pages/ListFindings.php └── tests/ └── Feature/ ├── Baselines/BaselineCompareFindingsTest.php ├── EntraAdminRoles/EntraAdminRolesFindingGeneratorTest.php ├── Findings/FindingRecurrenceTest.php ├── Findings/FindingAutomationWorkflowTest.php ├── Findings/FindingWorkflowServiceTest.php ├── Findings/MyWorkInboxTest.php ├── Findings/FindingsIntakeQueueTest.php ├── Rbac/BaselineCompareMatrixAuthorizationTest.php ├── EntraAdminRoles/AdminRolesSummaryWidgetTest.php └── PermissionPosture/PermissionPostureFindingGeneratorTest.php ``` **Structure Decision**: Laravel monolith. The implementation should stay inside the existing finding writer services and job, the shared findings lifecycle service and model, and the current focused feature suites rather than creating a new namespace or framework. ## Complexity Tracking No constitution violation is expected. If implementation later proposes a new persistence rule, a new lifecycle framework, or a broad helper layer that serves only speculative future writers, stop and split rather than justifying it inside this slice. ## Proportionality Review N/A - this feature introduces no new enum, presenter, persisted entity, interface, registry, or taxonomy. Any narrow helper extracted during implementation must replace existing duplicated write-time lifecycle normalization immediately across the three confirmed writers or it should not be introduced. ## Phase 0 — Research (output: `research.md`) See: `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/255-enforce-finding-creation-invariants/research.md` Goals: - confirm that the three already-named write paths are still the full active finding-writer inventory in app code - confirm where current code already repairs lifecycle fields inline and where `sla_days` or `due_at` normalization is still only implied on create or reopen - document the narrowest shared seam decision: keep repair logic local per writer unless one bounded findings-domain helper clearly replaces real duplication across all three cases - record the explicit stop condition for any database-level constraint or migration-based enforcement proposal ## Phase 1 — Design & Contracts (outputs: `data-model.md`, `contracts/`, `quickstart.md`) See: - `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/255-enforce-finding-creation-invariants/data-model.md` - `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/255-enforce-finding-creation-invariants/contracts/finding-creation-invariants.contract.yaml` - `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/255-enforce-finding-creation-invariants/quickstart.md` Design focus: - capture one lifecycle-ready finding contract that all three active writers must satisfy without introducing a new persistence or workflow layer - keep recurrence identity family-owned while making the create, refresh, and reopen guarantees explicit in one planning contract - keep downstream Filament findings surfaces, inboxes, and intake queues as regression consumers only; no UI redesign is part of this slice - document the no-migration, no-constraint-by-default posture and the explicit stop condition for any future constraint follow-up ## Phase 1 — Agent Context Update - Deferred in this prep-only pass because the user explicitly limited edits to this spec directory. - If maintainers later want full Spec Kit propagation outside the spec package, run: - `/Users/ahmeddarrazi/Documents/projects/wt-plattform/.specify/scripts/bash/update-agent-context.sh copilot` ## Phase 2 — Implementation Outline (tasks created later in `/speckit.tasks`) - keep the feature bounded to the three confirmed writer paths and the shared reopen service - align creation-time lifecycle initialization and open-record inline repair in `CompareBaselineToTenantJob`, `EntraAdminRolesFindingGenerator`, and `PermissionPostureFindingGenerator` - preserve family-specific recurrence and observation-boundary behavior while making it explicit in code and tests - preserve `FindingWorkflowService::reopenBySystem()` as the only reopened-state mutation path - extend the three focused feature suites so each family proves creation readiness, repeated observation, resolved-to-reopened behavior, and inline repair of incomplete lifecycle fields encountered on active paths - verify that no migration, no new capability, no new workflow state, no repair surface, and no operator-facing workflow expansion slipped into the implementation slice ## Constitution Check (Post-Design) Re-check target: PASS. The post-design shape remains prep-only, introduces no new persistence or state family, keeps Filament on Livewire v4.0+, leaves provider registration unchanged in `apps/platform/bootstrap/providers.php`, keeps global search unchanged through the existing `FindingResource` view page, leaves destructive actions untouched, and keeps the proving burden inside the three existing focused feature suites unless a bounded stop condition forces a split. - **Ownership cost created**: focused ongoing maintenance in the three writer suites plus bounded shared recurrence/workflow and trigger-authorization regressions; no migration, framework, or new persistence cost is added. - **Alternative intentionally rejected**: a generic invariant framework, a new repair or backfill path, and any DB-constraint rollout were rejected because the repo currently has three concrete writers and current-release truth only requires tightening those exact paths. - **Release truth**: current-release truth. This package hardens already-shipped finding writers rather than preparing speculative future families.