## 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
14 KiB
14 KiB
Implementation Report: Spec 431 - Exchange PowerShell Invocation Operation Registration and Execution Gate
Status
Implemented on 2026-07-05. Backend-only operation registration and fake-runner invocation gate are complete for exactly transportRule, remoteDomain, and inboundConnector.
Preflight
- Active spec:
specs/431-exchange-powershell-invocation-operation-registration-gate/ - Implementation branch:
431-exchange-powershell-invocation-operation-registration-gate-session-1783272254 - HEAD before implementation:
9b58a569 feat: add Exchange PowerShell adapter contract slice 1 (#497) - Initial dirty state: untracked active Spec 431 package only.
- Activated skills/gates:
spec-kit-implementation-loop,pest-testing,workflows/spec-readiness-gate,repo-contracts/workspace-scope-safety,repo-contracts/rbac-action-safety,repo-contracts/operation-run-truth,repo-contracts/provider-freshness-semantics. - Hard-gate stop conditions checked: no live provider execution, no shell/PowerShell process, no evidence, no UI, no migration, no scheduled capture/job, no
tenant_id, no Exchange mini-platform.
Files Changed
Runtime:
apps/platform/app/Support/OperationRunType.phpapps/platform/app/Support/OperationCatalog.phpapps/platform/app/Support/Operations/OperationRunCapabilityResolver.phpapps/platform/app/Support/Providers/Capabilities/ProviderCapabilityRegistry.phpapps/platform/app/Services/Providers/ProviderOperationRegistry.phpapps/platform/app/Services/Providers/ProviderConnectionResolver.phpapps/platform/app/Services/Providers/ProviderOperationStartGate.phpapps/platform/app/Services/Providers/ProviderOperationTrustedStarter.phpapps/platform/app/Support/Providers/Capabilities/ProviderCapabilityEvaluator.phpapps/platform/app/Providers/AppServiceProvider.phpapps/platform/config/tenantpilot.phpapps/platform/app/Services/TenantConfiguration/ExchangePowerShellInvocationGate.phpapps/platform/app/Services/TenantConfiguration/ExchangePowerShellCommandContract.phpapps/platform/app/Services/TenantConfiguration/ExchangePowerShellCommandRunner.phpapps/platform/app/Services/TenantConfiguration/ExchangePowerShellInvocationContext.phpapps/platform/app/Services/TenantConfiguration/ExchangePowerShellInvocationResult.phpapps/platform/app/Services/TenantConfiguration/FakeExchangePowerShellCommandRunner.phpapps/platform/app/Services/TenantConfiguration/DisabledExchangePowerShellCommandRunner.php
Tests:
apps/platform/tests/Unit/Providers/ProviderCapabilityRegistryTest.phpapps/platform/tests/Feature/Providers/ProviderCapabilityEvaluationTest.phpapps/platform/tests/Feature/Providers/ProviderOperationCapabilityGateTest.phpapps/platform/tests/Unit/Providers/ProviderOperationStartGateTest.phpapps/platform/tests/Unit/ManagedEnvironmentPermissionCheckClustersTest.phpapps/platform/tests/Unit/Support/TenantConfiguration/Spec431ExchangePowerShellOperationRegistrationTest.phpapps/platform/tests/Feature/TenantConfiguration/Spec431ExchangePowerShellInvocationGateTest.php
Spec artifacts:
specs/431-exchange-powershell-invocation-operation-registration-gate/tasks.mdspecs/431-exchange-powershell-invocation-operation-registration-gate/implementation-report.md
Proportionality Review
- New abstractions added: one public gate, one internal trusted provider-operation starter, one command contract DTO, one runner interface, one invocation context DTO, one result DTO, one fake runner, one disabled runner.
- Why needed: the gate centralizes authorization, provider scope, source-contract state, feature flag, credential policy, operation lifecycle, shape validation, and redaction. The runner interface keeps command execution structured and typed, making raw shell/string invocation impossible at the contract boundary.
- Existing mechanisms extended:
OperationRunService,ProviderOperationStartGate,ProviderOperationRegistry,ProviderCapabilityRegistry,OperationRunCapabilityResolver,CoverageSourceContractResolver, andExchangePowerShellCommandContracts. - Why no smaller change was sufficient: wiring the fake runner directly to tests would not prove OperationRun-before-runner ordering, provider scope/capability gates, feature default-off behavior, or bypass prevention.
- Mini-platform avoidance: no Exchange namespace, no adapter framework, no route/UI/job/migration/evidence writer, no provider-client dependency, no Teams or wider Exchange catalog.
Operation Registration Proof
- OperationRun type:
tenant_configuration.exchange_powershell_invocationadded toOperationRunType. - OperationCatalog: canonical definition and canonical alias added with internal label
Exchange PowerShell invocation. - ProviderOperationRegistry: added
exchange_powershellmodule definition requiringCapabilities::PROVIDER_RUNand provider capability keyexchange_powershell_invoke; Microsoft binding explicitly states fake-gated/no-evidence posture. - ProviderCapabilityRegistry: added
exchange_powershell_invokewithprovider.exchange_powershell_invocationpluspermissions.admin_consentrequirements. - Execution capability:
OperationRunCapabilityResolvermaps the invocation type toCapabilities::PROVIDER_RUN. - Feature flag:
tenantpilot.features.exchange_powershell_invocation, defaultfalsethroughTENANTPILOT_EXCHANGE_POWERSHELL_INVOCATION_ENABLED.
Invocation Gate Proof
- Single public entry point:
ExchangePowerShellInvocationGate::invoke(). - Direct provider-start bypass: public
ProviderOperationStartGate::start()always blockstenant_configuration.exchange_powershell_invocationwithext.exchange_powershell_invocation_gate_required. The invocation gate uses an explicit internalProviderOperationTrustedStarter, so caller-supplied context cannot unlock this operation through the public start boundary. - Direct runner bypass:
ExchangePowerShellCommandRunner::run()accepts onlyExchangePowerShellCommandContractandExchangePowerShellInvocationContext. The typed contract is created only after full canonical contract and parameter validation, stores the canonical allowlisted contract rather than caller-mutated arrays, and the runner receives no separate raw parameter array; tests prove raw command strings, raw arrays, mutating contract arrays, mutated safety metadata, unexpected contract fields, and unverified parameters fail before runner execution. - Spec 430 source gate: requires
contract_verified_pending_capture,adapter_contract_available,provider_calls_allowed = false, andexecution_enabled = falsefromCoverageSourceContractResolver. - Target/command gate: exactly
transportRule,remoteDomain,inboundConnector; exactlyGet-TransportRule,Get-RemoteDomain,Get-InboundConnector. - Provider scope: explicit workspace and managed-environment match on
ProviderConnection; out-of-workspace/out-of-scope results stay 404. - RBAC: actor requires
Capabilities::PROVIDER_RUN; readonly in-scope members get 403. - Provider capability: provider readiness still flows through the internal
ProviderOperationTrustedStarterandProviderCapabilityEvaluator, using connection-scoped readiness evidence. Fake mode bypasses only provider identity resolution, not scope, consent, provider binding, provider health freshness, or provider capability evidence. - No shared verification surface promotion: the Exchange PowerShell permission requirement remains internal to provider capability evaluation and is not registered as a shared
VerificationReportcheck or rendered Required Permissions product-surface claim. - Credential source: non-fake mode uses existing
ProviderIdentityResolver/ProviderConnection::credential/ProviderCredential. Fake mode usesprovider_identity_resolution_mode = fake_runner_bypassonly on the trusted Exchange invocation operation after scope, enabled-state, consent, and provider capability checks; generic provider operation starts still require provider identity. - Operation lifecycle: OperationRun is created by the internal provider-operation starter before runner execution; status/outcome transitions are made through
OperationRunService.
Runner, Shape, Redaction, And Failure Proof
- Fake runner: supports success, empty success, auth failure, authorization failure, timeout, throttling, module unavailable, command unavailable, malformed scalar/text payload, and unexpected exception.
- Production runner:
DisabledExchangePowerShellCommandRunner, bound by default inAppServiceProvider, returns blockedexecution_blocked_runner_disabled; no shell or Microsoft call exists. - Summary counts: only existing keys are emitted:
total,processed,succeeded,failed,skipped,items. - No new summary vocabulary:
OperationSummaryKeysunchanged; unknown-key behavior remains handled bySummaryCountsNormalizer. - Failure reasons: use existing provider reason codes, with local failure codes in sanitized OperationRun context/failure summaries.
- Shape validation: successful runner payload must be a list of arrays and each non-empty item must include a required or derived identity candidate field. Empty success is valid only from a success envelope.
- Malformed output: scalar/text output fails safely with
execution_failed_shape_unsafe. - Redaction: OperationRun context stores command/resource metadata, counts, reason codes, and safe contract metadata only; tests assert no raw stdout/stderr/provider payload/transcript/token/secret promotion.
No-Promotion / No-Surface Proof
- No evidence: tests assert
TenantConfigurationResourceandTenantConfigurationResourceEvidencerows remain absent. - No payload promotion: no raw or normalized evidence payload is written.
- No customer/product claim: no content-backed, comparable, renderable, certified, restore-ready, customer-ready, report, Review Pack, or customer output state is introduced.
- No UI: no route, Filament page/resource, Livewire component, navigation, global search, asset, or browser surface introduced.
- No migration: no database migration and no
operation_runs.provider_connection_idcolumn; provider connection id remains sanitized OperationRun context metadata. - No tenant_id: new Spec 431 runtime source is guarded for
tenant_id,provider_tenant_id, andentra_tenant_id. - No mini-platform: no Exchange-specific service namespace/table/job/legacy shim/fallback reader/dual-write path.
Product Surface And Filament Close-Out
- Product Surface Impact: no rendered product surface changed.
- UI Surface Impact: none.
- Page archetype/surface budgets/Technical Annex/deep-link demotion/canonical status vocabulary:
N/A - no rendered UI surface changed. - Product Surface exceptions: none.
- Focused browser proof:
N/A - no rendered UI surface changed. - Human Product Sanity:
N/A - no product surface changed. - Visible complexity outcome: neutral.
- No-legacy posture: no legacy shim, fallback reader, dual truth, old route, duplicate UI, or mini-platform.
- Livewire v4 compliance: no Livewire runtime code changed; repo baseline remains Livewire v4.1.4.
- Provider registration location: no Filament panel provider registration changed; Laravel panel providers remain in
apps/platform/bootstrap/providers.php. One service binding was added inAppServiceProvider. - Global search: no Filament resource/global search surface changed.
- Destructive/high-impact actions: none added.
- Asset strategy: no assets registered;
filament:assetsis not required for this slice. - Deployment impact: no migrations, queues, scheduler, storage, assets, or Dokploy/container runtime change. New env flag is optional and defaults disabled.
Validation
Successful:
cd apps/platform && ./vendor/bin/pint --dirtycd apps/platform && php artisan test --filter=Spec431 --compact
Result: 30 passed, 416 assertions.cd apps/platform && php artisan test tests/Unit/Support/TenantConfiguration/Spec430ExchangePowerShellCommandAllowlistTest.php --compact
Result: 28 passed, 120 assertions.cd apps/platform && php artisan test tests/Unit/Providers/ProviderOperationStartGateTest.php tests/Unit/Support/TenantConfiguration/Spec430ExchangePowerShellCommandAllowlistTest.php --compact
Result: 39 passed, 220 assertions.cd apps/platform && php artisan test tests/Unit/Providers/ProviderCapabilityRegistryTest.php tests/Unit/Providers/ProviderOperationRegistryCanonicalTypeTest.php tests/Feature/TenantConfiguration/Spec430ExchangePowerShellNoPromotionTest.php tests/Feature/TenantConfiguration/Spec426ExchangeTeamsNoMiniPlatformTest.php tests/Feature/TenantConfiguration/Spec427ExchangeTeamsNoEvidencePromotionTest.php tests/Feature/TenantConfiguration/Spec427ExchangeTeamsNoTenantIdTest.php tests/Unit/Support/TenantConfiguration/Spec417CoverageIdentityStrategyRegistryTest.php tests/Feature/TenantConfiguration/Spec419M365RegistryExpansionTest.php --compact
Result: 20 passed, 1 PostgreSQL-only Spec 419 check skipped, 688 assertions.cd apps/platform && php artisan test tests/Feature/Providers/ProviderCapabilityEvaluationTest.php tests/Feature/Providers/ProviderOperationCapabilityGateTest.php tests/Unit/Providers/ProviderOperationStartGateTest.php tests/Unit/ManagedEnvironmentPermissionCheckClustersTest.php tests/Unit/Providers/Spec394ProviderReadinessResolverTest.php tests/Unit/Support/TenantConfiguration/Spec430ExchangePowerShellCommandAllowlistTest.php tests/Unit/Verification/ManagedEnvironmentPermissionCapabilityMappingTest.php --compact
Result: 58 passed, 299 assertions.git diff --check
Attempted / noted:
cd apps/platform && ./vendor/bin/sail artisan test --filter=Spec431 --compactwas killed by signal 9 before producing test results. Non-Docker fallback was used afterward.- The previously noted adjacent provider-start fixture failures were resolved; the focused provider capability/start/freshness suite now passes in non-Docker PHP.
Deferred Work
- Production Exchange PowerShell execution hardening remains out of scope.
- Evidence capture/promotion for Exchange remains out of scope and should be a separate spec after runtime/permission proof.
- Teams invocation/evidence work remains separate.
- No staging/production feature enablement is included; the feature flag defaults disabled.