TenantAtlas/specs/255-enforce-finding-creation-invariants/plan.md
ahmido 51ea80ca05
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m5s
Automatische PR: 255-enforce-finding-creation-invariants → platform-dev (#298)
Automatisch erstellt: Commit & Push aus Workspace (WIP)

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

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 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)

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