Cycle State Transitions
Cycle State Transitions
Entity: CycleEntity States: Scheduled, Committed, FulfillmentInProgress, OutboundInTransit, Delivered, WearWindowOpen, ReturnWindowOpen, ReturnInTransit, CloseoutInspection, Settled, Closed
State Diagram
┌───────────────┐
│ Scheduled │ ◄── create_cycle (one per user/week)
└───────┬───────┘
│ commit (all checks pass)
▼
┌───────────────┐
│ Committed │ ◄── inventory locked
└───────┬───────┘
│ start_fulfillment
▼
┌───────────────────┐
│FulfillmentInProgress│ ◄── packing underway
└───────┬───────────┘
│ ship
▼
┌───────────────────┐
│OutboundInTransit │ ◄── en route to user
└───────┬───────────┘
│ deliver
▼
┌───────────────┐
│ Delivered │ ◄── at user location
└───────┬───────┘
│ open_wear_window (automatic)
▼
┌───────────────────┐
│ WearWindowOpen │ ◄── user wearing (default: 5 days)
└───────┬───────────┘
│ transition (automatic at window end)
▼
┌───────────────────┐
│ ReturnWindowOpen │ ◄── expecting return (default: 2 days)
└───────┬───────────┘
│ return_in_transit
▼
┌───────────────────┐
│ ReturnInTransit │ ◄── en route back
└───────┬───────────┘
│ receive
▼
┌────────────────────┐
│CloseoutInspection │ ◄── inspecting garments
└───────┬────────────┘
│ settle
▼
┌───────────────┐
│ Settled │ ◄── charges computed, fit updated
└───────┬───────┘
│ close
▼
┌───────────────┐
│ Closed │ ◄── immutable record
└───────────────┘
Exception paths:
- Scheduled → Cancelled (before commitment)
- Any active state → Holds (via user state changes)
Transition Contracts
T-C001: (none) → Scheduled
Trigger: System schedules next week’s cycle for user.
Preconditions:
user.operational_state == 'Active'- No existing cycle for this
user_id + week_idcombination week_idis valid future week
Actions:
- Create CycleEntity with
cycle_state = 'Scheduled' - Set
user_id,week_id - Set
scheduled_at = NOW() - Emit event:
CycleScheduled
Postconditions:
- Exactly one cycle exists for this user/week
- Cycle is reversible (no inventory locked)
Error Codes:
E002- Cycle already exists for this user/weekE004- User not in Active state
T-C002: Scheduled → Committed
Trigger: All feasibility checks pass at commitment deadline.
Preconditions:
cycle.cycle_state == 'Scheduled'user.operational_state == 'Active'box.container_state == 'Planned'- All garments in
box.planned_contentsare inReservedstate - Payment pre-authorization successful
Actions:
- Set
cycle.cycle_state = 'Committed' - Set
cycle.committed_at = NOW() - Emit event:
CycleCommitted - Lock inventory (garments cannot be unassigned without compensating event)
Postconditions:
- Inventory is committed
- Cycle must proceed or be explicitly cancelled with cost
Error Codes:
E004- User not in Active stateE012- Box not in Planned stateE013- Garment reservation mismatchE014- Payment authorization failed
T-C003: Scheduled → Cancelled
Trigger: Cycle cancelled before commitment (user skip, capacity shortage).
Preconditions:
cycle.cycle_state == 'Scheduled'
Actions:
- Set
cycle.cycle_state = 'Cancelled' - Release all garment reservations (Reserved → Available)
- Emit event:
CycleCancelledwithreason
Postconditions:
- No inventory locked
- User may get another cycle for different week
Error Codes:
E015- Cannot cancel after commitment
T-C004: Committed → FulfillmentInProgress
Trigger: Warehouse begins picking/packing.
Preconditions:
cycle.cycle_state == 'Committed'box.container_state == 'Planned'
Actions:
- Set
cycle.cycle_state = 'FulfillmentInProgress' - Set
box.container_state = 'Picking' - Emit event:
CycleFulfillmentStarted
Postconditions:
- Box is ready for garment scanning
T-C005: FulfillmentInProgress → OutboundInTransit
Trigger: Box ships.
Preconditions:
cycle.cycle_state == 'FulfillmentInProgress'box.container_state == 'PackedVerified'ORbox.variance_resolved == truebox.tracking_outbound != null
Actions:
- Set
cycle.cycle_state = 'OutboundInTransit' - Set
cycle.shipped_at = NOW() - Set
box.container_state = 'Shipped' - Transition all garments in
box.actual_contentstoInTransitOutbound - Emit event:
CycleShipped
Postconditions:
- Carrier has custody
- Tracking available
Error Codes:
E006- Box variance unresolvedE016- No tracking number
T-C006: OutboundInTransit → Delivered
Trigger: Delivery confirmation received.
Preconditions:
cycle.cycle_state == 'OutboundInTransit'- Delivery signal received (carrier or proxy)
Actions:
- Set
cycle.cycle_state = 'Delivered' - Set
cycle.delivered_at = NOW() - Set
box.container_state = 'Delivered' - Transition all garments to
Delivered - Emit event:
CycleDelivered
Postconditions:
- User has custody
- Wear window will open
T-C007: Delivered → WearWindowOpen
Trigger: Automatic (immediate after delivery or scheduled).
Preconditions:
cycle.cycle_state == 'Delivered'
Actions:
- Set
cycle.cycle_state = 'WearWindowOpen' - Transition all garments to
InUse - Emit event:
WearWindowOpened - Schedule
WearWindowClosedevent for +5 days (configurable)
Postconditions:
- User is expected to wear garments
- No action required from user
T-C008: WearWindowOpen → ReturnWindowOpen
Trigger: Automatic at wear window end.
Preconditions:
cycle.cycle_state == 'WearWindowOpen'- Wear window duration elapsed
Actions:
- Set
cycle.cycle_state = 'ReturnWindowOpen' - Set
box.container_state = 'ReturnInitiated' - Emit event:
ReturnWindowOpened - Schedule return reminders
Postconditions:
- User should return box
- Reminders will escalate if no return signal
T-C009: ReturnWindowOpen → ReturnInTransit
Trigger: Return pickup confirmed or user drop-off.
Preconditions:
cycle.cycle_state == 'ReturnWindowOpen'- Return initiated signal received
Actions:
- Set
cycle.cycle_state = 'ReturnInTransit' - Set
cycle.return_initiated_at = NOW() - Set
box.container_state = 'Returning' - Set
box.tracking_returnif available - Transition all garments to
InTransitReturn - Emit event:
CycleReturning
Postconditions:
- Carrier has return custody
- Expecting arrival at facility
T-C010: ReturnInTransit → CloseoutInspection
Trigger: Box received at facility.
Preconditions:
cycle.cycle_state == 'ReturnInTransit'
Actions:
- Set
cycle.cycle_state = 'CloseoutInspection' - Set
cycle.return_received_at = NOW() - Set
box.container_state = 'Received' - Transition all garments to
ReceivedReturn - Emit event:
CycleReceived
Postconditions:
- Garments pending inspection
- Settlement can begin after inspection
T-C011: CloseoutInspection → Settled
Trigger: All garments inspected, settlement computed.
Preconditions:
cycle.cycle_state == 'CloseoutInspection'- All garments in
box.actual_contentshave completed inspection (transitioned out ofReceivedReturn) - Settlement charges computed
Actions:
- Set
cycle.cycle_state = 'Settled' - Set
cycle.settled_at = NOW() - Set
box.container_state = 'Reconciled' - Apply charges/credits to user account
- Update fit beliefs (via FitIntelligenceSubsystem)
- Emit event:
CycleSettled
Postconditions:
- Financial accounting complete
- Fit learning updated
- Garments routed (Available, Retired, etc.)
T-C012: Settled → Closed
Trigger: Finalization.
Preconditions:
cycle.cycle_state == 'Settled'
Actions:
- Set
cycle.cycle_state = 'Closed' - Set
cycle.closed_at = NOW() - Set
box.container_state = 'Closed' - Emit event:
CycleClosed
Postconditions:
- Cycle is immutable (except audit corrections)
- Box available for reuse
Exception Handling
Late Return Escalation
When cycle.cycle_state == 'ReturnWindowOpen' and return window exceeded:
- Day 1 overdue: Send reminder
- Day 3 overdue: Send escalation
- Day 7 overdue: Block next cycle commitment via
user.operational_state = 'HoldLogistics' - Day 14 overdue: Declare garments Lost, initiate settlement
Missing Delivery Signal
When cycle.cycle_state == 'OutboundInTransit' and expected delivery window exceeded:
- Enter bounded uncertainty state
- If user signals usage: Create
DeliveredByProxyevent, proceed - If carrier eventually confirms: Reconcile with actual timestamp
- If carrier confirms failure: Trigger reship or skip with compensation
Packing Variance at Ship Time
If box.has_variance == true at T-C005:
- Block shipment until variance resolved
- Resolution options:
- Correct pack: Update
actual_contents, re-verify - Commit to observed: Create
CompensatingAllocationevent, ship with actual
- Correct pack: Update
- Fit learning receives
actual_contents, notplanned_contents