TenantAtlas/specs/435-exchange-structured-output-target-normalizer-readiness/plan.md
ahmido a09cc6ca8d Spec 435: Exchange structured output readiness (#502)
## Summary

- Add Spec 435 backend-only Exchange structured output envelope and target normalizer readiness helpers for transportRule, remoteDomain, and inboundConnector.
- Add deterministic hash input/readiness handling and no-promotion guard coverage.
- Add Spec 435 spec, plan, tasks, checklist, and implementation report artifacts.

## Validation

- git diff --cached --check
- cd apps/platform && php artisan test --filter=Spec435 --compact

## Product Surface / Deployment

- Livewire v4 compliance unchanged.
- Filament provider registration unchanged under apps/platform/bootstrap/providers.php.
- Global search unchanged; no resources added or modified.
- Destructive/high-impact actions: none.
- Asset strategy: none; no filament:assets requirement for this slice.
- Browser proof: N/A - no rendered UI surface changed.
- Deployment impact: no env vars, migrations, queues, scheduler, storage, assets, or Dokploy runtime behavior changes.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #502
2026-07-08 14:33:23 +00:00

18 KiB

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:

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:

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.