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

21 KiB

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)

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)

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:

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.