class_name InventoryService extends RefCounted const InventoryReservationType = preload("res://scripts/inventory/inventory_reservation.gd") const SNAPSHOT_VERSION := 2 const LEGACY_SNAPSHOT_VERSION := 1 const DEFAULT_QUALITY := 100 const DEFAULT_UNIT_WEIGHT := 1 var current_minute := 0 var last_restore_error := "" var last_repair_report: Dictionary = {} var _inventories: Dictionary = {} ## Derived O(1) stack location index. It is rebuilt after restore and repaired ## lazily if diagnostic code mutates the public test seam `_inventories`. var _stack_owner_by_id: Dictionary = {} var _reservations: Dictionary = {} ## Persisted tombstones keep generated and explicit ids single-use even after ## their live stack/reservation has been removed. var _known_stack_ids: Dictionary = {} var _known_reservation_ids: Dictionary = {} var _idempotency_results: Dictionary = {} var _next_stack_sequence := 1 var _next_reservation_sequence := 1 var _atomic_batch_depth := 0 func add_stack( owner_inventory_id: StringName, item_id: StringName, quantity: int, quality: int = DEFAULT_QUALITY, expires_at: int = -1, created_at: int = -1, unit_weight: int = DEFAULT_UNIT_WEIGHT, stack_id: StringName = &"" ) -> Dictionary: if owner_inventory_id == &"" or item_id == &"": return _failure(&"INVALID_ID", "Inventory and item ids are required.") if quantity <= 0 or unit_weight <= 0: return _failure(&"INVALID_QUANTITY", "Quantity and unit weight must be positive.") var effective_stack_id := stack_id if effective_stack_id == &"": effective_stack_id = _next_stack_id() elif _known_stack_ids.has(effective_stack_id) or bool(_find_stack(effective_stack_id)[&"found"]): return _failure(&"DUPLICATE_STACK", "Stack id has already been used.") var stack := _make_stack( effective_stack_id, owner_inventory_id, item_id, quantity, quality, -1 if expires_at < 0 else expires_at, current_minute if created_at < 0 else created_at, unit_weight ) _insert_stack(stack) return _success({&"stack_id": effective_stack_id, &"quantity": quantity}) func reserve_item( owner_inventory_id: StringName, item_id: StringName, quantity: int, requester_id: StringName, purpose: StringName, created_at: int = -1, expires_at: int = -1, operation_id: StringName = &"" ) -> Dictionary: var minute := current_minute if created_at < 0 else created_at var normalized_expires_at := -1 if expires_at < 0 else expires_at var fingerprint := _canonical_fingerprint([ owner_inventory_id, item_id, quantity, requester_id, purpose, minute, normalized_expires_at, ]) var replay := _get_replay(&"reserve", operation_id, fingerprint) if not replay.is_empty(): return replay if owner_inventory_id == &"" or item_id == &"" or requester_id == &"": return _remember_failure( &"reserve", operation_id, fingerprint, &"INVALID_ID", "Inventory, item, and requester ids are required." ) if quantity <= 0: return _remember_failure( &"reserve", operation_id, fingerprint, &"INVALID_QUANTITY", "Reservation quantity must be positive." ) var candidates := _available_stacks(owner_inventory_id, item_id, minute) var total_available := 0 for stack: Dictionary in candidates: total_available += _available_in_stack(stack) if total_available < quantity: return _remember_failure( &"reserve", operation_id, fingerprint, &"INSUFFICIENT_AVAILABLE", "Available stock is below the requested quantity.", {&"requested": quantity, &"available": total_available} ) var remaining := quantity var allocations: Array[Dictionary] = [] for stack: Dictionary in candidates: if remaining <= 0: break var allocated := mini(remaining, _available_in_stack(stack)) if allocated > 0: allocations.append({&"stack_id": stack[&"stack_id"], &"quantity": allocated}) remaining -= allocated var reservation_ids: Array[StringName] = [] var stack_ids: Array[StringName] = [] for allocation: Dictionary in allocations: var stack_id: StringName = allocation[&"stack_id"] var location := _find_stack(stack_id) var stack: Dictionary = location[&"stack"] var allocated := int(allocation[&"quantity"]) stack[&"reserved_quantity"] = int(stack[&"reserved_quantity"]) + allocated _set_stack(location[&"owner_inventory_id"], stack) var reservation_id := _next_reservation_id() var reservation := InventoryReservationType.new().configure( reservation_id, stack_id, owner_inventory_id, item_id, allocated, requester_id, purpose, minute, normalized_expires_at ) _reservations[reservation_id] = reservation reservation_ids.append(reservation_id) stack_ids.append(stack_id) var result := _success({ &"reservation_ids": reservation_ids, &"stack_ids": stack_ids, &"quantity": quantity, }) return _remember(&"reserve", operation_id, result, fingerprint) func reserve( owner_inventory_id: StringName, item_id: StringName, quantity: int, requester_id: StringName, purpose: StringName, created_at: int = -1, expires_at: int = -1, operation_id: StringName = &"" ) -> Dictionary: return reserve_item( owner_inventory_id, item_id, quantity, requester_id, purpose, created_at, expires_at, operation_id ) func consume_reservation( reservation_id: StringName, operation_id: StringName = &"", absolute_minute: int = -1 ) -> Dictionary: var minute := current_minute if absolute_minute < 0 else absolute_minute var fingerprint := _canonical_fingerprint([reservation_id, minute]) var replay := _get_replay(&"consume", operation_id, fingerprint) if not replay.is_empty(): return replay if not _reservations.has(reservation_id): return _remember_failure( &"consume", operation_id, fingerprint, &"NOT_FOUND", "Reservation does not exist." ) var reservation: InventoryReservation = _reservations[reservation_id] if reservation.state == InventoryReservationType.State.CONSUMED: return _remember(&"consume", operation_id, _success({ &"reservation_id": reservation_id, &"quantity": 0, &"replay": true, &"code": &"ALREADY_CONSUMED", }), fingerprint) if not reservation.is_active(): return _remember_failure( &"consume", operation_id, fingerprint, &"RESERVATION_CLOSED", "Reservation is no longer active." ) var validation := _validate_active_reservation(reservation, minute) if not bool(validation[&"ok"]): return _remember(&"consume", operation_id, validation, fingerprint) var location: Dictionary = validation[&"location"] var stack: Dictionary = location[&"stack"] stack[&"quantity"] = int(stack[&"quantity"]) - reservation.quantity stack[&"reserved_quantity"] = int(stack[&"reserved_quantity"]) - reservation.quantity reservation.mark_consumed(minute) _set_or_remove_stack(location[&"owner_inventory_id"], stack) var result := _success({ &"reservation_id": reservation_id, &"stack_id": reservation.stack_id, &"quantity": reservation.quantity, }) return _remember(&"consume", operation_id, result, fingerprint) func consume_reservations( reservation_ids: Array[StringName], operation_id: StringName = &"", absolute_minute: int = -1 ) -> Dictionary: var minute := current_minute if absolute_minute < 0 else absolute_minute var fingerprint_ids := _sorted_names(reservation_ids) var fingerprint := _canonical_fingerprint([fingerprint_ids, minute]) var replay := _get_replay(&"consume_many", operation_id, fingerprint) if not replay.is_empty(): return replay var unique_ids := _unique_sorted_names(reservation_ids) if unique_ids.is_empty() or unique_ids.size() != reservation_ids.size(): return _remember_failure( &"consume_many", operation_id, fingerprint, &"INVALID_RESERVATIONS", "Reservation ids must be unique and non-empty." ) for reservation_id: StringName in unique_ids: if not _reservations.has(reservation_id): return _remember_failure( &"consume_many", operation_id, fingerprint, &"NOT_FOUND", "Reservation does not exist." ) var validation := _validate_active_reservation(_reservations[reservation_id], minute) if not bool(validation[&"ok"]): return _remember(&"consume_many", operation_id, validation, fingerprint) var capture := create_snapshot() var consumed := 0 for reservation_id: StringName in unique_ids: var result := consume_reservation(reservation_id, &"", minute) if not bool(result[&"ok"]): restore_snapshot(capture) return _remember_failure( &"consume_many", operation_id, fingerprint, &"ATOMIC_APPLY_FAILED", "Reservation group consumption was rolled back.", {&"cause": result} ) consumed += int(result.get(&"quantity", 0)) var final_result := _success({ &"reservation_ids": unique_ids, &"quantity": consumed, }) return _remember(&"consume_many", operation_id, final_result, fingerprint) func release_reservation( reservation_id: StringName, operation_id: StringName = &"", absolute_minute: int = -1 ) -> Dictionary: var minute := current_minute if absolute_minute < 0 else absolute_minute var fingerprint := _canonical_fingerprint([reservation_id, minute]) var replay := _get_replay(&"release", operation_id, fingerprint) if not replay.is_empty(): return replay if not _reservations.has(reservation_id): return _remember_failure( &"release", operation_id, fingerprint, &"NOT_FOUND", "Reservation does not exist." ) var reservation: InventoryReservation = _reservations[reservation_id] if reservation.state == InventoryReservationType.State.RELEASED or reservation.state == InventoryReservationType.State.EXPIRED: return _remember(&"release", operation_id, _success({ &"reservation_id": reservation_id, &"quantity": 0, &"replay": true, &"code": &"ALREADY_RELEASED", }), fingerprint) if not reservation.is_active(): return _remember_failure( &"release", operation_id, fingerprint, &"RESERVATION_CLOSED", "Consumed stock cannot be released." ) var location := _find_stack(reservation.stack_id) if not bool(location[&"found"]): reservation.mark_released(minute) var orphan_result := _success({ &"reservation_id": reservation_id, &"quantity": 0, &"orphan_repaired": true, }) return _remember(&"release", operation_id, orphan_result, fingerprint) var stack: Dictionary = location[&"stack"] stack[&"reserved_quantity"] = maxi( int(stack[&"reserved_quantity"]) - reservation.quantity, 0 ) reservation.mark_released(minute) _set_or_remove_stack(location[&"owner_inventory_id"], stack) var result := _success({ &"reservation_id": reservation_id, &"quantity": reservation.quantity, }) return _remember(&"release", operation_id, result, fingerprint) func transfer_available( source_inventory_id: StringName, destination_inventory_id: StringName, item_id: StringName, quantity: int, operation_id: StringName = &"", absolute_minute: int = -1 ) -> Dictionary: var minute := current_minute if absolute_minute < 0 else absolute_minute var fingerprint := _transfer_fingerprint( source_inventory_id, destination_inventory_id, item_id, quantity, minute ) var replay := _get_replay(&"transfer", operation_id, fingerprint) if not replay.is_empty(): return replay if source_inventory_id == &"" or destination_inventory_id == &"" or item_id == &"": return _remember_failure(&"transfer", operation_id, fingerprint, &"INVALID_ID", "Transfer ids are required.") if source_inventory_id == destination_inventory_id: return _remember_failure( &"transfer", operation_id, fingerprint, &"SAME_INVENTORY", "Source and destination must differ." ) if quantity <= 0: return _remember_failure( &"transfer", operation_id, fingerprint, &"INVALID_QUANTITY", "Transfer quantity must be positive." ) var candidates := _available_stacks(source_inventory_id, item_id, minute) var available := 0 for stack: Dictionary in candidates: available += _available_in_stack(stack) if available < quantity: return _remember_failure( &"transfer", operation_id, fingerprint, &"INSUFFICIENT_AVAILABLE", "Available stock is below the transfer quantity.", {&"requested": quantity, &"available": available} ) var capture := {} if _atomic_batch_depth > 0 else create_snapshot() var remaining := quantity var destination_stack_ids: Array[StringName] = [] for candidate: Dictionary in candidates: if remaining <= 0: break var moved := mini(remaining, _available_in_stack(candidate)) if moved <= 0: continue var location := _find_stack(candidate[&"stack_id"]) if not bool(location[&"found"]): _restore_standalone_capture(capture) return _remember_failure( &"transfer", operation_id, fingerprint, &"ATOMIC_APPLY_FAILED", "Transfer was rolled back." ) var source_stack: Dictionary = location[&"stack"] source_stack[&"quantity"] = int(source_stack[&"quantity"]) - moved _set_or_remove_stack(source_inventory_id, source_stack) var destination_stack_id := _next_stack_id() _insert_stack(_make_stack( destination_stack_id, destination_inventory_id, item_id, moved, int(candidate[&"quality"]), int(candidate[&"expires_at"]), int(candidate[&"created_at"]), int(candidate[&"unit_weight"]) )) destination_stack_ids.append(destination_stack_id) remaining -= moved if remaining != 0 or (_atomic_batch_depth == 0 and not validate_invariants()[&"ok"]): _restore_standalone_capture(capture) return _remember_failure( &"transfer", operation_id, fingerprint, &"ATOMIC_APPLY_FAILED", "Transfer invariant failed and was rolled back." ) var result := _success({ &"source_inventory_id": source_inventory_id, &"destination_inventory_id": destination_inventory_id, &"item_id": item_id, &"quantity": quantity, &"destination_stack_ids": destination_stack_ids, }) return _remember(&"transfer", operation_id, result, fingerprint) func transfer( source_inventory_id: StringName, destination_inventory_id: StringName, item_id: StringName, quantity: int, operation_id: StringName = &"", absolute_minute: int = -1 ) -> Dictionary: return transfer_available( source_inventory_id, destination_inventory_id, item_id, quantity, operation_id, absolute_minute ) ## Applies the same ordered transfers as repeated transfer_available calls, but ## captures and validates state once for the whole atomic group. This keeps a ## large logistics wave deterministic without doing a full-world snapshot for ## every item line. func transfer_available_batch( transfers: Array[Dictionary], absolute_minute: int = -1 ) -> Dictionary: if transfers.is_empty() or _atomic_batch_depth != 0: return _failure(&"INVALID_BATCH", "A non-empty top-level transfer batch is required.") var minute := current_minute if absolute_minute < 0 else absolute_minute var preflight := _preflight_available_batch(transfers, minute) if not bool(preflight[&"ok"]): return preflight var capture := create_snapshot() var results: Array[Dictionary] = [] _atomic_batch_depth = 1 for index: int in range(transfers.size()): var entry: Dictionary = transfers[index] var result := transfer_available( StringName(entry.get(&"source_inventory_id", entry.get("source_inventory_id", ""))), StringName(entry.get(&"destination_inventory_id", entry.get("destination_inventory_id", ""))), StringName(entry.get(&"item_id", entry.get("item_id", ""))), int(entry.get(&"quantity", entry.get("quantity", 0))), StringName(entry.get(&"operation_id", entry.get("operation_id", ""))), minute ) if not bool(result.get(&"ok", false)): _atomic_batch_depth = 0 restore_snapshot(capture) return _failure( &"ATOMIC_BATCH_FAILED", "Available-stock transfer batch was rolled back.", {&"failed_index": index, &"cause": result} ) results.append(result) _atomic_batch_depth = 0 if not validate_invariants()[&"ok"]: restore_snapshot(capture) return _failure( &"ATOMIC_BATCH_FAILED", "Available-stock transfer batch invariant failed and was rolled back." ) return _success({&"results": results, &"transfer_count": results.size()}) func transfer_reserved( reservation_ids: Array[StringName], destination_inventory_id: StringName, operation_id: StringName = &"", absolute_minute: int = -1 ) -> Dictionary: var minute := current_minute if absolute_minute < 0 else absolute_minute var fingerprint_ids := _sorted_names(reservation_ids) var fingerprint := _reserved_transfer_fingerprint( fingerprint_ids, destination_inventory_id, minute ) var replay := _get_replay(&"transfer_reserved", operation_id, fingerprint) if not replay.is_empty(): return replay var ids := _unique_sorted_names(reservation_ids) if ids.is_empty() or ids.size() != reservation_ids.size() or destination_inventory_id == &"": return _remember_failure( &"transfer_reserved", operation_id, fingerprint, &"INVALID_ARGUMENT", "Reservation ids must be unique and destination is required." ) var source_inventory_id: StringName = &"" for reservation_id: StringName in ids: if not _reservations.has(reservation_id): return _remember_failure( &"transfer_reserved", operation_id, fingerprint, &"NOT_FOUND", "Reservation does not exist." ) var reservation: InventoryReservation = _reservations[reservation_id] var validation := _validate_active_reservation(reservation, minute) if not bool(validation[&"ok"]): return _remember(&"transfer_reserved", operation_id, validation, fingerprint) if source_inventory_id == &"": source_inventory_id = reservation.owner_inventory_id elif source_inventory_id != reservation.owner_inventory_id: return _remember_failure( &"transfer_reserved", operation_id, fingerprint, &"MULTIPLE_SOURCES", "One transfer cannot mix source inventories." ) if source_inventory_id == destination_inventory_id: return _remember_failure( &"transfer_reserved", operation_id, fingerprint, &"SAME_INVENTORY", "Source and destination must differ." ) var capture := {} if _atomic_batch_depth > 0 else create_snapshot() var moved_quantity := 0 var moved_weight := 0 var destination_stack_ids: Array[StringName] = [] for reservation_id: StringName in ids: var reservation: InventoryReservation = _reservations[reservation_id] var location := _find_stack(reservation.stack_id) if not bool(location[&"found"]): _restore_standalone_capture(capture) return _remember_failure( &"transfer_reserved", operation_id, fingerprint, &"ATOMIC_APPLY_FAILED", "Transfer was rolled back." ) var source_stack: Dictionary = location[&"stack"] source_stack[&"quantity"] = int(source_stack[&"quantity"]) - reservation.quantity source_stack[&"reserved_quantity"] = int(source_stack[&"reserved_quantity"]) - reservation.quantity _set_or_remove_stack(source_inventory_id, source_stack) reservation.mark_consumed(minute) var destination_stack_id := _next_stack_id() _insert_stack(_make_stack( destination_stack_id, destination_inventory_id, reservation.item_id, reservation.quantity, int(source_stack[&"quality"]), int(source_stack[&"expires_at"]), int(source_stack[&"created_at"]), int(source_stack[&"unit_weight"]) )) destination_stack_ids.append(destination_stack_id) moved_quantity += reservation.quantity moved_weight += reservation.quantity * int(source_stack[&"unit_weight"]) if _atomic_batch_depth == 0 and not validate_invariants()[&"ok"]: _restore_standalone_capture(capture) return _remember_failure( &"transfer_reserved", operation_id, fingerprint, &"ATOMIC_APPLY_FAILED", "Transfer invariant failed and was rolled back." ) var result := _success({ &"source_inventory_id": source_inventory_id, &"destination_inventory_id": destination_inventory_id, &"reservation_ids": ids, &"destination_stack_ids": destination_stack_ids, &"quantity": moved_quantity, &"weight": moved_weight, }) return _remember(&"transfer_reserved", operation_id, result, fingerprint) ## Atomic ordered counterpart to repeated transfer_reserved calls. Individual ## operation ids retain their normal replay semantics and snapshots remain ## byte-structure equivalent to the sequential path. func transfer_reserved_batch( transfers: Array[Dictionary], absolute_minute: int = -1 ) -> Dictionary: if transfers.is_empty() or _atomic_batch_depth != 0: return _failure(&"INVALID_BATCH", "A non-empty top-level reserved transfer batch is required.") var minute := current_minute if absolute_minute < 0 else absolute_minute var preflight := _preflight_reserved_batch(transfers, minute) if not bool(preflight[&"ok"]): return preflight var capture := create_snapshot() var results: Array[Dictionary] = [] _atomic_batch_depth = 1 for index: int in range(transfers.size()): var entry: Dictionary = transfers[index] var reservation_ids: Array[StringName] = [] var raw_ids: Variant = entry.get(&"reservation_ids", entry.get("reservation_ids", [])) if raw_ids is Array: for raw_id: Variant in raw_ids: reservation_ids.append(StringName(raw_id)) var result := transfer_reserved( reservation_ids, StringName(entry.get(&"destination_inventory_id", entry.get("destination_inventory_id", ""))), StringName(entry.get(&"operation_id", entry.get("operation_id", ""))), minute ) if not bool(result.get(&"ok", false)): _atomic_batch_depth = 0 restore_snapshot(capture) return _failure( &"ATOMIC_BATCH_FAILED", "Reserved-stock transfer batch was rolled back.", {&"failed_index": index, &"cause": result} ) results.append(result) _atomic_batch_depth = 0 if not validate_invariants()[&"ok"]: restore_snapshot(capture) return _failure( &"ATOMIC_BATCH_FAILED", "Reserved-stock transfer batch invariant failed and was rolled back." ) return _success({&"results": results, &"transfer_count": results.size()}) func process_expired(absolute_minute: int, waste_inventory_id: StringName = &"") -> Dictionary: current_minute = maxi(current_minute, absolute_minute) var expired_reservations: Array[StringName] = [] for reservation_id: StringName in _sorted_names(_reservations.keys()): var reservation: InventoryReservation = _reservations[reservation_id] if not reservation.is_active() or not reservation.is_expired(absolute_minute): continue var location := _find_stack(reservation.stack_id) if bool(location[&"found"]): var stack: Dictionary = location[&"stack"] stack[&"reserved_quantity"] = maxi( int(stack[&"reserved_quantity"]) - reservation.quantity, 0 ) _set_or_remove_stack(location[&"owner_inventory_id"], stack) reservation.mark_expired(absolute_minute) expired_reservations.append(reservation_id) var discarded: Array[Dictionary] = [] var waste_lines: Array[Dictionary] = [] for owner_inventory_id: StringName in _sorted_names(_inventories.keys()): var stack_ids := _sorted_names((_inventories[owner_inventory_id] as Dictionary).keys()) for stack_id: StringName in stack_ids: var location := _find_stack(stack_id) if not bool(location[&"found"]): continue var stack: Dictionary = location[&"stack"] if not _stack_is_expired(stack, absolute_minute): continue var disposable := _available_in_stack(stack) if disposable <= 0: continue stack[&"quantity"] = int(stack[&"quantity"]) - disposable _set_or_remove_stack(owner_inventory_id, stack) var line := { &"owner_inventory_id": owner_inventory_id, &"stack_id": stack_id, &"item_id": stack[&"item_id"], &"quantity": disposable, } discarded.append(line) if waste_inventory_id != &"": waste_lines.append(line) for line: Dictionary in waste_lines: _insert_stack(_make_stack( _next_stack_id(), waste_inventory_id, StringName("waste/%s" % String(line[&"item_id"])), int(line[&"quantity"]), 0, -1, absolute_minute, DEFAULT_UNIT_WEIGHT )) return { &"ok": true, &"expired_reservation_ids": expired_reservations, &"discarded": discarded, &"discarded_quantity": _sum_lines(discarded), } func is_stack_expired(stack_id: StringName, absolute_minute: int = -1) -> bool: var location := _find_stack(stack_id) if not bool(location[&"found"]): return false return _stack_is_expired( location[&"stack"], current_minute if absolute_minute < 0 else absolute_minute ) func available_quantity( owner_inventory_id: StringName, item_id: StringName, absolute_minute: int = -1 ) -> int: var minute := current_minute if absolute_minute < 0 else absolute_minute var total := 0 for stack: Dictionary in _available_stacks(owner_inventory_id, item_id, minute): total += _available_in_stack(stack) return total func total_quantity(owner_inventory_id: StringName, item_id: StringName) -> int: var total := 0 if not _inventories.has(owner_inventory_id): return total for stack: Dictionary in (_inventories[owner_inventory_id] as Dictionary).values(): if stack[&"item_id"] == item_id: total += int(stack[&"quantity"]) return total func get_stack(stack_id: StringName) -> Dictionary: var location := _find_stack(stack_id) if not bool(location[&"found"]): return {} return (location[&"stack"] as Dictionary).duplicate(true) func get_stacks(owner_inventory_id: StringName = &"") -> Array[Dictionary]: var result: Array[Dictionary] = [] var owners := _sorted_names(_inventories.keys()) if owner_inventory_id == &"" else [owner_inventory_id] for owner: StringName in owners: if not _inventories.has(owner): continue for stack_id: StringName in _sorted_names((_inventories[owner] as Dictionary).keys()): result.append(((_inventories[owner] as Dictionary)[stack_id] as Dictionary).duplicate(true)) return result func get_reservation(reservation_id: StringName) -> InventoryReservation: return _reservations.get(reservation_id) as InventoryReservation func get_reservations(active_only := false) -> Array[InventoryReservation]: var result: Array[InventoryReservation] = [] for reservation_id: StringName in _sorted_names(_reservations.keys()): var reservation: InventoryReservation = _reservations[reservation_id] if not active_only or reservation.is_active(): result.append(reservation) return result func audit_and_repair_reservations(absolute_minute: int = -1) -> Dictionary: var minute := current_minute if absolute_minute < 0 else absolute_minute var report := { &"ok": true, &"scanned_reservations": _reservations.size(), &"orphan_count": 0, &"capacity_violation_count": 0, &"released_reservation_ids": [], &"corrected_stacks": [], } var reservations_by_stack: Dictionary = {} for reservation_id: StringName in _sorted_names(_reservations.keys()): var reservation: InventoryReservation = _reservations[reservation_id] if not reservation.is_active(): continue var location := _find_stack(reservation.stack_id) var orphan: bool = ( not bool(location[&"found"]) or location[&"owner_inventory_id"] != reservation.owner_inventory_id or (bool(location[&"found"]) and location[&"stack"][&"item_id"] != reservation.item_id) or reservation.quantity <= 0 ) if orphan: reservation.mark_released(minute) report[&"orphan_count"] = int(report[&"orphan_count"]) + 1 report[&"released_reservation_ids"].append(reservation_id) continue if not reservations_by_stack.has(reservation.stack_id): reservations_by_stack[reservation.stack_id] = [] (reservations_by_stack[reservation.stack_id] as Array).append(reservation_id) for stack_id: StringName in _sorted_names(reservations_by_stack.keys()): var location := _find_stack(stack_id) if not bool(location[&"found"]): continue var stack: Dictionary = location[&"stack"] var reservation_ids: Array = reservations_by_stack[stack_id] var expected := 0 for reservation_id: StringName in reservation_ids: expected += (_reservations[reservation_id] as InventoryReservation).quantity if expected > int(stack[&"quantity"]): reservation_ids.sort_custom(func(a: StringName, b: StringName) -> bool: return String(a) > String(b)) for reservation_id: StringName in reservation_ids: if expected <= int(stack[&"quantity"]): break var reservation: InventoryReservation = _reservations[reservation_id] expected -= reservation.quantity reservation.mark_released(minute) report[&"capacity_violation_count"] = int(report[&"capacity_violation_count"]) + 1 report[&"released_reservation_ids"].append(reservation_id) var expected_by_stack: Dictionary = {} for reservation_id: StringName in _sorted_names(_reservations.keys()): var reservation: InventoryReservation = _reservations[reservation_id] if reservation.is_active(): expected_by_stack[reservation.stack_id] = int(expected_by_stack.get(reservation.stack_id, 0)) + reservation.quantity for owner_inventory_id: StringName in _sorted_names(_inventories.keys()): for stack_id: StringName in _sorted_names((_inventories[owner_inventory_id] as Dictionary).keys()): var stack: Dictionary = (_inventories[owner_inventory_id] as Dictionary)[stack_id] var expected := int(expected_by_stack.get(stack_id, 0)) if int(stack[&"reserved_quantity"]) == expected: continue report[&"corrected_stacks"].append({ &"stack_id": stack_id, &"before": int(stack[&"reserved_quantity"]), &"after": expected, }) stack[&"reserved_quantity"] = expected _set_stack(owner_inventory_id, stack) last_repair_report = report.duplicate(true) return report func repair_orphan_reservations(absolute_minute: int = -1) -> Dictionary: return audit_and_repair_reservations(absolute_minute) func validate_invariants() -> Dictionary: var error := _validate_state(_inventories, _reservations) return { &"ok": error.is_empty(), &"error": error, } func create_snapshot() -> Dictionary: var inventories: Array[Dictionary] = [] for owner_inventory_id: StringName in _sorted_names(_inventories.keys()): var stacks: Array[Dictionary] = [] for stack_id: StringName in _sorted_names((_inventories[owner_inventory_id] as Dictionary).keys()): var stack: Dictionary = (_inventories[owner_inventory_id] as Dictionary)[stack_id] stacks.append(_serialize_stack(stack)) inventories.append({ &"owner_inventory_id": String(owner_inventory_id), &"stacks": stacks, }) var reservations: Array[Dictionary] = [] for reservation_id: StringName in _sorted_names(_reservations.keys()): reservations.append((_reservations[reservation_id] as InventoryReservation).create_snapshot()) var idempotency_results: Array[Dictionary] = [] var operation_keys := _idempotency_results.keys() operation_keys.sort_custom(func(a: Variant, b: Variant) -> bool: return String(a) < String(b)) for operation_key: Variant in operation_keys: var record: Dictionary = _idempotency_results[operation_key] var serialized_record := { &"operation_id": String(operation_key), &"action": String(record[&"action"]), &"input_fingerprint": String(record[&"input_fingerprint"]), &"result": (record[&"result"] as Dictionary).duplicate(true), } if bool(record.get(&"legacy_unbound", false)): serialized_record[&"legacy_unbound"] = true idempotency_results.append(serialized_record) return { &"version": SNAPSHOT_VERSION, &"current_minute": current_minute, &"next_stack_sequence": _next_stack_sequence, &"next_reservation_sequence": _next_reservation_sequence, &"known_stack_ids": _string_array(_sorted_names(_known_stack_ids.keys())), &"known_reservation_ids": _string_array(_sorted_names(_known_reservation_ids.keys())), &"inventories": inventories, &"reservations": reservations, &"idempotency_results": idempotency_results, } func restore_snapshot(snapshot: Dictionary) -> bool: last_restore_error = "" var version := int(snapshot.get(&"version", -1)) if version != SNAPSHOT_VERSION and version != LEGACY_SNAPSHOT_VERSION: last_restore_error = "Unsupported inventory snapshot version." return false var next_current_minute := int(snapshot.get(&"current_minute", -1)) if next_current_minute < 0: last_restore_error = "Inventory snapshot current minute is invalid." return false if not snapshot.get(&"inventories", null) is Array or not snapshot.get(&"reservations", null) is Array: last_restore_error = "Inventory snapshot collections are malformed." return false var next_inventories: Dictionary = {} var live_stack_ids: Dictionary = {} for raw_inventory: Variant in snapshot[&"inventories"]: if not raw_inventory is Dictionary: last_restore_error = "Inventory entry must be a Dictionary." return false var owner_inventory_id := StringName(raw_inventory.get(&"owner_inventory_id", "")) var raw_stacks: Variant = raw_inventory.get(&"stacks", null) if owner_inventory_id == &"" or not raw_stacks is Array or next_inventories.has(owner_inventory_id): last_restore_error = "Inventory owner entry is invalid or duplicated." return false var stacks: Dictionary = {} for raw_stack: Variant in raw_stacks: var stack := _parse_stack(raw_stack, owner_inventory_id) if stack.is_empty(): last_restore_error = "Inventory stack entry is invalid." return false var stack_id: StringName = stack[&"stack_id"] if live_stack_ids.has(stack_id): last_restore_error = "Inventory stack id is duplicated." return false live_stack_ids[stack_id] = true stacks[stack_id] = stack next_inventories[owner_inventory_id] = stacks var next_reservations: Dictionary = {} var live_reservation_ids: Dictionary = {} for raw_reservation: Variant in snapshot[&"reservations"]: if not raw_reservation is Dictionary: last_restore_error = "Reservation entry must be a Dictionary." return false if not _reservation_timestamps_are_valid(raw_reservation): last_restore_error = "Reservation timestamps are invalid." return false var reservation := InventoryReservationType.from_snapshot(raw_reservation) if reservation == null or next_reservations.has(reservation.reservation_id): last_restore_error = "Reservation entry is invalid or duplicated." return false next_reservations[reservation.reservation_id] = reservation live_reservation_ids[reservation.reservation_id] = true var next_known_stack_ids: Dictionary = {} var next_known_reservation_ids: Dictionary = {} if version == SNAPSHOT_VERSION: var raw_known_stack_ids: Variant = snapshot.get(&"known_stack_ids", null) var raw_known_reservation_ids: Variant = snapshot.get(&"known_reservation_ids", null) if not raw_known_stack_ids is Array or not raw_known_reservation_ids is Array: last_restore_error = "Inventory known-id collections are malformed." return false for raw_id: Variant in raw_known_stack_ids: var known_id := StringName(raw_id) if known_id == &"" or next_known_stack_ids.has(known_id): last_restore_error = "Known stack id is invalid or duplicated." return false next_known_stack_ids[known_id] = true for raw_id: Variant in raw_known_reservation_ids: var known_id := StringName(raw_id) if known_id == &"" or next_known_reservation_ids.has(known_id): last_restore_error = "Known reservation id is invalid or duplicated." return false next_known_reservation_ids[known_id] = true else: next_known_stack_ids = live_stack_ids.duplicate(true) next_known_reservation_ids = live_reservation_ids.duplicate(true) for stack_id: Variant in live_stack_ids: if not next_known_stack_ids.has(stack_id): last_restore_error = "Live stack id is missing from known-id history." return false for reservation_id: Variant in live_reservation_ids: if not next_known_reservation_ids.has(reservation_id): last_restore_error = "Live reservation id is missing from known-id history." return false var invariant_error := _validate_state(next_inventories, next_reservations) if not invariant_error.is_empty(): last_restore_error = invariant_error return false var next_idempotency: Dictionary = {} var raw_results: Variant = snapshot.get(&"idempotency_results", []) if not raw_results is Array: last_restore_error = "Idempotency result collection is malformed." return false for raw_result: Variant in raw_results: if not raw_result is Dictionary or not raw_result.get(&"result", null) is Dictionary: last_restore_error = "Idempotency result entry is malformed." return false var operation_id := StringName(raw_result.get(&"operation_id", "")) var action := StringName(raw_result.get(&"action", "")) var input_fingerprint := String(raw_result.get(&"input_fingerprint", "")) var legacy_unbound := bool(raw_result.get(&"legacy_unbound", false)) if version == LEGACY_SNAPSHOT_VERSION: var legacy_key := String(raw_result.get(&"operation_key", "")) var separator := legacy_key.find("|") if separator <= 0 or separator >= legacy_key.length() - 1: last_restore_error = "Legacy idempotency result key is invalid." return false action = StringName(legacy_key.substr(0, separator)) operation_id = StringName(legacy_key.substr(separator + 1)) input_fingerprint = "" legacy_unbound = true if ( operation_id == &"" or action == &"" or (input_fingerprint.is_empty() and not legacy_unbound) ): last_restore_error = "Idempotency result key or binding is invalid or duplicated." return false if next_idempotency.has(operation_id): if version != LEGACY_SNAPSHOT_VERSION: last_restore_error = "Idempotency operation id is duplicated." return false # V1 namespaced ids by action, so two otherwise-valid legacy records can # share one operation id. Preserve both payloads but fail closed on any # replay until the caller issues a fresh operation id under V2 semantics. var prior_record: Dictionary = next_idempotency[operation_id] var legacy_results: Array[Dictionary] = [] if prior_record[&"result"].has(&"legacy_ambiguous_results"): legacy_results.assign(prior_record[&"result"][&"legacy_ambiguous_results"]) else: legacy_results.append({ &"action": String(prior_record[&"action"]), &"result": (prior_record[&"result"] as Dictionary).duplicate(true), }) legacy_results.append({ &"action": String(action), &"result": (raw_result[&"result"] as Dictionary).duplicate(true), }) next_idempotency[operation_id] = { &"action": &"__legacy_ambiguous__", &"input_fingerprint": "", &"legacy_unbound": true, &"result": {&"legacy_ambiguous_results": legacy_results}, } continue next_idempotency[operation_id] = { &"action": action, &"input_fingerprint": input_fingerprint, &"legacy_unbound": legacy_unbound, &"result": (raw_result[&"result"] as Dictionary).duplicate(true), } var next_stack_sequence := int(snapshot.get(&"next_stack_sequence", 0)) var next_reservation_sequence := int(snapshot.get(&"next_reservation_sequence", 0)) if next_stack_sequence < 1 or next_reservation_sequence < 1: last_restore_error = "Inventory snapshot sequence is invalid." return false if version == LEGACY_SNAPSHOT_VERSION: # V1 only persisted the next counters. Reconstruct the consumed canonical # ranges once so the V2 migration cannot later reuse a deleted legacy id. for sequence: int in range(1, next_stack_sequence): next_known_stack_ids[StringName("stack_%08d" % sequence)] = true for sequence: int in range(1, next_reservation_sequence): next_known_reservation_ids[StringName("reservation_%08d" % sequence)] = true if ( next_stack_sequence <= _max_canonical_sequence(next_known_stack_ids.keys(), "stack_") or next_reservation_sequence <= _max_canonical_sequence(next_known_reservation_ids.keys(), "reservation_") ): last_restore_error = "Inventory snapshot sequence would reuse a known canonical id." return false _inventories = next_inventories _rebuild_stack_location_index() _reservations = next_reservations _known_stack_ids = next_known_stack_ids _known_reservation_ids = next_known_reservation_ids _idempotency_results = next_idempotency _next_stack_sequence = next_stack_sequence _next_reservation_sequence = next_reservation_sequence current_minute = next_current_minute last_restore_error = "" return true func _validate_active_reservation(reservation: InventoryReservation, absolute_minute: int) -> Dictionary: if reservation.is_expired(absolute_minute): return _failure(&"RESERVATION_EXPIRED", "Reservation has expired.") var location := _find_stack(reservation.stack_id) if not bool(location[&"found"]): return _failure(&"ORPHAN_RESERVATION", "Reserved stack is missing.") var stack: Dictionary = location[&"stack"] if location[&"owner_inventory_id"] != reservation.owner_inventory_id: return _failure(&"ORPHAN_RESERVATION", "Reserved stack belongs to another inventory.") if _stack_is_expired(stack, absolute_minute): return _failure(&"STACK_EXPIRED", "Reserved stock has expired.") if int(stack[&"quantity"]) < reservation.quantity or int(stack[&"reserved_quantity"]) < reservation.quantity: return _failure(&"INVARIANT_VIOLATION", "Reserved stack no longer contains the reserved quantity.") return {&"ok": true, &"location": location} func _available_stacks(owner_inventory_id: StringName, item_id: StringName, absolute_minute: int) -> Array[Dictionary]: var result: Array[Dictionary] = [] if not _inventories.has(owner_inventory_id): return result for stack: Dictionary in (_inventories[owner_inventory_id] as Dictionary).values(): if stack[&"item_id"] == item_id and not _stack_is_expired(stack, absolute_minute) and _available_in_stack(stack) > 0: result.append(stack.duplicate(true)) result.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: var a_expiry := int(a[&"expires_at"]) var b_expiry := int(b[&"expires_at"]) var a_order := 9223372036854775807 if a_expiry < 0 else a_expiry var b_order := 9223372036854775807 if b_expiry < 0 else b_expiry if a_order != b_order: return a_order < b_order if int(a[&"quality"]) != int(b[&"quality"]): return int(a[&"quality"]) > int(b[&"quality"]) return String(a[&"stack_id"]) < String(b[&"stack_id"]) ) return result func _find_stack(stack_id: StringName) -> Dictionary: if _stack_owner_by_id.has(stack_id): var indexed_owner_id: StringName = _stack_owner_by_id[stack_id] if _inventories.has(indexed_owner_id): var indexed_stacks: Dictionary = _inventories[indexed_owner_id] if indexed_stacks.has(stack_id): return { &"found": true, &"owner_inventory_id": indexed_owner_id, &"stack": (indexed_stacks[stack_id] as Dictionary).duplicate(true), } # A stale derived entry must never mask an externally corrupted state. _stack_owner_by_id.erase(stack_id) for owner_inventory_id: Variant in _inventories: var stacks: Dictionary = _inventories[owner_inventory_id] if stacks.has(stack_id): _stack_owner_by_id[stack_id] = StringName(owner_inventory_id) return { &"found": true, &"owner_inventory_id": StringName(owner_inventory_id), &"stack": (stacks[stack_id] as Dictionary).duplicate(true), } return {&"found": false, &"owner_inventory_id": &"", &"stack": {}} func _insert_stack(stack: Dictionary) -> void: var owner_inventory_id: StringName = stack[&"owner_inventory_id"] var stack_id: StringName = stack[&"stack_id"] if not _inventories.has(owner_inventory_id): _inventories[owner_inventory_id] = {} var stacks: Dictionary = _inventories[owner_inventory_id] stacks[stack_id] = stack.duplicate(true) _inventories[owner_inventory_id] = stacks _stack_owner_by_id[stack_id] = owner_inventory_id _known_stack_ids[stack_id] = true var canonical_sequence := _canonical_sequence(stack_id, "stack_") if canonical_sequence >= _next_stack_sequence: _next_stack_sequence = canonical_sequence + 1 func _set_stack(owner_inventory_id: StringName, stack: Dictionary) -> void: if not _inventories.has(owner_inventory_id): _inventories[owner_inventory_id] = {} var stacks: Dictionary = _inventories[owner_inventory_id] stacks[stack[&"stack_id"]] = stack.duplicate(true) _inventories[owner_inventory_id] = stacks _stack_owner_by_id[stack[&"stack_id"]] = owner_inventory_id _known_stack_ids[stack[&"stack_id"]] = true func _set_or_remove_stack(owner_inventory_id: StringName, stack: Dictionary) -> void: if int(stack[&"quantity"]) > 0: _set_stack(owner_inventory_id, stack) return if not _inventories.has(owner_inventory_id): return var stacks: Dictionary = _inventories[owner_inventory_id] stacks.erase(stack[&"stack_id"]) _stack_owner_by_id.erase(stack[&"stack_id"]) if stacks.is_empty(): _inventories.erase(owner_inventory_id) else: _inventories[owner_inventory_id] = stacks func _restore_standalone_capture(capture: Dictionary) -> void: if _atomic_batch_depth == 0 and not capture.is_empty(): restore_snapshot(capture) func _rebuild_stack_location_index() -> void: _stack_owner_by_id.clear() for owner_inventory_id: Variant in _inventories: var stacks: Dictionary = _inventories[owner_inventory_id] for stack_id: Variant in stacks: _stack_owner_by_id[StringName(stack_id)] = StringName(owner_inventory_id) func _next_stack_id() -> StringName: while true: var value := StringName("stack_%08d" % _next_stack_sequence) _next_stack_sequence += 1 # Generated ids are fully represented by the maintained index. Falling # back to an all-inventory diagnostic scan here made every destination # insertion O(owner count) during large delivery waves. if not _known_stack_ids.has(value): _known_stack_ids[value] = true return value return &"" func _next_reservation_id() -> StringName: while true: var value := StringName("reservation_%08d" % _next_reservation_sequence) _next_reservation_sequence += 1 if not _known_reservation_ids.has(value): _known_reservation_ids[value] = true return value return &"" func _get_replay( action: StringName, operation_id: StringName, input_fingerprint: String, bind_legacy: bool = true ) -> Dictionary: if operation_id == &"": return {} if not _idempotency_results.has(operation_id): return {} var record: Dictionary = _idempotency_results[operation_id] if ( StringName(record[&"action"]) != action or ( not bool(record.get(&"legacy_unbound", false)) and String(record[&"input_fingerprint"]) != input_fingerprint ) ): return _failure( &"IDEMPOTENCY_CONFLICT", "Operation id is already bound to a different action or normalized input.", {&"operation_id": operation_id} ) if bool(record.get(&"legacy_unbound", false)) and bind_legacy: # Version-1 snapshots did not retain request fingerprints. Preserve their # first valid replay, then permanently bind that id to the observed input. record[&"input_fingerprint"] = input_fingerprint record[&"legacy_unbound"] = false _idempotency_results[operation_id] = record var result: Dictionary = (record[&"result"] as Dictionary).duplicate(true) result[&"replay"] = true return result func _remember( action: StringName, operation_id: StringName, result: Dictionary, input_fingerprint: String ) -> Dictionary: if operation_id != &"": if _idempotency_results.has(operation_id): return _get_replay(action, operation_id, input_fingerprint) _idempotency_results[operation_id] = { &"action": action, &"input_fingerprint": input_fingerprint, &"legacy_unbound": false, &"result": result.duplicate(true), } return result func _remember_failure( action: StringName, operation_id: StringName, input_fingerprint: String, code: StringName, message: String, details: Dictionary = {} ) -> Dictionary: return _remember(action, operation_id, _failure(code, message, details), input_fingerprint) func _preflight_available_batch(transfers: Array[Dictionary], minute: int) -> Dictionary: var seen_operation_ids: Dictionary = {} for index: int in range(transfers.size()): var entry: Dictionary = transfers[index] var operation_id := StringName(entry.get(&"operation_id", entry.get("operation_id", ""))) var fingerprint := _transfer_fingerprint( StringName(entry.get(&"source_inventory_id", entry.get("source_inventory_id", ""))), StringName(entry.get(&"destination_inventory_id", entry.get("destination_inventory_id", ""))), StringName(entry.get(&"item_id", entry.get("item_id", ""))), int(entry.get(&"quantity", entry.get("quantity", 0))), minute ) var validation := _preflight_operation_id( &"transfer", operation_id, fingerprint, seen_operation_ids, index ) if not bool(validation[&"ok"]): return validation return {&"ok": true} func _preflight_reserved_batch(transfers: Array[Dictionary], minute: int) -> Dictionary: var seen_operation_ids: Dictionary = {} for index: int in range(transfers.size()): var entry: Dictionary = transfers[index] var reservation_ids: Array[StringName] = [] var raw_ids: Variant = entry.get(&"reservation_ids", entry.get("reservation_ids", [])) if raw_ids is Array: for raw_id: Variant in raw_ids: reservation_ids.append(StringName(raw_id)) var operation_id := StringName(entry.get(&"operation_id", entry.get("operation_id", ""))) var fingerprint := _reserved_transfer_fingerprint( _sorted_names(reservation_ids), StringName(entry.get(&"destination_inventory_id", entry.get("destination_inventory_id", ""))), minute ) var validation := _preflight_operation_id( &"transfer_reserved", operation_id, fingerprint, seen_operation_ids, index ) if not bool(validation[&"ok"]): return validation return {&"ok": true} func _preflight_operation_id( action: StringName, operation_id: StringName, input_fingerprint: String, seen_operation_ids: Dictionary, index: int ) -> Dictionary: if operation_id == &"": return {&"ok": true} if seen_operation_ids.has(operation_id): return _failure( &"IDEMPOTENCY_CONFLICT", "A non-empty operation id may occur only once in an atomic batch.", {&"operation_id": operation_id, &"failed_index": index} ) seen_operation_ids[operation_id] = true var existing := _get_replay(action, operation_id, input_fingerprint, false) if ( not existing.is_empty() and not bool(existing.get(&"ok", false)) and existing.get(&"code", &"") == &"IDEMPOTENCY_CONFLICT" ): existing[&"failed_index"] = index return existing return {&"ok": true} static func _transfer_fingerprint( source_inventory_id: StringName, destination_inventory_id: StringName, item_id: StringName, quantity: int, minute: int ) -> String: return _canonical_fingerprint([ source_inventory_id, destination_inventory_id, item_id, quantity, minute, ]) static func _reserved_transfer_fingerprint( reservation_ids: Array[StringName], destination_inventory_id: StringName, minute: int ) -> String: return _canonical_fingerprint([reservation_ids, destination_inventory_id, minute]) static func _canonical_fingerprint(values: Array) -> String: var encoded := "" for value: Variant in values: var token := _canonical_fingerprint_token(value) encoded += "%d:%s" % [token.length(), token] return encoded static func _canonical_fingerprint_token(value: Variant) -> String: if value is Array: var items := "" for item: Variant in value: var token := _canonical_fingerprint_token(item) items += "%d:%s" % [token.length(), token] return "a%d:%s" % [value.size(), items] if value is int: return "i:%d" % value var text := String(value) return "s%d:%s" % [text.length(), text] static func _success(details: Dictionary = {}) -> Dictionary: var result := {&"ok": true, &"code": &"OK", &"replay": false} result.merge(details, true) return result static func _failure(code: StringName, message: String, details: Dictionary = {}) -> Dictionary: var result := { &"ok": false, &"code": code, &"message": message, &"replay": false, } result.merge(details, true) return result static func _make_stack( stack_id: StringName, owner_inventory_id: StringName, item_id: StringName, quantity: int, quality: int, expires_at: int, created_at: int, unit_weight: int ) -> Dictionary: return { &"stack_id": stack_id, &"owner_inventory_id": owner_inventory_id, &"item_id": item_id, &"quality": quality, &"quantity": quantity, &"reserved_quantity": 0, &"created_at": maxi(created_at, 0), &"expires_at": expires_at, &"unit_weight": unit_weight, } static func _serialize_stack(stack: Dictionary) -> Dictionary: var serialized := stack.duplicate(true) serialized[&"stack_id"] = String(stack[&"stack_id"]) serialized[&"owner_inventory_id"] = String(stack[&"owner_inventory_id"]) serialized[&"item_id"] = String(stack[&"item_id"]) return serialized static func _parse_stack(raw_stack: Variant, expected_owner_id: StringName) -> Dictionary: if not raw_stack is Dictionary: return {} var stack_id := StringName(raw_stack.get(&"stack_id", "")) var owner_inventory_id := StringName(raw_stack.get(&"owner_inventory_id", "")) var item_id := StringName(raw_stack.get(&"item_id", "")) var quantity := int(raw_stack.get(&"quantity", -1)) var reserved_quantity := int(raw_stack.get(&"reserved_quantity", -1)) var unit_weight := int(raw_stack.get(&"unit_weight", 0)) var created_at := int(raw_stack.get(&"created_at", -1)) var expires_at := int(raw_stack.get(&"expires_at", -2)) if ( stack_id == &"" or owner_inventory_id == &"" or owner_inventory_id != expected_owner_id or item_id == &"" or quantity <= 0 or reserved_quantity < 0 or reserved_quantity > quantity or unit_weight <= 0 or created_at < 0 or expires_at < -1 ): return {} return { &"stack_id": stack_id, &"owner_inventory_id": owner_inventory_id, &"item_id": item_id, &"quality": int(raw_stack.get(&"quality", DEFAULT_QUALITY)), &"quantity": quantity, &"reserved_quantity": reserved_quantity, &"created_at": created_at, &"expires_at": expires_at, &"unit_weight": unit_weight, } static func _validate_state(inventories: Dictionary, reservations: Dictionary) -> String: var stack_locations: Dictionary = {} for owner_inventory_id: Variant in inventories: var stacks: Dictionary = inventories[owner_inventory_id] for stack_id: Variant in stacks: var stack: Dictionary = stacks[stack_id] if stack_locations.has(stack_id): return "Stack id is duplicated across inventories." if int(stack[&"quantity"]) <= 0: return "Empty stack remained in inventory state." if int(stack[&"reserved_quantity"]) < 0 or int(stack[&"reserved_quantity"]) > int(stack[&"quantity"]): return "Stack available quantity invariant is violated." stack_locations[stack_id] = { &"owner_inventory_id": StringName(owner_inventory_id), &"stack": stack, } var expected_reserved: Dictionary = {} for reservation_id: Variant in reservations: var reservation: InventoryReservation = reservations[reservation_id] if not reservation.is_active(): continue if not stack_locations.has(reservation.stack_id): return "Active reservation refers to a missing stack." var location: Dictionary = stack_locations[reservation.stack_id] if location[&"owner_inventory_id"] != reservation.owner_inventory_id: return "Active reservation owner does not match its stack." var stack: Dictionary = location[&"stack"] if stack[&"item_id"] != reservation.item_id: return "Active reservation item does not match its stack." expected_reserved[reservation.stack_id] = int(expected_reserved.get(reservation.stack_id, 0)) + reservation.quantity for stack_id: Variant in stack_locations: var stack: Dictionary = stack_locations[stack_id][&"stack"] if int(stack[&"reserved_quantity"]) != int(expected_reserved.get(stack_id, 0)): return "Stack reserved quantity does not match active reservations." return "" static func _available_in_stack(stack: Dictionary) -> int: return int(stack[&"quantity"]) - int(stack[&"reserved_quantity"]) static func _stack_is_expired(stack: Dictionary, absolute_minute: int) -> bool: var expires_at := int(stack[&"expires_at"]) return expires_at >= 0 and absolute_minute >= expires_at static func _unique_sorted_names(values: Array[StringName]) -> Array[StringName]: var seen: Dictionary = {} var result: Array[StringName] = [] for value: StringName in values: if value == &"" or seen.has(value): continue seen[value] = true result.append(value) result.sort_custom(func(a: StringName, b: StringName) -> bool: return String(a) < String(b)) return result static func _sorted_names(values: Variant) -> Array[StringName]: var result: Array[StringName] = [] for value: Variant in values: result.append(StringName(value)) result.sort_custom(func(a: StringName, b: StringName) -> bool: return String(a) < String(b)) return result static func _string_array(values: Array[StringName]) -> Array[String]: var result: Array[String] = [] for value: StringName in values: result.append(String(value)) return result static func _max_canonical_sequence(values: Variant, prefix: String) -> int: var maximum := 0 for value: Variant in values: var sequence := _canonical_sequence(value, prefix) if sequence > maximum: maximum = sequence return maximum static func _canonical_sequence(value: Variant, prefix: String) -> int: var text := String(value) if not text.begins_with(prefix): return 0 var suffix := text.substr(prefix.length()) if suffix.is_empty() or not suffix.is_valid_int(): return 0 return maxi(int(suffix), 0) static func _reservation_timestamps_are_valid(snapshot: Dictionary) -> bool: var created_at := int(snapshot.get(&"created_at", -1)) var expires_at := int(snapshot.get(&"expires_at", -2)) var terminal_at := int(snapshot.get(&"terminal_at", -2)) var state_name := String(snapshot.get(&"state", "")) if created_at < 0 or expires_at < -1 or terminal_at < -1: return false if state_name == "ACTIVE": return terminal_at == -1 return state_name in ["CONSUMED", "RELEASED", "EXPIRED"] and terminal_at >= 0 static func _sum_lines(lines: Array[Dictionary]) -> int: var total := 0 for line: Dictionary in lines: total += int(line.get(&"quantity", 0)) return total