Skip to content

eat(custody): implement custody lifecycle state machine#112

Open
Neziahtech wants to merge 4 commits into
amina69:mainfrom
Neziahtech:feat/custody-state-machine
Open

eat(custody): implement custody lifecycle state machine#112
Neziahtech wants to merge 4 commits into
amina69:mainfrom
Neziahtech:feat/custody-state-machine

Conversation

@Neziahtech
Copy link
Copy Markdown

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

@drips-wave
Copy link
Copy Markdown

drips-wave Bot commented Jun 1, 2026

@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! 🚀

Learn more about application limits

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.

Custody Lifecycle State Machine

1 participant