# Implementation Plan: Spec 424 - Security Defaults Content-Backed Comparable Support **Branch**: `424-security-defaults-content-backed-comparable-support` | **Date**: 2026-06-30 | **Spec**: [spec.md](./spec.md) **Input**: Feature specification from `specs/424-security-defaults-content-backed-comparable-support/spec.md` ## Summary Close the precise Coverage v2 gap for `securityDefaults`: it is registry-only/out-of-scope today and cannot participate in later Entra certification. The implementation should either prove and promote Security Defaults through the existing Coverage v2 path as content-backed, comparable, and renderable internal/operator evidence, or keep it safely blocked with no fake support. The path is intentionally narrow: source contract, capture eligibility, evidence persistence, canonical identity, typed normalization, deterministic compare, operator-safe render summary, Claim Guard, redaction, RBAC/scope, Product Surface proof if rendered, and focused tests. No certification, restore/apply, customer output, new dashboard, new route, new table, or additional Entra type is in scope. ## Technical Context **Language/Version**: PHP 8.4, Laravel 12, Filament v5, Livewire v4, Pest v4. **Primary Dependencies**: Existing TenantConfiguration Coverage v2 services (`ResourceTypeRegistry`, `CoverageSourceContractResolver`, `GenericContentEvidenceCaptureService`, `CoverageResourceUpserter`, `CoverageEvidenceWriter`, `CoverageIdentityStrategyRegistry`, `CanonicalIdentityResolver`, `CoveragePayloadRedactor`, `ClaimGuard`, `CoverageV2ReadinessReadModel`, Entra comparable/renderable helpers). **Storage**: Existing PostgreSQL-backed Coverage v2 tables only. No migration is planned. **Tests**: Focused Pest unit/feature tests; browser test only if rendered output changes; PostgreSQL lane only if implementation unexpectedly changes schema/index/JSONB behavior. **Constraints**: No restore/apply/certification/customer claims; no new route/navigation/dashboard/action/report/export; no `tenant_id`; no completed-spec rewrites; no direct HTTP/Graph SDK bypass; no live docs/network calls in render/compare/tests. ## Current Repo Evidence - `ResourceTypeRegistry::m365RepresentativeDefinitions()` seeds `securityDefaults` as an Entra representative row with `source_class = tcm`, `support_state = out_of_scope`, `default_coverage_level = detected`, `default_evidence_state = not_captured`, `default_claim_state = internal_only`, `restore_tier = not_restorable`, and registry-only metadata. - `CoverageSourceContractResolver::CONTRACT_KEYS` maps `conditionalAccessPolicy`, `assignmentFilter`, `notificationMessageTemplate`, and `roleScopeTag`, but not `securityDefaults`. - `config/graph_contracts.php` contains `conditionalAccessPolicy` but not `securityDefaults`. - `CoverageIdentityStrategyRegistry` contains `conditionalAccessPolicy` but not `securityDefaults`. - `SupportedScopeResolver` includes `securityDefaults` in the Entra planning list, but no internal comparable scope or certified scope is active for this slice. - `EntraComparablePayloadNormalizer`, `EntraCoverageComparator`, and `EntraRenderableSummaryBuilder` currently support Conditional Access only. - `GenericContentEvidenceCaptureService::responseItems()` can handle list responses and single-object payloads with an `id`, but implementation must prove the Security Defaults source shape works safely before promotion. - Existing capture outcomes do not include `capture_blocked_source_unavailable`; use existing outcomes and stable reason codes unless the spec is amended. ## Constitution Check - **Inventory/snapshots**: PASS. This works over Coverage v2 observed evidence only; no snapshot/backup semantics are changed. - **Read/write separation**: PASS. The feature is read/capture/compare/render only; no write or restore behavior. - **Single Graph contract path**: REQUIRED. Any source contract must live in the repo graph contract registry and be invoked through `GraphClientInterface` via existing provider/capture services. - **Proportionality**: PASS with a bounded exception. One source mapping, one identity strategy, and one typed mapping are justified by a concrete denominator blocker. - **No premature abstraction**: PASS if implementation extends existing concrete helpers. Stop if a broad singleton framework or Entra mini-platform appears necessary. - **Workspace/managed-environment/provider ownership**: REQUIRED. Existing `workspace_id`, `managed_environment_id`, and same-scope `provider_connection_id` remain the only internal ownership truth. - **OperationRun**: PASS if existing tenant-configuration capture OperationRun is reused and no new start UX is introduced. - **Product Surface Contract**: APPLIES only if rendered Coverage v2 output changes. Focused browser proof and Human Product Sanity are required in that case. - **Test governance**: REQUIRED. Focused unit/feature tests must prove source contract, evidence, identity, compare/render, redaction, RBAC, and overclaim boundaries. ## Affected Repository Surfaces Expected runtime surfaces if implementation proceeds: ```text apps/platform/config/graph_contracts.php apps/platform/app/Services/TenantConfiguration/ResourceTypeRegistry.php apps/platform/database/migrations/2026_06_26_000419_expand_tenant_configuration_workloads.php (one-row fresh-install seed alignment for `securityDefaults` only) apps/platform/app/Services/TenantConfiguration/CoverageSourceContractResolver.php apps/platform/app/Services/TenantConfiguration/SupportedScopeResolver.php apps/platform/app/Services/TenantConfiguration/CoverageIdentityStrategyRegistry.php apps/platform/app/Services/TenantConfiguration/EntraComparablePayloadNormalizer.php apps/platform/app/Services/TenantConfiguration/EntraCoverageComparator.php apps/platform/app/Services/TenantConfiguration/EntraRenderableSummaryBuilder.php apps/platform/app/Services/TenantConfiguration/CoverageV2ReadinessReadModel.php apps/platform/app/Services/TenantConfiguration/ClaimGuard.php apps/platform/app/Services/TenantConfiguration/CoveragePayloadRedactor.php apps/platform/tests/Unit/Support/TenantConfiguration/ apps/platform/tests/Feature/TenantConfiguration/ apps/platform/tests/Browser/ only if rendered output changes ``` Potential no-change surfaces: - No new schema migration expected. Existing migration seed data may be edited only for the already-identified `securityDefaults` row in `2026_06_26_000419_expand_tenant_configuration_workloads.php` so fresh installs match `ResourceTypeRegistry::syncDefaults()` after real source support is proven. - No Filament Resource/Page/Widget class expected unless rendered output cannot be exposed through the existing read model/data path; amend this plan and tasks before editing runtime UI files. - No route, navigation, dashboard, customer output, report/export, action, or asset registration expected. ## Domain / Model Implications - `TenantConfigurationResourceType` continues to represent the resource type definition. - `TenantConfigurationResource` continues to represent the observed resource identity within a workspace, managed environment, and provider connection. - `TenantConfigurationResourceEvidence` continues to represent append-only content-backed evidence and payload hashes. - `securityDefaults` should move out of registry-only/out-of-scope posture only if source contract and capture proof exist. - `RestoreTier::NotRestorable` remains the no-restore posture for this slice. - No new persisted compare result or readiness table is justified. ## Data / Truth Flow 1. Registry defines `securityDefaults` conservatively. 2. Source contract resolver either finds an explicit approved contract or returns a blocked missing-contract/unsupported outcome. 3. Capture runs through existing provider gateway and `GraphClientInterface`; no direct HTTP or render-time provider calls. 4. Generic capture persists raw and normalized evidence only after successful response handling. 5. Canonical identity resolver produces stable/derived/blocked identity state. 6. Entra typed normalizer reduces Security Defaults to deterministic enabled-state semantics and diagnostics. 7. Comparator classifies enabled-state changes as critical and volatile-only changes as ignored. 8. Render summary exposes operator-safe enabled/evidence/identity/claim/blocker fields. 9. Claim Guard allows only scoped internal/operator content-backed/comparable/renderable statements and blocks broader claims. 10. Supported-scope behavior stays internal/operator-only if touched and never advertises certified, restore-ready, customer-ready, full Entra, or broad M365 scope. ## UI / Filament / Livewire Implications - Livewire v4 compliance remains required; no Livewire v3 API may be introduced. - Panel providers remain registered in `apps/platform/bootstrap/providers.php`; no provider registration change is expected. - No globally searchable Filament Resource is added or changed. Global search posture should remain unchanged. - No destructive/high-impact action is added. If implementation unexpectedly adds any action, stop and amend the spec first. - Asset strategy: no new Filament assets expected; no new `filament:assets` requirement beyond existing deploy practice. - Existing Coverage v2 surface may render data-driven Security Defaults summary if implementation wires the read model. If rendered output changes, browser smoke and Human Product Sanity are required. Blade, Livewire, Filament Resource/Page/Widget, route, navigation, or action edits are outside the current plan until the affected files and proof criteria are added here. ## RBAC / Policy Implications - Non-member or wrong workspace/environment access must remain 404. - Established member without the required evidence/view capability must receive 403. - Readonly users must not start tenant-configuration capture. - Provider connection, OperationRun, resource, and evidence rows must belong to the same workspace and managed environment. - UI visibility is not authorization; server-side policies/gates/read model scoping must enforce boundaries. ## Audit / Observability / OperationRun Implications - Capture is remote/provider work and must remain OperationRun-backed through existing tenant-configuration capture behavior. - No new OperationRun type, toast, DB notification, start UX, or terminal notification path is expected. - OperationRun context and summaries must stay sanitized: numeric summary counts only, no raw payloads, no tokens/secrets, no provider response bodies. - Render/compare paths must be DB-only and must not trigger provider calls. ## Test Strategy ### Unit Tests - Source contract resolver allows Security Defaults only with explicit mapping and blocks missing/unsupported/beta-only source safely. - Registry defaults are conservative: no restore, no certification, no customer claim, no `tenant_id`. - Identity strategy never uses display name alone and blocks unsafe states. - Normalizer emits deterministic enabled-state representation and drops/demotes volatile fields. - Comparator detects enabled true/false/null, added/removed, unchanged, ignored volatile, redacted, and unsupported-field cases. - Render summary hides raw payload/normalized JSON/permission context and exposes only operator-safe fields. - Claim Guard allows scoped internal/operator content-backed/comparable/renderable wording and blocks certification/restore/customer/full claims. - Redaction covers tokens, secrets, authorization headers, cookies, raw payload markers, and unsafe diagnostic context. ### Feature Tests - Fake provider response persists content-backed Security Defaults evidence with raw/normalized payloads, payload hash, source metadata, operation run, and same-scope provider connection. - Missing contract/permission/unsupported source does not create evidence and does not promote coverage levels. - Promotion to comparable/renderable happens only after content-backed evidence and typed helpers exist. - RBAC: non-member/wrong scope 404, member without capability 403, readonly cannot start capture, provider connection scope enforced. - No certification, no restore, no customer claim, no `tenant_id`, no route/navigation/dashboard/report/export, no mini-platform. - Render/compare performs no remote/provider/docs calls. ### Browser Tests Required only if rendered output changes. Focused smoke should prove: - Existing Coverage v2 page loads for an authorized operator. - Security Defaults state is visible as enabled/disabled/unknown. - Comparable/renderable state and blockers are readable. - Raw payload, normalized JSON, secrets, source keys, certified/restore/customer-ready claims are absent. - No console, Livewire, Filament, network, or 500 errors. ## Implementation Phases ### Phase 0 - Preflight Record branch, HEAD, dirty state, activated skills, completed-spec guardrail, current registry row, contract mapping absence, identity strategy absence, typed helper support absence, and no duplicate Spec 424 package. ### Phase 1 - Tests First: Contract, Capture, and Identity Add failing tests for source-contract resolver behavior, graph contract shape, registry promotion, singleton-safe capture, evidence persistence, provider scope, OperationRun scope, and canonical identity. ### Phase 2 - Tests First: Typed Semantics and Claim Safety Add failing tests for normalizer, comparator, render summary, redaction, Claim Guard allowed/blocked wording, no restore/certification/customer claims, no `tenant_id`, and no mini-platform. ### Phase 3 - Source Contract and Registry Closure Add the minimal approved `securityDefaults` graph contract and resolver mapping if safe. Update registry defaults only when capture is real; otherwise leave blocked/out-of-scope and document the blocker. ### Phase 4 - Capture and Evidence Integration Ensure existing generic capture handles the Security Defaults source response shape. Add bounded handling only inside existing source/capture machinery if singleton shape requires it. Persist evidence only for real captured payloads. ### Phase 5 - Identity, Normalization, Compare, Render Add Security Defaults identity strategy and typed Entra semantics. Normalize enabled state, compare deterministic changes, and render a concise operator-safe summary. ### Phase 6 - Claim Guard, Read Model, Product Surface Harden Claim Guard. Wire the summary into the existing read model only if rendered output is intended. Keep diagnostics demoted and customer/restore/certification claims blocked. If `SupportedScopeResolver` is touched, keep any Security Defaults scope internal/operator-only and forbid certified or restore-ready scope names. ### Phase 7 - Browser Proof If Rendered Run focused browser smoke and Human Product Sanity if the existing Coverage v2 surface renders new Security Defaults output. Otherwise record exact no-rendered-change proof. ### Phase 8 - Validation and Implementation Report Run focused tests, formatting, `git diff --check`, and complete an implementation report with source/capture/identity/compare/render/claim matrices and deployment impact. ## Stop Conditions - Only a beta or undocumented source is available. - Source contract requires direct HTTP, Graph SDK bypass, runtime docs fetch, or endpoint guessing. - Existing capture cannot support singleton payload safely without a broader framework. - Implementation needs a schema migration, a migration-seed edit beyond the one-row `securityDefaults` alignment in `2026_06_26_000419_expand_tenant_configuration_workloads.php`, a new table, new persisted enum/status family, new route/navigation/dashboard, or customer output. - Restore/apply, certification, customer-ready, Review Pack/report/export, full Entra, 100 percent, or broad M365 claims appear. - Raw payload, normalized JSON, permission context, tokens, secrets, source keys, or provider response bodies must be default-visible. - `tenant_id` appears as Coverage v2 ownership truth. - Completed specs 414, 415, 417, 418, 419, 420, 421, or 423 would need to be rewritten. ## Rollout / Deployment Considerations - **Migrations**: no new schema migration expected. Align only the existing `securityDefaults` fresh-install seed row in `2026_06_26_000419_expand_tenant_configuration_workloads.php` if real source support is proven; any other existing migration seed edit or new migration remains a stop condition. - **Existing database default sync**: after deployment and before Security Defaults capture, run `cd apps/platform && php artisan tenant-configuration:sync-defaults` to synchronize Coverage v2 resource-type and supported-scope defaults and deactivate any stale active Security Defaults TCM planning row. - **Environment variables**: none expected. - **Queues/workers**: existing tenant-configuration capture queue/worker behavior applies. - **Scheduler**: no change expected. - **Storage/volumes**: no change expected. - **Assets**: no new assets expected; no new `filament:assets` requirement beyond current deploy practice. - **Staging/Dokploy**: validate capture and rendered existing Coverage v2 surface on staging before any production promotion if this support later participates in certification work. ## Complexity Tracking | Risk | Why Needed | Simpler Alternative Rejected Because | |---|---|---| | Security Defaults source contract mapping | Real content-backed evidence is required before later certification inclusion | Registry-only support would fake evidence | | Security Defaults typed helper support | Enabled-state compare/render cannot be safely derived from generic raw payload display | Raw JSON display leaks technical payloads and forces manual interpretation | | Singleton source handling if needed | Security Defaults may be one object rather than a collection | A broad singleton framework would be premature; keep handling bounded inside existing capture path | ## Draft-To-Repo Deviation Handling - Use `not_restorable` instead of draft `compare_only`. - Use existing `configuration` resource class unless the spec is amended. - Use existing capture outcomes and stable reason codes. - Treat `securityDefaults` TCM registry row as planning truth only until a real source contract is proven.