TenantAtlas/specs/430-exchange-powershell-adapter-contract-slice-1/implementation-report.md
ahmido 9b58a5696d feat: add Exchange PowerShell adapter contract slice 1 (#497)
Spec 430: Exchange PowerShell adapter contract slice 1. Validation was not rerun during PR creation handoff; branch contains implementation report and tests.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #497
2026-07-05 12:08:16 +00:00

13 KiB

Implementation Report: Spec 430 - Exchange PowerShell Adapter Contract Slice 1

Preflight

  • Active spec: specs/430-exchange-powershell-adapter-contract-slice-1/
  • Branch: 430-exchange-powershell-adapter-contract-slice-1
  • HEAD before implementation: 0e2cea30 spec: add Exchange Teams source-surface catalog adapter strategy (#496)
  • Initial dirty state: untracked active spec package only: specs/430-exchange-powershell-adapter-contract-slice-1/
  • Activated skills/gates:
    • spec-kit-implementation-loop: requested implementation loop.
    • .agent/workflows/spec-readiness-gate: active Spec Kit implementation readiness check.
    • .agent/repo-contracts/workspace-scope-safety: provider-connection and future execution handoff scope proof.
    • .agent/repo-contracts/provider-freshness-semantics: provider permission/source-boundary metadata proof.
    • .agent/temporary-migrations/tcm-cutover-guard: Coverage v2/source-contract no-promotion guard.
    • pest-testing: focused Pest 4 test implementation and validation.
  • Hard-gate stop conditions checked: no unrelated dirty files; no completed spec rewrite; no live provider execution; no evidence rows; no OperationRun creation; no UI/routes/navigation/Filament/Livewire changes; no migrations; no tenant_id; no fallback readers, legacy shims, dual writes, customer output, restore/certification/customer claim, or Exchange mini-platform required.

Repo Truth And Path Decisions

  • Spec 429 source path: specs/429-exchange-teams-source-surface-catalog-adapter-strategy/implementation-report.md identifies new_exchange_powershell_adapter as the recommended first Spec 430 adapter path and lists transportRule, remoteDomain, and inboundConnector as the first Exchange PowerShell slice candidates.
  • Runtime support path: use the existing TenantConfiguration/Coverage v2 service path under apps/platform/app/Services/TenantConfiguration/.
  • Command contract support file: apps/platform/app/Services/TenantConfiguration/ExchangePowerShellCommandContracts.php.
  • Resolver integration: apps/platform/app/Services/TenantConfiguration/CoverageSourceContractResolver.php.
  • Registry metadata updates: apps/platform/app/Services/TenantConfiguration/ResourceTypeRegistry.php, SupportedScopeResolver.php, and CoverageIdentityStrategyRegistry.php only where required for inboundConnector and the three included identity handoffs.
  • Fake runner boundary: apps/platform/tests/Support/TenantConfiguration/Spec430ExchangePowerShellFakeCommandRunner.php; no production runner is introduced or wired.
  • Focused tests: new Spec 430 Pest files under apps/platform/tests/Unit/Support/TenantConfiguration/ and apps/platform/tests/Feature/TenantConfiguration/.

Included And Excluded Types

  • Included: transportRule, remoteDomain, inboundConnector.
  • Excluded/deferred: acceptedDomain, organizationConfig, mailboxPlan, outboundConnector, sharingPolicy, Teams policy types including appPermissionPolicy, appSetupPolicy, meetingPolicy, messagingPolicy, teamsUpdateManagementPolicy, teamsChannelsPolicy, externalAccessPolicy, and the rest of Cohort 1.

Implementation Summary

Implemented the first Exchange PowerShell adapter-contract slice as a structured, no-live-execution command contract boundary for:

  • transportRule -> Get-TransportRule
  • remoteDomain -> Get-RemoteDomain
  • inboundConnector -> Get-InboundConnector

The contract boundary is metadata-only and fake-runner-testable. It does not introduce a production runner, shell execution, provider calls, Microsoft service calls, evidence promotion, OperationRun creation, compare/render/certification/restore/customer readiness, UI, routes, navigation, downloads, or customer-facing copy.

Files Changed

Runtime:

  • apps/platform/app/Services/TenantConfiguration/ExchangePowerShellCommandContracts.php
  • apps/platform/app/Services/TenantConfiguration/CoverageSourceContractResolver.php
  • apps/platform/app/Services/TenantConfiguration/ClaimGuard.php
  • apps/platform/app/Services/TenantConfiguration/CoverageIdentityStrategyRegistry.php
  • apps/platform/app/Services/TenantConfiguration/ResourceTypeRegistry.php
  • apps/platform/app/Services/TenantConfiguration/SupportedScopeResolver.php

Tests:

  • apps/platform/tests/Support/TenantConfiguration/Spec430ExchangePowerShellFakeCommandRunner.php
  • apps/platform/tests/Unit/Support/TenantConfiguration/Spec430ExchangePowerShellCommandAllowlistTest.php
  • apps/platform/tests/Unit/Support/TenantConfiguration/Spec430TransportRuleCommandContractTest.php
  • apps/platform/tests/Unit/Support/TenantConfiguration/Spec430RemoteDomainCommandContractTest.php
  • apps/platform/tests/Unit/Support/TenantConfiguration/Spec430InboundConnectorCommandContractTest.php
  • apps/platform/tests/Unit/Support/TenantConfiguration/Spec430ExchangePowerShellResolverTest.php
  • apps/platform/tests/Unit/Support/TenantConfiguration/Spec430ExchangePowerShellMetadataTest.php
  • apps/platform/tests/Feature/TenantConfiguration/Spec430ExchangePowerShellNoPromotionTest.php
  • Focused Spec 417/419/420/426/427 regression expectations updated for the verified-pending source-contract state and inboundConnector default-sync denominator.

Spec artifacts:

  • specs/430-exchange-powershell-adapter-contract-slice-1/implementation-report.md

Command Boundary And Allowlist Proof

  • Only the three read-only command names are allowlisted: Get-TransportRule, Get-RemoteDomain, Get-InboundConnector.
  • Mutation command families are rejected before fake-runner execution: Set-*, New-*, Remove-*, Enable-*, Disable-*, Update-*, Start-*, Stop-*, Invoke-*, Search-*, Export-*, and Import-*.
  • Raw command strings, shell metacharacters, pipelines, script blocks, semicolon-separated commands, redirection, file writes, module installation fragments, unknown commands, and unknown parameters are rejected.
  • The fake runner accepts only structured contract arrays and returns test-only structured results. No production runner was added.

Resolver Proof

  • transportRule, remoteDomain, and inboundConnector now resolve to:
    • source_contract_state: contract_verified_pending_capture
    • provider_adapter_state: adapter_contract_available
    • outcome: capture_blocked_missing_contract
    • provider_calls_allowed: false
    • execution_enabled: false
    • evidence_promotion_allowed: false
    • customer_claims_allowed: false
    • restore_allowed: false
    • certification_allowed: false
  • CoverageSourceContractDecision::capturable() remains false for these decisions because there is no source endpoint and the outcome is blocked pending capture.
  • Excluded Exchange and Teams types remain blocked/deferred. No guessed Graph endpoint or fallback reader was added.

Metadata Proof

  • Permission metadata is runtime_validation_pending, includes Exchange.ManageAsApp only as a pending runtime validation note, and keeps Exchange RBAC validation deferred to a future execution spec.
  • Response-shape metadata distinguishes empty collection, permission denied, command unavailable, adapter unavailable, malformed response, and unexpected object shape.
  • Identity handoff rejects display-name-only identity and records stable/derived candidates per type.
  • Redaction metadata forbids tokens, secrets, authorization headers, cookies, certificate private material, passwords, raw transcripts, raw shell stdout/stderr, mail body/content, mailbox content, file content, and Teams transcript/content.
  • Protected configuration examples cover email/domain/rule-pattern fields for transport rules, domain fields for remote domains, and IP/certificate/routing metadata for inbound connectors.
  • Provider-owned Exchange cmdlet and response-field details remain internal source-contract metadata, not customer vocabulary, platform-core ownership truth, or product surface copy.
  • Future execution scope requires workspace, managed environment, and provider connection. Provider-native scope identifiers are metadata only and cannot bypass workspace/provider scope.

Claim And Promotion Boundary

  • The only allowed claim wording is the exact internal/operator-only statement: Exchange PowerShell adapter contracts exist for transportRule, remoteDomain, and inboundConnector.
  • The same statement is blocked when not marked internal/operator-only.
  • No customer claim, certification, restore, compare/render, content-backed evidence, or readiness promotion is introduced.
  • No TenantConfigurationResource, TenantConfigurationResourceEvidence, or OperationRun record is created by this slice.

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: unchanged.
  • No-legacy posture: no legacy shim, fallback reader, dual truth, v1 adapter, or mini-platform added.
  • Livewire v4 compliance: no Livewire/Filament runtime code changed; repo baseline remains Livewire v4.
  • Provider registration: no panel/provider changes; Laravel providers remain in apps/platform/bootstrap/providers.php.
  • Global search: no Filament resource/global search surface changed.
  • Destructive/high-impact actions: none added.
  • Asset strategy: no assets registered; filament:assets is not required for this slice.
  • Deployment impact: no env vars, migrations, queues, scheduler, storage, assets, or browser build required. Runtime default sync now adds inboundConnector to registry/scope defaults when ResourceTypeRegistry::syncDefaults() and SupportedScopeResolver::syncDefaults() are run; migration baseline stays historical at 32 resource rows.

Validation

  • cd apps/platform && ./vendor/bin/sail artisan test --filter=Spec430 --compact
    • 64 passed, 479 assertions.
  • Selected Spec 426 regressions:
    • Spec426ExchangeTeamsSourceContractResolverTest
    • Spec426ExchangeTeamsCoreEvidenceReadinessTest
    • Spec426ExchangeTeamsNoCertificationTest
    • Spec426ExchangeTeamsNoRestoreTest
    • Spec426ExchangeTeamsNoMiniPlatformTest
    • Spec426ExchangeTeamsNoTenantIdTest
    • 11 passed, 123 assertions.
  • Selected Spec 427 regressions:
    • Spec427ExchangeTeamsSourceContractStateTest
    • Spec427ExchangeTransportRuleContractTest
    • Spec427SourceContractIdentityHandoffTest
    • Spec427SourceContractPermissionMetadataTest
    • Spec427SourceContractRedactionTest
    • Spec427SourceContractResponseShapeTest
    • Spec427ExchangeTeamsNoEvidencePromotionTest
    • Spec427ExchangeTeamsNoCompareRenderCertificationTest
    • Spec427ExchangeTeamsNoCustomerRestoreClaimTest
    • Spec427ExchangeTeamsNoMiniPlatformTest
    • Spec427ExchangeTeamsNoTenantIdTest
    • 30 passed, 306 assertions.
  • Registry/identity/capture outcome regressions:
    • Spec415CoverageCaptureOutcomeTest
    • Spec419M365WorkloadRegistryTest
    • Spec419M365RegistryExpansionTest
    • TenantConfigurationResourceTypeRegistryTest
    • Spec420M365CaptureEligibilityTest
    • Spec417CoverageIdentityStrategyRegistryTest
    • 21 passed, 983 assertions, 1 PostgreSQL-only check skipped in the local driver.
  • cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent
    • pass.
  • git diff --check
    • pass.
  • rg -- "- \[ \]" specs/430-exchange-powershell-adapter-contract-slice-1/tasks.md
    • no unchecked Spec 430 tasks remain.
  • Broad parallel Spec426/Spec427/Spec428 filter attempts were not used as final proof because Sail processes were killed with signal 9 under parallel load. Final proof uses serial direct-file runs listed above.
  • Spec 428 executable test status: no apps/platform/tests/**/Spec428*.php files exist. Spec 428 remains a prepared no-op spec package; runtime no-evidence proof is covered by the Spec430 and selected Spec427 no-promotion tests above.

Post-Implementation Analysis

  • Iteration 1 finding: adding capture_blocked_pending_capture as a new CaptureOutcome enum value would have diverged from the historical evidence-table check constraint while this spec explicitly avoids migrations and does not persist blocked evidence.
  • Fix applied: keep contract_verified_pending_capture as the resolver/source-contract state and use the existing capture_blocked_missing_contract capture outcome for non-capturable transient capture result rows.
  • Post-fix validation: final Spec430, Spec426, Spec427, registry/identity/capture outcome, Pint, and git diff --check validations passed.
  • Browser Smoke Test Gate: passed as N/A because no rendered UI surface changed.
  • Remaining confirmed in-scope findings: none.

Final Repository State

  • Final dirty state is expected implementation work: runtime TenantConfiguration/Coverage v2 service updates, focused Spec 417/419/420/426/427 regression updates, new Spec 430 tests/support fake runner, and the active Spec 430 package.
  • No unrelated dirty files were observed.

Deferred Work

  • A future spec must add any production Exchange PowerShell execution adapter, runtime RBAC/permission validation, provider connection handshake, command execution, capture normalization, evidence promotion, compare/render/certification/restore behavior, UI, or customer-facing claims.