TenantAtlas/specs/434-exchange-evidence-capture-adapter-content-only-guard/plan.md
ahmido a23131cdbc feat: add Exchange evidence capture adapter guard (#501)
Summary: add Spec 434 Exchange PowerShell evidence capture adapter and prerequisite/identity/content-only guards; cap Exchange evidence at content_backed while preserving Graph capture. Validation: php artisan test --filter=Spec434 --compact; ./vendor/bin/pint --dirty --test; git diff --cached --check. Product Surface: N/A - no rendered UI surface changed; no deployment impact.
Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #501
2026-07-08 10:46:05 +00:00

20 KiB

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)

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:

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:

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:

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.