fix(backend): make archive and unarchive timestamps monotonic and auditable - #144
Open
woahwhattheheck wants to merge 1 commit into
Open
woahwhattheheck wants to merge 1 commit into
woahwhattheheck wants to merge 1 commit into
Conversation
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #126.
Repeated archive/unarchive cycles previously cleared
archivedAton 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
archivedAt(non-null = archived) so list filters stay unchanged.archiveHistoryarray of frozen{ action, at, actor, reason, requestId }events. Event timestamps never move backward and are never rewritten.lastArchivedAtretains the prior archive instant after unarchive so callers can reconcile without digging the full history.expectedUpdatedAt(JSON body or bareIf-Match) rejects stale commands with409 STALE_ARCHIVE_COMMANDso concurrent workers cannot silently overwrite each other.archivedAt/ history length), matching prior behaviour.transfer.archived/transfer.unarchivedentries include actor and reason in the payload.Compatibility
archiveTransfer(id)/unarchiveTransfer(id)call sites keep working.archiveHistory,lastArchivedAt) are additive on transfer objects.Acceptance criteria
active ↔ archivedonly; unarchive when not archived still409archiveHistoryentries;nextTimestampfloor from last eventexpectedUpdatedAt/If-Match→STALE_ARCHIVE_COMMANDtransfer.archived/transfer.unarchivedTest plan
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.