TenantAtlas/specs/433-exchange-credential-permission-evidence-support/implementation-report.md
ahmido a6f4529941 feat: add Exchange credential permission evidence readiness (#500)
Validation: sail artisan test --filter=Spec433|Spec432|Spec431 --compact; pint --dirty --test; git diff --check
Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #500
2026-07-07 23:55:32 +00:00

13 KiB

Implementation Report: Exchange Credential and Permission Evidence Support

Result

  • Gate result: PASS WITH CONDITIONS.
  • Active spec: specs/433-exchange-credential-permission-evidence-support/.
  • Branch: 433-exchange-credential-permission-evidence-support.
  • HEAD at implementation start: f4e34212 feat: add Exchange PowerShell production runner gate (#499).
  • Initial dirty state: only untracked active Spec 433 package.
  • Final dirty state: application/config/test changes plus the active Spec 433 package and this report.
  • Condition: browser Product Surface sanity found an existing broader provider-readiness guidance card can still show a generic provider credential blocker next to the Exchange-specific capability result. The changed Exchange capability slots themselves were canonical and redacted. No UI code was changed because this spec forbids new UI/surface work.

Activated Skills and Gates

  • spec-kit-implementation-loop: active implementation workflow.
  • pest-testing: Pest 4 focused unit/feature coverage.
  • Browser skills: browser:control-in-app-browser and repo browsertest for focused read-only browser proof.
  • TenantPilot repo gates: spec readiness, workspace-scope safety, RBAC/action safety, provider freshness semantics, OperationRun truth, evidence-anchor contract, Product Surface gate, Filament/Livewire v5 change loop.

Implementation Summary

  • Added ExchangePowerShellInvocationReadinessEvaluator as the shared readiness truth for exchange_powershell_invoke capability evaluation and the production runner gate.
  • Kept the no-migration path. Existing ProviderCredential fields and ManagedEnvironmentPermission.details are sufficient when permission details explicitly match workspace, managed environment, provider, and provider connection.
  • Tightened certificate readiness so certificate references require both rotation and expiry metadata.
  • Extended ExchangePowerShellPermissionEvidenceEvaluator with supported-provider checks, status normalization, configurable currentness, safe source metadata, scoped provider-connection validation, and sanitized blocker states.
  • Post-review hardening requires trusted permission evidence provenance: source=provider_verification, canonical, calendar-valid ISO/RFC3339 non-future observed_at/verified_at, verified_at >= observed_at, evaluator identity/version metadata, and current last_checked_at plus current observed/verified timestamps. Missing, malformed, relative, untrusted, or time-inconsistent metadata now fails closed as unvalidated evidence; stale row or provenance timestamps fail closed as stale evidence.
  • Post-review scope hardening requires workspace_id, managed_environment_id, and provider_connection_id evidence metadata to be canonical positive integer values before any scope comparison. Numeric-prefix, decimal, scientific-notation, whitespace-padded, missing, or otherwise malformed scope IDs now fail closed as unvalidated evidence instead of passing through PHP integer coercion.
  • Updated ProviderCapabilityEvaluator so exchange_powershell_invoke is Supported only when combined credential and permission readiness passes.
  • Post-review hardening makes the shared Exchange readiness evaluator the single capability truth for exchange_powershell_invoke; the capability no longer applies a separate generic provider-readiness freshness precheck that can diverge from the Exchange evidence currentness config.
  • Updated ExchangePowerShellProductionRunner to consume combined readiness before runtime checks or process execution.
  • Added safe readiness_state and readiness_blocker propagation into OperationRun runner-result context.
  • Added env/config defaults for TENANTPILOT_EXCHANGE_POWERSHELL_PERMISSION_EVIDENCE_CURRENTNESS_DAYS=30.

Data Model Decision

Area Decision Proof
Provider credential No migration provider_connection_id, credential_kind, source, last_rotated_at, and expires_at cover the observable readiness states for this slice.
Permission evidence No migration ManagedEnvironmentPermission remains one row per environment + permission key; details must match workspace, environment, provider, and provider connection or fail closed.
Provider connection scope Existing model Readiness requires matching workspace_id and managed_environment_id; wrong connection details block.
Secret material No new storage Existing encrypted payload remains hidden and is never returned in readiness/OperationRun context.
Migration lane N/A No schema file added; no PostgreSQL schema lane required.

Credential Matrix

Credential state Result
Missing reference Blocks as credential_blocked_missing_reference.
Client secret Blocks as credential_blocked_secret_based_app.
Certificate missing rotation or expiry metadata Blocks as credential_blocked_certificate_missing.
Expired certificate Blocks as credential_blocked_certificate_expired.
Certificate unsupported by config Blocks as credential_blocked_certificate_unsupported.
Certificate explicitly supported for readiness Passes as certificate_supported.
Federated Blocks unless future config/support is added.
Managed identity string Blocks as unsupported/unknown.
Unknown kind Blocks fail-closed.

Permission Matrix

Permission evidence state Result
Missing row Blocks as permission_evidence_blocked_missing.
Missing/absent/revoked status Blocks as permission_evidence_blocked_absent.
Unknown/error status Blocks as permission_evidence_blocked_unknown.
Granted without trusted source, observed/verified timestamps, evaluator identity, or evaluator version Blocks as unvalidated.
Granted with malformed, relative, future, or time-inconsistent provenance metadata Blocks as unvalidated.
Granted with malformed scope identifiers Blocks as unvalidated before scope matching.
Stale row, observed timestamp, or verified timestamp beyond configured currentness Blocks as permission_evidence_blocked_stale.
Wrong workspace/environment/provider/provider connection Blocks with scoped failure code.
Current scoped Exchange.ManageAsApp Passes as verified.

Capability and Runner Proof

  • exchange_powershell_invoke uses the combined readiness evaluator and no longer relies on admin consent, generic provider freshness, or generic stored permission rows alone.
  • Missing credential maps to Missing; stale/unknown/unvalidated permission evidence maps to Unknown; wrong-scope or unsupported readiness maps to Blocked.
  • The production runner evaluates combined readiness before runtime readiness, command building, locks, or process execution.
  • Successful runner context persists only safe state: runner mode, execution enabled, readiness state, runtime state, credential state, permission evidence state, command builder/output/concurrency state, counts, and duration.

No-Promotion Proof

  • No TenantConfigurationResource or TenantConfigurationResourceEvidence rows are created.
  • No raw or normalized Exchange payload, compare/render/certification/restore/customer-ready state, report output, Review Pack output, PDF output, route, Filament page/resource/widget/action, Livewire component, navigation, asset, global search change, job, schedule, listener, console trigger, Exchange-specific table, legacy shim, fallback reader, dual write, or tenant_id ownership path was introduced.
  • Completed Specs 429-432 were read-only context and were not rewritten.

Product Surface and Filament Close-Out

  • No-legacy posture: canonical addition only; no aliases, fallback readers, hidden routes, duplicate UI, or historical compatibility path.
  • Product Surface Impact: existing provider capability/readiness surfaces can render changed evaluator output; no new surface budget introduced.
  • UI Surface Impact: no UI files changed; existing provider connection and required-permissions surfaces render the changed status data.
  • Browser proof: PASS WITH CONDITION. See browser section below.
  • Human Product Sanity: PASS WITH CONDITION. Exchange capability slots are canonical/redacted; existing broader readiness card can still show a generic credential blocker beside Exchange-specific Supported.
  • Visible complexity outcome: neutral; no new visible components, only more truthful existing status values.
  • Livewire v4 compliance: unchanged repo baseline; no Livewire runtime code added.
  • Provider registration location: no Filament panel provider registration changed; Laravel panel providers remain in apps/platform/bootstrap/providers.php.
  • Global search: no Filament resources or global search behavior changed.
  • Destructive/high-impact actions: no UI action added; runner remains behind existing Spec 432 gates and provider-run authorization.
  • Asset strategy: no assets; filament:assets not required for this slice.
  • Deployment impact: no migration, queue, schedule, storage, asset, or build impact. New env example key: TENANTPILOT_EXCHANGE_POWERSHELL_PERMISSION_EVIDENCE_CURRENTNESS_DAYS=30.

Browser Proof

  • Sail was running and migrations were current. A local smoke fixture was seeded with:
    • ready environment + certificate credential + scoped current Exchange.ManageAsApp evidence + all normal required permission rows;
    • blocked environment + client-secret credential + stale permission evidence.
  • A temporary ignored local .env override TENANTPILOT_EXCHANGE_POWERSHELL_SUPPORTED_REFERENCE_KINDS=certificate was used only during browser proof and removed afterward; config cache was cleared after removal.
  • Provider connection view, ready fixture:
    • Exchange PowerShell invocation: Supported rendered in existing capability detail.
    • No raw grant, access token, private key, certificate password, or client secret sentinel rendered.
    • No browser console errors from the tab.
  • Provider connection view, blocked fixture:
    • Exchange PowerShell invocation: Unknown rendered for the blocked client-secret/stale-evidence fixture.
    • No raw token/client-secret sentinel rendered.
    • No browser console errors from the tab.
  • Required-permissions view, ready fixture:
    • Provider capabilities section rendered Exchange PowerShell invocation as Supported with 0 missing, 0 need review.
    • No raw grant, access token, private key, certificate password, or client secret sentinel rendered.
    • No browser console errors from the tab.
  • Required-permissions view, blocked fixture:
    • Required-permissions/provider capability surface stayed non-supported and showed missing/stale guidance rather than ready.
    • No raw token/client-secret sentinel rendered.
    • No browser console errors from the tab.
  • Laravel Boost browser log contained older unrelated June operation-page errors, not Spec 433 route errors.

Validation

  • Post-review remediation:
    • cd apps/platform && php artisan test --filter=Spec433 --compact
      • Result: PASS, 48 tests, 211 assertions.
    • cd apps/platform && php artisan test --filter='Spec433|Spec432|Spec431' --compact
      • Result: PASS, 150 tests, 1057 assertions.
    • cd apps/platform && ./vendor/bin/pint --dirty
      • Result: PASS, 10 files.
    • git diff --check
      • Result: PASS.
  • cd apps/platform && php artisan test --filter=Spec433 --compact
    • Result: PASS, 30 tests, 157 assertions.
  • cd apps/platform && php artisan test --filter=Spec432 --compact
    • Result: PASS, 72 tests, 430 assertions.
  • cd apps/platform && php artisan test --filter=Spec431 --compact
    • Result: PASS, 30 tests, 416 assertions.
  • cd apps/platform && php artisan test --filter='Spec433|Spec432|Spec431' --compact
    • Result: PASS, 132 tests, 1003 assertions.
  • cd apps/platform && php artisan test --filter='Spec433|Spec432|Spec431|Spec430|Spec427ExchangeTeamsNoEvidencePromotion|Spec427ExchangeTeamsNoCompareRenderCertification|Spec426ExchangeTeamsNoTenantId|Spec426ExchangeTeamsNoMiniPlatform|Spec417IdentityNoLegacyNoUiActivation|Spec419M365RegistryExpansion|Spec420M365NoOverclaim|ProviderCapabilityRegistryTest|ProviderCapabilityEvaluationTest' --compact
    • Result: PASS, 222 tests, 2028 assertions, 1 PostgreSQL-only skip.
  • cd apps/platform && ./vendor/bin/pint --dirty
    • Result: PASS, 10 files.
  • cd apps/platform && ./vendor/bin/pint app/Services/TenantConfiguration/ExchangePowerShellInvocationReadinessEvaluator.php tests/Feature/TenantConfiguration/Spec433ExchangePowerShellEvidenceReadinessTest.php tests/Feature/TenantConfiguration/Spec431ExchangePowerShellInvocationGateTest.php tests/Feature/TenantConfiguration/Spec432ExchangePowerShellProductionRunnerGateTest.php --format=txt
    • Result: PASS.
  • git diff --check
    • Result: PASS.
  • cd apps/platform && ./vendor/bin/sail artisan migrate --force
    • Result: PASS, nothing to migrate.
  • cd apps/platform && ./vendor/bin/sail artisan config:clear
    • Result: PASS after browser override removal.

Deferred Work

  • Content-backed Exchange evidence capture/promotion remains Spec 434 or later.
  • Live Exchange execution success, customer claims, compare/render/certification/restore readiness, Teams, Exchange Admin API, broader Exchange targets, and UI productization remain out of scope.
  • A future UI/product-surface cleanup should decide how to distinguish generic provider readiness from Exchange PowerShell-specific capability readiness on provider connection pages.