Primitive spec
Hardin
The catalog primitive. Hardin holds the sellable concept and its rules — products, variants, modifiers, recipes, categories, price overrides, and tax classes — that POS and online ordering surfaces price and order against.
/menu resolver and price-scope expansion are the next slices.
What it owns
Hardin owns the sellable concept. Trantor owns the physical thing that gets allocated or consumed. A burger is not allocated — its ingredients get consumed; Hardin's RecipeIngredient links a variant to Trantor inventory items that decrement at sale.
Hardin does not decrement stock, evaluate daypart windows, or render UI. It owns the model; the resolver answers "what is sellable, at what price, right now, at this location" by walking it.
Concepts
- Category
- A hierarchical menu section. Drives display order, routing rules (Daneel reads
Product.categoryfor kitchen routing), and combo-eligibility shapes. - Product
- A sellable concept — "Bacon Cheeseburger", "T-Shirt", "18-hole Round". A Product is never ordered directly; orders reference its variants.
- ProductVariant
- A SKU. Every Product has at least one. Carries the orderable price, barcode, vendor codes, and the recipe that drives Trantor consumption on sale.
- ModifierGroup & Modifier
- Flat option lists that decorate a variant.
ModifierGrouphas selection rules (singleormulti, with min/max).Modifiercarries a price delta and an optionalRecipeIngredientfor inventory-affecting choices ("+ bacon" consumes two strips). - CompositeSlot
- A combo slot. Distinct from a ModifierGroup because the choice is a Product reference, not a flat option — "Pick a side" lets the customer choose any Product whose category is
sides; the chosen product carries its own modifiers, recipe, and price. - PriceOverride
- A scope-keyed price for a variant. v0 scopes are location (sub-tenant) and daypart (Seldon TimeScheme). Tier / channel / segment scopes are deferred. Resolution is most-specific wins, ties by
priority. - RecipeIngredient
- The link from a variant or modifier to a Trantor inventory item that gets consumed at sale. Keyed on
(sourceType, sourceId, trantorResourceTypeId, quantity, unit). - TaxClass
- A jurisdiction-aware tax classification. Mallow reads it on every OrderLine at close to compute line tax.
API surface
Endpoints are versioned under /hardin/v1/. The /menu resolver is the read path for POS UIs; everything else is admin / write.
| Method | Path | Purpose |
|---|---|---|
| POST / GET | /hardin/v1/categories | CRUD plus tree query. |
| POST / GET | /hardin/v1/products | CRUD plus query DSL (?categoryId=, ?attr_*=). |
| POST / GET | /hardin/v1/products/{id}/variants | Manage variants on a Product. |
| POST / DELETE | /hardin/v1/products/{id}/modifier-groups | Bind modifier groups to a Product. |
| POST / GET | /hardin/v1/modifier-groups | Manage ModifierGroups and their Modifiers. |
| POST / GET | /hardin/v1/products/{id}/composite-slots | Manage combo slots on a Product. |
| POST / GET | /hardin/v1/products/{id}/recipe | List or replace the RecipeIngredient set. |
| POST / GET | /hardin/v1/products/{id}/price-overrides | Manage scope-keyed prices. |
| POST / GET | /hardin/v1/tax-classes | Manage tax classifications. |
| PATCH | /hardin/v1/products/{id}/availability | Set manuallyDisabled (the "86" flag) or the daypart TimeScheme. |
| GET | /hardin/v1/menu?locationId=&at= | Read-optimised resolver. Filtered, priced, available at this instant. Planned for v2. |
Example: a burger with a temperature modifier
POST /hardin/v1/products
{
"categoryId": "cat_hot_food",
"name": "Bacon Cheeseburger",
"taxClassId": "tax_prepared_food",
"variants": [
{
"name": "Single",
"priceCents": 1495,
"recipe": [
{ "trantorResourceTypeId": "rt_beef_patty", "quantity": 1, "unit": "each" },
{ "trantorResourceTypeId": "rt_bun", "quantity": 1, "unit": "each" },
{ "trantorResourceTypeId": "rt_bacon", "quantity": 2, "unit": "strip" }
]
}
]
}
POST /hardin/v1/modifier-groups
{
"name": "Temperature",
"selectionRule": "single",
"min": 1, "max": 1,
"modifiers": [
{ "name": "Medium-rare", "priceDeltaCents": 0 },
{ "name": "Medium", "priceDeltaCents": 0 },
{ "name": "Well done", "priceDeltaCents": 0 }
]
}
How it fits with the rest
flowchart LR
Ops[Operator authoring] --> Ha(Hardin)
Ha -. recipe .-> T[Trantor inventory]
Ha -. daypart .-> S[Seldon TimeScheme]
Ho[Hober POS] -- read --> Ha
M[Mallow] -. taxClassId .-> Ha
Hober snapshots Hardin variants and modifiers at add-time onto OrderLines — the price the customer sees is the price they pay even if the catalog changes mid-order. Trantor is consumed via RecipeIngredient on Order close; Hardin itself never decrements. Seldon TimeSchemes back daypart availability. Mallow reads taxClassId per OrderLine to compute line tax. Daneel kitchen routing reads Product.category to pick a station.