@comity/order 0.9.2 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,7 +6,7 @@ Order domain contracts for Comity commerce modules.
6
6
 
7
7
  ## Purpose
8
8
 
9
- Defines order contracts and models consumed by commerce and storefront modules. Unifies cart and order into a single domain where a cart is an order in draft status. Provides repositories, item models, and structured errors without binding to a specific commerce backend.
9
+ Defines the generic order commitment contract consumed by commerce and storefront modules. Unifies cart and order into a single domain where a cart is an order in draft status. Provides the aggregate, repositories, line models and structured errors without binding to a specific commerce backend.
10
10
 
11
11
  ---
12
12
 
@@ -14,57 +14,114 @@ Defines order contracts and models consumed by commerce and storefront modules.
14
14
 
15
15
  This package:
16
16
 
17
- - ✅ defines the order entity with lifecycle and item mutations
18
- - ✅ defines order, item, and product contracts with status lifecycle
17
+ - ✅ defines the order entity with lifecycle, validating creation, and the single atomic draft-only priced-content operation
18
+ - ✅ defines generic order, line and subject contracts
19
19
  - ✅ defines order repository contracts
20
- - ✅ exposes the `error` subpath for order-specific error types
20
+ - ✅ exposes the `errors` subpath for order-specific error types
21
21
 
22
22
  This package does NOT:
23
23
 
24
+ - ❌ classify the purchased subject (product/asset/licence/service/plan)
25
+ - ❌ own commercial, billing, legal or fiscal buyer facts
26
+ - ❌ manage inventory, price composition or promotion rules
24
27
  - ❌ implement checkout orchestration or payment processing
25
- - ❌ manage inventory or pricing logic
26
- - ❌ validate coupons or apply promotion rules
27
28
  - ❌ render order UI
28
29
 
29
30
  ---
30
31
 
31
32
  ## Public API
32
33
 
33
- - `Order` — order entity with lifecycle and item mutations (`setItems`, `addItem`, `removeItem`, `updateItemQuantity`, `submit`, `confirm`, `fulfill`, `cancel`)
34
+ - `Order` — aggregate with lifecycle, `Order.create`, `setPricedContent`, `update`, `changeShippingDestination`, `attachPayment`, `submit`, `confirm`, `fulfill`, `cancel`, `snapshot`
34
35
  - `OrderId` — order identifier value object
35
36
  - `OrderRepository` — persistence-boundary contract (`getById`, `search`, `save`)
36
37
  - `OrderState`, `OrderCreate`, `OrderUpdate`, `OrderSnapshot`, `OrderData`, `OrderStatus` — domain contracts
37
- - `OrderItem`, `OrderProductSnapshot` (with `OrderVariantSnapshot`, `OrderProductAttribute`, `OrderProductOption`) — embedded item value structures
38
+ - `OrderLine`, `OrderLineSubject` — generic embedded line value structures
39
+ - `OrderCustomerSnapshot` — generic historical buyer fact
38
40
  - Error types (`@comity/order/errors`)
39
41
 
40
- The product data in an order is an **owned, immutable snapshot**
41
- (`OrderProductSnapshot`): it is self-contained and never references the
42
- catalog after order creation. The application/checkout maps
43
- `ProductProjection` into the snapshot at creation time.
42
+ A line subject is an owned, immutable, generic snapshot: an opaque
43
+ `reference?`, a required `name` and an optional `description`. It makes the line
44
+ renderable and traceable without any external system, and it never classifies
45
+ the purchase. Commercial taxonomy, catalog identity and commercial attributes
46
+ belong to Commerce.
44
47
 
45
- Price composition is owned by `@comity/pricing`: orders store the immutable
46
- `Price` value (line item and order level) and do not store modifiers
47
- separately — modifiers are accessed only through `price.modifiers`.
48
+ Prices are owned by `@comity/pricing`: lines and the order store the immutable
49
+ `PriceRecord`, and `OrderData.currency` is the structural `PriceCurrency`.
50
+ The order derives its own record through `PriceRecord.combine`; it never
51
+ performs pricing arithmetic.
48
52
 
49
53
  No exhaustive reference; see docs for constraints.
50
54
 
51
55
  ---
52
56
 
57
+ ## Migrating from 0.9.3
58
+
59
+ `0.9.3` is the current published release. `0.10.0` is the target of this
60
+ migration and is **not published yet**. The complete guide is
61
+ `docs/migration.md` in the repository; because the npm tarball contains the
62
+ README and `dist` only, this section is the npm-visible summary.
63
+
64
+ | `0.9.3` | `0.10.0` |
65
+ | ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- |
66
+ | `OrderData.items` | `OrderData.lines` |
67
+ | `OrderItem { id, product, ... }` | `OrderLine { lineId, subject, ... }` |
68
+ | `OrderItemInput` for `addItem` | no direct replacement; build `OrderLine` and call `setPricedContent` |
69
+ | `OrderProductSnapshot` (SKU/variant/options/attributes) | `OrderLineSubject { reference?, name, description? }`; commercial detail in Commerce or `line.meta` |
70
+ | `Price` on lines/order | `PriceRecord` |
71
+ | no order currency | required `OrderData.currency: PriceCurrency` |
72
+ | `new Order(data, id)` for creation | `Order.create(data, id) → Result`; constructor hydrates only |
73
+ | `setItems` / `addItem` / `removeItem` / `updateItemQuantity` | `setPricedContent(lines, adjustments)` |
74
+ | `update({ price })` | `update({ meta })` only |
75
+ | six error reasons | ten reasons (four additions; no `invalid_buyer`) |
76
+
77
+ Key steps:
78
+
79
+ 1. Map each legacy line to `OrderLine`: `id` → `lineId`, `product` → `subject`.
80
+ Put SKU/variant/options/attributes in a Commerce-owned type or `line.meta`;
81
+ the generic subject has no commercial taxonomy.
82
+ 2. Create every line and order price as a `PriceRecord`. Use
83
+ `PriceRecord.from(price)` for a computed line price and
84
+ `PriceRecord.combine(lineRecords, adjustments)` for the order record.
85
+ 3. Supply `currency` from `currentPriceCurrency(code)`.
86
+ 4. Replace all priced-content mutation with the atomic, draft-only
87
+ `setPricedContent`; `update` changes metadata only.
88
+ 5. Handle `Result` values from `create`, `setPricedContent`, `submit`,
89
+ `update` and `attachPayment`; the four new reasons are
90
+ `currency_mismatch`, `price_incoherent`, `commitment_immutable` and
91
+ `payment_fact_conflict`.
92
+ 6. Persist `PriceRecord.toPlain(record)` and hydrate with
93
+ `PriceRecord.restore(plain)`; never `JSON.stringify` a live record. Do not
94
+ recompute or reprice on load.
95
+
96
+ An empty order requires the canonical zero `PriceRecord` (`0` subtotal, `0`
97
+ total, no modifiers, current format/semantics, order currency).
98
+ `combine([])` is forbidden and `setPricedContent([], ...)` is rejected; clearing
99
+ all priced content is not supported.
100
+
101
+ The five-kind subject union, `priceBasis`, `term`, `invalid_buyer` and the
102
+ intermediate line mutators were never part of published `0.9.3`; consumers
103
+ migrating from `0.9.3` must not add them just to remove them.
104
+
105
+ ---
106
+
53
107
  ## Documentation
54
108
 
55
109
  - docs/overview.md
56
110
  - docs/conventions.md
111
+ - docs/design.md
112
+ - docs/migration.md
57
113
 
58
114
  ---
59
115
 
60
116
  ## Related Packages
61
117
 
62
- - @comity/pricing — price and money models (`order → pricing`, registered in ADR-008)
63
- - @comity/catalog — source of `ProductProjection`, mapped by the application into owned `OrderProductSnapshot` (no direct dependency)
118
+ - @comity/pricing — `PriceRecord`/`PriceCurrency` and `combine` (`order → pricing`, registered in ADR-008)
119
+ - @comity/catalog — source of subject data, mapped by the application into the generic `OrderLineSubject` (no direct dependency)
64
120
  - @comity/kernel — module lifecycle runtime
65
121
 
66
122
  ---
67
123
 
68
124
  ## Status
69
125
 
70
- Stable
126
+ `0.9.3` is the current published release. `0.10.0` — the breaking contract in
127
+ this README — is in preparation and is **not published yet**.
@@ -6,11 +6,11 @@ import type { OrderContact } from "./contact.js";
6
6
  * A plain, owned representation of the customer as it was at purchase time.
7
7
  * It is NOT `Customer` from `@comity/customer` and it is not a reference to
8
8
  * the customer module: once the order is created it MUST NOT depend on the
9
- * customer for its content. Historical order rendering, customer support, and
10
- * invoice generation rely exclusively on this record.
9
+ * customer for its content.
11
10
  *
12
- * Only identity and display facts are preserved. Operational customer state
13
- * (preferences, lifecycle metadata) is not copied.
11
+ * Only generic counterparty facts are preserved. Commercial, billing, legal
12
+ * and fiscal facts (party kind, legal name, tax identifier) belong to
13
+ * Commerce; they are never part of the generic Order contract.
14
14
  */
15
15
  export interface OrderCustomerSnapshot {
16
16
  /** Reference to the customer identifier, if still known. */
@@ -0,0 +1,51 @@
1
+ import type { PriceRecord } from "@comity/pricing";
2
+ /**
3
+ * Owned, immutable snapshot of what a line bought.
4
+ *
5
+ * Generic by construction: the Order preserves a displayable, traceable
6
+ * description of the purchased subject and never classifies it. Commercial
7
+ * taxonomy (product/asset/licence/service/plan), catalog identity, variants
8
+ * and options belong to Commerce.
9
+ *
10
+ * Guarantees:
11
+ * - `reference` is opaque and caller-owned; the Order never parses, resolves
12
+ * or compares it. It exists so a reader can trace the line back to its
13
+ * source when that is still known.
14
+ * - `name` (required) and optional `description` make the line renderable
15
+ * without any external system.
16
+ * - The value is an owned snapshot, defensively copied on creation and on
17
+ * `snapshot()`.
18
+ * - Only JSON-representable strings are used.
19
+ */
20
+ export interface OrderLineSubject {
21
+ /** Opaque, application-owned reference to what was bought, when known. */
22
+ readonly reference?: string;
23
+ /** Human-readable label. */
24
+ readonly name: string;
25
+ /** Optional additional display text. */
26
+ readonly description?: string;
27
+ }
28
+ /**
29
+ * A committed line: an occurrence within the order.
30
+ *
31
+ * Embedded value structure: identity only as `lineId`, no lifecycle, no
32
+ * repository. The full line price is a `PriceRecord` owned by
33
+ * `@comity/pricing`; the Order stores it and never recomputes it.
34
+ */
35
+ export interface OrderLine {
36
+ /**
37
+ * Caller-assigned technical key for one occurrence within the order.
38
+ *
39
+ * The key identifies the occurrence, not the subject, is unique within the
40
+ * order, and is generated outside the Order.
41
+ */
42
+ readonly lineId: string;
43
+ /** Self-contained snapshot of what was bought. */
44
+ readonly subject: OrderLineSubject;
45
+ /** Integer quantity, `>= 1`. */
46
+ readonly quantity: number;
47
+ /** Frozen committed price for the entire line. */
48
+ readonly price: PriceRecord;
49
+ /** Non-semantic metadata; the domain never reads it. */
50
+ readonly meta?: Record<string, unknown>;
51
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=line.js.map
@@ -1,16 +1,16 @@
1
- import type { Price } from "@comity/pricing";
1
+ import type { PriceCurrency, PriceRecord } from "@comity/pricing";
2
2
  import type { Instant } from "@comity/primitives/time";
3
3
  import type { OrderId } from "../value-objects/order-id.js";
4
4
  import type { ChannelId } from "@comity/organization";
5
5
  import type { OrderAddressSnapshot } from "./address-snapshot.js";
6
6
  import type { OrderCustomerSnapshot } from "./customer-snapshot.js";
7
- import type { OrderItem, OrderProductSnapshot } from "./item.js";
7
+ import type { OrderLine } from "./line.js";
8
8
  import type { OrderPaymentSnapshot } from "./payment-snapshot.js";
9
9
  /**
10
10
  * Order status.
11
11
  *
12
- * An order starts in {@link OrderStatus."draft"} and progresses through a linear lifecycle.
13
- * Each transition is explicit; the status cannot move backwards.
12
+ * An order starts in `draft` and progresses through a linear lifecycle. Each
13
+ * transition is explicit; the status cannot move backwards.
14
14
  *
15
15
  * Transitions:
16
16
  * - `draft` → `pending` (submit)
@@ -19,16 +19,35 @@ import type { OrderPaymentSnapshot } from "./payment-snapshot.js";
19
19
  * - `draft | pending | confirmed` → `cancelled` (cancel)
20
20
  *
21
21
  * `fulfilled` and `cancelled` are terminal.
22
+ *
23
+ * - `draft`: terms are being assembled; nothing is owed.
24
+ * - `pending`: submitted for acceptance.
25
+ * - `confirmed`: terms accepted; delivery obligations exist.
26
+ * - `fulfilled`: every delivery obligation has been discharged.
27
+ * - `cancelled`: the commitment is terminated.
22
28
  */
23
29
  export type OrderStatus = "draft" | "pending" | "confirmed" | "fulfilled" | "cancelled";
24
30
  /**
25
31
  * Business data of an order, independent of identity and lifecycle metadata.
26
32
  */
27
33
  export interface OrderData {
28
- /** Order items. */
29
- readonly items: ReadonlyArray<OrderItem>;
30
- /** Order price. `@comity/pricing` owns price composition: modifiers are accessed through `price.modifiers`. */
31
- readonly price: Price;
34
+ /** Committed lines. */
35
+ readonly lines: ReadonlyArray<OrderLine>;
36
+ /**
37
+ * Frozen order-level price composed by `@comity/pricing`.
38
+ *
39
+ * The aggregate derives `price.subtotal` (the sum of the line totals) and
40
+ * `price.total` (after order-level adjustments) through
41
+ * `PriceRecord.combine`; it never performs pricing arithmetic itself (O-7).
42
+ */
43
+ readonly price: PriceRecord;
44
+ /**
45
+ * Monetary unit of the order and of every line and amount in `price`.
46
+ *
47
+ * A structural `PriceCurrency`, recorded verbatim. O-6 compares `(code,
48
+ * exponent)` exactly.
49
+ */
50
+ readonly currency: PriceCurrency;
32
51
  /** The commercial channel through which the order was placed. */
33
52
  readonly channelId: ChannelId;
34
53
  /** Historical buyer fact, captured at purchase time. */
@@ -52,6 +71,14 @@ export interface OrderState extends OrderData {
52
71
  readonly createdAt: Instant;
53
72
  /** Last update timestamp. */
54
73
  readonly updatedAt: Instant;
74
+ /**
75
+ * Persisted-version token.
76
+ *
77
+ * `0` while the aggregate has never been persisted; restored verbatim on
78
+ * hydration. The aggregate NEVER increments it — the repository does, once
79
+ * per successful save. ADVISORY until ADR-029 is accepted.
80
+ */
81
+ readonly version: number;
55
82
  }
56
83
  /**
57
84
  * Immutable point-in-time snapshot of an existing order.
@@ -61,14 +88,13 @@ export type OrderSnapshot = Readonly<OrderState & {
61
88
  readonly capturedAt: Instant;
62
89
  }>;
63
90
  /**
64
- * Data required to create a new order.
91
+ * Data required to create or hydrate an order.
65
92
  *
66
- * The identifier is supplied separately as the entity constructor's `id`
67
- * argument: the caller (application service or hydration path) owns ID
68
- * generation and the Order never generates its own aggregate ID. An order
69
- * without a supplied status starts as {@link OrderStatus."draft"}. Lifecycle
70
- * timestamps are optional so the same contract supports both creation and
71
- * hydration from persisted state (ADR-001).
93
+ * The identifier is supplied separately (`Order.create(data, id)` for
94
+ * creation, the constructor `id` argument for hydration): the caller owns ID
95
+ * generation. Creation ignores the lifecycle fields and always produces a
96
+ * `draft` order with `version: 0`; hydration restores `status`, timestamps
97
+ * and `version` verbatim (ADR-001).
72
98
  */
73
99
  export type OrderCreate = OrderData & {
74
100
  /** Order lifecycle status, used to restore persisted state during hydration. */
@@ -77,44 +103,18 @@ export type OrderCreate = OrderData & {
77
103
  readonly createdAt?: Instant;
78
104
  /** The timestamp when the order was last updated. */
79
105
  readonly updatedAt?: Instant;
106
+ /** Persisted-version token restored during hydration. Defaults to `0`. */
107
+ readonly version?: number;
80
108
  };
81
109
  /**
82
110
  * Partial mutation data for an order.
83
111
  *
84
- * Status transitions are not part of the update contract: they must pass
112
+ * Only non-semantic metadata is updatable here. Status transitions pass
85
113
  * through the entity's domain methods (`submit`, `confirm`, `fulfill`,
86
- * `cancel`). Items are mutated through `setItems`/`addItem`/`removeItem`/
87
- * `updateItemQuantity`, which protect the order's invariants.
114
+ * `cancel`); priced content is replaced atomically through
115
+ * `setPricedContent`.
88
116
  */
89
117
  export interface OrderUpdate {
90
- /** Order price. Modifiers are updated through `price`; see `@comity/pricing`. */
91
- readonly price?: Price;
92
118
  /** Custom metadata. */
93
119
  readonly meta?: Record<string, unknown>;
94
120
  }
95
- /**
96
- * Input for adding an item to the order.
97
- *
98
- * The item price is supplied by the caller as a complete `Price` value:
99
- * price computation belongs to `@comity/pricing` orchestration, not to the
100
- * Order aggregate. The product data is an owned snapshot mapped by the caller
101
- * from `ProductProjection`: the Order never depends on `@comity/catalog`.
102
- */
103
- export interface OrderItemInput {
104
- /**
105
- * Caller-assigned technical key for the new occurrence within the order.
106
- *
107
- * The ID identifies one occurrence, not a product: two occurrences may
108
- * carry identical product/configuration data under distinct IDs. It must be
109
- * unique within the order; the aggregate rejects duplicates. The ID is
110
- * generated outside the Order (application service or caller); the Order
111
- * never mints occurrence IDs itself.
112
- */
113
- readonly id: string;
114
- /** Product snapshot to add to the order. */
115
- readonly product: OrderProductSnapshot;
116
- /** Quantity to add. */
117
- readonly quantity: number;
118
- /** Full line price (subtotal, applied modifiers, total). */
119
- readonly price: Price;
120
- }
@@ -1,28 +1,58 @@
1
- import type { Price } from "@comity/pricing";
1
+ import type { PriceCurrency, PriceModifier } from "@comity/pricing";
2
2
  import type { Result } from "@comity/primitives/result";
3
3
  import type { OrderAddressSnapshot } from "../contracts/address-snapshot.js";
4
4
  import type { OrderCustomerSnapshot } from "../contracts/customer-snapshot.js";
5
- import type { OrderItem } from "../contracts/item.js";
6
- import type { OrderCreate, OrderItemInput, OrderSnapshot, OrderStatus, OrderUpdate } from "../contracts/order.js";
5
+ import type { OrderLine } from "../contracts/line.js";
6
+ import type { OrderCreate, OrderData, OrderSnapshot, OrderStatus, OrderUpdate } from "../contracts/order.js";
7
7
  import type { OrderPaymentSnapshot } from "../contracts/payment-snapshot.js";
8
8
  import type { OrderId } from "../value-objects/order-id.js";
9
9
  import type { ChannelId } from "@comity/organization";
10
+ import { PriceRecord } from "@comity/pricing";
10
11
  import { Instant } from "@comity/primitives/time";
11
12
  import { OrderError } from "../errors/order.js";
12
13
  /**
13
14
  * Represents an order entity in the system.
14
15
  *
15
16
  * @remark
16
- * An order is a lifecycle aggregate: it owns its status transitions, its line
17
- * items, the applied pricing result, and its historical facts (buyer,
18
- * addresses, payment). It does not validate coupons, does not know promotion
19
- * rules, and does not orchestrate external services — those concerns belong
20
- * to `@comity/pricing`, other Core Modules, and application/domain services.
17
+ * An order is a lifecycle aggregate: it owns its status transitions, its
18
+ * committed lines, the applied pricing result, its advisory version token, and
19
+ * its historical facts (buyer, addresses, payments). It does not validate
20
+ * coupons, does not know promotion rules, and does not orchestrate external
21
+ * services — those concerns belong to `@comity/pricing`, other Core Modules,
22
+ * and application/domain services.
23
+ *
24
+ * @remark
25
+ * The version token is ADVISORY: the aggregate carries it and never increments
26
+ * it. Optimistic concurrency belongs to the repository (Comity precondition
27
+ * ADR-029) and is not implemented here.
21
28
  */
22
29
  export declare class Order {
23
30
  #private;
24
31
  /**
25
- * @param fields - The fields used to create or hydrate the order.
32
+ * Creates a new, validated order (ADR-001, ADR-032 AM-5a).
33
+ *
34
+ * Applies the invariants decidable from creation data — O-1 (integer
35
+ * quantity `>= 1`), O-2 (unique line ids), O-6 (one currency on `(code,
36
+ * exponent)`), O-7 (exact `PriceRecord` commitment equality against
37
+ * `combine`) and O-9 (supplied payment facts) — before any state exists. An
38
+ * empty order is accepted only with the canonical zero record. A new order
39
+ * is `draft` with `version: 0`.
40
+ *
41
+ * @param data - The business data of the order.
42
+ * @param id - The unique identifier of the order, supplied by the caller.
43
+ *
44
+ * @returns The order, or `invalid_quantity` / `duplicate_item_id` /
45
+ * `currency_mismatch` / `price_incoherent` / `payment_fact_conflict`.
46
+ */
47
+ static create(data: OrderData, id: OrderId): Result<Order, OrderError>;
48
+ /**
49
+ * Hydrates an existing order (ADR-001).
50
+ *
51
+ * The constructor performs NO validation: it restores persisted state
52
+ * verbatim, including `version`. Use {@link Order.create} for the validating
53
+ * creation path.
54
+ *
55
+ * @param fields - The persisted fields of the order.
26
56
  * @param id - The unique identifier of the order, supplied by the caller.
27
57
  * The Order never generates its own aggregate ID; hydration preserves the
28
58
  * persisted ID verbatim.
@@ -41,13 +71,17 @@ export declare class Order {
41
71
  */
42
72
  get status(): OrderStatus;
43
73
  /**
44
- * @returns The order items.
74
+ * @returns The committed lines.
45
75
  */
46
- get items(): ReadonlyArray<OrderItem>;
76
+ get lines(): ReadonlyArray<OrderLine>;
47
77
  /**
48
78
  * @returns The order price.
49
79
  */
50
- get price(): Price;
80
+ get price(): PriceRecord;
81
+ /**
82
+ * @returns The monetary unit of the order and of every amount it stores.
83
+ */
84
+ get currency(): PriceCurrency;
51
85
  /**
52
86
  * @returns The historical buyer fact, if captured.
53
87
  */
@@ -73,14 +107,24 @@ export declare class Order {
73
107
  */
74
108
  get updatedAt(): Instant;
75
109
  /**
76
- * Updates the applied pricing result and metadata.
110
+ * Returns the persisted-version token (read-only; advisory, never
111
+ * incremented by the aggregate).
112
+ *
113
+ * @returns The version token: `0` while unpersisted.
114
+ */
115
+ get version(): number;
116
+ /**
117
+ * Updates non-semantic metadata.
77
118
  *
78
- * Status transitions are performed through the domain methods; items are
79
- * mutated through `setItems`/`addItem`/`removeItem`/`updateItemQuantity`.
119
+ * Status transitions pass through the domain methods; priced content is
120
+ * replaced atomically through {@link setPricedContent}. Metadata is
121
+ * updatable in any status.
80
122
  *
81
123
  * @param changes - The changes to apply to the order.
124
+ *
125
+ * @returns Success.
82
126
  */
83
- update(changes: OrderUpdate): void;
127
+ update(changes: OrderUpdate): Result<void, OrderError>;
84
128
  /**
85
129
  * Appends a historical payment fact to the order.
86
130
  *
@@ -89,20 +133,28 @@ export declare class Order {
89
133
  * This records a historical fact only: it performs no payment logic, knows
90
134
  * no payment module, and does not coordinate, authorize, or capture.
91
135
  *
136
+ * Facts dedupe on `(paymentId, status)`: an identical repeat is an idempotent
137
+ * no-op that touches nothing; the same key with a different amount, and an
138
+ * illegal progression for a known `paymentId`, are conflicts. A fact without
139
+ * a `paymentId` is always appended.
140
+ *
92
141
  * @param snapshot - The payment fact to record.
142
+ *
143
+ * @returns Success (including an idempotent no-op), or
144
+ * `payment_fact_conflict`.
93
145
  */
94
- attachPayment(snapshot: OrderPaymentSnapshot): void;
146
+ attachPayment(snapshot: OrderPaymentSnapshot): Result<void, OrderError>;
95
147
  /**
96
148
  * Replaces the shipping destination of the order.
97
149
  *
98
150
  * Allowed only while the order is `draft | pending`; in later statuses the
99
- * shipping destination is immutable (ADR-018). The order must hold exactly
100
- * one shipping address fact; a missing or ambiguous one is an error rather
101
- * than a guess. A structurally identical destination is a successful no-op
102
- * that leaves timestamps untouched. The operation never reaches the generic
103
- * `update()` path, never touches billing, customer, item, price, or payment
104
- * facts, and records no history: the aggregate retains only the current
105
- * shipping destination.
151
+ * shipping destination is immutable. The order must hold exactly one shipping
152
+ * address fact; a missing or ambiguous one is an error rather than a guess. A
153
+ * structurally identical destination is a successful no-op that leaves
154
+ * timestamps untouched. The operation never reaches the generic `update()`
155
+ * path, never touches billing, customer, line, price, or payment facts, and
156
+ * records no history: the aggregate retains only the current shipping
157
+ * destination.
106
158
  *
107
159
  * @param destination - The replacement shipping destination. The address
108
160
  * role is owned by this operation and is always stored as `"shipping"`.
@@ -111,63 +163,31 @@ export declare class Order {
111
163
  */
112
164
  changeShippingDestination(destination: Omit<OrderAddressSnapshot, "role">): Result<void, OrderError>;
113
165
  /**
114
- * Replaces the entire item collection with the supplied one (ADR-030).
115
- *
116
- * The supplied collection is the complete desired collection: items omitted
117
- * from it are removed, and no merging, appending, or quantity aggregation
118
- * occurs. The supplied order is the resulting order and an empty collection
119
- * is valid. Every entry is validated before any state changes — quantity
120
- * (integer `>= 1`) first, then ID uniqueness against the IDs already seen —
121
- * and the first violation rejects the whole operation, leaving the aggregate
122
- * unchanged. Every successful replacement refreshes `updatedAt`, including
123
- * identical replacements. Supplied item IDs are preserved verbatim and
124
- * caller-owned data is defensively copied. Item mutation is not status-gated.
125
- *
126
- * @param items - The complete desired item collection.
127
- *
128
- * @returns A result indicating the success or failure of the replacement.
129
- */
130
- setItems(items: ReadonlyArray<OrderItem>): Result<void, OrderError>;
131
- /**
132
- * Appends one item occurrence to the order.
133
- *
134
- * The occurrence ID is supplied by the caller in the input: the Order never
135
- * mints occurrence IDs itself. The ID must be unique within the order —
136
- * appending an already-used ID is rejected so the `setItems` duplicate-ID
137
- * invariant cannot be bypassed through this path. Validation runs before
138
- * any mutation — quantity (integer `>= 1`) first, then ID uniqueness — and
139
- * failures leave the aggregate unchanged. A successful append refreshes
140
- * `updatedAt`. Occurrence IDs carry no product meaning: identical
141
- * product/configuration data may coexist under distinct IDs.
142
- *
143
- * @param input - The item data to add, including the caller-assigned
144
- * occurrence ID.
145
- *
146
- * @returns The created item copy, an `invalid_quantity` error when the
147
- * quantity is not a positive integer, or a `duplicate_item_id` error when
148
- * the ID is already used within the order.
149
- */
150
- addItem(input: OrderItemInput): Result<OrderItem, OrderError>;
151
- /**
152
- * Removes an item from the order.
153
- *
154
- * @param itemId - The order item ID.
166
+ * Atomically replaces the priced content of the order.
155
167
  *
156
- * @returns A result indicating the success or failure of the removal.
157
- */
158
- removeItem(itemId: string): Result<void, OrderError>;
159
- /**
160
- * Updates the quantity of an order item.
168
+ * `lines` and `adjustments` are the complete desired priced content. The
169
+ * order-level `PriceRecord` is derived with `PriceRecord.combine`; line
170
+ * modifiers are provenance and are never reapplied or copied. All validation
171
+ * happens before any state changes, so a failure leaves the aggregate
172
+ * completely unchanged. Allowed only while the order is `draft`.
161
173
  *
162
- * @param itemId - The order item ID.
163
- * @param quantity - The new quantity.
174
+ * @param lines - The complete desired line collection; must be non-empty.
175
+ * @param adjustments - The order-level adjustments, in application order.
164
176
  *
165
- * @returns A result indicating the success or failure of the update.
177
+ * @returns Success, or `commitment_immutable` outside `draft` /
178
+ * `currency_mismatch` / `price_incoherent` / `invalid_quantity` /
179
+ * `duplicate_item_id`.
166
180
  */
167
- updateItemQuantity(itemId: string, quantity: number): Result<void, OrderError>;
181
+ setPricedContent(lines: ReadonlyArray<OrderLine>, adjustments: ReadonlyArray<PriceModifier>): Result<void, OrderError>;
168
182
  /**
169
183
  * Submits the order: `draft` → `pending`.
170
184
  *
185
+ * Before leaving `draft`, re-derives the order-level record from the stored
186
+ * line records and the stored order-level modifiers and requires exact
187
+ * commitment equality (ADR-032 AM-5c). An empty draft is validated against
188
+ * the canonical zero record. Reconciliation uses only `PriceRecord.combine`,
189
+ * never `verify()` and never an aggregate-side `PriceRecord.from()`.
190
+ *
171
191
  * @returns A result indicating the success or failure of the transition.
172
192
  */
173
193
  submit(): Result<void, OrderError>;
@@ -186,6 +206,9 @@ export declare class Order {
186
206
  /**
187
207
  * Cancels the order: `draft | pending | confirmed` → `cancelled`.
188
208
  *
209
+ * Takes no argument: the Order is business-model neutral and the application
210
+ * records why the commitment ended.
211
+ *
189
212
  * @returns A result indicating the success or failure of the transition.
190
213
  */
191
214
  cancel(): Result<void, OrderError>;