TenantAtlas/specs/431-exchange-powershell-invocation-operation-registration-gate/plan.md
ahmido 9374260ae1 feat: add Exchange PowerShell invocation gate (#498)
## Summary
- Adds the Spec 431 Exchange PowerShell invocation gate and operation registration slice.
- Introduces trusted provider operation start handling and read-only Exchange PowerShell runner abstractions.
- Updates provider capability/readiness evaluation and coverage for Exchange PowerShell invocation safety.

## Verification
- `cd apps/platform && ./vendor/bin/sail artisan test tests/Feature/Providers/ProviderCapabilityEvaluationTest.php tests/Feature/Providers/ProviderOperationCapabilityGateTest.php tests/Feature/TenantConfiguration/Spec431ExchangePowerShellInvocationGateTest.php tests/Unit/Providers/ProviderCapabilityRegistryTest.php tests/Unit/Providers/ProviderOperationStartGateTest.php tests/Unit/Support/TenantConfiguration/Spec430ExchangePowerShellCommandAllowlistTest.php tests/Unit/Support/TenantConfiguration/Spec431ExchangePowerShellOperationRegistrationTest.php tests/Unit/Verification/ManagedEnvironmentPermissionCapabilityMappingTest.php`
- Result: 80 passed, 685 assertions.

## Product Surface / Ops
- Rendered UI surface changed: N/A.
- Filament/Livewire surface changed: N/A.
- Deployment impact: config/runtime service changes only; no migrations, queues, or assets added.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #498
2026-07-07 15:23:29 +00:00

288 lines
21 KiB
Markdown

# 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.