Implements spec 424 with comparable renderable capture/readiness changes and supporting tests/spec artifacts. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #491
133 lines
15 KiB
Markdown
133 lines
15 KiB
Markdown
# Implementation Report: Spec 424 - Security Defaults Content-Backed Comparable Support
|
|
|
|
## Preflight
|
|
|
|
- **Active spec**: `specs/424-security-defaults-content-backed-comparable-support/`
|
|
- **Implementation start**: 2026-06-30 19:22:46 CEST
|
|
- **Branch**: `424-security-defaults-content-backed-comparable-support`
|
|
- **HEAD**: `c49784b3 feat: complete spec 423 security compliance readiness pack (#490)`
|
|
- **Initial dirty state**: untracked active spec directory only.
|
|
- **Activated skills**: `spec-kit-implementation-loop`, `pest-testing`, `.agent/workflows/spec-readiness-gate`, `.agent/repo-contracts/workspace-scope-safety`, `.agent/repo-contracts/rbac-action-safety`, `.agent/repo-contracts/operation-run-truth`, `.agent/repo-contracts/evidence-anchor-contract`, `.agent/repo-contracts/provider-freshness-semantics`, `.agent/repo-contracts/product-surface-gate`.
|
|
- **Hard-gate stop conditions checked**: no unrelated dirty files; no completed-spec rewrite; no new schema/table/persisted enum/status family; no new route/navigation/dashboard/action/customer output; no restore/apply/certification/Review Pack/report/export scope; no direct HTTP, Graph SDK bypass, runtime docs fetch, or broad singleton framework; no `tenant_id` ownership path. Existing fresh-install seed alignment is allowed only for the already-identified one-row `securityDefaults` entry in `2026_06_26_000419_expand_tenant_configuration_workloads.php`.
|
|
|
|
## Completed-Spec Guardrail
|
|
|
|
Specs 414, 415, 417, 418, 419, 420, 421, and 423 are read-only dependency context. No completed historical spec files are edited.
|
|
|
|
## Preflight Evidence
|
|
|
|
- `securityDefaults` currently exists as a registry-only/out-of-scope TCM planning row in `ResourceTypeRegistry` and the fresh-install workload expansion migration.
|
|
- `CoverageSourceContractResolver` has no `securityDefaults` mapping before this spec.
|
|
- `config/graph_contracts.php` has no Security Defaults contract before this spec.
|
|
- `CoverageIdentityStrategyRegistry` has no `securityDefaults` identity strategy before this spec.
|
|
- `EntraComparablePayloadNormalizer` supports Conditional Access before this spec and not Security Defaults.
|
|
- `GenericContentEvidenceCaptureService` already accepts singleton Graph payloads that contain an `id`, so no broad singleton capture framework is planned.
|
|
- Official Microsoft Graph documentation confirms the Security Defaults read endpoint as `GET /policies/identitySecurityDefaultsEnforcementPolicy`, the resource fields `id`, `displayName`, `description`, and `isEnabled`, and least-privileged read permission `Policy.Read.All`.
|
|
|
|
## Implementation Summary
|
|
|
|
- Added `securityDefaults` as an explicit Coverage v2 source contract in `apps/platform/config/graph_contracts.php` using `/policies/identitySecurityDefaultsEnforcementPolicy`, `graph_version = v1.0`, `response_shape = singleton`, safe select fields, singleton-safe volatile fields, and `Policy.Read.All`.
|
|
- Added `securityDefaults` to `CoverageSourceContractResolver` explicit mappings; missing contract resource still blocks as `capture_blocked_missing_contract`.
|
|
- Added request-local Graph version handling in `MicrosoftGraphClient::listPolicies()` so this contract calls the v1.0 endpoint without changing global Graph defaults or bypassing `GraphClientInterface`.
|
|
- Updated `MicrosoftGraphClient`, Security Defaults capture, and `graph:contract:check` singleton probes so they use the contract-local v1.0 endpoint and do not send `$top` to `/policies/identitySecurityDefaultsEnforcementPolicy`; the endpoint remains constrained to the contract `$select`.
|
|
- Promoted only the `securityDefaults` registry row to active `graph_v1_fallback` / `fallback_supported` / internal-only / non-restorable support in `ResourceTypeRegistry`, and aligned the one existing fresh-install migration seed row allowed by the amended plan.
|
|
- Added the idempotent `tenant-configuration:sync-defaults` deploy command to run the existing resource-type and supported-scope default sync paths on already-migrated environments.
|
|
- Added a bounded identity strategy requiring a stable Graph `id`; display-name-only and `sourceId`-only Security Defaults payloads remain `missing_external_id`.
|
|
- Extended existing Entra typed helpers for Security Defaults normalization, critical enabled-state compare, and safe render summaries. No Blade, Filament Resource/Page/Widget, route, navigation, dashboard, action, export, report, Review Pack, restore, certify, or customer output was added.
|
|
- Hardened `ClaimGuard` so Security Defaults-specific claims are treated as workload claims: selected internal/operator comparable/renderable wording is allowed, while certification, restore, customer-ready, full, 100 percent, M365 certified, and Review Pack wording is blocked.
|
|
|
|
## Source Contract And Capture Matrix
|
|
|
|
| Area | Result |
|
|
| --- | --- |
|
|
| Contract key | `securityDefaults` |
|
|
| Endpoint | `/policies/identitySecurityDefaultsEnforcementPolicy` |
|
|
| Version | `v1.0` via contract-local `graph_version` |
|
|
| Response shape | singleton; `GraphClientInterface` list calls, capture, and live contract probes use v1.0 and omit `$top` |
|
|
| Permission proof | `Policy.Read.All` recorded as the least-privileged read permission |
|
|
| Capture path | Existing `GenericContentEvidenceCaptureService`; singleton payloads with `id` are already captured without new framework code |
|
|
| Missing contract | Blocks as `capture_blocked_missing_contract` / `missing_graph_contract_resource`; no fake evidence |
|
|
| Permission block | 403 response maps to `capture_blocked_permission`; no fake evidence |
|
|
| Scope safety | Provider connection and OperationRun target-scope mismatches fail before provider work |
|
|
| Registry posture | one active `graph_v1_fallback` row; old TCM planning row is inactive after `ResourceTypeRegistry::syncDefaults()` |
|
|
| Fresh-install seed | Existing migration `2026_06_26_000419_expand_tenant_configuration_workloads.php` aligned for only the `securityDefaults` row |
|
|
| Existing DB sync | run `cd apps/platform && php artisan tenant-configuration:sync-defaults` after deployment and before Security Defaults capture |
|
|
|
|
## Identity, Normalize, Compare, And Render Matrix
|
|
|
|
| Area | Result |
|
|
| --- | --- |
|
|
| Identity | `graph.security_defaults.v1`, stable `graph_object_id` only when `id` is present |
|
|
| Missing identity | display-name-only and `sourceId`-only payloads resolve to `missing_external_id`; no stable claim |
|
|
| Normalized fields | `display_name`, `enabled`, `enabled_state`, `source_identity.id`, diagnostics |
|
|
| Volatile fields | `@odata.context` and `@odata.etag` ignored as volatile |
|
|
| Compare | enabled and enabled-state changes are critical material changes; redacted/unsupported diagnostics are informational |
|
|
| Render | existing Coverage v2 inspector shows Security Defaults display name, enabled state, evidence state, identity state, claim state, last captured, compare summary, and redaction diagnostics |
|
|
| Read model | Existing Entra dispatch path supports Security Defaults; no new generic registry/framework |
|
|
|
|
## Claim Guard, Redaction, And Scope Proof
|
|
|
|
- Claim Guard allows only selected internal/operator Security Defaults comparable/renderable/ready-for-operator-review wording.
|
|
- Claim Guard blocks Security Defaults certified, restore-ready, customer-ready, Review Pack, all/full, 100 percent, and M365 certification wording.
|
|
- `CoveragePayloadRedactor` was reused; no redactor extension was required. Tests prove secret values are redacted from normalized payloads, render summaries, and compare results.
|
|
- Existing Coverage v2 owner/workspace/managed-environment/provider connection scoping remains authoritative; no `tenant_id` ownership path was added.
|
|
- `SupportedScopeResolver` was not changed. No certified, restore-ready, customer-ready, full Entra, or broad M365 scope names were added.
|
|
|
|
## Product Surface Close-Out
|
|
|
|
- **No-legacy posture**: no legacy path or compatibility exception; this is a canonical Coverage v2 extension.
|
|
- **Product Surface Impact**: existing Coverage v2 readiness/inspect surface can render Security Defaults summaries when renderable content-backed evidence exists.
|
|
- **UI Surface Impact**: no route, navigation item, dashboard, action, table, Filament Resource/Page/Widget, provider registration, or Blade file changed. Existing inspector content changes only for Security Defaults renderable evidence.
|
|
- **Page archetype**: Technical Annex / internal operator evidence inspection.
|
|
- **Surface budgets**: decision-first status remains Coverage/Evidence/Identity/Claim badges; Security Defaults summary adds compact `summary_fields`; diagnostics remain secondary; raw/support details remain collapsed under existing technical details.
|
|
- **Technical Annex / deep-link demotion**: source endpoint, contract key, schema hash, canonical key, operation link, and source class remain in existing technical details, not default-visible summary fields.
|
|
- **Canonical status vocabulary**: existing Coverage v2 states only (`renderable`, `content_backed`, `stable`, `internal_only`, blockers where applicable).
|
|
- **Product Surface exceptions**: none.
|
|
- **Focused browser proof**: `php artisan test --compact tests/Browser/Spec424SecurityDefaultsComparableRenderableOperatorSurfaceSmokeTest.php` passed, proving rendered Security Defaults inspect output, compare summary, redaction badges, no raw/secrets/source endpoint default exposure, no restore/certified/customer-ready wording, no new high-impact action, no remote Graph/TCM/provider resource calls, and no JavaScript/console errors.
|
|
- **Human Product Sanity result**: pass. An internal operator can see that Security Defaults is enabled and materially changed versus prior evidence without seeing raw payloads, secrets, source endpoints, or overclaim wording.
|
|
- **Visible complexity outcome**: neutral. The existing inspector hierarchy is reused; no nested cards, new actions, new navigation, or new page structure.
|
|
|
|
## Filament v5 Output Contract
|
|
|
|
- **Livewire v4.0+ compliance**: unchanged; platform remains Filament v5 on Livewire v4 and the focused browser smoke exercises the existing Filament surface.
|
|
- **Provider registration location**: unchanged; Laravel 12 provider registration remains in `apps/platform/bootstrap/providers.php`.
|
|
- **Global search**: unchanged; no Filament Resource was added or made globally searchable.
|
|
- **Destructive/high-impact actions**: none added or changed. No restore/apply/capture-start/export/certify action was introduced.
|
|
- **Asset strategy**: no assets registered and no new frontend bundle or `filament:assets` deployment requirement introduced.
|
|
- **Testing plan coverage**: unit tests cover contract, graph version URL, singleton no-`$top` probes, identity, normalization, compare, render, and claims; feature tests cover registry, deploy sync command, capture, permission/scope/RBAC/no-remote/no-mini-platform; browser smoke covers the existing rendered inspector.
|
|
|
|
## Validation
|
|
|
|
- Sail attempts:
|
|
- `cd apps/platform && ./vendor/bin/sail artisan test --compact --filter=Spec424` -> interrupted after about two minutes with no output because Sail/Docker exec did not progress in this session.
|
|
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Unit/Support/TenantConfiguration/Spec424SecurityDefaultsSourceContractTest.php tests/Unit/Support/TenantConfiguration/Spec424SecurityDefaultsTypedSemanticsTest.php tests/Feature/TenantConfiguration/Spec424SecurityDefaultsCaptureReadinessTest.php` -> interrupted after about one minute with no output for the same reason.
|
|
- Local fallback validation:
|
|
- `cd apps/platform && php artisan list --raw | rg '^tenant-configuration:sync-defaults'` -> passed; command is registered.
|
|
- `cd apps/platform && php artisan test --compact tests/Unit/Support/TenantConfiguration/Spec424SecurityDefaultsSourceContractTest.php tests/Unit/Support/TenantConfiguration/Spec424SecurityDefaultsTypedSemanticsTest.php tests/Feature/TenantConfiguration/Spec424SecurityDefaultsCaptureReadinessTest.php` -> passed, 32 tests / 206 assertions.
|
|
- `cd apps/platform && php artisan test --compact tests/Browser/Spec424SecurityDefaultsComparableRenderableOperatorSurfaceSmokeTest.php` -> passed, 1 test / 46 assertions.
|
|
- `cd apps/platform && php artisan test --compact tests/Feature/TenantConfiguration/Spec421EntraCoverageLevelPromotionTest.php tests/Feature/TenantConfiguration/Spec419M365RegistryExpansionTest.php tests/Unit/Support/TenantConfiguration/Spec419M365WorkloadRegistryTest.php` -> passed, 11 tests / 814 assertions, 1 skipped.
|
|
- `cd apps/platform && php artisan test --compact --filter=ClaimGuard` -> passed, 110 tests / 123 assertions.
|
|
- `cd apps/platform && php artisan test --compact tests/Feature/TenantConfiguration/Spec420M365GenericEvidenceCaptureTest.php tests/Feature/TenantConfiguration/Spec421EntraComparableRenderableTest.php tests/Unit/Support/TenantConfiguration/Spec421EntraConditionalAccessNormalizerTest.php tests/Unit/Support/TenantConfiguration/Spec421EntraComparableDiffTest.php tests/Unit/Support/TenantConfiguration/Spec421EntraRenderableSummaryTest.php` -> passed, 14 tests / 120 assertions.
|
|
- `cd apps/platform && php artisan test --compact tests/Feature/TenantConfiguration/Spec420M365NoLegacyTest.php tests/Feature/TenantConfiguration/Spec415NoLegacyNoUiActivationTest.php tests/Feature/TenantConfiguration/TenantConfigurationKernelSchemaTest.php` -> passed, 5 tests / 41 assertions, 1 skipped.
|
|
- `cd apps/platform && ./vendor/bin/pint --dirty --format agent` -> passed and formatted `apps/platform/app/Services/TenantConfiguration/GenericContentEvidenceCaptureService.php`.
|
|
- `git diff --check` -> passed.
|
|
|
|
## Deployment Impact
|
|
|
|
- **Schema migrations**: none added.
|
|
- **Migration seed alignment**: one existing fresh-install seed row was aligned for `securityDefaults` only, as allowed by the amended plan. No other existing migration seed was changed.
|
|
- **Existing databases**: already-migrated staging/production databases must run `cd apps/platform && php artisan tenant-configuration:sync-defaults` after deployment and before Security Defaults capture is attempted. The command is idempotent, syncs Coverage v2 resource-type and supported-scope defaults, and deactivates any stale active Security Defaults TCM planning row.
|
|
- **Environment variables**: none.
|
|
- **Queues / scheduler / workers**: no new jobs, queues, scheduler entries, or worker requirements. Existing capture queue behavior applies.
|
|
- **Storage / volumes**: none.
|
|
- **Runtime assets**: none.
|
|
- **Provider registration**: none.
|
|
- **External services**: no new live Microsoft Graph, TCM, provider, Microsoft docs, or remote network calls in render/compare paths. Capture uses the existing provider gateway and Graph client abstraction.
|
|
- **Staging / production**: validate registry sync/default state and the focused Coverage v2 inspect path in Staging before Production promotion.
|
|
|
|
## Residual Risks / Follow-Up Candidates
|
|
|
|
- This spec supports only `securityDefaults`. Other Entra types remain deferred.
|
|
- This is internal/operator content-backed comparable/renderable support only. It is not restore readiness, certification, legal/regulatory attestation, customer proof, Review Pack output, or full Entra/M365 coverage.
|
|
- Existing long-lived environments need the documented `tenant-configuration:sync-defaults` deployment step before Security Defaults capture; no schema migration handles that automatically.
|