Automatisch erstellt: Commit & Push aus Workspace (WIP) Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #298
25 KiB
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
Findingrecords: baseline compare drift, Entra admin roles, and permission posture. - Keep the slice narrow and repo-grounded: reuse existing
Findingfields, existing recurrence identities, existingFindingWorkflowService::reopenBySystem(), and existingFindingSlaPolicybehavior; 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.phpapps/platform/app/Services/EntraAdminRoles/EntraAdminRolesFindingGenerator.phpapps/platform/app/Services/PermissionPosture/PermissionPostureFindingGenerator.php
- Shared lifecycle and due-date seams already reused by those paths:
apps/platform/app/Services/Findings/FindingWorkflowService.phpapps/platform/app/Services/Findings/FindingSlaPolicy.phpapps/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.phpapps/platform/app/Filament/Resources/FindingResource/Pages/ListFindings.phpapps/platform/app/Filament/Pages/Findings/MyFindingsInbox.phpapps/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.phpapps/platform/tests/Feature/EntraAdminRoles/EntraAdminRolesFindingGeneratorTest.phpapps/platform/tests/Feature/PermissionPosture/PermissionPostureFindingGeneratorTest.php
Domain / Model Fit
Findingremains the single tenant-owned source of truth with requiredworkspace_idandtenant_idanchors. 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, andreopenedremain the active statuses;resolved,closed, andrisk_acceptedremain 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 ordue_attruth 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_keyplusfingerprint, withcurrent_operation_run_idpreventing 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
- baseline compare uses
OperationRunandStoredReportremain 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.
FindingResourcealready 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, notbootstrap/app.php. - No asset change is planned. Deployment keeps the existing
cd apps/platform && php artisan filament:assetsexpectation 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 stay403, 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_idandStoredReportcorrelation 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
FindingWorkflowServiceandFindingSlaPolicyonly - 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
Findinglifecycle 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 remain403, 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 -
FindingResourcealready 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:assetsremains 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.phpexport PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/EntraAdminRoles/EntraAdminRolesFindingGeneratorTest.phpexport PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/PermissionPosture/PermissionPostureFindingGeneratorTest.phpexport 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.phpexport 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:
- confirm the shared invariant vocabulary and stop conditions against the three active writers only
- harden baseline compare first because it already carries the strictest observation-boundary rule through
current_operation_run_id - align permission posture and Entra admin roles creation and refresh logic around the same lifecycle-ready contract while preserving their family-specific recurrence rules
- 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
- 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)
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)
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_daysordue_atnormalization 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, andPermissionPostureFindingGenerator - 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.