# Implementation Report: Exchange Evidence Capture Adapter and Content-Only Guard Date: 2026-07-08 ## Preflight - Branch: `434-exchange-evidence-capture-adapter-content-only-guard` - HEAD: `a6f45299 feat: add Exchange credential permission evidence readiness (#500)` - Initial dirty state: active Spec 434 package was untracked; no completed Specs 429-433 were edited. - Final dirty state: intended Spec 434 runtime/test files plus active Spec 434 package only. - Activated gates/skills: `spec-kit-implementation-loop`, `pest-testing`, spec readiness, workspace scope safety, OperationRun truth, evidence anchor contract, provider freshness semantics, Product Surface gate, customer output gate, TCM cutover guard, RBAC/action safety. - Hard-gate stop conditions: none. ## Close-Out Dirty State - `git status --short --branch`: - `## 434-exchange-evidence-capture-adapter-content-only-guard` - ` M apps/platform/app/Services/TenantConfiguration/CoverageEvidenceWriter.php` - `?? apps/platform/app/Services/TenantConfiguration/ExchangePowerShellCaptureEligibilityGate.php` - `?? apps/platform/app/Services/TenantConfiguration/ExchangePowerShellContentOnlyEvidenceGuard.php` - `?? apps/platform/app/Services/TenantConfiguration/ExchangePowerShellEvidenceCaptureAdapter.php` - `?? apps/platform/app/Services/TenantConfiguration/ExchangePowerShellIdentityEvidenceGate.php` - `?? apps/platform/tests/Feature/TenantConfiguration/Spec434ExchangeEvidenceCaptureAdapterTest.php` - `?? specs/434-exchange-evidence-capture-adapter-content-only-guard/` - `git diff --name-only`: `apps/platform/app/Services/TenantConfiguration/CoverageEvidenceWriter.php` - Untracked Spec 434 package files are expanded in the Files Changed section below. ## Completed-Spec Context - Specs 429-433 were treated as read-only context. - Spec 433 implementation report state: PASS WITH CONDITIONS; the remaining condition was a broader existing provider readiness card and did not block the Spec 434 adapter. - Spec 434 keeps the existing `tenant_configuration.capture` OperationRun as the only evidence-owning operation type for this slice. ## Runtime Changes - Added `ExchangePowerShellEvidenceCaptureAdapter`. - Added `ExchangePowerShellCaptureEligibilityGate`. - Added `ExchangePowerShellIdentityEvidenceGate`. - Added `ExchangePowerShellContentOnlyEvidenceGuard`. - Extended `CoverageEvidenceWriter::append()` with an optional maximum coverage level cap. ## Boundary Proof - Included capture targets only: `transportRule`, `remoteDomain`, `inboundConnector`. - Unsupported Exchange/Teams targets block before resource/evidence append. - Same-scope checks cover workspace, managed environment, provider connection, and `OperationRun.context.target_scope`. - Wrong operation type blocks before evidence append. - Spec 433 combined readiness is required. - `ProviderCapabilityEvaluator` must return `Supported` for `exchange_powershell_invoke`. - Spec 432 runner results are accepted only as successful list collections with `output_state` of `structured_collection` or `empty_collection`. - Spec 430 verified pending command contracts are required. - Adapter-owned endpoints use `exchange_online_powershell_rest:`, not Graph endpoints. - No `ProviderGateway`, generic Graph list path, fake Graph endpoint, `fake_empty_success`, or fake captured outcome is used. ## Evidence And Identity - Identity hard-stop runs before resource upsert/evidence append. - Blocks missing stable ID, display-name-only identity, derived-only remote domain identity, duplicate stable identity, unsupported identity, and existing same-scope identity conflict. - Empty collections write zero summary counts only and create no resource/evidence rows. - Non-empty captures store internal content-backed evidence only. - Exchange adapter calls `CoverageEvidenceWriter` with a `content_backed` maximum and verifies evidence state, coverage level, and latest claim state after write. - The path does not promote to comparable, renderable, certified, restore-ready, or customer-claimable states. ## Product Surface - Product Surface Impact: none. - UI Surface Impact: none. - No legacy posture exception required. - Page archetype, surface budgets, Technical Annex/deep-link demotion, canonical status vocabulary: N/A - no rendered UI surface changed. - Product Surface exceptions: none. - Browser proof: N/A - no rendered UI surface changed. - Human Product Sanity: N/A - no rendered UI surface changed. - Visible complexity outcome: neutral. ## Filament / Livewire / Assets - Livewire v4 compliance unchanged; installed Livewire is v4.1.4. - Provider registration unchanged under `apps/platform/bootstrap/providers.php`. - Global search unchanged; no resources were added or modified. - Destructive/high-impact actions: none. - Asset strategy: none; no Filament assets added and no deploy-time `filament:assets` requirement introduced. ## No-Scope-Expansion Proof - No UI, routes, navigation, Filament pages/resources/widgets, Livewire components, Blade views, assets, jobs, schedules, listeners, customer output, Review Pack, PDF, restore, certification, compare/render surface, migration, Exchange-specific evidence table, `tenant_id`, legacy shim, fallback reader, dual write, or Exchange mini-platform added. - Laravel 12 / PHP 8.4 / PostgreSQL / Pest 4 compatibility unchanged. - Deployment impact: none; no env vars, migrations, queues, cron, storage, assets, or Dokploy runtime changes. ## Validation - `cd apps/platform && ./vendor/bin/sail artisan test --filter=Spec434 --compact`: failed before results with process signal 9; fallback used per task T053. - `cd apps/platform && php artisan test --filter=Spec434 --compact`: PASS, 25 tests / 154 assertions. - `cd apps/platform && php artisan test --filter=Spec433 --compact`: PASS, 48 tests / 211 assertions. - `cd apps/platform && php artisan test --filter='Spec432|Spec431|Spec430' --compact`: PASS, 173 tests / 1357 assertions. - `cd apps/platform && php artisan test --filter='Spec415|Spec417|Spec419|Spec420|Spec426|Spec427' --compact`: PASS, 202 tests / 2137 assertions, 8 existing PostgreSQL-specific skips. - `cd apps/platform && php artisan test --filter='ProviderCapabilityRegistryTest|ProviderCapabilityEvaluationTest' --compact`: PASS, 7 tests / 30 assertions. - `cd apps/platform && ./vendor/bin/pint --dirty --test`: PASS, 6 files. - `git diff --check`: PASS for tracked changes. Close-out review note: because the new Spec 434 package and Exchange adapter/test files remain untracked in the current dirty state, `git diff --check` does not validate their content until they are staged/tracked; `git status --short` is the source of truth for the untracked file list. ## Post-Implementation Analysis - Iteration 1 findings fixed: - Captured `maximumCoverageLevel` into the writer transaction closure. - Treated empty accepted collections as zero-item captured results, not unsupported failures. - Reused `CoverageResourceIdentityEvaluator` in the pre-writer identity gate to catch existing unsafe same-scope conflicts. - Fixed Pest dataset shape and Pint formatting. - Remaining in-scope findings: none. - Residual risk: the adapter is internal and not wired to a job/start surface in Spec 434; a future spec must explicitly design invocation-to-capture orchestration if needed. - Close-out hotfix corrections: - Requirements checklist synchronized to implementation evidence. - Changed-files inventory corrected to include all active Spec 434 artifacts in the dirty state. - Identity proof wording corrected: unsafe identity uses adapter code order plus zero resource/evidence assertions, not a literal writer spy. - Static validation wording corrected so `git diff --check` is not claimed to validate untracked file content. ## Files Changed - `apps/platform/app/Services/TenantConfiguration/CoverageEvidenceWriter.php` - `apps/platform/app/Services/TenantConfiguration/ExchangePowerShellCaptureEligibilityGate.php` - `apps/platform/app/Services/TenantConfiguration/ExchangePowerShellContentOnlyEvidenceGuard.php` - `apps/platform/app/Services/TenantConfiguration/ExchangePowerShellEvidenceCaptureAdapter.php` - `apps/platform/app/Services/TenantConfiguration/ExchangePowerShellIdentityEvidenceGate.php` - `apps/platform/tests/Feature/TenantConfiguration/Spec434ExchangeEvidenceCaptureAdapterTest.php` - `specs/434-exchange-evidence-capture-adapter-content-only-guard/spec.md` - `specs/434-exchange-evidence-capture-adapter-content-only-guard/plan.md` - `specs/434-exchange-evidence-capture-adapter-content-only-guard/tasks.md` - `specs/434-exchange-evidence-capture-adapter-content-only-guard/checklists/requirements.md` - `specs/434-exchange-evidence-capture-adapter-content-only-guard/implementation-report.md` ## Merge Readiness Gate Status: PASS. Merge-ready: yes. Manual review prompt: Review Spec 434 for backend-only Exchange PowerShell evidence capture. Focus on the new adapter and gates, the optional writer coverage cap, same-scope/OperationRun truth, identity hard-stop behavior, empty collection behavior, and proof that no UI, jobs, routes, migrations, Graph fallback, customer output, restore/certification/compare/render path, mini-platform, or `tenant_id` ownership truth was introduced.