TenantAtlas/specs/432-exchange-powershell-production-runner-boundary-runtime-gate/plan.md
ahmido f4e342121a feat: add Exchange PowerShell production runner gate (#499)
Spec 432: Exchange PowerShell production runner boundary and runtime gate. Validation: php artisan test --filter=Spec432 --compact; ./vendor/bin/pint --dirty --test --format agent; git diff --check.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #499
2026-07-07 18:34:18 +00:00

25 KiB

Implementation Plan: Exchange PowerShell Production Runner Boundary and Runtime Gate

Branch: 432-exchange-powershell-production-runner-boundary-runtime-gate | Date: 2026-07-07 | Spec: specs/432-exchange-powershell-production-runner-boundary-runtime-gate/spec.md Input: Feature specification from specs/432-exchange-powershell-production-runner-boundary-runtime-gate/spec.md

Summary

Implement a production-runner boundary and runtime gate for the existing Exchange PowerShell invocation path covering exactly transportRule, remoteDomain, and inboundConnector. Disabled runner remains the default. Production runner selection requires explicit invocation and production-runner config gates plus runtime readiness, credential reference, Exchange permission evidence, provider scope, OperationRun ownership, command safety, process execution safety, output safety, timeout/concurrency controls, and redaction. Live invocation may remain blocked when supported credential or verified permission evidence is absent. No evidence promotion, UI, routes, jobs, schedules, listeners, migrations, 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; Symfony Process may be used if already available through Laravel dependencies Storage: PostgreSQL via existing operation_runs only; no migration Testing: Pest 4 focused unit and feature tests Validation Lanes: fast-feedback/confidence focused tests, selected regressions, browser N/A Target Platform: Laravel Sail local, Dokploy staging/production with production runner disabled by default Project Type: Laravel monolith under apps/platform Performance Goals: No unbounded process execution; per-invocation timeout and concurrency locks must bound runtime work Constraints: No raw shell strings, no Microsoft calls during readiness checks, no module install, no credential material persistence, no provider payload persistence, no rendered UI Scale/Scope: Exactly three Exchange target types and one existing invocation operation

Preflight Findings

  • Current prep branch before Spec Kit execution: platform-dev.
  • HEAD before Spec Kit execution: 9374260a feat: add Exchange PowerShell invocation gate (#498).
  • Initial dirty state before Spec Kit execution: clean.
  • Spec Kit helper created branch: 432-exchange-powershell-production-runner-boundary-runtime-gate.
  • Existing related packages: specs/430-exchange-powershell-adapter-contract-slice-1/ and specs/431-exchange-powershell-invocation-operation-registration-gate/.
  • No existing specs/432-* package was found before preparation.
  • Spec 430 implementation report proves the three command contracts and no live provider/evidence/UI/migration scope.
  • Spec 431 implementation report proves OperationRun/provider operation registration, exchange_powershell_invoke capability, fake runner proof, disabled runner default, sanitized context, no evidence, no UI, no migration, and no tenant_id.
  • Current AppServiceProvider binds ExchangePowerShellCommandRunner to DisabledExchangePowerShellCommandRunner.
  • Current config has tenantpilot.features.exchange_powershell_invocation defaulting false through TENANTPILOT_EXCHANGE_POWERSHELL_INVOCATION_ENABLED.
  • Current OperationSummaryKeys::all() includes generic keys such as total, processed, succeeded, failed, skipped, and items; use these unless a new key is explicitly justified and tested.
  • RunFailureSanitizer already normalizes provider/auth/permission/timeout-like failures and redacts secret-like strings.

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 runtime and operation safety boundary.
  • Native vs custom classification summary: N/A.
  • Shared-family relevance: OperationRun/provider operation shared contracts only; no rendered UI shared family.
  • State layers in scope: OperationRun context, summary counts, failure reasons, runtime/credential/permission gate states.
  • Audience modes in scope: internal operator/reviewer only; no customer surface.
  • Decision/diagnostic/raw hierarchy plan: No product view. Raw process/provider output is forbidden from persistence.
  • Raw/support gating plan: Raw output and credential material are never stored.
  • One-primary-action / duplicate-truth control: N/A.
  • Handling modes by drift class or surface: N/A.
  • Repository-signal treatment: service-provider binding changes are backend service-container behavior only; panel provider registration remains unchanged.
  • 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; Laravel panel providers remain in apps/platform/bootstrap/providers.php.
  • 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 and no new links are added.
  • 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 for product surfaces.
  • Implementation report target: specs/432-exchange-powershell-production-runner-boundary-runtime-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 panel providers remain in apps/platform/bootstrap/providers.php.
  • Global search posture: no Filament resource/global search surface changed.
  • Destructive/high-impact action posture: no UI action added; production runner remains backend-gated and read-only command only.
  • 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 migrations, queues, scheduler, storage, assets, or browser build. New env/config gate for production runner defaults disabled and must be documented in the implementation report if added.

Shared Pattern & System Fit

  • Cross-cutting feature marker: yes.
  • Systems touched: ExchangePowerShellInvocationGate, ExchangePowerShellCommandRunner, DisabledExchangePowerShellCommandRunner, OperationRunService, ProviderOperationTrustedStarter, ProviderCapabilityEvaluator, ProviderCapabilityRegistry, ProviderOperationRegistry, OperationSummaryKeys, SummaryCountsNormalizer, RunFailureSanitizer, provider credential/identity infrastructure, and config under tenantpilot.features.
  • Shared abstractions reused: OperationRun lifecycle, provider operation/capability registries, SummaryCountsNormalizer, RunFailureSanitizer, Spec 430 command contracts, Spec 431 invocation gate.
  • New abstraction introduced? why?: Production runner, runtime readiness checker, credential resolver/evaluator, permission evidence evaluator, process executor, command builder, and runtime policy may be introduced because process execution and credential safety require explicit testable boundaries.
  • Why the existing abstraction was sufficient or insufficient: Existing paths are sufficient for operation truth, provider scope, and fake/disabled runner proof. They are insufficient for production process execution, non-invasive runtime checks, credential/permission evidence gating, no-shell command construction, output guards, timeout/concurrency cleanup, and runner selection.
  • Bounded deviation / spread control: No generic PowerShell platform, no multi-provider process framework, no evidence writer, no UI. Keep new services under the repo-canonical TenantConfiguration service path unless implementation finds an existing narrower home.

OperationRun UX Impact

  • Touches OperationRun start/completion/link UX?: yes, backend OperationRun lifecycle and sanitized context only.
  • Central contract reused: OperationRunService, OperationSummaryKeys, SummaryCountsNormalizer, RunFailureSanitizer, and Spec 431 invocation path.
  • 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 command names, ExchangeOnlineManagement module readiness, Exchange permission evidence, PowerShell process command construction, response/output shape, and command safety.
  • Platform-core seams: OperationRun lifecycle, provider connection/scope, provider operation/capability gating, summary/failure sanitizer, credential-reference metadata, and config gating.
  • Neutral platform terms / contracts preserved: operation, provider connection, capability, managed environment, workspace, runtime readiness, credential reference, permission evidence, process executor, summary counts, failure reason, runner mode.
  • Retained provider-specific semantics and why: Exchange command/runtime semantics stay in provider-owned TenantConfiguration runner-boundary services because Spec 432 is explicitly Exchange PowerShell only.
  • Bounded extraction or follow-up path: document-in-feature; verified credential/permission evidence support, evidence promotion, compare/render/certification, Teams runtime, and customer output remain separate follow-up specs.

Constitution Check

  • Inventory-first: no inventory, snapshot, backup, or evidence truth is changed.
  • Read/write separation: only read-only Exchange Get-* commands are in scope; mutation commands are rejected.
  • Graph contract path: no Microsoft Graph call path changed; no Graph call added.
  • Deterministic capabilities: provider/actor capability mappings remain central and tested through Spec 431 path.
  • RBAC-UX: non-member workspace/environment access is 404; member without capability is 403 or repo-equivalent; readonly cannot invoke.
  • Workspace isolation: workspace and managed environment are mandatory scope inputs.
  • Tenant isolation: provider connection and permission evidence must match managed environment before process execution.
  • Feature gates: existing invocation feature flag remains false by default; new production-runner flag must default false.
  • Credential source: use existing provider credential/identity reference truth; no new credential store.
  • Run observability: public invocation returns or references OperationRun/safe blocker through the existing invocation path.
  • 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 gate states and metadata only; no raw output, credentials, tokens, transcripts, or payloads.
  • Test governance: focused unit/feature tests with fake process executor and fake runtime-readiness seams; no browser lane and no CI dependency on host PowerShell or ExchangeOnlineManagement.
  • Proportionality: new runtime boundaries are justified by credential safety, shell/process safety, provider scope, and no-promotion truth.
  • No premature abstraction: no generic PowerShell framework or multi-provider process platform.
  • Persisted truth: no new tables or persisted evidence truth.
  • Behavioral state: blocker/failure codes change execution handling and remain bounded to runner safety.
  • Provider boundary: Exchange-specific terms remain provider-owned and do not become platform-core customer truth.
  • Product Surface Contract: N/A - no rendered product surface changed.

Test Governance Check

  • Test purpose / classification by changed surface: Unit for binding/runtime/credential/permission/process/output/sanitizer behavior; Feature for OperationRun/provider scope/no evidence/no trigger/no migration/no tenant_id.
  • Affected validation lanes: fast-feedback/confidence focused tests and selected regressions; browser N/A.
  • Why this lane mix is the narrowest sufficient proof: The change is backend service/runtime safety behavior with no rendered UI and no schema.
  • Narrowest proving command(s):
    • cd apps/platform && ./vendor/bin/sail artisan test --filter=Spec432 --compact
    • selected Spec 431 and Spec 430 regression tests
    • selected Spec 426/427/417/419/420 no-promotion, identity, registry, and generic evidence regressions
    • cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent
    • git diff --check
  • Fixture / helper / factory / seed / context cost risks: workspace, managed environment, provider connection, credential reference, permission evidence, OperationRun, and fake process executor setup. Keep helpers explicit and feature-local unless reused intentionally.
  • Expensive defaults or shared helper growth introduced?: no planned defaults.
  • Heavy-family additions, promotions, or visibility changes: none planned; PostgreSQL-only lock proof must be named if implementation requires it.
  • 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 expected.
  • Review-stop questions: verify disabled default, no live invocation without all gates, no shell strings, no Microsoft calls during readiness, no credential material, no raw output, no evidence, no UI/trigger surface, no migration, no tenant_id, no mini-platform.
  • Escalation path: reject-or-split if implementation attempts live evidence, UI, routes/jobs/schedules/listeners, migrations, broader Exchange/Teams scope, or customer claims.
  • Active feature PR close-out entry: backend runner-boundary safety; no rendered UI surface changed.
  • Why no dedicated follow-up spec is needed: Routine runner-boundary test upkeep stays inside Spec 432. Credential/permission evidence support and evidence promotion are separate follow-up specs only when needed.

Project Structure

Documentation (this feature)

specs/432-exchange-powershell-production-runner-boundary-runtime-gate/
|-- spec.md
|-- plan.md
|-- tasks.md
|-- checklists/
|   `-- requirements.md
`-- implementation-report.md

Source Code (likely affected by later implementation)

apps/platform/app/Providers/AppServiceProvider.php
apps/platform/config/tenantpilot.php
apps/platform/app/Services/TenantConfiguration/ExchangePowerShellInvocationGate.php
apps/platform/app/Services/TenantConfiguration/ExchangePowerShellCommandRunner.php
apps/platform/app/Services/TenantConfiguration/DisabledExchangePowerShellCommandRunner.php
apps/platform/app/Services/TenantConfiguration/ExchangePowerShellInvocationContext.php
apps/platform/app/Services/TenantConfiguration/ExchangePowerShellInvocationResult.php
apps/platform/app/Services/TenantConfiguration/ExchangePowerShellCommandContract.php
apps/platform/app/Services/TenantConfiguration/ExchangePowerShellCommandContracts.php
apps/platform/app/Services/TenantConfiguration/ExchangePowerShellProductionRunner.php
apps/platform/app/Services/TenantConfiguration/ExchangePowerShellRuntimeReadinessChecker.php
apps/platform/app/Services/TenantConfiguration/ExchangePowerShellCredentialReferenceResolver.php
apps/platform/app/Services/TenantConfiguration/ExchangePowerShellPermissionEvidenceEvaluator.php
apps/platform/app/Services/TenantConfiguration/ExchangePowerShellProcessCommandBuilder.php
apps/platform/app/Services/TenantConfiguration/ExchangePowerShellProcessExecutor.php
apps/platform/app/Services/TenantConfiguration/FakeExchangePowerShellProcessExecutor.php
apps/platform/app/Services/TenantConfiguration/ExchangePowerShellRuntimePolicy.php
apps/platform/app/Support/OpsUx/RunFailureSanitizer.php
apps/platform/app/Support/OpsUx/OperationSummaryKeys.php
apps/platform/app/Support/OpsUx/SummaryCountsNormalizer.php
apps/platform/app/Support/Providers/Capabilities/ProviderCapabilityEvaluator.php
apps/platform/tests/Unit/Support/TenantConfiguration/
apps/platform/tests/Feature/TenantConfiguration/
apps/platform/tests/Unit/OpsUx/
apps/platform/tests/Feature/Guards/

Forbidden source changes:

apps/platform/routes/**
apps/platform/resources/views/**
apps/platform/app/Filament/**
apps/platform/app/Livewire/**
apps/platform/database/migrations/**
new Exchange-specific evidence tables
jobs/schedules/listeners that invoke the production runner
customer report or Review Pack output

Structure Decision: Use the existing TenantConfiguration and OpsUx support paths. Do not create a new Exchange subsystem or base folder without explicit approval.

Complexity Tracking

Violation Why Needed Simpler Alternative Rejected Because
New runner boundary services Live process execution requires explicit runtime, credential, permission, process, output, and redaction gates. Extending the fake/disabled runner only would not prove production execution safety.
New process executor abstraction Tests must prove execution behavior without running PowerShell and without shell strings. Direct Symfony Process use inside the runner would make fake testing and shell-string guard proof weaker.
New failure states Operators/reviewers need actionable blocked/failed reasons, and code needs behavior-specific handling. Collapsing every failure to unknown_error would hide unsafe runtime, credential, permission, output, and concurrency conditions.

Proportionality Review

  • Current operator problem: Prevent false confidence that Exchange evidence is ready while allowing a safe production-runner boundary to be implemented and tested.
  • Existing structure is insufficient because: Spec 431 has only disabled/fake execution and no production process, credential, permission, runtime readiness, output, timeout, or concurrency proof.
  • Narrowest correct implementation: Add a safely blocked/gated production boundary for the three existing Exchange command contracts.
  • Ownership cost created: Focused service/test surface that future evidence specs must maintain and use.
  • Alternative intentionally rejected: Enabling live execution inside an evidence promotion spec, or using admin consent/feature flag alone as runtime permission proof.
  • Release truth: Current-release safety truth before evidence promotion.

Implementation Phases

Phase 0 - Preflight

  • Capture branch, HEAD, and dirty state.
  • Confirm Spec 431 PASS and no open blocking bypass/capability/redaction findings.
  • Confirm current disabled runner binding and invocation feature flag.
  • Confirm current credential infrastructure and provider permission evidence path.
  • Confirm summary keys and sanitizer behavior.
  • Confirm no evidence/UI/migration/job/schedule/listener scope.

Phase 1 - Runner Binding and Config

  • Preserve disabled runner as default.
  • Add production-runner config flag under a repo-canonical tenantpilot.features or nested Exchange PowerShell config path.
  • Prove default false behavior and repo-canonical config-cache binding stability, using actual config:cache when viable or a documented service-container/config-resolved simulation when that is the repo-safe test path.
  • Production runner may be selected only when invocation gate and production-runner gate are enabled and all other gates pass.

Phase 2 - Runtime Readiness

  • Add non-invasive runtime readiness checker.
  • Check configured PowerShell executable availability without profile loading or shell strings; automated tests must fake the executor/path-check seam.
  • Check ExchangeOnlineManagement module availability only through a non-invasive fixed command or safe unknown/missing state; automated tests must not require the real module.
  • Validate runtime environment allowlist, timeout policy, process executor, and temp policy.
  • Forbid Microsoft connection, module install, env dump, tenant session import, and interactive prompts.

Phase 3 - Credential and Permission Gates

  • Add safe credential reference resolver/evaluator.
  • Block client-secret credentials by default.
  • Test certificate, federated, managed-identity, missing, expired, inaccessible, unsupported, and unknown states.
  • Add Exchange permission evidence evaluator.
  • Preserve admin-consent-not-sufficient behavior.
  • If no canonical verified evidence exists, fail closed and document Spec 433 as follow-up.

Phase 4 - Process Executor and Command Builder

  • Add process executor abstraction and fake executor.
  • Add command builder using argument vectors or structured process calls.
  • Build only from Spec 430 command contracts.
  • Reject shell strings, pipelines, script fragments, redirection, aliases, profile loading, mutation command families, and unknown parameters.

Phase 5 - Production Runner Boundary

  • Add production runner service behind the binding/config gates.
  • Require OperationRun context, runtime readiness, credential reference, permission evidence, provider scope, and redaction policy before process execution.
  • Return transient runner envelopes only.
  • Persist no provider payload, stdout, stderr, transcript, or credential material.

Phase 6 - Output, Timeout, Concurrency, and Cleanup Guards

  • Guard stdout/stderr byte limits, item limits, UTF-8, structured shape, warning-prefix, non-zero exit, scalar/text output, binary output, and unexpected exceptions.
  • Enforce per-invocation timeout and process termination/cleanup.
  • Enforce provider/workspace concurrency locks and cleanup on success/failure/timeout/exception.
  • Prefer no temp files; if used, prove safe content and cleanup.

Phase 7 - OperationRun, Summary, Redaction, and No-Promotion

  • Store only safe OperationRun context.
  • Reuse existing summary keys unless a narrow tested key is unavoidable.
  • Add or map sanitized failure reasons.
  • Prove redaction across context, summaries, failures, runner envelopes, process output, exceptions, logs if testable, and temp files if any.
  • Prove no evidence, compare/render/certification, restore, customer claim, UI, trigger surface, migration, tenant_id, legacy shim, fallback reader, or Exchange mini-platform.

Phase 8 - Regression and Report

  • Run focused Spec 432 tests.
  • Run selected Spec 431/430/426/427/417/419/420 regressions.
  • Run Pint and git diff --check.
  • Complete implementation-report.md with runner/credential/target/no-promotion matrices and validation results.

Rollout and Deployment Considerations

  • New production-runner config must default disabled in all environments.
  • No migration, queue, scheduler, storage, asset, or browser build impact is planned.
  • Dokploy/staging validation is required before any future spec enables live invocation outside test/fake contexts.
  • If implementation adds environment variables, the implementation report must list names, defaults, and staging/production validation requirements.

Risk Controls

  • Hard stop if production runner can activate by default.
  • Hard stop if client-secret credentials or admin consent alone enable live invocation.
  • Hard stop if raw PowerShell strings, mutation commands, or unknown parameters can execute.
  • Hard stop if raw output, secrets, provider payload, or transcripts persist.
  • Hard stop if evidence or customer/product claims are promoted.
  • Hard stop if routes, UI, jobs, schedules, listeners, migrations, or tenant_id are introduced without amending the spec.