Skip to content

Inventory Pools — Data Model

The Inventory module stores one table, inventory_pool, and one state column on it, content — a canonical unit→amount map. It’s called “Inventory Pools” rather than “Inventory Items” deliberately: pools are all that exists at Phase 2. This page is the field-complete reference for that table and its DTOs; see Data Models — Reading Guide for the shared envelope shapes referenced throughout, and for the pools-only honesty note.

inventory_pool is a Layer-1 occurrence-carrying bitemporal table: a single row is the (occurrence, post-record) pair, with no separate occurrence table. It has no outbound references of its own — every reference into a pool comes from Demand, which holds a pool via holding_ref and member_pools.

PlantUML diagram

ColumnTypeNullRole
idUUIDNOT NULL (PK)identity
tenant_idUUIDNOT NULLidentity
effective_as_oftimestamptzNOT NULLidentity
recorded_as_oftimestamptzNOT NULL, default now()identity
previousUUIDNULLidentity
eidUUIDNOT NULLidentity
retiredbooleanNOT NULL, default falsebookkeeping
bts_authorvarchar(255)NOT NULLbookkeeping
bts_author_subvarchar(255)NULLbookkeeping
bts_created_by_subvarchar(255)NULLbookkeeping
bts_notevarchar(512)NULLpayload
created_byvarchar(255)NOT NULL, default ‘system’bookkeeping
created_at_effectivetimestamptzNOT NULL, default now()bookkeeping
created_at_recordedtimestamptzNOT NULL, default now()bookkeeping
occ_kind (was kind)varchar(64)NOT NULLpayload
occ_outcome (was outcome)varchar(16)NOT NULLpayload
occ_reason (was reason)varchar(64)NULLpayload
contentJSONBNOT NULLpayload — the pool’s entire state

The V002 rename (kind→occ_kind etc.) conforms the table to common-module 16.0.0’s OccurrenceColumns trait; PostgreSQL propagated the rename through the partial indexes automatically.

Unique constraints: inventory_pool_committed_previous — UNIQUE index on (previous) WHERE occ_outcome = 'COMMITTED', the P2-H1 committed-successor fork guard (PDEV-1166). Genesis rows are exempt by NULL semantics; REFUSED rows by the partial predicate.

No inventory_item, pool_member, or pool_item table exists anywhere under this module — see Schema notes.

FieldTypeStored or computedNotes
eIdEntityIdStored
kindInventoryPoolOccurrenceKindStored← occ_kind
outcomeInventoryPoolOutcomeStored← occ_outcome
reasonString?Stored
contentGeneralizedQuantityStoreddefault ZeroQuantity
FieldTypeStored or computedNotes
tenantIdUUIDStored

PoolCommitResponse (mutation responses, all three verbs)

Section titled “PoolCommitResponse (mutation responses, all three verbs)”
FieldTypeNotes
occurrenceOccurrenceDtosee Data Models — Reading Guide; note is left at its shared-type default — no inventory code path sets it
recordEntityRecord<InventoryPoolPayload, InventoryPoolMetadata>

inventory_pool itself carries no outbound reference — content is a value only (a GeneralizedQuantity map), not a pointer to any other entity. Every reference into a pool is held by the consumer, not by the pool:

FromColumnTargetKind
Demand — DemandItemholding_refInventoryPoolfloating
Demand — DemandItemmember_pools (map values)InventoryPoolfloating
Demand — DemandItemmember_pools (map keys)Kanban — CardCyclefloating

InventoryPool has no locator, no item/SKU reference, and no stored membership relation of its own — see Schema notes for what that means for “inventory items.”

Mounted at .../inventory-pool/.... Follows the request/response call-shape convention in The Orders v2 Call Shape.

MethodPathPurposeMain refusals
POSTcreateCreate a pool (empty or seeded)
POSTmergecontent += qty
POSTextractcontent -= qtyoverdraw, incomparable unit
GET{eId}?effectiveasof&recordedasofThe pool as of a coordinateabsent = 404
GET{eId}/log?page&sizeOccurrence log, most recent first, including REFUSED

None. This module has no Observer/Listener registration of any kind — it’s consumed by Demand as a direct injected Kotlin service, not through the notification channel every other module uses. Main.kt wires inventory before demand specifically so the injection is available.

  • “Inventory items” is not a stored concept at Phase 2. inventory_pool’s only state column is content, a canonical unit→amount aggregate — there is no InventoryItem type, no per-item row, no holder reference, and no locator anywhere in the module. The nearest artifact is demand_item.member_pools, which maps a production member’s own eId to that member’s separate pool eId — “which pool does this member have,” not “which items are in this pool.” That map’s own migration comment marks it an interim Phase-3 bridge, expected to migrate onto a future CardCycle’s own field once that lands.
  • No observer/notification wiring at all. Every sibling module either emits or listens for DataAuthorityNotification; inventory does neither. Its only integration point is the direct service injection Demand uses for its production-arc sagas.