## 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
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 bySummaryCountsNormalizer. - Existing
ProviderOperationStartGatestoresprovider_connection_idin 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, andProviderIdentityResolver; Spec 431 must use that path and must not invent a credential reference store. - Existing feature flags live under
tenantpilot.features.*; Spec 431 usestenantpilot.features.exchange_powershell_invocation, defaultfalse.
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:assetsis 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_RUNfor 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:
ExchangePowerShellInvocationGateis the sole public invocation entry point. It may callProviderOperationStartGateinternally 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:
OperationRunServicefor creation/transition;OperationSummaryKeysandSummaryCountsNormalizer;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_invocationis implemented viatenantpilot.features.exchange_powershell_invocationand defaults to false. - Credential source: non-fake mode uses existing
ProviderConnection::credential/ProviderCredential/ProviderIdentityResolvertruth 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 agentgit 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, anditemsunless 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 gatetenantpilot.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_RUNproves 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_idis 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.