265 lines
20 KiB
Markdown
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`.
|