TenantAtlas/specs/424-security-defaults-content-backed-comparable-support/plan.md
Ahmed Darrazi 6fbb5c97ad
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 5m4s
chore: complete spec 424 implementation
2026-07-01 16:40:08 +02:00

223 lines
18 KiB
Markdown

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