TenantAtlas/specs/435-exchange-structured-output-target-normalizer-readiness/plan.md
Ahmed Darrazi 73d84c2706
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m14s
feat: add Exchange structured output readiness
2026-07-08 16:24:20 +02:00

365 lines
18 KiB
Markdown

# Implementation Plan: Exchange Structured Output and Target Normalizer Readiness
**Branch**: `435-exchange-structured-output-target-normalizer-readiness`
**Date**: 2026-07-08
**Spec**: `specs/435-exchange-structured-output-target-normalizer-readiness/spec.md`
**Input**: User-provided Spec 435 draft and repo evidence from Specs 430-434.
## Summary
Prepare Exchange structured runner output and target normalizer readiness for exactly `transportRule`, `remoteDomain`, and `inboundConnector`. The implementation must define a safe structured envelope, prove raw stdout/stderr are not evidence, add or refine target-specific normalizer readiness, prove or explicitly block identity/redaction safety, define deterministic ordering/hash input rules, preserve empty-collection no-evidence behavior, and prove no evidence rows or product promotion occur.
Spec 435 is readiness-only. It must not create evidence, promote `content_backed`, call Exchange, execute tenant PowerShell, add UI, add jobs/schedules/listeners, add migrations, introduce `tenant_id`, or create customer claims.
## Technical Context
- **Language/Version**: PHP 8.4.15, Laravel 12.
- **Primary Dependencies**: existing TenantConfiguration/Coverage v2 services, Exchange PowerShell runner boundary, Pest 4 tests.
- **Storage**: no schema change and no new persistence.
- **External Calls**: none. Tests use fake process executor or fake runner output only.
- **UI**: none. Filament v5/Livewire v4 compliance unchanged.
- **OperationRun**: no new operation type or UX. Existing `tenant_configuration.capture` remains the future evidence-owning operation from Spec 434; Spec 435 only proves context remains sanitized.
- **Evidence Truth**: no evidence rows, no raw/normalized evidence persistence, no content-backed promotion.
- **Provider Boundary**: Exchange output shape and target normalization are provider-owned; Coverage v2, identity safety, redaction, and claim boundaries remain platform-core.
## Constitution and Gate Alignment
- **Proportionality**: The new envelope/normalizer readiness helpers are justified because evidence promotion would otherwise be unsafe. They are non-persisted, provider-owned, target-limited, and immediate prerequisites for Spec 436.
- **No premature abstraction**: Do not create a generic multi-provider PowerShell framework. Keep names and code under Exchange/TenantConfiguration boundaries.
- **No new persisted truth**: No migrations, tables, rows, or durable collection-level proof.
- **OperationRun truth**: OperationRun remains execution truth only; it is not customer proof or evidence payload truth.
- **Evidence anchor contract**: No evidence anchors or evidence rows are created. Future proof must be explicit and scoped in Spec 436.
- **Provider freshness semantics**: Do not treat runner success as provider readiness or customer-safe evidence.
- **Product Surface Contract**: N/A - no rendered UI surface changed.
- **Customer output gate**: no customer/auditor output, reports, Review Packs, PDFs, downloads, or customer-safe labels.
## Current Repo Evidence
- Spec 430 created Exchange PowerShell command contracts for `Get-TransportRule`, `Get-RemoteDomain`, and `Get-InboundConnector`.
- Spec 431 registered/gated Exchange PowerShell invocation through OperationRun/provider capability paths.
- Spec 432 added the production runner boundary, process executor abstraction, output guard, timeout/concurrency guards, and sanitized OperationRun context.
- Spec 433 added Exchange credential and permission readiness, including client-secret blocking and scoped/current `Exchange.ManageAsApp` proof.
- Spec 434 added the Exchange evidence capture adapter boundary, identity hard-stop, content-only writer guard, empty collection no-fake-evidence behavior, and generic Graph capture preservation.
## Likely Affected Runtime Surfaces for Later Implementation
These are planning targets only. This preparation pass does not edit application code.
- `apps/platform/app/Services/TenantConfiguration/ExchangePowerShellEvidenceCaptureAdapter.php`
- `apps/platform/app/Services/TenantConfiguration/ExchangePowerShellCommandContracts.php`
- `apps/platform/app/Services/TenantConfiguration/ExchangePowerShellProductionRunner.php`
- `apps/platform/app/Services/TenantConfiguration/ExchangePowerShellProcessCommandBuilder.php`
- `apps/platform/app/Services/TenantConfiguration/ExchangePowerShellOutputGuard.php`
- `apps/platform/app/Services/TenantConfiguration/CoverageIdentityStrategyRegistry.php`
- `apps/platform/app/Services/TenantConfiguration/ExchangeTeamsComparablePayloadNormalizer.php`
- `apps/platform/app/Services/TenantConfiguration/CanonicalIdentityResolver.php`
- `apps/platform/config/tenantpilot.php`
Hard boundary: Spec 435 readiness flows must not invoke `ExchangePowerShellEvidenceCaptureAdapter::capture()`, `CoverageResourceUpserter::upsert()`, `CoverageEvidenceWriter::append()`, or any equivalent evidence/resource append path. If `ExchangePowerShellEvidenceCaptureAdapter.php` is touched, it must be for guard/regression coverage or explicit separation only; evidence append remains Spec 436.
`ExchangeTeamsComparablePayloadNormalizer.php` may be used only as read-only repo evidence for existing field handling, or for extracting non-comparable shared field lists if that is the narrowest safe implementation. Spec 435 must not expand comparable/renderable support for `remoteDomain`, `inboundConnector`, or any other Exchange type.
## Likely New Runtime Files for Later Implementation
Use repo-canonical names if current conventions differ.
- `apps/platform/app/Services/TenantConfiguration/ExchangePowerShellStructuredOutputEnvelope.php`
- `apps/platform/app/Services/TenantConfiguration/ExchangePowerShellEvidenceNormalizer.php`
- `apps/platform/app/Services/TenantConfiguration/ExchangeTransportRuleEvidenceNormalizer.php`
- `apps/platform/app/Services/TenantConfiguration/ExchangeRemoteDomainEvidenceNormalizer.php`
- `apps/platform/app/Services/TenantConfiguration/ExchangeInboundConnectorEvidenceNormalizer.php`
- `apps/platform/app/Services/TenantConfiguration/ExchangePowerShellNormalizerReadiness.php`
- `apps/platform/app/Services/TenantConfiguration/ExchangePowerShellHashInputBuilder.php`
## Forbidden Runtime Surfaces
Implementation must stop and amend or split before touching:
- `apps/platform/routes/**`
- `apps/platform/app/Filament/**`
- `apps/platform/app/Livewire/**`
- `apps/platform/resources/views/**`
- `apps/platform/app/Jobs/**`
- `apps/platform/app/Listeners/**`
- `apps/platform/app/Console/**`
- database migrations
- customer report, Review Pack, PDF, restore, certification, compare/render customer code
- Exchange-specific evidence tables
- `tenant_id` ownership truth
- legacy shims, fallback readers, dual writes, mini-platform structures
## Architecture Decisions
### Structured Output Envelope
Define a value object or service equivalent to `ExchangePowerShellStructuredOutputEnvelope` with:
- `resource_type`
- `command_contract_name`
- `command_contract_version`
- `source_surface = exchange_online_powershell_rest`
- `runner_mode`
- `shape_state`
- `items`
- `item_count`
- `empty_collection`
- `warnings_classified`
- `output_guard_state`
- `normalizer_readiness_state`
- `redaction_readiness_state`
- `identity_readiness_state`
The envelope must explicitly exclude raw stdout/stderr, transcripts, free-text serialized objects, credentials, provider secrets, tokens, authorization headers, and cookies.
### Shape State Mapping
Use repo-canonical equivalents when existing names differ. Required semantics:
- accepted: `structured_collection`, `structured_empty_collection`
- blocked: malformed, scalar, warning-prefixed, binary, non-UTF8, oversized, non-zero exit, timeout, unknown
Shape blockers are readiness blockers, not evidence outcomes.
Minimum repo-code mapping for current implementation review:
| Spec semantic | Current repo code or required mapping |
|---|---|
| `structured_collection` | `structured_collection` |
| `structured_empty_collection` | `empty_collection` unless a repo-canonical envelope alias is added in memory only |
| malformed collection | `malformed_json_collection` or `malformed_collection_item` |
| warning-prefixed output | `warning_prefixed` |
| binary/non-UTF8 output | `non_utf8_or_binary` |
| oversized output | `stdout_oversized`, `stderr_oversized`, or repo-equivalent aggregate blocker |
| non-zero exit | `nonzero_exit` |
| timeout | `timeout` |
### Target Normalizer Readiness
Each target normalizer or readiness evaluator defines:
- resource type and source surface
- command contract name/version
- payload shape version and normalizer version
- identity candidate fields
- material, volatile, and sensitive fields
- ordering rules
- hash input rules
- redaction policy
- readiness blockers
No normalizer is a renderer or compare engine. In-memory normalized previews and hash previews are allowed in tests only.
### Identity Readiness
Stable identity is required for future evidence append. Blockers include:
- `identity_conflict`
- `missing_external_id`
- `unsupported_identity`
- `display_name_only`
- `duplicate_identity`
- `identity_unproven`
`remoteDomain` must prove source `Identity` stability or stay explicitly blocked. Domain/display-name derived identity must not pass silently.
Minimum identity-code mapping for current implementation review:
| Spec semantic | Current repo code or required mapping |
|---|---|
| `identity_conflict` | `identity_conflict` |
| `missing_external_id` / missing identity | `missing_stable_external_id` or repo-equivalent readiness blocker |
| `unsupported_identity` | `unsupported_identity` |
| derived/domain-only identity | `derived_identity_blocked` |
| duplicate identity | `duplicate_stable_identity` |
| display-name-only identity | must map to a derived/unsupported/display-name-only readiness blocker and fail closed |
| identity unproven | `blocked_identity_unproven` or repo-equivalent readiness blocker |
### Redaction Readiness
No raw provider payload, stdout/stderr, serialized Exchange object, transcript, secret, token, credential material, mail/message/file content, transport rule sensitive pattern, or inbound connector protected config may enter OperationRun context/logs/summaries.
Allowed test-only previews must use redacted placeholders, protected-config markers, count-only metadata, or field-presence metadata.
### Hash Input Readiness
Hash previews must be deterministic and based only on normalized material payload, target type, source surface, command contract metadata, payload shape version, and normalizer version according to repo pattern. They must exclude raw output, OperationRun context, runtime timestamps, PowerShell session metadata, credential readiness, and permission readiness.
## Target Matrix
| Type | Command | Structured Output | Identity | Normalizer | Redaction | Hash Input | Ready for Spec 436? |
|---|---|---|---|---|---|---|---|
| `transportRule` | `Get-TransportRule` | prove | prove stable identity | prove deterministic rules/actions/exceptions/order | classify sensitive patterns/words/header values | prove deterministic | yes if all tests pass |
| `remoteDomain` | `Get-RemoteDomain` | prove | prove source `Identity` or block | prove settings normalization or block | classify sensitive fields | prove deterministic | yes or explicit blocker |
| `inboundConnector` | `Get-InboundConnector` | prove | prove stable connector identity or block | prove deterministic fields/order or block | prove protected config redaction or block | prove deterministic | yes or explicit blocker |
## No-Promotion Matrix
| Area | Required State |
|---|---|
| Evidence rows | No |
| Raw payload evidence | No |
| Normalized payload evidence | No |
| Content-backed | No |
| Compare | No |
| Render | No |
| Certification | No |
| Restore | No |
| Customer claim | No |
| UI | No |
| Jobs/Schedules/Listeners | No |
| Migration | No |
| `tenant_id` | No |
## Data and Migration Impact
No migration. No new table. No new persisted entity. No new durable artifact. No `tenant_id`. No Exchange-specific evidence table. No raw payload table. No customer output table. No UI state table. No legacy snapshot table.
If implementation discovers persistence is required, stop and amend the spec before application code changes continue.
## UI / Filament / Livewire Impact
- No rendered UI surface changed.
- No route, Filament page/resource/widget, Livewire component, Blade view, navigation item, action, asset, or global search change.
- Livewire v4 compliance unchanged.
- Provider registration location unchanged under `apps/platform/bootstrap/providers.php`.
- Destructive/high-impact UI actions: none.
- Asset strategy: none; no `filament:assets` deployment impact.
- Browser proof: N/A - no rendered UI surface changed.
## Test Strategy
### Unit Tests
Required focused files or repo-canonical equivalents:
- `Spec435ExchangeStructuredOutputEnvelopeTest`
- `Spec435ExchangeTransportRuleNormalizerReadinessTest`
- `Spec435ExchangeRemoteDomainNormalizerReadinessTest`
- `Spec435ExchangeInboundConnectorNormalizerReadinessTest`
- `Spec435ExchangeIdentityReadinessTest`
- `Spec435ExchangeRedactionReadinessTest`
- `Spec435ExchangeHashInputReadinessTest`
- `Spec435ExchangeEmptyCollectionReadinessTest`
- `Spec435ExchangeFailureReadinessTest`
- `Spec435NoEvidencePromotionTest`
- `Spec435NoProductSurfaceTest`
### Feature Tests
Required focused files or repo-canonical equivalents:
- `Spec435ExchangeNormalizerReadinessFeatureTest`
- `Spec435ExchangeStructuredOutputReadinessFeatureTest`
- `Spec435ExchangeNoEvidenceFeatureTest`
- `Spec435ExchangeNoProductSurfaceFeatureTest`
- `Spec435ExchangeNoTenantIdFeatureTest`
### Regression Tests
Run focused regressions for Specs 434, 433, 432, 431, 430, 415, 417, 419, 420, 426, 427, and provider capability registry/evaluation tests.
### Browser Tests
N/A - no rendered UI surface changed. If any UI change appears, stop and amend or split.
## Implementation Phases
### Phase 0 - Preflight
- Capture branch, HEAD, and dirty state.
- Confirm runtime implementation branch is `feat/435-exchange-structured-output-target-normalizer-readiness` or record an explicit Spec Kit branch-name exception before changing runtime/test code.
- Confirm Spec 434 merge-ready and no active checklist/report blockers.
- Confirm target scope and forbidden surfaces.
- Confirm current runner output, normalizer, identity, redaction, and hash state.
- Confirm readiness implementation will not call the existing capture/upsert/evidence-writer append path.
### Phase 1 - Structured Output Envelope
- Add structured output envelope.
- Exclude raw stdout/stderr and credential/provider secrets.
- Add shape state handling and output blocker mapping.
- Test malformed/scalar/warning/binary/non-UTF8/oversized/non-zero/timeout behavior.
### Phase 2 - transportRule Readiness
- Add/refine transportRule normalizer readiness.
- Prove stable identity, deterministic ordering, state/mode, conditions/actions/exceptions, volatile exclusions, sensitive classification, and hash rules.
### Phase 3 - remoteDomain Readiness
- Prove source `Identity` stability or block.
- Distinguish default and custom remote domains.
- Block missing/duplicate/domain-only/display-name-only identity.
- Define deterministic settings normalization, volatile fields, redaction, and hash rules.
### Phase 4 - inboundConnector Readiness
- Prove stable connector identity or block.
- Prove missing/duplicate identity blocks.
- Classify and redact IPs, smart hosts, certificate names, comments, and routing metadata.
- Define deterministic normalization and hash rules.
### Phase 5 - Hash / Redaction / Empty Collection
- Prove hash determinism and volatile/order behavior.
- Prove protected config cannot enter OperationRun context/logs/summaries.
- Preserve empty collection no-resource/no-evidence policy.
### Phase 6 - No-Promotion Safety
- Prove no evidence rows.
- Prove no content-backed or product claim state.
- Prove readiness dispatch does not invoke `ExchangePowerShellEvidenceCaptureAdapter::capture()`, `CoverageResourceUpserter::upsert()`, or `CoverageEvidenceWriter::append()`.
- Prove no compare/render/certification/restore/customer output.
- Prove no UI, routes, jobs, schedules, listeners, migrations, `tenant_id`, or mini-platform.
### Phase 7 - Regression / Report
- Run focused tests and regressions.
- Run Pint and `git diff --check`.
- Complete implementation report with target matrix and no-promotion matrix.
## Validation Commands
Preferred local commands:
```bash
cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent
cd apps/platform && ./vendor/bin/sail artisan test --filter=Spec435 --compact
cd apps/platform && ./vendor/bin/sail artisan test --filter=Spec434 --compact
cd apps/platform && ./vendor/bin/sail artisan test --filter=Spec433 --compact
cd apps/platform && ./vendor/bin/sail artisan test --filter='Spec432|Spec431|Spec430' --compact
cd apps/platform && ./vendor/bin/sail artisan test --filter='Spec415|Spec417|Spec419|Spec420|Spec426|Spec427' --compact
cd apps/platform && ./vendor/bin/sail artisan test --filter='ProviderCapabilityRegistryTest|ProviderCapabilityEvaluationTest' --compact
git diff --check
git status --short
```
If Sail is unavailable, document the failure and use the repo's non-Docker fallback for the same focused filters:
```bash
cd apps/platform && php artisan test --filter=Spec435 --compact
```
## Rollout and Deployment Impact
- Environment variables: none expected.
- Migrations: none.
- Queues/workers: none.
- Scheduler: none.
- Storage/volumes: none.
- Assets: none.
- Dokploy/staging/production: no deployment behavior change unless implementation unexpectedly adds runtime config, which requires spec amendment.
## Risk Controls
- Stop if evidence persistence is needed.
- Stop if UI or trigger surface is needed.
- Stop if `tenant_id`, migration, Exchange evidence table, or mini-platform appears.
- Stop if raw stdout/stderr or protected config is required outside guarded internals.
- Stop if `remoteDomain` or `inboundConnector` cannot prove identity/redaction and the implementation tries to pass instead of block.
## Spec Readiness Gate
PASS for preparation. The plan names affected surfaces, forbidden surfaces, phases, tests, no-promotion constraints, Product Surface N/A path, proportionality controls, and validation commands. No application implementation has been performed.