Skip to main content

Wave Policy Settings

This page lists every field of a wave policy, a policy run, a wave and a wave member. Page 12 explains how waves work. Read it first.

There is no screen. Every field below is set through the API. The JSON key of a policy field is data.policy.<field> in wave_policy_create and wave_policy_save. The UI label of every field is therefore "none, API only".

Paths start at crates/libs/ unless they say otherwise. file:line shows where to check a claim.

1. How a policy is read and written​

  • A policy is one flat record. Selection filters, grouping, size limits, schedule and release mode all live in the same row (wave_policy). Migrations: wms-lib-sql/sql/wms-app/migrations/078_wave_policy_builder.sql, 081_wave_policy_scheduling.sql, 082_wave_policy_automatic_release.sql.
  • wave_policy_save replaces every user field at once. It is not a patch. A field you leave out becomes empty or false, with one exception rule below. Read the policy first (wave_policy_get), change what you need, send it all back with the version you read (wms-lib-core/src/model/outbound/wave_policy.rs:212-246).
  • Required keys on the wire: code, eligible_statuses, include_undated, group_by_owner, group_by_carrier, group_by_requested_ship_date, max_orders, max_waves_per_run, is_active. Optional keys: description, priority_from, priority_to, requested_ship_days_ahead, owner_id, carrier_id, max_lines, schedule_interval_minutes, schedule_starts_at, auto_release (wave_policy.rs:66-89).
  • The schema defaults do not apply through the API. The API has no default for max_orders (schema 100), max_waves_per_run (schema 10), is_active (schema true) or eligible_statuses (schema all three). You must send them. The defaults apply only to a direct SQL insert.
  • auto_release has a default on the wire: if you leave it out, it is false. A wave_policy_save that forgets this key turns auto-release off (wave_policy.rs:86-87).
  • No wave policy exists after install. No migration and no development seed creates one (grep of wms-lib-sql finds no insert).
  • Normalisation: the code is trimmed; statuses are trimmed and duplicates dropped (waves/policy.rs:355-423).

Example (create a policy that builds every hour, without auto-release):

{
"jsonrpc": "2.0", "id": 1, "method": "wave_policy_create",
"params": {
"meta": { "idempotency_key": "6f1c2c1e-3c0e-4d51-8f0e-3f3f6c9d2a11" },
"data": { "policy": {
"code": "DAY",
"description": "Daily waves per owner",
"eligible_statuses": ["New", "Allocated"],
"priority_from": 1, "priority_to": 50,
"requested_ship_days_ahead": 2, "include_undated": false,
"owner_id": null, "carrier_id": null,
"group_by_owner": true, "group_by_carrier": false,
"group_by_requested_ship_date": false,
"max_orders": 3, "max_lines": 10, "max_waves_per_run": 2,
"schedule_interval_minutes": 60,
"schedule_starts_at": "2026-10-12T08:00:00Z",
"auto_release": false, "is_active": true
} }
}
}

Roles: wave_policy_create, wave_policy_save, wave_policy_build = Admin, Manager. Read and preview = Admin, Manager, Supervisor (app-server/src/web/rpcs/wave_rpc.rs:15-22).

2. Policy fields (table wave_policy)​

Each card has the same rows. "Code" in the last column of "What it affects" is the line that reads the field.

2.1 id​

ItemValue
UI labelNone (API only).
Columnwave_policy.id
Typebigint, generated
Defaultgenerated
What it doesKey. wave.policy_id and wave_policy_run.policy_id point to it (restrict on delete).
When you change itYou cannot.

2.2 code​

ItemValue
UI labelNone (API only). Key data.policy.code.
Columnwave_policy.code
Type and rangetext, 1 to 30 characters after trimming, unique (uq_wave_policy_code)
Defaultnone (required). No seed.
What it doesThe name of the policy. Used only to find it (wave_policy_list sorts by it). It is not part of generated wave codes.
What it affectsNothing in the engine.
ExampleDAY, NIGHT-EXPRESS
Refusalswave policy code must not be blank; wave policy code must not exceed 30 characters; a wave policy with this code already exists (error code WAVE_POLICY_CODE_ALREADY_EXISTS) (waves/policy.rs:28-36, error.rs:57-60)
When you change itwave_policy_save can rename the policy. Existing waves are not affected.

2.3 description​

ItemValue
UI labelNone (API only).
Columnwave_policy.description
Type and rangetext up to 200 characters, may be empty (NULL)
DefaultNULL
What it doesStored only. It has no effect today. It is returned by wave_policy_get and wave_policy_list.

2.4 eligible_statuses​

ItemValue
UI labelNone (API only).
Columnwave_policy.eligible_statuses (list of text)
Type and rangeA list with at least one value. Each value must be one of New, PartiallyAllocated, Allocated (case matters). Double values are dropped.
DefaultSchema: all three. API: required.
What it doesTakes into the policy only orders whose status is in the list. All of them are orders not yet released (released_at empty and not cancelled).
What it affectswave_policy.rs:320 (so.status::text = ANY(...)). Checked on save in waves/policy.rs:366-379 and again by the database (chk_wave_policy_statuses_supported).
Example["New"] takes only orders with nothing reserved. ["Allocated"] takes only orders fully reserved before the wave.
InteractionsThe statuses mean: New = nothing reserved; PartiallyAllocated = some reserved; Allocated = all reserved. Any released status (PartiallyReleased, Released, Picked, ...) is not allowed. The release itself allocates stock, so New is a normal choice.
When emptyRefused: wave policy must name at least one eligible status.
Other refusalunsupported pre-release order status in wave policy: {value}
When you change itThe next preview or build uses the new list. Existing waves are unchanged. Orders already in a wave stay there.

2.5 priority_from​

ItemValue
UI labelNone (API only).
Columnwave_policy.priority_from
Type and rangewhole number 1 to 99, may be empty
DefaultNULL
What it doesTakes only orders whose shipment_order.priority is at least this number. Order priority is 1 to 99, default 50. A smaller number is handled earlier (the plan and the task board both sort ascending).
What it affectswave_policy.rs:321
Examplepriority_from = 10 skips orders with priority 1 to 9.
InteractionsBoth bounds are inclusive. priority_from must not exceed priority_to.
When emptyNo lower bound.
Refusalswave policy priority bounds must be between 1 and 99; wave policy priority_from must not exceed priority_to

2.6 priority_to​

Same as 2.5 but the upper bound: takes orders with priority at most this number (wave_policy.rs:322). Example: priority_to = 50 skips orders with priority 51 or more. Empty = no upper bound.

2.7 requested_ship_days_ahead​

ItemValue
UI labelNone (API only).
Columnwave_policy.requested_ship_days_ahead
Type and rangewhole number 0 to 3650 (days), may be empty
DefaultNULL
What it doesTakes orders whose requested_ship_date is on or before today + N days. Dates in the past also pass (overdue orders). 0 means "today and overdue".
What it affectswave_policy.rs:323-325. "Today" is current_date of the database.
ExampleToday 10 Oct, N = 2: dates up to 12 Oct pass; 15 Oct does not.
InteractionsOrders with no ship date pass only if include_undated is true.
When emptyNo date filter at all. Then every order passes, dated or not, and include_undated has no effect.
Refusalrequested_ship_days_ahead must be between 0 and 3650

2.8 include_undated​

ItemValue
UI labelNone (API only).
Columnwave_policy.include_undated
Typetrue or false
DefaultSchema false. API: required.
What it doesWhen a ship-date horizon is set, true lets orders without a requested ship date in.
What it affectswave_policy.rs:325
ExampleHorizon 2 days, include_undated = true: order O8 with no date is a candidate. With false it is not.
When there is no horizonNo effect.

2.9 owner_id​

ItemValue
UI labelNone (API only).
Columnwave_policy.owner_id (foreign key to owners, restrict on delete)
Typeowner id, may be empty
DefaultNULL
What it doesTakes only orders of this owner.
What it affectswave_policy.rs:326
InteractionsIf you set it, group_by_owner makes no difference (one owner only). An id that does not exist fails with a database error on save.
When emptyAll owners.

2.10 carrier_id​

ItemValue
UI labelNone (API only).
Columnwave_policy.carrier_id (foreign key to owners: a carrier is an owner record; restrict on delete)
Typeowner id of the carrier, may be empty
DefaultNULL
What it doesTakes only orders whose carrier_id equals it. Orders without a carrier do not pass.
What it affectswave_policy.rs:327
When emptyAll carriers, and orders with no carrier.

2.11 group_by_owner​

ItemValue
UI labelNone (API only).
Columnwave_policy.group_by_owner
Typetrue or false
DefaultSchema false. API: required.
What it doesOrders of different owners never share a wave.
What it affectswaves/policy.rs:430-441 (the group key)
ExampleOwners 7 and 8, max_orders 3: owner 7 orders fill waves for owner 7; owner 8 orders go into other waves.
InteractionsThe group key is the combination of the three group_by_* flags. All three false = one common group. Groups appear in order of the first order they contain (the best-priority order). Capacity limits apply inside each group.
When falseOwners may mix in one wave.

2.12 group_by_carrier​

Same as 2.11, with the order's carrier. Orders without a carrier form their own group (carrier empty). Code: waves/policy.rs:432-436.

2.13 group_by_requested_ship_date​

Same as 2.11, with the requested ship date. Orders with no date form their own group. Code: waves/policy.rs:437-441. Example: with true, orders for 11 Oct and 12 Oct never share a wave.

2.14 max_orders​

ItemValue
UI labelNone (API only).
Columnwave_policy.max_orders
Type and rangewhole number 1 to 1000
DefaultSchema 100. API: required.
What it doesThe most orders in one generated wave. When the current batch is full, the next order starts a new wave.
What it affectswaves/policy.rs:471-479
Example7 orders in one group, max_orders 3: waves of 3, 3 and 1.
InteractionsTogether with max_waves_per_run it limits one run to max_orders x max_waves_per_run orders (at most 20 000). It does not split an order.
Refusalmax_orders must be between 1 and 1000
When you change itApplies to the next run only.

2.15 max_lines​

ItemValue
UI labelNone (API only).
Columnwave_policy.max_lines
Type and rangewhole number 1 to 100 000, may be empty
DefaultNULL
What it doesThe most order lines (lines not cancelled) in one generated wave. An order is never split.
What it affectswaves/policy.rs:461-479
Examplemax_lines 10: orders with 5, 4 and 3 lines give a wave of the first two (9 lines) and a new wave for the third.
InteractionsAn order that alone has more lines than max_lines can never be placed. It is left out with the reason order exceeds max_lines and cannot be split, in every run, until the limit grows or lines are cancelled.
When emptyNo line limit.
Refusalmax_lines must be between 1 and 100000

2.16 max_waves_per_run​

ItemValue
UI labelNone (API only).
Columnwave_policy.max_waves_per_run
Type and rangewhole number 1 to 20
DefaultSchema 10. API: required.
What it doesThe most waves one run creates. Batches beyond the limit are dropped for this run. Their orders stay free for the next run.
What it affectswaves/policy.rs:488-499
ExampleThree batches, limit 2: two waves; the orders of the third batch get the reason deferred by max_waves_per_run.
InteractionsBatches are kept in the order of their groups. The group of the best-priority order is first.
Refusalmax_waves_per_run must be between 1 and 20

2.17 schedule_interval_minutes​

ItemValue
UI labelNone (API only).
Columnwave_policy.schedule_interval_minutes
Type and rangewhole number 1 to 10 080 (7 days), may be empty
DefaultNULL = manual policy
What it doesThe pause between two automatic builds.
What it affectswave_policy.rs:250-295 (due policy, next slot). The scheduler loop is in app-server/src/services/cleanup_tasks.rs:289-325.
InteractionsMust be set together with schedule_starts_at. Only an active policy has a next run.
When emptyThe policy never runs by itself. You can still preview and build by hand.
Refusalsschedule_interval_minutes and schedule_starts_at must be set together; schedule_interval_minutes must be between 1 and 10080. The database has the same rules (chk_wave_policy_schedule_interval, chk_wave_policy_schedule_pair).
When you change itSee 2.18.

2.18 schedule_starts_at​

ItemValue
UI labelNone (API only).
Columnwave_policy.schedule_starts_at
Typedate and time with time zone (send a value with a zone, for example 2026-10-12T08:00:00Z), may be empty
DefaultNULL
What it doesThe anchor of the schedule. The first run is due at this time. Later runs are due at anchor + n x interval.
What it affectswave_policy.rs:182-246 (sets next_run_at)
ExampleStart 08:00, interval 60: slots at 08:00, 09:00, 10:00 ...
InteractionsIn wave_policy_create, next_run_at is set to this value if the policy is active and has an interval. In wave_policy_save, next_run_at goes back to this value when the interval or the start changes, when the policy was inactive, or when there was no next run. If the value is in the past, the policy is due at once (one run), and then the next slot is the first one after now.
When emptySee 2.17.

2.19 next_run_at​

ItemValue
UI labelNone. Read-only.
Columnwave_policy.next_run_at
Typedate and time, may be empty
What it doesWhen the policy is next due. Owned by the scheduler; you cannot write it. Returned by wave_policy_get.
What it affectswave_policy.rs:250-267 (a policy is due when active and next_run_at <= now)
InteractionsEmpty while the policy is inactive or has no schedule (chk_wave_policy_next_run). After a run it moves to next_run_at + interval x (whole intervals since the slot + 1), that is the first boundary after now (wave_policy.rs:283-290).
ExampleSlot 08:00, interval 60, finished at 08:00:05: next 09:00. Server down until 10:30: one run, then 11:00.
Does not bumpversion (the scheduler does not change the user version).

2.20 last_scheduled_at​

Read-only. The slot time that the scheduler committed most recently (wave_policy.rs:283). Informational. Empty until the first scheduled run. A build by hand does not set it.

2.21 auto_release​

ItemValue
UI labelNone (API only).
Columnwave_policy.auto_release
Typetrue or false
Defaultfalse (schema and wire)
What it doesFalse: generated waves stay Draft for a person to inspect and release. True: every generated wave is prepared for release in the same transaction (Planned, members Pending), and the background worker releases the orders.
What it affectswaves/policy.rs:288-294 (prepare_release_locked, the same path as wave_release)
Exampleauto_release = true, schedule 60 minutes: every hour the eligible orders are released with no human step.
InteractionsWorks for build-now and for scheduled builds. For a scheduled build the release is requested by the system user (id 0). For build-now it is requested by the user who called wave_policy_build. A wave released this way settles as Released even when members are Blocked; nobody is told except through wave_get and the logs.
When you change itOnly future builds change. Waves that are already Planned keep going.

2.22 is_active​

ItemValue
UI labelNone (API only).
Columnwave_policy.is_active
Typetrue or false
DefaultSchema true. API: required.
What it doesAn inactive policy cannot be built (wave policy {id} is inactive) and is never scheduled. Preview still works.
What it affectswaves/policy.rs:228-233, wave_policy.rs:261
InteractionsSaving with false clears next_run_at. Saving with true again sets next_run_at to the start time. If that time is in the past, the policy runs once at the next tick. Waves already built stay as they are, and a Planned wave of an inactive policy is still released by the worker.

2.23 version​

ItemValue
UI labelNone. Read-only.
Columnwave_policy.version
Typeinteger, at least 1
Default1
What it doesOptimistic lock. wave_policy_save and wave_policy_build must send expected_policy_version. A stale value is refused with code 40 and the current policy: version conflict on wave policy {id} (error code WAVE_POLICY_VERSION_CONFLICT).
When you change it+1 on every wave_policy_save. Not changed by builds or by the scheduler.

2.24 Audit columns​

add_who, add_when, mod_who, mod_when. Filled by the system. No effect.

3. Policy run (table wave_policy_run)​

One row per build (by hand or by schedule), and one row per scheduled slot whose build failed. There is no list method. The row comes back in the answer of wave_policy_build as run, and the waves carry its id in wave.policy_run_id. All fields are written by the system.

FieldMeaning
idThe run number. It is part of generated wave codes (in hexadecimal).
policy_idThe policy.
candidate_countOrders that passed all filters in the first read.
selected_order_countOrders placed in waves.
excluded_order_countcandidate_count - selected_order_count.
wave_countWaves created.
scheduled_forThe slot time for a scheduled run; empty for build-now. Unique per policy (uq_wave_policy_run_scheduled_slot).
started_at, completed_atStart and end of the build.
add_whoThe user, or 0 for the scheduler.
outcomeSucceeded (default; every build before wms-app 094) or Failed.
errorWhy a Failed run failed. Empty exactly when the run succeeded (check chk_wave_policy_run_failure_says_why). A failed run has 0 candidates and takes the slot's unique index, so one slot is never recorded twice.

The counts are results. Nothing reads them back (wave_policy.rs:347-397).

4. Wave (table wave)​

FieldTypeDefaultMeaning and use
idbigintgeneratedKey.
codetext 1 to 30, unique (uq_wave_code)noneSet by you in wave_create. Generated as A{run hex}-{batch hex} for policy waves. Duplicate: a wave with this code already exists (WAVE_CODE_ALREADY_EXISTS). Blank: wave code must not be blank. Over 30: wave code must not exceed 30 characters.
policy_id, policy_run_idbigint, both set or both emptyemptyProvenance of a policy wave. Manual waves have neither.
statuswave_statusDraftSee page 12, section 4.1.
release_generationinteger0Counts how many times the wave was frozen for release. A member is processed only when its generation equals the wave's. Set to +1 by wave_release/auto-release. Retry keeps it.
release_requested_byuser idemptyThe user who released. 0 = system (scheduler). The worker releases the orders as this user.
versioninteger1Optimistic lock for every wave command. +1 on add, remove, prepare, cancel, retry, and when the worker settles the wave. Read the wave again before the next command.
planned_attimeemptySet when frozen.
released_attimeemptySet when the wave settles as Released; cleared again when a retry reopens the wave.
cancelled_attimeemptySet by cancel.
completed_attimeemptyNever written today.
add_who, add_when, mod_who, mod_whenaudit

Constraints (077_manual_order_waves.sql:36-58): the markers must agree with the status. For example Draft has no planned_at; Released, InProgress and Completed have released_at; Cancelled has cancelled_at; a wave with a release generation has a releasing user.

5. Wave member (table wave_order)​

FieldTypeDefaultMeaning and use
idbigintgeneratedKey. The worker processes members in this order.
wave_id, order_idbigintnoneThe pair is unique.
sequenceinteger above 0, may be emptyemptyStored only. It has no effect today. Policy waves fill it with 1, 2, 3 ... (position in the batch). wave_add_order accepts it.
priorityinteger 0 or more, may be emptyemptyStored only. It has no effect today. Policy waves copy the order priority.
statuswave_order_statusDraftSee page 12, section 4.2.
release_generationinteger0Generation of the last prepare.
release_expected_order_versionintegeremptyThe version of the order that the release must still find. A change in between makes the member Blocked with a version conflict. Retry replaces it with the current version.
release_idempotency_keyuuidemptyFixed key of the release (wave.release_order + wave + generation + order). The same key is reused by retries.
attempt_countinteger0+1 each time the worker claims the member. Reset to 0 by prepare, kept by retry. Informational.
processing_started_attimeemptyThe lease start. A lease older than 300 seconds is taken over.
released_attimeemptySet for Released and Skipped.
outcome_order_version, outcome_order_statusinteger, order statusemptyThe order's version and status after the release (for Released and Skipped).
last_error_code, last_error_detailtext 80, textemptySet for Blocked and Failed. See page 12, section 6.2.
audit columns

6. Claim (table wave_order_claim)​

FieldMeaning
order_id (primary key)The order. One row at most, so an order is in at most one active wave.
wave_idThe wave.
claimed_at, claimed_byWhen and who.

A claim is written when an order is added to a wave, removed when a Draft member is removed, and removed for all members when a wave is cancelled. It is not removed when the wave is released or settled. A policy build and a manual add skip claimed orders (wave_policy.rs:328, aggregate.rs:55-80).

7. Fixed values (not settings)​

ValueWhatCode
30 charactersLongest wave or policy codeaggregate.rs:44-50, waves/policy.rs:363
50 000Most candidates read in one planwave_policy.rs:333
300 secondsLease of a Processing memberwaves/worker.rs:14
2 seconds, 100 membersRelease worker tick and members per warehouse per tickcleanup_tasks.rs:158-159
5 seconds, 10 slotsScheduler tick and policy slots per warehouse per tickcleanup_tasks.rs:162-163
1 to 200, default 50Page size of wave_list and wave_policy_listwaves/queries.rs:49-56, waves/policy.rs:136-143, wave_rpc.rs:41-43
New, PartiallyAllocated, AllocatedThe only statuses a policy may namewaves/policy.rs:26

8. Enums​

EnumValues
wave_statusDraft, Planned, Released, InProgress, Completed, Cancelled
wave_order_statusDraft, Pending, Processing, Released, Blocked, Failed, Skipped
Policy eligible_statuses itemsNew, PartiallyAllocated, Allocated

Meanings: page 12, section 4.

9. Refusal texts of the policy commands​

TextCause
wave policy {id} not found (WAVE_POLICY_NOT_FOUND)Wrong id
a wave policy with this code already existsDuplicate code
version conflict on wave policy {id}Old expected_policy_version; the answer carries the current policy
wave policy {id} is inactiveBuild on an inactive policy
wave policy code must not be blank, ... must not exceed 30 charactersCode
wave policy must name at least one eligible statusEmpty list
unsupported pre-release order status in wave policy: {value}Bad status
wave policy priority bounds must be between 1 and 99Priority out of range
wave policy priority_from must not exceed priority_toRange turned round
requested_ship_days_ahead must be between 0 and 3650Horizon
max_orders must be between 1 and 1000Size
max_lines must be between 1 and 100000Size
max_waves_per_run must be between 1 and 20Size
schedule_interval_minutes and schedule_starts_at must be set togetherOne of the two missing
schedule_interval_minutes must be between 1 and 10080Interval
wave membership changed while the command was acquiring locks; retry with the current versionRace during build

All these refusals are hard failures except the version conflict and the membership change (code 40, class Retry). Source: waves/policy.rs:28-56, waves/error.rs.

10. What happens when you change a policy​

ChangeNew runsWaves already builtOrders already in waves
Any selection field, grouping, size limitUses the new valueUnchangedStay in their wave
auto_releaseNew waves follow itUnchangedUnchanged
is_active falseNo build, no schedule; next_run_at clearedUnchanged. A Planned wave is still released.Unchanged
is_active true againRuns at the next tick if the start time is in the pastUnchangedUnchanged
Interval or start timenext_run_at restarts at the start timeUnchangedUnchanged
code, descriptionLabel onlyUnchangedUnchanged

11. Settings that have no effect​

FieldStatus
wave_policy.descriptionStored only. It has no effect today.
wave_policy.codeA label only. Nothing in the engine reads it.
wave_order.sequenceStored only. It has no effect today.
wave_order.priorityStored only. It has no effect today.
wave.completed_at and the statuses InProgress, CompletedNever set.
wave_order.attempt_countCounter for information. Nothing reads it for a decision.