TenantAtlas/specs/420-m365-generic-evidence-coverage-pack/implementation-report.md
Ahmed Darrazi 9405058433
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 5m46s
feat: complete m365 generic evidence coverage pack
2026-06-27 13:00:22 +02:00

155 lines
16 KiB
Markdown

# Implementation Report: Spec 420 - M365 Generic Evidence Coverage Pack
## Status
- Result: passed implementation loop; ready for manual review.
- Active spec directory: `specs/420-m365-generic-evidence-coverage-pack`.
- Branch: `420-m365-generic-evidence-coverage-pack`.
- HEAD at implementation start/final validation: `52523980 feat: expand m365 tcm workload registry (#486)`.
- Initial dirty state: active spec directory was untracked; no unrelated modified runtime files were present.
- Final dirty state: expected active-spec, runtime, and test changes only. No unrelated work was modified.
- Historical specs: Specs 414, 415, 417, 418, and 419 were used as read-only dependency context only. No closed historical spec was rewritten or stripped of validation history.
## Activated Skills And Gates
- Activated skills: `spec-kit-implementation-loop`, `pest-testing`; repo gates applied for spec readiness, workspace scope safety, RBAC/action safety, operation-run truth, evidence anchor contract, Product Surface, TCM cutover guard, customer output, and browser read-only audit.
- Hard-gate result: pass with conditions before code, then pass after validation.
- Stop conditions: none hit. No compare/render/restore/certification/customer output, no UI start action, no dashboard, no endpoint guessing, no direct HTTP, no `tenant_id`, no v1 compatibility adapter, and no M365 mini-platform were introduced.
- Analysis/fix iterations: two implementation loops; one browser assertion correction because the existing safe inspect modal intentionally does not render source endpoint, followed by a manual-review fix loop for redaction proof, RBAC role precision, and OperationRun browser-fixture summary keys. Final post-implementation analysis found no confirmed in-scope findings.
- Merge Readiness Gate: passed, subject to human review.
## Changed Files
- Runtime:
- `apps/platform/app/Services/TenantConfiguration/GenericContentEvidenceCaptureService.php`
- `apps/platform/app/Services/TenantConfiguration/CoverageSourceContractResolver.php`
- `apps/platform/app/Services/TenantConfiguration/CoverageIdentityStrategyRegistry.php`
- `apps/platform/app/Services/TenantConfiguration/CanonicalIdentityResolver.php`
- Existing test update:
- `apps/platform/tests/Unit/Support/TenantConfiguration/Spec417CoverageIdentityStrategyRegistryTest.php`
- New Spec 420 tests:
- `apps/platform/tests/Unit/Support/TenantConfiguration/Spec420M365CaptureSourceContractResolverTest.php`
- `apps/platform/tests/Unit/Support/TenantConfiguration/Spec420M365CaptureEligibilityTest.php`
- `apps/platform/tests/Unit/Support/TenantConfiguration/Spec420M365GenericPayloadNormalizerTest.php`
- `apps/platform/tests/Unit/Support/TenantConfiguration/Spec420M365CaptureIdentityStrategyTest.php`
- `apps/platform/tests/Unit/Support/TenantConfiguration/Spec420M365CaptureClaimGuardTest.php`
- `apps/platform/tests/Unit/Support/TenantConfiguration/Spec420M365CaptureRedactionTest.php`
- `apps/platform/tests/Feature/TenantConfiguration/Spec420M365GenericEvidenceCaptureTest.php`
- `apps/platform/tests/Feature/TenantConfiguration/Spec420M365CaptureOperationRunTest.php`
- `apps/platform/tests/Feature/TenantConfiguration/Spec420M365CaptureAuthorizationTest.php`
- `apps/platform/tests/Feature/TenantConfiguration/Spec420M365ProviderConnectionScopeTest.php`
- `apps/platform/tests/Feature/TenantConfiguration/Spec420M365NoOverclaimTest.php`
- `apps/platform/tests/Feature/TenantConfiguration/Spec420M365NoLegacyTest.php`
- `apps/platform/tests/Feature/TenantConfiguration/Spec420M365NoTenantIdTest.php`
- `apps/platform/tests/Browser/Spec420M365GenericEvidenceOperatorSurfaceSmokeTest.php`
- Spec artifacts:
- `specs/420-m365-generic-evidence-coverage-pack/tasks.md`
- `specs/420-m365-generic-evidence-coverage-pack/implementation-report.md`
## Implementation Summary
- `conditionalAccessPolicy` now resolves through an explicit `graph_contracts.types.conditionalAccessPolicy` mapping in the existing generic capture resolver.
- The selected missing-contract types `acceptedDomain`, `appPermissionPolicy`, and `dlpCompliancePolicy` fail closed as `capture_blocked_missing_contract` with `missing_source_contract_mapping`.
- Registry truth from Spec 419 is preserved: selected M365 types remain registry-seeded as `source_class = tcm`, `support_state = out_of_scope`, and `default_claim_state = internal_only`; runtime capture eligibility is narrow and contract-driven.
- Source metadata now carries actual source contract key, endpoint, source version, source schema hash, registry source class, registry support state, and schema-hash availability where captured.
- `conditionalAccessPolicy` uses a narrow identity strategy with Graph object ID stable keys. `CanonicalIdentityResolver` remains the only resolver path and now honors a strategy-level `stable_key_kind` before falling back to source-class defaults.
- Existing `CoverageResourceUpserter`, `CoverageEvidenceWriter`, `StartTenantConfigurationCapture`, `CaptureTenantConfigurationEvidenceJob`, and `OperationRunService` are reused. `GenericContentEvidenceCaptureService` remains the capture path and now redacts normalized payloads before persistence/hashing while preserving the raw-payload evidence boundary.
## Capture Eligibility Matrix
| Canonical type | Runtime result | Provider called | Evidence row | Notes |
| --- | --- | --- | --- | --- |
| `conditionalAccessPolicy` | `captured` when fake Graph payload exists | yes, through `GraphClientInterface::listPolicies()` | yes | Explicit repo-real source contract only; no alias or endpoint guessing. |
| `acceptedDomain` | `capture_blocked_missing_contract` | no | no | Stable reason `missing_source_contract_mapping`. |
| `appPermissionPolicy` | `capture_blocked_missing_contract` | no | no | Stable reason `missing_source_contract_mapping`. |
| `dlpCompliancePolicy` | `capture_blocked_missing_contract` | no | no | Stable reason `missing_source_contract_mapping`; source aliases are ignored at runtime. |
## Source Contract Matrix
| Canonical type | Contract key | Endpoint | Version/schema | Outcome |
| --- | --- | --- | --- | --- |
| `conditionalAccessPolicy` | `conditionalAccessPolicy` | `/identity/conditionalAccess/policies` | `v1.0`; schema hash captured when available, explicit unavailable state tested | Capture enabled. |
| `acceptedDomain` | none | none | none | Missing contract blocker. |
| `appPermissionPolicy` | none | none | none | Missing contract blocker. |
| `dlpCompliancePolicy` | none | none | none | Missing contract blocker. |
## Evidence And OperationRun Proof
- Captured Conditional Access evidence persists one `TenantConfigurationResource` and one append-only `TenantConfigurationResourceEvidence` row with raw payload, normalized payload, deterministic payload hash, redacted permission context, source metadata, and `operation_run_id`.
- Missing-contract selected types create no fake resources or evidence rows.
- OperationRun type remains `tenant_configuration.capture`; no `tenant_configuration.m365_capture` type was introduced.
- OperationRun lifecycle transitions remain service-owned through `OperationRunService`.
- Summary counts use existing whitelisted numeric keys.
- Dedupe/retry behavior reuses the active-run check in `StartTenantConfigurationCapture`.
- OperationRun has no separate message column in the current model; OperationRun context and failure summary do not contain raw payloads, provider response bodies, tokens, or secrets.
## RBAC And Scope Proof
- Authorized owner and manager paths can start the selected capture.
- Operator, readonly, and missing-capability paths are denied with 403 after membership is established.
- Non-member and missing environment entitlement paths are denied without leaking environment data.
- Cross-workspace/cross-environment provider connections are rejected before run creation.
- Job execution revalidates workspace, managed environment, provider connection, OperationRun type, and target scope before provider work.
## Redaction, Claims, And Safety Proof
- Secret-bearing provider payload tests prove raw payload remains inside the evidence raw-payload storage boundary, while normalized evidence payload, permission context, OperationRun context/failure summary, audit metadata, and logs do not persist or emit the secret values.
- Broad M365, certified, restore-ready, customer-ready, complete-tenant, all-resource, and unscoped 100% claims remain blocked.
- Captured Conditional Access evidence remains `internal_only` by default.
- No customer output, report, export, publish, restore, certify, or Review Pack path was added.
## No-Legacy And Data Model Proof
- No v1 adapter, fallback reader, dual-write path, old gap taxonomy, workload-specific table/model/engine/namespace/service, or mini-platform was introduced.
- No `tenant_id` ownership truth was added to Coverage v2 tables or changed capture paths.
- No migrations, constraints, indexes, enums/status families, operation types, tables, or persisted taxonomies were added.
## Product Surface Close-Out
- No-legacy posture: no legacy UI/API compatibility path and no approved exception.
- Product Surface Impact: data-impact only on the existing Coverage v2 Readiness surface when a captured M365 generic evidence row exists.
- UI Surface Impact: no runtime UI files, routes, navigation entries, Filament resources/widgets/pages/actions, Blade views, Livewire components, labels, customer outputs, dashboards, or downloads were edited.
- Page archetype: existing internal operator readiness/review page.
- Surface budgets: unchanged; one existing table row and existing inspect slide-over render seeded data.
- Technical Annex/deep-link demotion: unchanged; source endpoint is intentionally not rendered in the safe inspect modal.
- Canonical status vocabulary: existing Coverage v2 labels only (`Content backed`, `Stable`, `Internal only`, existing capture outcome/status values).
- Product Surface exceptions: none.
- Human Product Sanity: pass. Existing page renders the Conditional Access row as internal generic evidence, shows no broad M365/certified/restore-ready/customer-ready claim, does not expose payload secrets, and adds no new high-impact UI action.
- Visible complexity outcome: unchanged; no new surface controls or navigation.
- Browser proof: `Spec420M365GenericEvidenceOperatorSurfaceSmokeTest` passed. It loads `CoverageV2Readiness`, verifies the row and inspect slide-over, checks Livewire availability, checks no Graph/TCM/provider network calls during render, checks no console/JS errors, and checks no new capture/restore/certify/export/download action on the main surface.
## Filament v5 Output Contract
- Livewire v4.0+ compliance: no Livewire API changes; existing Filament v5/Livewire v4 surface is exercised by the browser test.
- Provider registration location: no panel/provider registration changed. Laravel 12 provider registration remains under `apps/platform/bootstrap/providers.php`.
- Global search: no Filament Resource global-search behavior changed; no new Resource was added.
- Destructive/high-impact actions: no new Filament action or UI start action was added. Backend capture start remains capability-gated and OperationRun/audit backed; there is no new destructive action requiring a Filament confirmation modal.
- Asset strategy: no assets registered and no frontend bundles changed. `php artisan filament:assets` is not newly required by this implementation beyond existing deployment practice.
- Testing plan: page/data impact covered by browser smoke; resolver/capture/evidence/identity/RBAC/scope/OperationRun/no-overclaim/no-legacy/no-tenant-id covered by Pest unit and feature tests.
## Validation Commands
- `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` - passed.
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Unit/Support/TenantConfiguration/Spec420M365CaptureSourceContractResolverTest.php tests/Unit/Support/TenantConfiguration/Spec420M365CaptureEligibilityTest.php tests/Unit/Support/TenantConfiguration/Spec420M365GenericPayloadNormalizerTest.php tests/Unit/Support/TenantConfiguration/Spec420M365CaptureIdentityStrategyTest.php tests/Unit/Support/TenantConfiguration/Spec420M365CaptureClaimGuardTest.php tests/Unit/Support/TenantConfiguration/Spec420M365CaptureRedactionTest.php` - passed in latest combined focused run.
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/TenantConfiguration/Spec420M365GenericEvidenceCaptureTest.php tests/Feature/TenantConfiguration/Spec420M365CaptureOperationRunTest.php tests/Feature/TenantConfiguration/Spec420M365CaptureAuthorizationTest.php tests/Feature/TenantConfiguration/Spec420M365ProviderConnectionScopeTest.php tests/Feature/TenantConfiguration/Spec420M365NoOverclaimTest.php tests/Feature/TenantConfiguration/Spec420M365NoLegacyTest.php tests/Feature/TenantConfiguration/Spec420M365NoTenantIdTest.php` - passed in latest combined focused run.
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Unit/Support/TenantConfiguration/Spec420M365CaptureSourceContractResolverTest.php tests/Unit/Support/TenantConfiguration/Spec420M365CaptureEligibilityTest.php tests/Unit/Support/TenantConfiguration/Spec420M365GenericPayloadNormalizerTest.php tests/Unit/Support/TenantConfiguration/Spec420M365CaptureIdentityStrategyTest.php tests/Unit/Support/TenantConfiguration/Spec420M365CaptureClaimGuardTest.php tests/Unit/Support/TenantConfiguration/Spec420M365CaptureRedactionTest.php tests/Feature/TenantConfiguration/Spec420M365GenericEvidenceCaptureTest.php tests/Feature/TenantConfiguration/Spec420M365CaptureOperationRunTest.php tests/Feature/TenantConfiguration/Spec420M365CaptureAuthorizationTest.php tests/Feature/TenantConfiguration/Spec420M365ProviderConnectionScopeTest.php tests/Feature/TenantConfiguration/Spec420M365NoOverclaimTest.php tests/Feature/TenantConfiguration/Spec420M365NoLegacyTest.php tests/Feature/TenantConfiguration/Spec420M365NoTenantIdTest.php` - 35 passed, 215 assertions.
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Browser/Spec420M365GenericEvidenceOperatorSurfaceSmokeTest.php` - 1 passed, 43 assertions.
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Unit/Support/TenantConfiguration/Spec415CoverageSourceContractResolverTest.php tests/Unit/Support/TenantConfiguration/Spec417CanonicalIdentityResolverTest.php tests/Unit/Support/TenantConfiguration/Spec417CoverageIdentityStrategyRegistryTest.php tests/Unit/Support/TenantConfiguration/Spec419M365WorkloadRegistryTest.php tests/Feature/TenantConfiguration/Spec415GenericContentBackedCaptureTest.php tests/Feature/TenantConfiguration/Spec417CanonicalIdentityPersistenceTest.php tests/Feature/TenantConfiguration/Spec417CoverageResourceIdentityUpsertTest.php tests/Feature/TenantConfiguration/Spec419M365RegistryExpansionTest.php` - 31 passed, 2 skipped, 975 assertions.
- `git diff --check` - passed.
## Deployment Impact
- Migrations: none.
- Environment variables: none.
- Queue/cron: existing tenant-configuration capture queue path still applies; no new worker type.
- Storage/volumes: none.
- Assets: none.
- Dokploy/staging: deploy as ordinary app code/test change; validate on Staging before Production per repo release rules. No additional rollback step beyond reverting code.
- PostgreSQL lane: N/A because no schema/check constraint/index changed.
## Deferred Work And Residual Risk
- Deferred by spec: explicit source contracts for `acceptedDomain`, `appPermissionPolicy`, and `dlpCompliancePolicy`; compare/render/restore/certification/customer output; broader M365 dashboard or support claims.
- Residual risk: this proves a fake-provider generic capture path for the selected first pack, not real Microsoft Graph production coverage for all M365 resources.
- Remaining in-scope findings: none.