# Implementation Report: Spec 415 - Generic Content-Backed Capture ## Preflight - Branch: `415-generic-content-backed-capture` - Baseline HEAD: `dfda397e feat: migrate tcm first coverage core cutover (#481)` - Initial dirty state: `specs/415-generic-content-backed-capture/` untracked as the active spec artifact set. - Spec 414 dependency: treated as completed/validated context only; no files under `specs/414-tcm-first-coverage-core-cutover/` were modified. - Existing equivalent check: no existing `tenant_configuration_resources` or `tenant_configuration_resource_evidence` tables/models were present before implementation. ## Implementation Scope - Added `tenant_configuration_resources` and `tenant_configuration_resource_evidence` persistence with `workspace_id`, `managed_environment_id`, `provider_connection_id`, and `resource_type_id`; no `tenant_id` fields. - Added PostgreSQL composite scope constraints for managed environment/workspace, provider connection/workspace/environment, evidence/resource/provider/type, evidence/latest-resource/provider/type, and evidence/operation-run/workspace/environment integrity while keeping SQLite-compatible migrations for tests. - Added a database-level `provider_connections(managed_environment_id, workspace_id)` binding so provider connections cannot be attached to a managed environment from another workspace. - Added a scoped `latest_evidence_id` foreign key so a resource can only point at evidence for the same resource, workspace, managed environment, provider connection, and resource type. - Added models and factories for concrete resources and append-only evidence. - Added bounded capture outcomes only: `captured`, `capture_blocked_missing_contract`, `capture_blocked_permission`, `capture_blocked_beta`, `capture_blocked_unsupported`, `capture_failed`. - Added contract resolution through `GraphContractRegistry` with explicit mappings only: - `deviceAndAppManagementAssignmentFilter` -> `assignmentFilter` -> captured when Graph succeeds. - `notificationMessageTemplate` -> `notificationMessageTemplate` -> captured when Graph succeeds. - `roleScopeTag` -> beta-backed and blocked by default. - Other initial TCM types remain `capture_blocked_missing_contract` until explicit contracts are introduced. - Added deterministic normalization, configured volatile-field stripping, SHA-256 payload hashes, and recursive secret/token/header/cookie redaction for metadata/context. - Added `tenant_configuration.capture` operation type/catalog label and a queued capture job. - Added defense-in-depth scope validation before remote provider work: the job resolves provider connections only inside the OperationRun workspace/environment, and the capture service validates tenant/provider/run/context scope before calling the provider gateway. ## Capture Eligibility Matrix | Canonical type | Source class | Source contract key | Default outcome | | --- | --- | --- | --- | | `deviceAndAppManagementAssignmentFilter` | `tcm` | `assignmentFilter` | `captured` when Graph succeeds | | `deviceEnrollmentLimitRestriction` | `tcm` | none | `capture_blocked_missing_contract` | | `deviceEnrollmentPlatformRestriction` | `tcm` | none | `capture_blocked_missing_contract` | | `deviceEnrollmentStatusPageWindows10` | `tcm` | none | `capture_blocked_missing_contract` | | `appProtectionPolicyAndroid` | `tcm` | none | `capture_blocked_missing_contract` | | `appProtectionPolicyiOS` | `tcm` | none | `capture_blocked_missing_contract` | | `notificationMessageTemplate` | `graph_v1_fallback` | `notificationMessageTemplate` | `captured` when Graph succeeds | | `roleScopeTag` | `graph_beta_experimental` | `roleScopeTag` | `capture_blocked_beta` unless beta capture is explicitly enabled | ## Operation And RBAC - Start capability: existing `Capabilities::EVIDENCE_MANAGE`. - Authorization behavior: - non-member and excluded explicit environment entitlement -> 404. - workspace member without capability / readonly -> 403. - same workspace/environment/provider connection -> allowed. - OperationRun behavior: - creates/reuses `tenant_configuration.capture` queued runs from idempotent provider/resource-type inputs. - remote/provider work is queued through the central OperationRun lifecycle. - the central queued execution gate validates `provider_connection_id` against both the OperationRun workspace and managed environment before provider work is allowed. - the capture job and capture service also fail closed before Graph access when the provider connection, managed environment, OperationRun columns, or OperationRun target scope do not match. - summary keys are existing canonical keys only: `total`, `processed`, `succeeded`, `skipped`, `failed`, `errors_recorded`. - raw payloads and Graph credential options are not written into OperationRun context or audit metadata. - exception messages are reduced to bounded `RunFailureSanitizer` reason codes for resource outcomes; dispatch/job failure audit messages are sanitized before persistence. - Audit action IDs used: - `tenant_configuration.capture.started` - `tenant_configuration.capture.completed` - `tenant_configuration.capture.failed` ## Product Surface - No rendered UI surface changed. - No Filament resources, pages, widgets, relation managers, routes, panel providers, navigation entries, views, assets, review/report/evidence pages, or restore readiness surfaces were added or modified. - Browser proof: `N/A - no rendered UI surface changed`. - Human Product Sanity: `N/A - no product surface changed`. - Visible complexity outcome: unchanged. - Product Surface exceptions: none. - No completed historical spec was rewritten or stripped of validation/task/review history. ## Filament V5 Contract - Livewire v4.0+ compliance: no Livewire/Filament runtime UI code changed; application package baseline remains Livewire v4. - Provider registration location: unchanged; Laravel 11+/12 panel provider registration remains `bootstrap/providers.php`. - Global search: no globally searchable resource was added; `N/A`. - Destructive/high-impact actions: no UI action added; queued capture start is backend service only and RBAC-gated. - Asset strategy: no assets registered; no `filament:assets` requirement from this spec. - Testing plan: Unit/Feature/PostgreSQL lanes only; no Livewire page/widget/relation-manager/action tests required. ## Files Changed - Persistence: - `apps/platform/database/migrations/2026_06_25_000415_create_tenant_configuration_capture_tables.php` - `apps/platform/app/Models/TenantConfigurationResource.php` - `apps/platform/app/Models/TenantConfigurationResourceEvidence.php` - `apps/platform/database/factories/TenantConfigurationResourceFactory.php` - `apps/platform/database/factories/TenantConfigurationResourceEvidenceFactory.php` - Capture support/services: - `apps/platform/app/Support/TenantConfiguration/CaptureOutcome.php` - `apps/platform/app/Services/TenantConfiguration/CoverageSourceContractResolver.php` - `apps/platform/app/Services/TenantConfiguration/CoverageSourceContractDecision.php` - `apps/platform/app/Services/TenantConfiguration/GenericPayloadNormalizer.php` - `apps/platform/app/Services/TenantConfiguration/CoveragePayloadRedactor.php` - `apps/platform/app/Services/TenantConfiguration/CoverageCaptureOutcomeSummarizer.php` - `apps/platform/app/Services/TenantConfiguration/CoverageResourceUpserter.php` - `apps/platform/app/Services/TenantConfiguration/CoverageEvidenceWriter.php` - `apps/platform/app/Services/TenantConfiguration/GenericContentEvidenceCaptureService.php` - `apps/platform/app/Services/TenantConfiguration/StartTenantConfigurationCapture.php` - `apps/platform/app/Jobs/TenantConfiguration/CaptureTenantConfigurationEvidenceJob.php` - Operation/audit registry: - `apps/platform/app/Services/Operations/QueuedExecutionLegitimacyGate.php` - `apps/platform/app/Support/OperationRunType.php` - `apps/platform/app/Support/OperationCatalog.php` - `apps/platform/app/Support/Audit/AuditActionId.php` - Tests: - `apps/platform/tests/Unit/Support/TenantConfiguration/Spec415CoverageSourceContractResolverTest.php` - `apps/platform/tests/Unit/Support/TenantConfiguration/Spec415GenericPayloadNormalizerTest.php` - `apps/platform/tests/Unit/Support/TenantConfiguration/Spec415CoverageRedactionTest.php` - `apps/platform/tests/Unit/Support/TenantConfiguration/Spec415CoverageCaptureOutcomeTest.php` - `apps/platform/tests/Unit/Support/TenantConfiguration/Spec415CoverageCaptureOperationRunSummaryTest.php` - `apps/platform/tests/Feature/TenantConfiguration/Spec415CoverageEvidencePersistenceTest.php` - `apps/platform/tests/Feature/TenantConfiguration/Spec415ProviderConnectionScopeTest.php` - `apps/platform/tests/Feature/TenantConfiguration/Spec415CoverageCaptureAuthorizationTest.php` - `apps/platform/tests/Feature/TenantConfiguration/Spec415CoverageCaptureOperationRunTest.php` - `apps/platform/tests/Feature/TenantConfiguration/Spec415GenericContentBackedCaptureTest.php` - `apps/platform/tests/Feature/TenantConfiguration/Spec415NoLegacyNoUiActivationTest.php` - `apps/platform/tests/Unit/Operations/QueuedExecutionLegitimacyGateTest.php` ## Validation - `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` -> pass. - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Unit/Support/TenantConfiguration` -> 22 passed, 69 assertions. - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Unit/Operations/QueuedExecutionLegitimacyGateTest.php` -> 12 passed, 106 assertions. - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/TenantConfiguration/Spec415GenericContentBackedCaptureTest.php` -> 4 passed, 50 assertions. - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/TenantConfiguration` -> 24 passed, 7 skipped, 129 assertions. - `cd apps/platform && ./vendor/bin/sail php vendor/bin/pest -c phpunit.pgsql.xml tests/Feature/TenantConfiguration` -> 31 passed, 141 assertions. - PostgreSQL migration smoke on `tenantatlas_testing`: `migrate:fresh --force` -> pass; targeted rollback of `2026_06_25_000415_create_tenant_configuration_capture_tables.php` -> pass; final `migrate:fresh --force` -> pass. - PostgreSQL latest-evidence lifecycle coverage: scoped pointer mismatch is rejected, same-scope pointer is accepted, and resource deletion cascades linked evidence without constraint failure. - `git diff --check` -> pass. - Additional untracked-file whitespace scan -> pass. ## Deployment Impact - Migrations: yes, adds two new tenant configuration capture tables with PostgreSQL JSONB/check constraints and composite scope foreign keys; also adds a provider-connection environment/workspace binding constraint on the existing `provider_connections` table. - Queue worker: yes, new queued capture job. - Environment variables: no new variables. - Scheduler: no change. - Storage/volumes: no change. - Assets: no change; `filament:assets` not required by this spec. - Staging/production: run migrations before enabling any future caller; ensure queue workers are running; validate staging data has no existing provider-connection workspace/environment mismatches before production promotion. ## Deferred Work - Add explicit source contracts for the remaining TCM-backed resource types before claiming captured evidence for them. - Add any UI start surface only through a future UI-affecting spec with Product Surface review.