TenantAtlas/specs/436-exchange-content-backed-evidence-promotion-slice-1/implementation-report.md
ahmido 30a6733f78 Spec 436: Exchange content-backed evidence promotion (#503)
Summary: Wire Exchange PowerShell evidence capture through Spec 435 structured envelopes, target normalizers, and hash builder for transportRule, remoteDomain, and inboundConnector; preserve append-only internal content-backed evidence with fail-closed readiness, output, identity, and redaction behavior; add Spec 436 artifacts and focused coverage. Validation: php artisan test --filter=Spec436 --compact PASS, 28 tests / 307 assertions; git diff --cached --check PASS before commit. Product/Ops: Livewire v4 unchanged; provider registration unchanged under apps/platform/bootstrap/providers.php; global search unchanged; no destructive/high-impact actions; no assets/browser/UI/deployment impact.
Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #503
2026-07-09 14:03:10 +00:00

15 KiB

Implementation Report: Exchange Content-Backed Evidence Promotion Slice 1

Date: 2026-07-09

Result

  • Status: PASS.
  • Active spec: specs/436-exchange-content-backed-evidence-promotion-slice-1/.
  • Branch: feat/436-exchange-content-backed-evidence-promotion-slice-1.
  • HEAD at implementation start/end: a09cc6ca Spec 435: Exchange structured output readiness (#502).
  • Implementation/fix iterations: 2 in-scope findings fixed, then no remaining in-scope findings.

Candidate Gate Result

PASS. Spec 435 is present and records PASS/merge-ready. Specs 430-435 were used as read-only prerequisite context. No hard-gate stop condition was met.

Branch And Dirty State

  • Branch at implementation start: feat/436-exchange-content-backed-evidence-promotion-slice-1.
  • HEAD at implementation start: a09cc6ca Spec 435: Exchange structured output readiness (#502).
  • Initial dirty state: untracked active Spec 436 package only.
  • Final dirty state: intended Spec 436 runtime/test/spec files plus Spec 434 regression test alignment.

Files Changed

  • apps/platform/app/Services/TenantConfiguration/ExchangePowerShellEvidenceCaptureAdapter.php
  • apps/platform/app/Services/TenantConfiguration/ExchangePowerShellEvidenceNormalizer.php
  • apps/platform/app/Services/TenantConfiguration/ExchangePowerShellTargetEvidenceNormalizer.php
  • apps/platform/tests/Feature/TenantConfiguration/Spec434ExchangeEvidenceCaptureAdapterTest.php
  • apps/platform/tests/Feature/TenantConfiguration/Spec436ExchangeContentBackedEvidenceFeatureTest.php
  • specs/436-exchange-content-backed-evidence-promotion-slice-1/checklists/requirements.md
  • specs/436-exchange-content-backed-evidence-promotion-slice-1/implementation-report.md
  • specs/436-exchange-content-backed-evidence-promotion-slice-1/tasks.md

Prerequisite Proof

  • Spec 435 prerequisite proof: specs/435-exchange-structured-output-target-normalizer-readiness/implementation-report.md records PASS and merge-ready.
  • Spec 434 prerequisite proof: cd apps/platform && php artisan test --filter=Spec434 --compact passed after updating fixture aliases to current Spec435 identity semantics.
  • Spec 433 prerequisite proof: cd apps/platform && ./vendor/bin/sail artisan test --filter=Spec433 --compact passed.
  • Spec 432 prerequisite proof: cd apps/platform && ./vendor/bin/sail artisan test --filter='Spec432|Spec431|Spec430' --compact passed.
  • Spec 430 prerequisite proof: covered in the same Spec430-432 regression command.

Target Type Outcomes

Type Command Capture Outcome Evidence? Blocker
transportRule Get-TransportRule captured yes, content-backed only none
remoteDomain Get-RemoteDomain captured yes, content-backed only domain-only, display-only, duplicate, and conflicting identities block
inboundConnector Get-InboundConnector captured yes, content-backed only missing and unsafe identities block

Evidence Matrix

Type Raw Payload Normalized Payload Hash Identity OperationRun Content-backed?
transportRule evidence row only target normalizer output plus source metadata Spec435 hash builder over persisted normalized payload stable source identity required tenant_configuration.capture linked yes
remoteDomain evidence row only target normalizer output plus source metadata Spec435 hash builder over persisted normalized payload stable source identity required tenant_configuration.capture linked yes
inboundConnector evidence row only target normalizer output plus source metadata Spec435 hash builder over persisted normalized payload stable source identity required tenant_configuration.capture linked yes

No-Promotion Matrix

Area State
Compare No
Render No
Certification No
Restore No
Customer claim No
UI No
Jobs/Schedules/Listeners No

OperationRun Proof

  • Evidence append is gated to OperationRunType::TenantConfigurationCapture through the existing eligibility gate.
  • Exchange PowerShell invocation runs remain runner truth only and do not own evidence.
  • OperationRun context, summary_counts, and failure_summary are asserted to exclude raw payload, raw stdout/stderr, normalizer previews, hash previews, access tokens, client secrets, and protected inbound connector routing values.
  • Summary counts are updated through existing SummaryCountsNormalizer behavior only.

Evidence Persistence Proof

Spec436 feature tests assert one successful fake capture for each target creates one tenant_configuration_resources row and one tenant_configuration_resource_evidence row with:

  • workspace_id
  • managed_environment_id
  • provider_connection_id
  • operation_run_id
  • target-specific raw payload
  • target-specific normalized payload
  • source endpoint and source contract metadata
  • deterministic payload hash
  • permission context

Persisted capture_outcome repo-allowed value proof: successful evidence persists CaptureOutcome::Captured.

Adapter-only outcome labels absent from persisted evidence proof: prerequisite, identity, empty collection, and sanitized failure adapter labels are returned only in adapter results and do not create evidence rows.

Append-Only Proof

Spec436 feature coverage runs two captures for the same target and asserts a second evidence row is inserted while the prior evidence row and first payload hash remain unchanged.

Latest Evidence Pointer Proof

The same append-only test asserts latest_evidence_id, latest_payload_hash, and latest_captured_at advance to the newest evidence row according to the existing writer/upserter pattern.

Raw Payload Location Proof

Raw provider item payload is persisted only in tenant_configuration_resource_evidence.raw_payload. OperationRun state and summaries are tested to exclude raw provider output. Raw stdout/stderr are never used as evidence.

Structured Envelope Proof

ExchangePowerShellEvidenceCaptureAdapter now converts accepted runner output into ExchangePowerShellStructuredOutputEnvelope::fromInvocationResult(...) using a verified ExchangePowerShellCommandContract before normalizer readiness, identity gating, resource upsert, and evidence append.

Normalizer Integration Proof

  • ExchangePowerShellEvidenceNormalizer::normalizedEvidenceItems() dispatches to the target normalizer for exactly transportRule, remoteDomain, and inboundConnector.
  • ExchangePowerShellTargetEvidenceNormalizer::normalizedEvidenceItems() prepares the same validated, sorted, target-normalized items used by readiness.
  • The adapter no longer depends on GenericPayloadNormalizer or ExchangeTeamsComparablePayloadNormalizer for Exchange evidence promotion.

Hash Proof

Persisted payload_hash is computed through ExchangePowerShellHashInputBuilder using target type, source surface, command contract name/version, payload shape version, normalizer version, and the persisted normalized payload with source metadata.

Identity Proof

Spec436 tests prove unsafe identity states block before writer use with no resource/evidence rows:

  • remoteDomain domain-only identity
  • remoteDomain display-only identity
  • duplicate identity
  • conflicting aliases
  • inboundConnector missing identity

Spec434 regression fixtures were updated so legacy adapter tests use non-conflicting alias values under the current Spec435 identity contract.

Credential Readiness Proof

Spec436 tests prove missing credential and client-secret credential readiness failures return prerequisite-blocked outcomes with zero summary counts and no evidence rows.

Permission Readiness Proof

Spec436 tests prove missing permission, stale permission, and wrong-scope permission evidence block with no evidence. The readiness gate remains the source of permission freshness truth.

Runtime Readiness Proof

Spec436 tests prove warning-prefixed structured output, authentication failure, authorization/permission failure, source unavailable, malformed output, scalar output, binary/non-UTF8 output, oversized output, non-zero exit, partial output, and timeout/non-success runner states block with no evidence.

Failure Blocking Proof

  • Unsupported provider capability blocker proof: covered by Spec434/Spec433 regression commands and provider capability tests.
  • Source unavailable is not empty collection proof: covered directly in Spec436 failure-blocking assertions.
  • Partial output is not empty collection or success proof: covered directly in Spec436 failure-blocking assertions.
  • Empty collections return zero-item summaries and create no resource/evidence rows, covered directly in Spec436.

Redaction Proof

Inbound connector protected routing values stay in raw evidence only when present in the source payload. Target-normalized payload and OperationRun state use redacted sentinel values for sender IPs, smart hosts, certificate names, and comments.

Forbidden Path Proof

  • No GenericPayloadNormalizer for Exchange evidence promotion: static guard test asserts the adapter source excludes it.
  • No ExchangeTeamsComparablePayloadNormalizer for Exchange evidence promotion: static guard test asserts the adapter source excludes it.
  • No Graph gateway or fake Graph endpoint: static guard test asserts no Graph gateway/fake endpoint path was introduced.
  • No compare/render/certification: content-only guard and static assertions cover this.
  • No restore/customer claim: content-only guard and static assertions cover this.
  • No UI/route/job/schedule/listener: static guard test asserts no route, UI, Livewire, job, console, or migration trigger was added for the slice.
  • No tenant_id: static/schema guard asserts no tenant_id ownership path was introduced.
  • No mini-platform: static guard asserts no Exchange-specific table/dashboard/fallback reader/mini-platform was introduced.

Product Surface Close-Out

  • Runtime UI files changed: no.
  • UI impact: N/A - no rendered UI surface changed.
  • Product Surface exceptions: none.
  • Browser proof: N/A - no rendered UI surface changed.
  • Human Product Sanity: N/A - no product surface changed.
  • Visible complexity outcome: neutral.
  • No-legacy confirmation: canonical replacement; no aliases, fallback readers, hidden routes, duplicate UI, or compatibility shim.
  • Completed-spec rewrite assertion: completed Specs 430-435 were not rewritten.
  • Livewire v4 compliance: unchanged; no Livewire code changed.
  • Provider registration location: unchanged under apps/platform/bootstrap/providers.php.
  • Global search posture: unchanged; no resources were added or modified.
  • Destructive/high-impact action posture: none; no UI action or runtime start trigger was added.
  • Asset strategy: none; filament:assets not required for this slice.
  • Deployment impact: no env vars, migrations, queues, scheduler, storage, assets, or Dokploy runtime behavior changes.

Validation

  • php -l on changed PHP files
    • Result: PASS for adapter, dispatcher, target normalizer, and Spec436 feature test.
  • cd apps/platform && ./vendor/bin/sail artisan test --filter=Spec436 --compact
    • Result: failed before completion with Symfony Process signal 9 after final output/permission cases. Non-Docker fallback used.
  • cd apps/platform && php artisan test --filter=Spec436 --compact
    • Result: PASS, 28 tests / 307 assertions.
  • cd apps/platform && ./vendor/bin/sail artisan test --filter=Spec435 --compact
    • Result: PASS, 28 tests / 167 assertions.
  • cd apps/platform && ./vendor/bin/sail artisan test --filter=Spec434 --compact
    • Result: failed before completion with Symfony Process signal 9. Non-Docker fallback used.
  • cd apps/platform && php artisan test --filter=Spec434 --compact
    • Result: PASS, 25 tests / 154 assertions.
  • cd apps/platform && ./vendor/bin/sail artisan test --filter=Spec433 --compact
    • Result: PASS, 48 tests / 211 assertions.
  • cd apps/platform && ./vendor/bin/sail artisan test --filter='Spec432|Spec431|Spec430' --compact
    • Result: PASS, 173 tests / 1357 assertions.
  • cd apps/platform && ./vendor/bin/sail artisan test --filter='Spec415|Spec417|Spec419|Spec420|Spec426|Spec427' --compact
    • Result: failed before completion with Symfony Process signal 9 after partial progress. Non-Docker fallback used.
  • cd apps/platform && php artisan test --filter='Spec415|Spec417|Spec419|Spec420|Spec426|Spec427' --compact
    • Result: PASS, 202 passed / 8 skipped / 2137 assertions. Skips are existing PostgreSQL-specific skips. Existing browser tests in this filter passed.
  • cd apps/platform && ./vendor/bin/sail artisan test --filter='ProviderCapabilityRegistryTest|ProviderCapabilityEvaluationTest' --compact
    • Result: PASS, 7 tests / 30 assertions.
  • cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent
    • Result: PASS.
  • git diff --check
    • Result: PASS.
  • git status --short
    • Result: intended Spec436 runtime/test/spec files plus Spec434 regression test alignment.

Post-Implementation Analysis

Iteration 1 confirmed and fixed one in-scope regression-alignment finding:

  • Medium: Spec434 regression fixtures still used conflicting Exchange alias IDs and expected older display-only reason labels. Updated fixtures and expectations to match the stricter Spec435 identity contract while keeping the adapter fail-closed.

Iteration 2 confirmed and fixed one in-scope test-coverage finding:

  • Medium: Spec436 initially relied on prerequisite regressions for several named output failure modes and did not directly prove empty collections create no fake evidence at the adapter boundary. Added direct Spec436 cases for empty collection, authentication failure, authorization/permission failure, source unavailable, malformed output, scalar output, binary/non-UTF8 output, oversized output, non-zero exit, partial output, and timeout.

Iteration 3 found no remaining in-scope findings.

Remaining in-scope findings: none.

Residual risks: none for this slice. Comparable/renderable promotion, Teams PowerShell evidence slices, certified compare packs, customer output, and restore/apply flows remain deferred.

Deferred Work

  • Spec 437 Exchange comparable/renderable promotion.
  • Teams PowerShell evidence slices.
  • Certified compare pack.
  • Customer output and claim guard.
  • Restore/apply flows.

Merge Readiness Gate

Status: PASS. Merge-ready: yes.

Manual review prompt:

Du bist ein Senior Staff Software Architect und Enterprise SaaS Reviewer.

Führe eine finale manuelle Review der implementierten Spec `436-exchange-content-backed-evidence-promotion-slice-1` streng repo-basiert durch.

Ziel:
Pruefe, ob die Implementierung nach dem Agenten-Loop wirklich merge-ready ist.

Wichtig:
- Keine Implementierung.
- Keine Codeaenderungen.
- Keine Scope-Erweiterung.
- Pruefe gegen spec.md, plan.md, tasks.md und constitution.md.
- Pruefe die geaenderten Dateien, Tests, Browser-Smoke-Test-Ergebnis, RBAC, Workspace-/Tenant-Isolation, Auditability, UX und OperationRun-Semantik, soweit relevant.
- Benenne nur konkrete Findings mit Repo-Beleg.
- Gib am Ende eine klare Entscheidung: Merge-ready, merge-ready with notes, oder not merge-ready.