264 lines
15 KiB
Markdown
264 lines
15 KiB
Markdown
# 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:
|
|
|
|
```markdown
|
|
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.
|
|
```
|