# Implementation Plan: Exchange PowerShell Invocation Operation Registration and Execution Gate **Branch**: `431-exchange-powershell-invocation-operation-registration-gate` | **Date**: 2026-07-05 | **Spec**: `specs/431-exchange-powershell-invocation-operation-registration-gate/spec.md` **Input**: Feature specification from `specs/431-exchange-powershell-invocation-operation-registration-gate/spec.md` ## Summary Implement a narrow OperationRun/provider operation registration and fake-runner invocation gate for the three Exchange PowerShell contracts introduced by Spec 430: `transportRule`, `remoteDomain`, and `inboundConnector`. The implementation registers the canonical operation path, provider operation, provider capability, capability resolver mapping, safe summaries, sanitized failures, one invocation gate, a structured runner interface, and a fake runner. Production execution remains disabled/inert. No live Exchange calls, evidence, UI, migrations, jobs, scheduled capture, customer claims, or `tenant_id` are allowed. ## Technical Context **Language/Version**: PHP 8.4 / Laravel 12 **Primary Dependencies**: Filament v5 / Livewire v4 baseline remains unchanged; no UI code planned **Storage**: PostgreSQL via existing `operation_runs` table only; no migration **Testing**: Pest 4 focused unit and feature tests **Validation Lanes**: fast-feedback for unit/feature; selected regressions for Spec 430/426/427/417/419/420; browser N/A **Target Platform**: Laravel Sail local, Dokploy staging/production remains unaffected because production execution is disabled **Project Type**: Laravel monolith under `apps/platform` **Performance Goals**: No real provider work; fake-runner tests complete locally without network or shell execution **Constraints**: No raw provider output persistence, no PowerShell process, no Microsoft calls, no UI, no migrations **Scale/Scope**: Exactly three Exchange target types and one invocation operation ## Preflight Findings - Current prep branch before Spec Kit execution: `platform-dev`. - HEAD before Spec Kit execution: `9b58a569 feat: add Exchange PowerShell adapter contract slice 1 (#497)`. - Initial dirty state before Spec Kit execution: clean. - Spec Kit helper created branch: `431-exchange-powershell-invocation-operation-registration-gate`. - Existing related packages: `specs/429-exchange-teams-source-surface-catalog-adapter-strategy/`, `specs/430-exchange-powershell-adapter-contract-slice-1/`. - Spec 430 implementation report proves the three command contracts and no OperationRun invocation/evidence/UI/migration. - Existing OperationRun type for generic capture: `tenant_configuration.capture`; no Exchange PowerShell invocation type exists. - Existing operation catalog has `tenant_configuration.capture`; no invocation entry exists. - Existing provider operation registry has provider connection, inventory, compliance snapshot, restore, directory groups, and role definitions entries; no Exchange PowerShell invocation entry exists. - Existing provider capability registry has provider connection, inventory, configuration, restore, directory groups, and role definitions capabilities; no Exchange PowerShell invocation capability exists. - Existing summary keys are fixed in `OperationSummaryKeys::all()` and unknown keys are dropped by `SummaryCountsNormalizer`. - Existing `ProviderOperationStartGate` stores `provider_connection_id` in OperationRun context and validates provider bindings/capabilities; implementation should reuse or compose with this path where practical. - Existing actor capability registry includes `App\Support\Auth\Capabilities::PROVIDER_RUN`; Spec 431 reuses it instead of adding a new actor/RBAC capability. - Existing provider credentials are represented through `ProviderConnection::credential`, `ProviderCredential`, `CredentialManager`, and `ProviderIdentityResolver`; Spec 431 must use that path and must not invent a credential reference store. - Existing feature flags live under `tenantpilot.features.*`; Spec 431 uses `tenantpilot.features.exchange_powershell_invocation`, default `false`. ## 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 operation registration/gate. - **Native vs custom classification summary**: N/A. - **Shared-family relevance**: OperationRun and provider operation shared contracts only; no rendered UI shared family. - **State layers in scope**: OperationRun lifecycle and summary/failure state only. - **Audience modes in scope**: internal operator/reviewer only; no customer surface. - **Decision/diagnostic/raw hierarchy plan**: No product view. Raw provider output is forbidden from persistence. - **Raw/support gating plan**: Raw output not stored. - **One-primary-action / duplicate-truth control**: N/A. - **Handling modes by drift class or surface**: N/A. - **Repository-signal treatment**: no rendered UI signal. - **Special surface test profiles**: N/A. - **Required tests or manual smoke**: `N/A - no rendered UI surface changed`. - **Exception path and spread control**: none. - **Active feature PR close-out entry**: no rendered product surface changed. - **UI/Productization coverage decision**: No UI surface impact. - **Coverage artifacts to update**: none. - **No-impact rationale**: The spec forbids routes, Filament pages, Livewire components, navigation, global search, and assets. - **Navigation / Filament provider-panel handling**: no panel/provider registration changes. - **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 aliases, fallback readers, hidden routes, duplicate UI, old labels, or historical fixtures. - **Page archetype and surface budget plan**: N/A. - **Technical Annex and deep-link demotion plan**: N/A - no rendered product surface changed. OperationRun remains internal/audit truth. - **Canonical status vocabulary plan**: N/A. - **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/431-exchange-powershell-invocation-operation-registration-gate/implementation-report.md`. ## Filament / Livewire / Deployment Posture - **Livewire v4 compliance**: repo baseline remains Livewire v4; no Livewire runtime code planned. - **Panel provider registration location**: no panel/provider change; Laravel providers remain in `apps/platform/bootstrap/providers.php`. - **Global search posture**: no Filament resource/global search surface changed. - **Destructive/high-impact action posture**: none added. - **Asset strategy**: no assets; `filament:assets` is not required for this slice. - **Testing plan**: no pages/widgets/relation managers/actions/browser tests because no rendered UI surface changes. - **Deployment impact**: no env vars, migrations, queues, scheduler, storage, assets, container, or Dokploy changes. Production execution remains disabled/inert. ## Shared Pattern & System Fit - **Cross-cutting feature marker**: yes. - **Systems touched**: OperationRun type/catalog/lifecycle, provider operation registry, provider capability registry, capability resolver, summary normalization, failure sanitizer, provider operation start/gate path, Coverage v2 source contract resolver/contracts. - **Shared abstractions reused**: `OperationRunService`, `OperationCatalog`, `ProviderOperationRegistry`, `ProviderCapabilityRegistry`, `OperationRunCapabilityResolver`, `OperationSummaryKeys`, `SummaryCountsNormalizer`, `RunFailureSanitizer`, `CoverageSourceContractResolver`, `ExchangePowerShellCommandContracts`. - **New abstraction introduced? why?**: One invocation gate and one runner interface are allowed because provider/shell invocation must be centrally blocked, structured, and fake-testable before evidence work can safely use it. - **Why the existing abstraction was sufficient or insufficient**: Existing registries and OperationRun lifecycle are sufficient foundations but lack this operation/capability/runner boundary. - **Actor capability path**: Reuse `Capabilities::PROVIDER_RUN` for operation execution. If implementation proves that capability is too broad, stop and amend the spec before adding a new actor capability or role mapping. - **Public gate boundary**: `ExchangePowerShellInvocationGate` is the sole public invocation entry point. It may call `ProviderOperationStartGate` internally for provider binding/capability/scope behavior; feature code and tests must not directly call the runner or provider start gate for this invocation. - **Bounded deviation / spread control**: No parallel provider framework. If a production runner class is introduced, it must be inert and blocked by default. ## OperationRun UX Impact - **Touches OperationRun start/completion/link UX?**: yes, backend OperationRun creation/lifecycle only. - **Central contract reused**: `OperationRunService` for creation/transition; `OperationSummaryKeys` and `SummaryCountsNormalizer`; `RunFailureSanitizer`. - **Delegated UX behaviors**: no toast, run link, artifact link, browser event, queued DB notification, or surface messaging. - **Surface-owned behavior kept local**: none. - **Queued DB-notification policy**: N/A. - **Terminal notification path**: central lifecycle mechanism where applicable; no feature-local notification. - **Exception path**: none. ## Provider Boundary & Portability Fit - **Shared provider/platform boundary touched?**: yes. - **Provider-owned seams**: Exchange PowerShell command names, command contracts, response shape, command safety, fake-runner outcome vocabulary. - **Platform-core seams**: OperationRun type/catalog, provider operation registry, provider capability registry, capability resolver, summary/failure sanitizer, provider connection context. - **Neutral platform terms / contracts preserved**: operation, provider connection, capability, managed environment, workspace, source contract, summary counts, failure reason, runner mode. - **Retained provider-specific semantics and why**: Exchange command names stay inside provider-owned contracts because Spec 431 is explicitly Exchange PowerShell only. - **Bounded extraction or follow-up path**: document-in-feature; production runner hardening and evidence promotion remain separate specs. ## Constitution Check - Inventory-first: no inventory or evidence truth is changed. - Read/write separation: no restore/apply/mutation provider action; only read-only command contracts are fake-invoked. - Graph contract path: no Microsoft Graph call path changed; no Graph call added. - Deterministic capabilities: provider/actor capability mappings must be central and tested. - Actor capability: the invocation operation maps to existing `Capabilities::PROVIDER_RUN`; no new actor capability is planned without a spec amendment. - RBAC-UX: non-member workspace/environment scope is 404; member without capability is 403; readonly cannot invoke. - Workspace isolation: workspace and managed environment are mandatory inputs. - Tenant isolation: managed-environment entitlement is enforced before provider connection disclosure. - Feature gate: logical key `tenant_configuration.exchange_powershell_invocation` is implemented via `tenantpilot.features.exchange_powershell_invocation` and defaults to false. - Credential source: non-fake mode uses existing `ProviderConnection::credential` / `ProviderCredential` / `ProviderIdentityResolver` truth and blocks safely when no repo-supported credential reference exists. - Run observability: invocation attempts are OperationRun-owned before fake runner execution. - OperationRun start UX: no rendered start surface; lifecycle/summary/failure paths stay central. - Ops-UX 3-surface feedback: no new UI feedback or notification policy. - Ops-UX lifecycle: status/outcome transitions only through `OperationRunService`. - Ops-UX summary counts: allowed keys only, flat numeric values only. - Data minimization: OperationRun context stores safe identifiers and runner mode only; no raw output, credentials, tokens, transcripts, or payloads. - Test governance: focused unit/feature tests with fake runners; no browser lane; no heavy shared fixtures by default. - Proportionality: one operation/capability/gate/runner boundary justified by provider credential safety, RBAC, and operation truth. - No premature abstraction: no generic PowerShell platform or multi-provider framework. - Persisted truth: no new tables or persisted evidence truth. - Behavioral state: blocker/failure codes must alter execution handling and sanitize operator diagnosis. - Provider boundary: Exchange semantics remain provider-owned and do not become platform-core ownership truth. - V1 explicitness / few layers: narrow explicit mapping and one gate preferred. - Spec discipline / bloat check: proportionality review included in `spec.md`. - Filament-native UI: N/A - no UI. - Product Surface Contract: N/A - no rendered surface changed. ## Test Governance Check - **Test purpose / classification by changed surface**: Unit for registries/summary/failure/runner shape; Feature for OperationRun/gate/provider scope/no evidence/no UI/no tenant_id. - **Affected validation lanes**: fast-feedback/confidence focused tests; selected regressions; browser N/A. - **Why this lane mix is the narrowest sufficient proof**: The behavior is backend registry/gate/service behavior with fake runner and DB OperationRun assertions; no rendered UI or PostgreSQL-specific migration exists. - **Narrowest proving command(s)**: - `cd apps/platform && ./vendor/bin/sail artisan test --filter=Spec431 --compact` - selected Spec 430/426/427/417/419/420 regression files or filters documented by implementation - `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` - `git diff --check` - **Fixture / helper / factory / seed / context cost risks**: Provider connection/workspace/managed-environment/actor setup required for feature tests; keep helper setup explicit and local. - **Expensive defaults or shared helper growth introduced?**: no; fake runner should be local to Spec 431 tests unless later specs adopt it intentionally. - **Heavy-family additions, promotions, or visibility changes**: none planned. - **Surface-class relief / special coverage rule**: backend-only; browser `N/A - no rendered UI surface changed`. - **Closing validation and reviewer handoff**: implementation report must list exact tests and pass/fail counts. - **Budget / baseline / trend follow-up**: none unless implementation materially expands regression lanes. - **Review-stop questions**: verify no shell execution, no Microsoft calls, no evidence, no raw output persistence, no UI, no migration, no `tenant_id`, no mini-platform. - **Escalation path**: reject-or-split if implementation attempts live provider execution, evidence promotion, UI, migrations, or broader Exchange types. - **Active feature PR close-out entry**: no rendered UI surface changed; no deployment impact. - **Why no dedicated follow-up spec is needed**: Routine test upkeep stays inside Spec 431; production runner hardening/evidence promotion are already separate follow-up candidates. ## Project Structure ### Documentation (this feature) ```text specs/431-exchange-powershell-invocation-operation-registration-gate/ |-- spec.md |-- plan.md |-- tasks.md |-- checklists/ | `-- requirements.md `-- implementation-report.md ``` ### Source Code (likely affected by later implementation) ```text apps/platform/app/Support/OperationRunType.php apps/platform/app/Support/OperationCatalog.php apps/platform/app/Services/Providers/ProviderOperationRegistry.php apps/platform/app/Services/Providers/ProviderOperationStartGate.php apps/platform/app/Support/Providers/Capabilities/ProviderCapabilityRegistry.php apps/platform/app/Support/Operations/OperationRunCapabilityResolver.php apps/platform/app/Support/Auth/Capabilities.php apps/platform/app/Support/OpsUx/OperationSummaryKeys.php apps/platform/app/Support/OpsUx/SummaryCountsNormalizer.php apps/platform/app/Support/OpsUx/RunFailureSanitizer.php apps/platform/app/Models/ProviderConnection.php apps/platform/app/Models/ProviderCredential.php apps/platform/app/Services/Providers/ProviderIdentityResolver.php apps/platform/app/Services/Providers/CredentialManager.php apps/platform/app/Services/TenantConfiguration/ExchangePowerShellCommandContracts.php apps/platform/app/Services/TenantConfiguration/CoverageSourceContractResolver.php apps/platform/app/Services/TenantConfiguration/ExchangePowerShellInvocationGate.php apps/platform/app/Services/TenantConfiguration/ExchangePowerShellCommandRunner.php apps/platform/app/Services/TenantConfiguration/FakeExchangePowerShellCommandRunner.php apps/platform/config/tenantpilot.php apps/platform/tests/Unit/Support/TenantConfiguration/ apps/platform/tests/Feature/TenantConfiguration/ ``` Forbidden source changes: ```text apps/platform/routes/** apps/platform/resources/views/** apps/platform/app/Filament/** apps/platform/app/Livewire/** apps/platform/database/migrations/** Exchange-specific evidence tables jobs for live provider execution provider clients that call Microsoft ``` ## Implementation Phases ### Phase 0 - Preflight - Capture branch, HEAD, dirty state. - Reconfirm Spec 430 contract state and no pending checklist blocker. - Reconfirm target types and command names. - Reconfirm no migration, no UI, no evidence, no live execution, no `tenant_id`. ### Phase 1 - Operation and Provider Registration - Add canonical OperationRun type. - Add OperationCatalog entry and alias inventory entry only if required by repo pattern. - Add ProviderOperationRegistry definition and Microsoft active binding or explicit internal binding. - Add ProviderCapabilityRegistry capability. - Add OperationRunCapabilityResolver execution mapping. ### Phase 2 - Summary and Failure Plumbing - Reuse `total`, `processed`, `succeeded`, `failed`, `skipped`, and `items` unless implementation proves a new key is necessary. - Extend failure normalization only with safe bounded mappings or known provider reason codes. - Test known/unknown failures and redaction. ### Phase 3 - Invocation Gate - Add single gate service. - Compose with existing provider operation/capability/scope gates where practical. - Require verified Spec 430 contracts, allowlisted commands, parameters, actor capability `Capabilities::PROVIDER_RUN`, provider scope, feature gate `tenantpilot.features.exchange_powershell_invocation`, runner mode, redaction policy, and existing provider credential/identity resolution unless fake-runner-only. - Create OperationRun before fake runner execution. ### Phase 4 - Runner Boundary - Add structured runner interface. - Add fake runner and structured envelopes. - Add disabled/inert production runner only if needed. - Reject raw/mutation/arbitrary command paths. ### Phase 5 - No-Promotion Safety - Prove no evidence rows, no coverage promotion, no compare/render/certification/restore/customer state, no customer output, no UI/routes/assets/global search, no migration, no `tenant_id`, no mini-platform. ### Phase 6 - Validation and Report - Run focused Spec 431 tests. - Run selected regressions. - Run Pint and `git diff --check`. - Complete implementation report with proportionality, proof, no-surface, and deployment close-out. ## Risk Controls - Stop if implementation needs a migration. - Stop if implementation needs live provider execution. - Stop if a production runner would call PowerShell. - Stop if evidence persistence is required. - Stop if UI/routes/jobs/scheduled capture are introduced. - Stop if new summary keys are emitted without registration/tests. - Stop if provider/capability resolution cannot fail closed. - Stop if `Capabilities::PROVIDER_RUN` proves too broad and a new actor capability would be required without a spec amendment. - Stop if the feature gate cannot default disabled through `tenantpilot.features.exchange_powershell_invocation`. - Stop if credential material would be persisted or logged. - Stop if credential checks require a new credential persistence path. - Stop if `tenant_id` is introduced. ## Preparation Analyze Result Initial preparation self-review recorded this package as aligned with current repo truth and preparation-only boundaries. Follow-up `/speckit-analyze` remediation clarified actor capability, feature-gate path/default, credential-source truth, and the single public invocation gate boundary. No application code was modified. The repo does not expose executable `speckit.plan`, `speckit.tasks`, or `speckit.analyze` commands; this package was created with the available Spec Kit feature/plan scripts plus manual artifact authoring against the repository templates and prompts.