223 lines
18 KiB
Markdown
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.
|