TenantAtlas/specs/255-enforce-finding-creation-invariants/plan.md
ahmido ab9c36f21e
Some checks failed
Main Confidence / confidence (push) Failing after 59s
Automatische PR: platform-dev → dev (#299)
Automatisch erstellt: Merge `platform-dev` into `dev` (via MCP)

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #299
2026-04-29 12:37:48 +00:00

296 lines
25 KiB
Markdown

# 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.