TenantAtlas/specs/434-exchange-evidence-capture-adapter-content-only-guard/plan.md
Ahmed Darrazi 6a6d9173e8
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m14s
feat: add Exchange evidence capture adapter guard
2026-07-08 12:38:38 +02:00

265 lines
20 KiB
Markdown

# Implementation Plan: Exchange Evidence Capture Adapter and Content-Only Guard
**Branch**: `434-exchange-evidence-capture-adapter-content-only-guard` | **Date**: 2026-07-08 | **Spec**: `specs/434-exchange-evidence-capture-adapter-content-only-guard/spec.md`
**Input**: Feature specification from `specs/434-exchange-evidence-capture-adapter-content-only-guard/spec.md`
## Summary
Build the internal Exchange PowerShell evidence capture adapter and guard layer that lets TenantPilot safely bridge verified runner output into existing Coverage v2 evidence storage without promoting beyond `content_backed`. The plan reuses `tenant_configuration.capture`, requires Spec 433 readiness, `exchange_powershell_invoke` provider capability support, and Spec 432 runner safety, blocks unsafe identity before writer append, keeps empty collections as zero-item execution outcomes only, preserves generic Graph capture, and adds no UI, migration, trigger, customer output, restore, compare/render, certification, or `tenant_id` ownership truth.
## Technical Context
**Language/Version**: PHP 8.4, Laravel 12
**Primary Dependencies**: Laravel services/jobs/models, existing TenantConfiguration services, `ProviderCapabilityEvaluator`, `ExchangePowerShellInvocationReadinessEvaluator`, `ExchangePowerShellCommandRunner`, `ExchangePowerShellInvocationResult`, OperationRun support
**Storage**: Existing PostgreSQL Coverage v2 tables only: `tenant_configuration_resources` and `tenant_configuration_resource_evidence`; no new table planned
**Testing**: Pest 4 unit/feature tests
**Validation Lanes**: fast-feedback/confidence focused tests; selected regressions; browser N/A
**Target Platform**: Laravel monolith under `apps/platform`
**Project Type**: Web application backend service slice
**Performance Goals**: Adapter uses fake/test runner output and existing resource/evidence writes; no live provider performance target
**Constraints**: No live Exchange calls in tests, no raw payload in OperationRun context/logs, summary counts flat numeric only, same-scope workspace/managed-environment/provider-connection enforcement, no new `CaptureOutcome` enum value without amending the spec
**Scale/Scope**: Three Exchange target types only: `transportRule`, `remoteDomain`, `inboundConnector`
## UI / Surface Guardrail Plan
- **Guardrail scope**: no operator-facing surface change.
- **Affected routes/pages/actions/states/navigation/panel/provider surfaces**: N/A.
- **No-impact class, if applicable**: backend-only internal service/evidence guard.
- **Native vs custom classification summary**: N/A.
- **Shared-family relevance**: evidence/OperationRun/provider internals only; no rendered shared interaction family.
- **State layers in scope**: none for UI.
- **Audience modes in scope**: N/A.
- **Decision/diagnostic/raw hierarchy plan**: no rendered hierarchy; raw/support data remains internal.
- **Raw/support gating plan**: raw payloads remain only in existing evidence storage and must not enter OperationRun context, logs, notifications, or customer output.
- **One-primary-action / duplicate-truth control**: N/A.
- **Handling modes by drift class or surface**: hard-stop if implementation needs UI, route, navigation, trigger, report, customer output, or browser proof.
- **Repository-signal treatment**: report-only for static no-UI/no-route/no-trigger proof; hard-stop if violated.
- **Special surface test profiles**: N/A.
- **Required tests or manual smoke**: static/feature guard tests; browser `N/A - no rendered UI surface changed`.
- **Exception path and spread control**: none.
- **Active feature PR close-out entry**: Guardrail / Exception / Smoke Coverage: `N/A - no rendered UI surface changed`.
- **UI/Productization coverage decision**: No UI surface impact.
- **Coverage artifacts to update**: none.
- **No-impact rationale**: Adapter/guard infrastructure only; no reachable UI surface changes.
- **Navigation / Filament provider-panel handling**: no panel/provider registration or navigation change.
- **Screenshot or page-report need**: no.
## Product Surface Contract Plan
- **Product Surface Contract reference**: `docs/product/standards/product-surface-contract.md`.
- **No-legacy posture**: canonical addition only; no compatibility exception.
- **Page archetype and surface budget plan**: N/A - no page changed.
- **Technical Annex and deep-link demotion plan**: OperationRun, raw evidence, source keys, command metadata, payloads, stdout/stderr, provider diagnostics, and logs remain internal and not default-visible.
- **Canonical status vocabulary plan**: N/A for UI; internal failure/outcome labels remain backend-only.
- **Product Surface exceptions**: none.
- **Browser verification plan**: `N/A - no rendered UI surface changed`.
- **Human Product Sanity plan**: N/A.
- **Visible complexity outcome target**: neutral.
- **Implementation report target**: `specs/434-exchange-evidence-capture-adapter-content-only-guard/implementation-report.md`.
## Filament / Livewire / Deployment Posture
- **Livewire v4 compliance**: unchanged repo baseline; no Livewire code planned.
- **Panel provider registration location**: no Filament panel provider change; Laravel panel providers remain under `apps/platform/bootstrap/providers.php`.
- **Global search posture**: no resource/global search behavior changed.
- **Destructive/high-impact action posture**: no UI action or start surface added.
- **Asset strategy**: no assets; `filament:assets` not required for this slice.
- **Testing plan**: no pages/widgets/relation managers/actions/browser smoke. Backend unit/feature and static guard tests only.
- **Deployment impact**: no env vars, migrations, queues, scheduler, storage, assets, or browser build planned. If implementation discovers one is required, stop and amend the spec/plan first.
## Shared Pattern & System Fit
- **Cross-cutting feature marker**: yes, backend shared systems.
- **Systems touched**: `GenericContentEvidenceCaptureService`, `CoverageSourceContractResolver`, `CoverageResourceUpserter`, `CoverageEvidenceWriter`, `ExchangePowerShellCommandContracts`, `ExchangePowerShellProductionRunner`, `ExchangePowerShellInvocationReadinessEvaluator`, `OperationSummaryKeys`, `SummaryCountsNormalizer`, `tenantpilot` Exchange config, and related tests.
- **Shared abstractions reused**: Coverage v2 models/writer path, `tenant_configuration.capture`, Spec 430 command contracts, Spec 432 runner output guard, Spec 433 readiness evaluator, provider capability/readiness gates, OperationRun summary count normalizer.
- **New abstraction introduced? why?**: likely a narrow Exchange evidence capture adapter and guard classes. They are justified because Exchange runner output is not Graph-backed and must not be forced through Graph capture or manual evidence writes.
- **Why the existing abstraction was sufficient or insufficient**: Existing Coverage v2 storage and operation truth are sufficient. Existing generic capture is Graph-oriented and insufficient for Exchange PowerShell runner output. Existing writer can over-promote, so the Exchange path needs a content-only maximum.
- **Bounded deviation / spread control**: Adapter is provider-owned and internal only. It must not become a generalized provider capture framework, UI vocabulary, persisted taxonomy, customer-output path, or Product Surface runtime framework.
## OperationRun UX Impact
- **Touches OperationRun start/completion/link UX?**: no rendered UX. It touches internal operation type/context requirements.
- **Central contract reused**: existing `tenant_configuration.capture` OperationRun lifecycle and summary-count conventions.
- **Delegated UX behaviors**: N/A.
- **Surface-owned behavior kept local**: none.
- **Queued DB-notification policy**: N/A; no queued notification change.
- **Terminal notification path**: unchanged central lifecycle behavior for capture operations.
- **Exception path**: none. New operation type, start UX, queued notification, or run link behavior requires spec amendment/split.
## Provider Boundary & Portability Fit
- **Shared provider/platform boundary touched?**: yes.
- **Provider-owned seams**: Exchange command contracts, Exchange runner output, Exchange response shape, Exchange-specific identity/redaction handoff.
- **Platform-core seams**: workspace/managed-environment/provider ownership, OperationRun truth, Coverage v2 evidence storage, identity safety, coverage level, claim boundary, customer-proof boundary.
- **Neutral platform terms / contracts preserved**: provider connection, operation, capture outcome, evidence state, coverage level, identity state, claim state.
- **Retained provider-specific semantics and why**: command names and source surface metadata are required to execute and audit the verified Exchange contracts safely.
- **Bounded extraction or follow-up path**: document-in-feature for adapter details; follow-up spec for target promotion and durable collection proof.
## Constitution Check
*GATE: Must pass before implementation. Re-check after implementation.*
- Inventory-first: PASS. This slice creates only internal evidence capture infrastructure; later target promotion remains separate.
- Read/write separation: PASS. No Microsoft tenant writes; no restore/remediation.
- Graph contract path: PASS WITH NOTE. Graph capture remains via existing Graph contracts; Exchange must not fake Graph endpoints or use Graph provider gateway.
- Deterministic capabilities: PASS. Spec 433 readiness and provider capability checks remain required.
- RBAC-UX: PASS. No new UI/action; trusted internal flow must preserve existing authorization/scope.
- Workspace isolation: PASS. Same workspace, managed environment, and provider connection are required before capture/append.
- Tenant isolation: PASS. No `tenant_id`; environment-owned evidence stays workspace + managed-environment scoped.
- Run observability: PASS. Reuses `tenant_configuration.capture`; no new OperationRun type.
- OperationRun start UX: N/A for rendered UX. No local start UX.
- Ops-UX 3-surface feedback: N/A for new UX. Existing capture lifecycle unchanged.
- Ops-UX lifecycle: PASS. Runtime must use service-owned status/outcome transitions.
- Ops-UX summary counts: PASS. Existing flat numeric keys only.
- Ops-UX guards: PASS. Add or extend tests/static proof for summary/context safety.
- Data minimization: PASS. Raw output forbidden outside existing evidence storage.
- Test governance: PASS. Unit/Feature lanes only; browser N/A.
- Proportionality: PASS. New adapter/guards are security and evidence correctness infrastructure.
- No premature abstraction: PASS WITH CONDITION. Keep adapter local and narrow; no broad provider framework.
- Persisted truth: PASS. No new persisted entity/table/artifact planned.
- Behavioral state: PASS WITH CONDITION. New outcome/failure labels must change blocking/validation behavior and stay internal.
- UI semantics: PASS. No UI semantics introduced.
- Shared pattern first: PASS. Reuse Coverage v2, OperationRun, readiness, and summary patterns.
- Provider boundary: PASS. Exchange semantics remain provider-owned.
- V1 explicitness/few layers: PASS. Explicit local adapter/guards, no platform engine.
- Spec discipline / bloat check: PASS. Proportionality review is in `spec.md`.
- Product Surface Contract: PASS. No rendered UI surface changed; browser N/A.
- Temporary TCM/Coverage v2 cutover guard: PASS WITH CONDITION. No customer/operator proof, no legacy adapter, no fallback reader, no `tenant_id`, no raw evidence default display.
## Test Governance Check
- **Test purpose / classification by changed surface**: Unit and Feature for backend service/evidence behavior.
- **Affected validation lanes**: fast-feedback/confidence; browser N/A.
- **Why this lane mix is the narrowest sufficient proof**: Adapter/guard behavior is service/database behavior; no UI, browser, migration, or customer output.
- **Narrowest proving command(s)**:
- `cd apps/platform && ./vendor/bin/sail artisan test --filter=Spec434 --compact`
- `cd apps/platform && ./vendor/bin/sail artisan test --filter='Spec433|Spec432|Spec431|Spec430|Spec415|Spec417|Spec419|Spec420|Spec426|Spec427|ProviderCapabilityRegistryTest|ProviderCapabilityEvaluationTest' --compact`
- `cd apps/platform && ./vendor/bin/pint --dirty --test`
- `git diff --check`
- **Fixture / helper / factory / seed / context cost risks**: keep fake runner output and adapter fixtures local; no broad workspace/provider default helpers. Unsafe-identity writer non-reachability is proven by code order plus zero resource/evidence assertions, not a literal writer spy.
- **Expensive defaults or shared helper growth introduced?**: no.
- **Heavy-family additions, promotions, or visibility changes**: none.
- **Surface-class relief / special coverage rule**: browser `N/A - no rendered UI surface changed`.
- **Closing validation and reviewer handoff**: Re-run Spec 434 focus and selected regressions; verify no UI/migration/trigger/customer output in diff.
- **Budget / baseline / trend follow-up**: none expected.
- **Review-stop questions**: stop if tests need live provider, shell, browser, broad fixture defaults, or schema.
- **Escalation path**: reject-or-split if scope expands beyond adapter/guard infrastructure.
- **Active feature PR close-out entry**: Guardrail / Exception / Smoke Coverage.
- **Why no dedicated follow-up spec is needed**: This spec is the dedicated adapter/guard prerequisite. Target promotion is a separate follow-up.
## Project Structure
### Documentation (this feature)
```text
specs/434-exchange-evidence-capture-adapter-content-only-guard/
├── spec.md
├── plan.md
├── tasks.md
└── checklists/
└── requirements.md
```
### Source Code (repository root)
Likely existing runtime files:
```text
apps/platform/app/Services/TenantConfiguration/GenericContentEvidenceCaptureService.php
apps/platform/app/Services/TenantConfiguration/CoverageSourceContractResolver.php
apps/platform/app/Services/TenantConfiguration/CoverageSourceContractDecision.php
apps/platform/app/Services/TenantConfiguration/CoverageResourceUpserter.php
apps/platform/app/Services/TenantConfiguration/CoverageEvidenceWriter.php
apps/platform/app/Services/TenantConfiguration/ExchangePowerShellCommandContracts.php
apps/platform/app/Services/TenantConfiguration/ExchangePowerShellProductionRunner.php
apps/platform/app/Services/TenantConfiguration/ExchangePowerShellInvocationReadinessEvaluator.php
apps/platform/app/Support/OpsUx/OperationSummaryKeys.php
apps/platform/app/Support/OpsUx/SummaryCountsNormalizer.php
apps/platform/config/tenantpilot.php
```
Likely new runtime files or repo-canonical equivalents:
```text
apps/platform/app/Services/TenantConfiguration/ExchangePowerShellEvidenceCaptureAdapter.php
apps/platform/app/Services/TenantConfiguration/ExchangePowerShellCaptureEligibilityGate.php
apps/platform/app/Services/TenantConfiguration/ExchangePowerShellContentOnlyEvidenceGuard.php
apps/platform/app/Services/TenantConfiguration/ExchangePowerShellIdentityEvidenceGate.php
```
Implemented tests:
```text
apps/platform/tests/Feature/TenantConfiguration/Spec434ExchangeEvidenceCaptureAdapterTest.php
```
**Structure Decision**: Backend TenantConfiguration service slice. No UI, routes, migrations, jobs, schedules, listeners, or customer-output paths.
## Complexity Tracking
| Violation | Why Needed | Simpler Alternative Rejected Because |
| --- | --- | --- |
| New internal Exchange adapter/guard classes | Security/evidence correctness boundary between Exchange runner output and Coverage v2 writer | Reusing Graph capture would require fake endpoints/outcomes; manual evidence writes would bypass existing scope/redaction/storage rules |
| Limited internal outcome/failure labels | Tests and implementation report need distinct safe blockers for prerequisite, identity, empty collection, over-promotion, and sanitized local failure behavior | Generic failed/unknown outcomes would hide whether evidence was safely blocked before append |
## Phase 0: Preflight and Scope Lock
- Capture branch, HEAD, dirty state, and active spec path.
- Re-check Specs 429-433 as read-only context.
- Confirm Spec 433 PASS WITH CONDITIONS has no merge-blocking adapter safety issue.
- Confirm no existing `specs/434-*` package was overwritten.
- Confirm no UI/routes/jobs/schedules/listeners/migrations/customer output/tenant-id scope.
## Phase 1: Adapter Boundary and Eligibility
- Add Exchange adapter or repo-canonical equivalent.
- Accept only `transportRule`, `remoteDomain`, and `inboundConnector`.
- Require `tenant_configuration.capture`, same workspace/environment/provider scope, Spec 433 readiness, `ProviderCapabilityEvaluator` `exchange_powershell_invoke` status `Supported`, Spec 432 accepted `ExchangePowerShellInvocationResult`, and Spec 430 verified command contract.
- Add Exchange eligibility representation without fake Graph endpoint or fake captured outcome.
- Prove no `ProviderGateway` / generic Graph list route is used for Exchange.
- Preserve existing `CaptureOutcome` values for persisted evidence outcomes and keep adapter-local outcome/failure codes limited to the Spec 434 allowlist.
## Phase 2: Identity Hard-Stop
- Evaluate identity before evidence writer append.
- Block identity conflict, missing stable external ID, unsupported identity, duplicate identity, and display-name-only identity.
- Ensure blocked identity returns sanitized outcome and creates no evidence row.
- Prove writer non-reachability through adapter code order plus zero resource/evidence database assertions.
## Phase 3: Content-Only Guard
- Add a max coverage level guard for the Exchange adapter path.
- Prevent `CoverageEvidenceWriter` or adjacent code from promoting Exchange adapter evidence beyond `content_backed`.
- Prove no comparable/renderable/certified/restore-ready/customer-claimable state appears.
## Phase 4: Empty Collection and Redaction
- Treat empty Exchange runner collections as zero-item execution outcomes only.
- Do not create fake resource/evidence rows for empty collections.
- Keep raw stdout/stderr, payloads, serialized objects, credential material, tokens, and provider response bodies out of OperationRun context/logs/summary counts.
- Use existing evidence storage only for non-empty guarded fixture evidence.
- Map blocked/failure paths through sanitized ProviderReasonCodes or `ext.*` reason codes and the Spec 434 adapter-local code allowlist only.
## Phase 5: Regression and Static Guards
- Run Spec 434 focus tests.
- Run selected regressions for Specs 415, 417, 419, 420, 426, 427, 430, 431, 432, 433, provider capability registry/evaluator, and generic capture.
- Add static/no-product-surface tests proving no route, Filament, Livewire, resource view, job, schedule, listener, migration, customer-output, or `tenant_id` path is introduced.
## Phase 6: Implementation Report and Close-Out
- Create `implementation-report.md`.
- Record candidate gate, branch/HEAD, dirty state, files changed, prerequisite proof, OperationRun decision, adapter proof, provider-capability proof, accepted runner-result proof, outcome/failure-code proof, identity hard-stop proof, content-only guard proof, empty collection proof, generic capture regression proof, redaction proof, no-promotion proof, no-product-surface proof, tests run, browser N/A, Laravel/PHP/PostgreSQL/Pest compatibility notes, deployment impact, and deferred work.
## Risk Controls
- Stop if implementation requires new persistence for empty collections.
- Stop if implementation requires a new OperationRun type or start surface.
- Stop if implementation requires UI/product surface or customer output.
- Stop if live Exchange provider calls are needed to validate the slice.
- Stop if Graph capture must be modified in a way that changes existing Graph behavior.
## Spec Readiness Result
PASS for preparation. Implementation remains a separate step and must follow `tasks.md`.