0047 — Retain RenewalSaga as the Temporal-written durable read-model (do not delete it)

Context

The renewal flow has been migrated to Temporal orchestration (ADR-0025). A 2026-06-04 audit (multi-agent code sweep + direct verification) established the current state at main:

Already true (orchestration cutover is effectively complete):

What is still legacy:

The open architectural fork (stated explicitly in the retirement handoff):

This ADR resolves the fork so the remaining cleanup and the spine's D2 step can proceed without contradicting each other.

Decision

Adopt Reading A. The RenewalSaga DynamoDB entity is retained as the durable identity, audit, and read-model anchor for a single renewal cycle. The Temporal renewal workflow — through its activity layer — is the sole writer of renewal lifecycle state; the saga is never transitioned by a legacy state machine again.

The one sanctioned non-workflow writer is the PMS rent-roll poller's mint path (src/lib/domain/pms/writers/renewal.tsopenExecutedSaga / openOfferPreparedSaga, and the Clara openIntentToVacateSaga bridge). It opens an observed/terminal saga only for the PM-manual-in-AppFolio edge case where no workflow exists, and it signals the workflow when one does. This is a read-model write for an externally-driven fact, not orchestration.

A future reader can tell whether code violates this ADR with one check: putSagaConditional must appear only in the Temporal activity layer (src/lib/temporal/activities/renewal.ts, saga-mint.ts) and the sanctioned mint helpers above. Any other production writer is a regression.

RenewalSaga is a canonical entity (spine trace: via leaseId; activity log LOG#renewal#<id>); it is unchanged by this ADR and introduces no new entity, so the entity-classification table is omitted.

Consequently: GSI4 will be built and ADR-0035 D2 keeps its RenewalSaga third (rather than that third disappearing under Reading B). Gera's open "R0" question — does the workflow still need to read the DDB saga to resume? — is moot under Reading A: the workflow reading loadSagaInitialState from the saga is the intended design, not migration debt to be removed.

Consequences

What becomes easier

What this commits us to

Definition of done (Reading A)

  1. Sole writer fenced — drift test green.
  2. Pre-cutover cohort drained — 0 sagas in OUTREACH_ACTIVE / INTENT_TO_RENEW / HUMAN_REVIEW_PENDING / MIGRATED_TO_WORKFLOW (gated; per-saga sign-off).
  3. Orphaned states + scaffold fields removed from the union and readers (after 2).
  4. Dead scheduler Lease fields removed + drift-guarded; live nudge-sent fields documented as Temporal-managed.
  5. Lying type/doc comments fixed; handoff + CLAUDE.md reconciled.
  6. GSI4 built; D2 re-scoped.

Follow-ups (tracked, not decided here)

Alternatives considered

Reading B — delete the RenewalSaga entity (the retirement-handoff plan). Rejected for now. It requires (R0) proving the workflow carries full resume state in Temporal history and removing the Phase-0 DDB read; (R1) repointing ~10 production readers to a workflow query / projection; (R2–R3) deleting the saga data layer and saga-mint; and (R4) an archive-first deletion of live RSAGA# rows. That is a multi-PR, higher-risk evacuation whose only structural payoff over Reading A is shedding GSI4 — which we are choosing to build anyway because the saga remains the renewal identity anchor the spine re-keys. Reading B stays documented as the long-horizon option; nothing in Reading A forecloses it.

Keep the saga but allow legacy writers (status quo drift). Rejected: a second unfenced writer is exactly the failure mode (ConflictError drops from concurrent transitionSaga + poller writes) the migration set out to remove. Reading A is only safe with the sole-writer drift fence.