Files
clean-architecture-backend-…/docs/superpowers/plans/2026-09-18-outbox-transport-only-cutover.md
T

106 lines
5.4 KiB
Markdown

# Outbox Transport-Only Cutover Implementation Plan
**Goal:** Preserve canonical integration-event bytes and metadata inside the existing legacy `outbox_event` authority, then route canonical claimed rows through the messaging platform while legacy rows keep the current broker path.
**Spec:** `docs/superpowers/specs/2026-09-18-outbox-transport-only-cutover-design.md`
## Constraints
- Preserve all existing uncommitted work. No reset/checkout/stage/commit/amend/push.
- Do not activate or switch to `POLLING_V2`.
- Do not synthesize missing canonical metadata for legacy rows.
- Do not re-encode a persisted canonical envelope.
- One claimed row goes through exactly one publish branch.
- Default configuration remains legacy-compatible and canonical transport is off.
### Task 1 — Split canonical and legacy append ports
- [x] Create `LegacyOutboxAppendPort` with the current `NewOutboxEvent` signature.
- [x] Change `OutboxAppendPort` to accept `ValidatedIntegrationEvent`.
- [x] Move all current raw production consumers and their tests to `LegacyOutboxAppendPort`.
- [x] Make `OutboxStoreAdapter` implement `LegacyOutboxAppendPort` only.
- [x] Run `:application-core:test` and focused sample/outbox compile tests.
### Task 2 — Add additive canonical columns to `outbox_event`
- [x] Add the next PostgreSQL migration after V12.
- [x] Widen event/correlation identifiers as required.
- [x] Add canonical metadata, exact `BYTEA`, hashes/revisions, and all-or-none check constraints.
- [x] Extend `OutboxEventEntity` mappings.
- [x] Update migration history expectations.
- [x] Add real PostgreSQL integration assertions for legacy rows and canonical shape constraints.
### Task 3 — Implement canonical append adapter
- [x] Add `CanonicalOutboxAppendAdapter`.
- [x] Strictly validate UTF-8 compatibility projection.
- [x] Persist every canonical field and exact `envelopeBytes`.
- [x] Preserve old required columns for legacy relay/storage compatibility.
- [x] Add unit tests for byte equality, field mapping and invalid UTF-8.
- [x] Gate bean exposure on `ca-skeleton.outbox.canonical-transport-enabled=true`.
### Task 4 — Split the claimed row model
- [x] Add sealed `ClaimedOutboxEvent`.
- [x] Keep `OutboxEvent` as legacy subtype.
- [x] Add `CanonicalClaimedOutboxEvent` carrying reconstructed `ValidatedIntegrationEvent`.
- [x] Change `OutboxStorePort.claimBatch` to return the sealed type.
- [x] Map all-canonical rows to canonical subtype and all-null rows to legacy subtype.
- [x] Reject partial canonical rows.
- [x] Update legacy relay tests without changing its state-machine semantics.
### Task 5 — Route canonical claims through the platform
- [x] Update `OutboxMessagePublishPort` to accept `ClaimedOutboxEvent`.
- [x] Extend `OutboxMessagePublishAdapter` with canonical `IntegrationEventPublishPort`.
- [x] Legacy subtype uses only `MessageBroker`.
- [x] Canonical subtype uses only the application canonical publish port and exact stored bytes.
- [x] Add focused branch-isolation and outcome tests.
### Task 6 — Add explicit activation and composition validation
- [x] Add `canonicalTransportEnabled` to `OutboxSettings` and `config/outbox.yml`, default false.
- [x] Startup fails when canonical transport is enabled but no `IntegrationEventPublishPort` exists.
- [x] Relay-enabled compatibility deployment still requires the legacy broker until a later zero-legacy-backlog proof.
- [x] Default-off composition keeps the existing legacy path.
- [x] Enabled composition exposes the canonical append/publish path without a second scheduler.
- [x] Update configuration docs/SSOT.
### Task 7 — Regression and architecture verification
- [x] `:application-core:check`
- [x] `:adapter:outbound:persistence-jpa:check`
- [x] focused PostgreSQL migration/outbox integration lane
- [x] `:adapter:outbound:messaging:check`
- [ ] `:sample-portfolio:check` — blocked by pre-existing `JpaLiveEventReplayAdapter` missing `Duration` wiring; the same 3 `SampleApplicationContextTest` failures reproduce on clean HEAD.
- [x] focused sample outbox regression tests (`PosterEventPublisherTest`, `CreateWorkLogOutboxTest`, `WorkLogUseCasesTest`, `WorkLogAuthorizationContractTest`)
- [x] `:app-bootstrap:architectureTest :app-bootstrap:systemTest`
- [x] `verifyCleanArchitectureDependencies`
- [x] `:app-bootstrap:verifyEnvKeys`
- [x] static scans: no platform runtime/Kafka import in canonical bridge
- [x] `git diff --check`
- [x] no separate LLM Wiki branch-note warranted; spec, plan, module README/CLAUDE and configuration reference carry the implementation decision.
## Verification result
Transport-only cutover implementation is complete for this slice.
Passed:
- `:application-core:check`
- `:adapter:outbound:persistence-jpa:check`
- `:adapter:outbound:persistence-jpa:postgresqlMigrationIntegrationTest`
- `:adapter:outbound:messaging:check`
- focused sample outbox regression tests
- `:app-bootstrap:test`
- focused `:app-bootstrap:integrationTest` outbox append + row-lifecycle contracts
- `:app-bootstrap:architectureTest`
- `:app-bootstrap:systemTest`
- `verifyCleanArchitectureDependencies`
- `:app-bootstrap:verifyEnvKeys`
- `git diff --check`
Known unrelated blocker:
- full `:sample-portfolio:check` still fails only the 3 previously documented `SampleApplicationContextTest` cases because `JpaLiveEventReplayAdapter` requires an unbound `Duration` bean. This reproduces on clean HEAD and was not introduced by this cutover.
Publication authority remains `LEGACY_POLLING`; no code path in this slice activates `POLLING_V2`.