Skip to content

fix(backend): make archive and unarchive timestamps monotonic and auditable - #144

Open
woahwhattheheck wants to merge 1 commit into
RemitFlow:mainfrom
woahwhattheheck:latch/remitflow-126-archive-timestamps
Open

woahwhattheheck wants to merge 1 commit into
RemitFlow:mainfrom
woahwhattheheck:latch/remitflow-126-archive-timestamps

Conversation

@woahwhattheheck

Copy link
Copy Markdown

Summary

Closes #126.

Repeated archive/unarchive cycles previously cleared archivedAt on unarchive and then wrote a new timestamp on the next archive, which hid earlier lifecycle order and made retention/reconciliation unreliable. This change keeps current-state flags usable while recording an immutable, monotonic event history for every transition.

Design

  • Current state still uses archivedAt (non-null = archived) so list filters stay unchanged.
  • History is an append-only archiveHistory array of frozen { action, at, actor, reason, requestId } events. Event timestamps never move backward and are never rewritten.
  • lastArchivedAt retains the prior archive instant after unarchive so callers can reconcile without digging the full history.
  • Optimistic concurrency: optional expectedUpdatedAt (JSON body or bare If-Match) rejects stale commands with 409 STALE_ARCHIVE_COMMAND so concurrent workers cannot silently overwrite each other.
  • Idempotent retries without a concurrency token still no-op when already archived (same archivedAt / history length), matching prior behaviour.
  • Audit: transfer.archived / transfer.unarchived entries include actor and reason in the payload.

Compatibility

  • Existing archiveTransfer(id) / unarchiveTransfer(id) call sites keep working.
  • New fields (archiveHistory, lastArchivedAt) are additive on transfer objects.
  • Archive remains orthogonal to claim/cancel status transitions.
  • No unrelated service, contract, or user-flow rewrites.

Acceptance criteria

Criterion How addressed
Monotonic state transitions active ↔ archived only; unarchive when not archived still 409
Immutable event timestamps frozen archiveHistory entries; nextTimestamp floor from last event
Reject stale commands expectedUpdatedAt / If-Match → STALE_ARCHIVE_COMMAND
Actor + reason recorded history events + audit payload
Consistent under retries/concurrency idempotent re-archive; stale second-racer loses
Timestamps never move backward event-order + updatedAt assertions
Each transition auditable transfer.archived / transfer.unarchived

Test plan

npm test
# focused:
node --test test/transferArchive.test.js test/transferArchiveLifecycle.test.js

Evidence on this branch: 269/269 passing (includes prior archive suite + new state-machine, stale-command, race, retry, event-order, audit, and regression coverage for the original overwrite/hide failure mode). No unrelated tests skipped or weakened.

…itable

Archive/unarchive now append immutable history events with actor and reason,
keep lastArchivedAt across unarchive, and reject stale expectedUpdatedAt
commands so retries and races cannot overwrite lifecycle order.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(backend): make archive and unarchive timestamps monotonic and auditable

1 participant