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
13 KiB
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-browserand repobrowsertestfor 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
ExchangePowerShellInvocationReadinessEvaluatoras the shared readiness truth forexchange_powershell_invokecapability evaluation and the production runner gate. - Kept the no-migration path. Existing
ProviderCredentialfields andManagedEnvironmentPermission.detailsare 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
ExchangePowerShellPermissionEvidenceEvaluatorwith 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-futureobserved_at/verified_at,verified_at >= observed_at, evaluator identity/version metadata, and currentlast_checked_atplus 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, andprovider_connection_idevidence 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
ProviderCapabilityEvaluatorsoexchange_powershell_invokeis 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
ExchangePowerShellProductionRunnerto consume combined readiness before runtime checks or process execution. - Added safe
readiness_stateandreadiness_blockerpropagation 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_invokeuses 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
TenantConfigurationResourceorTenantConfigurationResourceEvidencerows 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_idownership 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:assetsnot 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.ManageAsAppevidence + all normal required permission rows; - blocked environment + client-secret credential + stale permission evidence.
- ready environment + certificate credential + scoped current
- A temporary ignored local
.envoverrideTENANTPILOT_EXCHANGE_POWERSHELL_SUPPORTED_REFERENCE_KINDS=certificatewas used only during browser proof and removed afterward; config cache was cleared after removal. - Provider connection view, ready fixture:
Exchange PowerShell invocation: Supportedrendered 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: Unknownrendered 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 capabilitiessection renderedExchange PowerShell invocationas Supported with0 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.