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
15 KiB
15 KiB
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_idownership path. Existing fresh-install seed alignment is allowed only for the already-identified one-rowsecurityDefaultsentry in2026_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
securityDefaultscurrently exists as a registry-only/out-of-scope TCM planning row inResourceTypeRegistryand the fresh-install workload expansion migration.CoverageSourceContractResolverhas nosecurityDefaultsmapping before this spec.config/graph_contracts.phphas no Security Defaults contract before this spec.CoverageIdentityStrategyRegistryhas nosecurityDefaultsidentity strategy before this spec.EntraComparablePayloadNormalizersupports Conditional Access before this spec and not Security Defaults.GenericContentEvidenceCaptureServicealready accepts singleton Graph payloads that contain anid, so no broad singleton capture framework is planned.- Official Microsoft Graph documentation confirms the Security Defaults read endpoint as
GET /policies/identitySecurityDefaultsEnforcementPolicy, the resource fieldsid,displayName,description, andisEnabled, and least-privileged read permissionPolicy.Read.All.
Implementation Summary
- Added
securityDefaultsas an explicit Coverage v2 source contract inapps/platform/config/graph_contracts.phpusing/policies/identitySecurityDefaultsEnforcementPolicy,graph_version = v1.0,response_shape = singleton, safe select fields, singleton-safe volatile fields, andPolicy.Read.All. - Added
securityDefaultstoCoverageSourceContractResolverexplicit mappings; missing contract resource still blocks ascapture_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 bypassingGraphClientInterface. - Updated
MicrosoftGraphClient, Security Defaults capture, andgraph:contract:checksingleton probes so they use the contract-local v1.0 endpoint and do not send$topto/policies/identitySecurityDefaultsEnforcementPolicy; the endpoint remains constrained to the contract$select. - Promoted only the
securityDefaultsregistry row to activegraph_v1_fallback/fallback_supported/ internal-only / non-restorable support inResourceTypeRegistry, and aligned the one existing fresh-install migration seed row allowed by the amended plan. - Added the idempotent
tenant-configuration:sync-defaultsdeploy 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 andsourceId-only Security Defaults payloads remainmissing_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
ClaimGuardso 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.
CoveragePayloadRedactorwas 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_idownership path was added. SupportedScopeResolverwas 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.phppassed, 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:assetsdeployment requirement introduced. - Testing plan coverage: unit tests cover contract, graph version URL, singleton no-
$topprobes, 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 formattedapps/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
securityDefaultsonly, 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-defaultsafter 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-defaultsdeployment step before Security Defaults capture; no schema migration handles that automatically.