eat(custody): implement custody lifecycle state machine#112
Open
Neziahtech wants to merge 4 commits into
Open
Conversation
|
@Neziahtech Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
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.
Closes #60
ummary
This PR enforces valid lifecycle transitions for Custody.status using a CustodyStateMachine service. Without it, custody records can be arbitrarily reopened or reversed — corrupting the pet movement timeline, producing incorrect availability states, and silently invalidating trust scores. The state machine makes terminal states immutable and blocks any transition not explicitly permitted by the custody flow.
Changes
src/custody/custody-state-machine.service.ts (new file)
Implements CustodyStateMachine with a single public method: canTransition(from: CustodyStatus, to: CustodyStatus): boolean
Defines a static transition map: ACTIVE permits RETURNED, CANCELLED, and VIOLATION; all three terminal states permit nothing
Throws CustodyInvalidTransitionException when a blocked transition is attempted, including the from and to states in the error message for debuggability
src/custody/custody-state-machine.exception.ts (new file)
Defines CustodyInvalidTransitionException extending BadRequestException
Message format: Cannot transition custody from {from} to {to}
src/custody/custody.enum.ts (new or extended)
Defines CustodyStatus enum: ACTIVE, RETURNED, CANCELLED, VIOLATION
src/custody/custody.service.ts
Injects CustodyStateMachine
Calls canTransition(current.status, newStatus) before any status update
Throws CustodyInvalidTransitionException if the transition is blocked
Calls TimelineEventService.log() after every successful transition
Calls TrustScoreService.applyViolationPenalty(custody.fosterId) when transitioning to VIOLATION
src/timeline/timeline-event.service.ts
Extended to accept a CUSTODY_TRANSITION event type
Logs petId, fosterId, fromStatus, toStatus, and timestamp on every custody state change
src/trust-score/trust-score.service.ts
Extended with applyViolationPenalty(fosterId: string) called on ACTIVE → VIOLATION transitions
Decrements the foster's trust score by the configured violation penalty weight
src/custody/custody-state-machine.service.spec.ts (new file)
Valid transitions: ACTIVE → RETURNED, ACTIVE → CANCELLED, ACTIVE → VIOLATION all pass
Invalid transitions: RETURNED → ACTIVE, CANCELLED → ACTIVE, VIOLATION → ACTIVE all throw
Terminal immutability: RETURNED → CANCELLED, CANCELLED → RETURNED, VIOLATION → RETURNED all throw
Edge cases: same-state transition (ACTIVE → ACTIVE) blocked; unknown status values handled gracefully
Transition map
ACTIVE → RETURNED (terminal)
ACTIVE → CANCELLED (terminal)
ACTIVE → VIOLATION (terminal)
RETURNED → (none — immutable)
CANCELLED → (none — immutable)
VIOLATION → (none — immutable)
Side effects on transition
ACTIVE → RETURNED — timeline event logged; pet availability recomputed
ACTIVE → CANCELLED — timeline event logged; pet availability recomputed
ACTIVE → VIOLATION — timeline event logged; trust score penalty applied to fosterId; pet availability recomputed
Pet availability recomputation is handled by PetAvailabilityService (introduced in #61), which derives state from live custody and adoption records and requires no additional trigger beyond the custody record update.
Implementation detail
const transitions: Record<CustodyStatus, CustodyStatus[]> = {
[CustodyStatus.ACTIVE]: [CustodyStatus.RETURNED, CustodyStatus.CANCELLED, CustodyStatus.VIOLATION],
[CustodyStatus.RETURNED]: [],
[CustodyStatus.CANCELLED]: [],
[CustodyStatus.VIOLATION]: [],
};
canTransition(from: CustodyStatus, to: CustodyStatus): boolean {
const allowed = transitions[from] ?? [];
if (!allowed.includes(to)) {
throw new CustodyInvalidTransitionException(from, to);
}
return true;
}
Testing
npm run test -- custody-state-machine
Checklist
Invalid transitions blocked and throw CustodyInvalidTransitionException
All terminal states (RETURNED, CANCELLED, VIOLATION) are immutable
Timeline event logged on every successful transition
Trust score penalty applied on ACTIVE → VIOLATION
CustodyStateMachine injected into CustodyService and called before every status update
Unit tests cover all valid transitions, all invalid transitions, and terminal immutability