version: 1
kind: finding-creation-invariants

scope:
  goal: enforce lifecycle-ready finding creation and recurrence or reopen semantics across the active finding writers only
  non_goals:
    - repair tooling or backfill runtime surfaces
    - new workflow states or new findings lifecycle families
    - customer-facing workflow expansion
    - compare refresh work
    - external support handoff
    - broader findings redesign
    - silent database-constraint rollout
  stop_conditions:
    - another shipped finding writer is discovered outside the three confirmed paths
    - application-level write enforcement proves insufficient without a migration or DB constraint
    - the only available implementation shape is a new generic invariant framework

active_writer_families:
  baseline_compare:
    owner_files:
      - apps/platform/app/Jobs/CompareBaselineToTenantJob.php
    identity:
      canonical_key: recurrence_key
      fingerprint_contract: fingerprint equals recurrence_key
    observation_boundary:
      duplicate_guard: current_operation_run_id prevents double counting the same compare run
  entra_admin_roles:
    owner_files:
      - apps/platform/app/Services/EntraAdminRoles/EntraAdminRolesFindingGenerator.php
    identity:
      canonical_key: existing role-assignment or aggregate fingerprint
    observation_boundary:
      duplicate_guard: later observedAt advances seen history
  permission_posture:
    owner_files:
      - apps/platform/app/Services/PermissionPosture/PermissionPostureFindingGenerator.php
    identity:
      canonical_key: existing permission or error fingerprint
    observation_boundary:
      duplicate_guard: later observedAt advances seen history

shared_lifecycle_contract:
  model:
    owner_file: apps/platform/app/Models/Finding.php
    invariants:
      - workspace_id and tenant_id remain required ownership anchors
      - no new status or reason-code family is introduced
  reopen_service:
    owner_file: apps/platform/app/Services/Findings/FindingWorkflowService.php
    requirement:
      - terminal findings reopen only through reopenBySystem
      - reopened_at is set
      - resolved and closed markers clear according to current service behavior
      - sla_days and due_at are recalculated from reopenedAt
      - existing audit and alert side effects are preserved

lifecycle_invariants:
  create:
    required_fields:
      - status is new
      - first_seen_at equals observedAt
      - last_seen_at equals observedAt
      - times_seen equals 1
      - sla_days is initialized when the current severity policy returns a value
      - due_at is initialized when the current severity policy requires due-state truth
    contextual_fields:
      - current_operation_run_id remains populated where the current writer already sets it
  refresh_existing:
    required_behavior:
      - the same canonical finding identity is reused
      - missing first_seen_at, last_seen_at, and times_seen are repaired inline
      - missing sla_days or due_at covered by this slice are repaired inline without a second-pass repair tool
      - already-valid lifecycle fields are not reset unnecessarily
  reopen:
    required_behavior:
      - the same canonical finding identity is reopened, not duplicated
      - resolved_at and resolved_reason clear on reopen
      - first_seen_at is retained
      - last_seen_at and times_seen advance according to the family observation rule

downstream_regression_consumers:
  findings_surfaces:
    owner_files:
      - 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
    expectation:
      - no design change is required; these surfaces should continue to read truthful due_at and reopened_at data from the same Finding records

validation_expectations:
  required_feature_proof:
    - baseline compare proves create readiness, same-run retry protection, reopened reuse, and inline repair of incomplete lifecycle fields
    - Entra admin roles proves create readiness, repeated observation, reopened reuse, and inline repair of incomplete lifecycle fields
    - permission posture proves create readiness, repeated observation, reopened reuse, and inline repair of incomplete lifecycle fields
  excluded_lanes:
    - browser
    - heavy-governance
  migration_posture:
    - no new migration or schema artifact is allowed in this slice