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.
Entities and relationships
Section titled “Entities and relationships”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.
Stored shape
Section titled “Stored shape”inventory_pool
Section titled “inventory_pool”| Column | Type | Null | Role |
|---|---|---|---|
id | UUID | NOT NULL (PK) | identity |
tenant_id | UUID | NOT NULL | identity |
effective_as_of | timestamptz | NOT NULL | identity |
recorded_as_of | timestamptz | NOT NULL, default now() | identity |
previous | UUID | NULL | identity |
eid | UUID | NOT NULL | identity |
retired | boolean | NOT NULL, default false | bookkeeping |
bts_author | varchar(255) | NOT NULL | bookkeeping |
bts_author_sub | varchar(255) | NULL | bookkeeping |
bts_created_by_sub | varchar(255) | NULL | bookkeeping |
bts_note | varchar(512) | NULL | payload |
created_by | varchar(255) | NOT NULL, default ‘system’ | bookkeeping |
created_at_effective | timestamptz | NOT NULL, default now() | bookkeeping |
created_at_recorded | timestamptz | NOT NULL, default now() | bookkeeping |
occ_kind (was kind) | varchar(64) | NOT NULL | payload |
occ_outcome (was outcome) | varchar(16) | NOT NULL | payload |
occ_reason (was reason) | varchar(64) | NULL | payload |
content | JSONB | NOT NULL | payload — 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.
Projected shape
Section titled “Projected shape”InventoryPoolPayload
Section titled “InventoryPoolPayload”| Field | Type | Stored or computed | Notes |
|---|---|---|---|
eId | EntityId | Stored | |
kind | InventoryPoolOccurrenceKind | Stored | ← occ_kind |
outcome | InventoryPoolOutcome | Stored | ← occ_outcome |
reason | String? | Stored | |
content | GeneralizedQuantity | Stored | default ZeroQuantity |
InventoryPoolMetadata
Section titled “InventoryPoolMetadata”| Field | Type | Stored or computed | Notes |
|---|---|---|---|
tenantId | UUID | Stored |
PoolCommitResponse (mutation responses, all three verbs)
Section titled “PoolCommitResponse (mutation responses, all three verbs)”| Field | Type | Notes |
|---|---|---|
occurrence | OccurrenceDto | see Data Models — Reading Guide; note is left at its shared-type default — no inventory code path sets it |
record | EntityRecord<InventoryPoolPayload, InventoryPoolMetadata> |
Cross-module references
Section titled “Cross-module references”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:
| From | Column | Target | Kind |
|---|---|---|---|
Demand — DemandItem | holding_ref | InventoryPool | floating |
Demand — DemandItem | member_pools (map values) | InventoryPool | floating |
Demand — DemandItem | member_pools (map keys) | Kanban — CardCycle | floating |
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.”
API surface
Section titled “API surface”Mounted at .../inventory-pool/.... Follows the request/response call-shape convention in The Orders v2 Call Shape.
| Method | Path | Purpose | Main refusals |
|---|---|---|---|
| POST | create | Create a pool (empty or seeded) | |
| POST | merge | content += qty | |
| POST | extract | content -= qty | overdraw, incomparable unit |
| GET | {eId}?effectiveasof&recordedasof | The pool as of a coordinate | absent = 404 |
| GET | {eId}/log?page&size | Occurrence log, most recent first, including REFUSED |
Notifications
Section titled “Notifications”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.
Schema notes
Section titled “Schema notes”- “Inventory items” is not a stored concept at Phase 2.
inventory_pool’s only state column iscontent, a canonical unit→amount aggregate — there is noInventoryItemtype, no per-item row, no holder reference, and no locator anywhere in the module. The nearest artifact isdemand_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.
Copyright: © Arda Systems 2025-2026, All rights reserved