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