Skip to content

Production, resources, and transport: who owns what

Use this page when your game or model has stock that can run out, such as grain, cloth, ammunition, or building materials. It shows which crate owns each step, the order the steps happen in, and the rules for demand sources, access grants, loss, exchange, local handover, production output, and custom consumers.

The optional canwu-resource and canwu-production extensions handle this. The simulation core stays generic: grain, factories, prices, historical deposits, and armies live in extensions, content, and your application.

Owner Owns
Simulation core Command admission, deterministic time, phase order, atomic commit, rollback, visibility, persistence, and replay
canwu-resource Resource and unit revisions, accounts (ResourceAccount), protected floors, demands (ResourceDemand), reservations (ResourceReservation), allocation legs, transfers, consumption, loss, and fulfillment
canwu-production Processes, production sites, facilities, capacity, work orders, work in progress, execution, projects, and output settlement. It uses resource outcomes and technology evidence and keeps no inventory of its own.
canwu-transport and canwu-movement Itineraries, legs, custody handoffs, and delivery completion; canwu-movement runs movement executions and capacity pools. Cargo balance, loss, acceptance, return, and destination credit stay in canwu-resource.
Your population, military, and other domains Forming demand and reacting to fulfillment results
Your application Money, markets, wages, building UI, combat formulas, and historical content

canwu-resource is the only writer of stock balances. Other domains submit resource operations and read the outcomes; they must not write balances or another domain’s records. All of these crates sit on top of the public canwu-api.

Term Meaning
Account One stock balance (balance) of one exact resource and unit revision, held by a custodian: the holder responsible for that stock.
Protected floor An amount an account holds back from ordinary allocation, such as seed grain (ProtectedFloorPolicyRevision). A demand whose protection_override_class the policy lists may draw below it.
Demand A requester’s request for a quantity, with a minimum useful amount, a partial-fulfillment policy, due and expiry times, a priority, and a source policy.
Reservation and allocation leg The result of allocation: a quantity set aside for one demand in one account. A released or expired reservation frees the quantity again.
Transfer escrow Stock that has left its source account and is in transit. It stays counted until it is accepted, lost, or returned.
Completion lease A holder’s right to perform one irreversible operation, named by its operation key. Activating the lease yields a CompletionLeaseActivationCertificateV1, which pins the operation key, the time, and the exact records the operation depends on. Irreversible requests carry this certificate.
Adapter ingress The path provider plugins use (enqueue_resource_adapter_operation). Each operation cites the exact provider record version that justifies it.

A tracked command is a command sent with a CommandRequest (request ID and expected revision). Build the resource command with resource_command(&ResourceCommandV1 { subject, request }); the issuer must control subject.

Resource lifecycle and owners: demand, allocation by canwu-resource, then consumption or a transfer into escrow, transport, acceptance and destination credit, and later consumption. View diagram source.
View diagram source
flowchart TB
  Demand["Demand: requester sends SubmitDemand command"] --> Alloc["Allocation: host queues enqueue_resource_allocation, canwu-resource reserves stock"]
  Alloc --> Consume["Consume: consumer plugin via adapter ingress"]
  Alloc --> Begin["BeginTransfer: source custodian or grantee command, stock moves to escrow"]
  Begin --> Move["Transport: canwu-transport and canwu-movement"]
  Move --> Accept["Accept via adapter ingress"]
  Begin -. "same place_scope" .-> Local["AcceptLocal: destination custodian"]
  Move --> LoseReturn["Lose or Return: transfer controller"]
  Accept --> Credit["Destination account credited"]
  Local --> Credit
  Credit --> Later["Later boundary: consumers use the stock"]
  1. Demand. The requester submits SubmitDemand (and later AmendDemand or CancelDemand) as a tracked resource command. Adapter ingress cannot submit or amend demands.
  2. Allocation. The host queues an allocation pass for one requester with enqueue_resource_allocation. canwu-resource allocates deterministically, using protected floors, minimum useful amounts, partial-fulfillment policies, due times, priorities, and stable tie-break keys, and records reservations and allocation legs.
  3. Debit. A consumer plugin consumes an allocation through adapter ingress (Consume), or the source custodian (the grantee, for a granted allocation) starts a transfer (BeginTransfer) that moves the quantity into escrow.
  4. Transport. canwu-transport and canwu-movement carry the cargo. Transfer progress (AdvanceTransfer) arrives through adapter ingress and cites transport evidence.
  5. Arrival. CompleteTransfer settles the transfer: Accept through adapter ingress credits the destination; AcceptLocal handles a same-place handover; Lose and Return end it otherwise.
  6. Use. Production, force supply, and other consumers use the credited stock in a later boundary. They cite exact allocation and fulfillment evidence and write only their own records.

Every transfer step names the transfer’s expected_transfer_revision, so a stale or repeated step is rejected and one arrival cannot be credited twice. If a conservation, revision, or authority check fails, the operation’s changes roll back together.

canwu-resource checks conservation with ResourceState::validate_conservation over ConservationTotalsV1:

closing balances + closing transfer escrow
= opening balances + opening escrow + admitted production + external inflow
- admitted consumption - admitted loss - external outflow

Only balance is stored. Available, reserved, and protected amounts are derived (ResourceState::account_quantities). A route estimate, an accepted arrival, and a consumption are three separate facts, and each needs its own step.

For a runnable fourteen-month grain loop, run cargo run -p canwu-economy-reference --example grain_loop and read the production, resources, and military supply case.

Every ResourceDemand stores a source_policy (ResourceDemandSourcePolicyV1) that picks which accounts may supply it:

Policy Eligible accounts
Pooled (default) Every open account with the matching resource and unit revisions, visited in account-ID order
ExactAccounts(accounts) The listed accounts only. Each must exist, be open, match both revisions, and have the requester as custodian.
Granted { grant_id, accounts } The listed accounts only. Each must be custodied by the grantor of the named access grant, and the requester must be its grantee (see delegated access grants).

Rules:

  • ExactAccounts and Granted lists hold 1 to 256 accounts (MAX_DEMAND_SOURCE_ACCOUNTS), in strictly increasing ID order with no duplicates. An invalid list rejects the command before any stock changes, and allocation checks the list again.
  • Allocation computes available supply, minimum useful quantity, and partial fulfillment from the selected accounts alone. If the listed accounts cannot cover the demand, the demand gets only what they hold. Protected floors and later transfer and consumption checks still apply.
  • You can change the policy with the expected demand revision until the demand’s first allocation. After it has any reservation (including a consumed one that backs an in-flight transfer) or any fulfillment, the policy is fixed; cancel the demand and submit a new one. Cancelling a demand leaves transfers it already started in place.
  • AmendDemand cannot change a demand’s status, rejection reason, or requester; such an amendment is recorded as a rejection. Terminal demands cannot be amended.

Authority: Pooled and ExactAccounts choose where supply comes from; they give no right to spend another custodian’s stock. Your application controls who may submit pooled demands and under which requester. To let one custodian draw on another’s stock, with a cap and a time window, use an access grant and a Granted policy. Appointments, purposes, and who may grant to whom stay with your application.

Persistence: the policy is part of request digests, runtime snapshots, exact replay, and terminal demand archives. Holder-relative reports do not reveal the source list. A JSON demand without source_policy deserializes as Pooled.

An access grant (ResourceAccessGrantV1) lets a grantee draw on a grantor custodian’s stock without a prior transfer. It records:

  • grantor_custodian and grantee;
  • the exact resource_revision and unit_revision;
  • cap_quantity, the most the grant can ever supply;
  • the window valid_from..valid_until (end exclusive);
  • authority_evidence, the exact record version that justifies the grant, such as your application’s accepted requisition record.

The grantor issues it as a tracked command:

use canwu_api::{CommandEnvelope, CommandRequest, CommandRequestId, Issuer};
use canwu_resource::{
ResourceCommandV1, ResourceIssueAccessGrantRequestV1, ResourceOperationRequestV1,
resource_command,
};
let command = resource_command(&ResourceCommandV1 {
subject: grant.grantor_custodian.clone(),
request: ResourceOperationRequestV1::IssueAccessGrant(ResourceIssueAccessGrantRequestV1 {
operation_key,
grant, // a ResourceAccessGrantV1
}),
})?;
canwu.enqueue_command(
now,
0,
CommandRequest::new(
CommandRequestId::new(41),
canwu.revision(),
CommandEnvelope::new(Issuer::Actor(grantor_person), command).at_time(now),
),
)?;

How a grant is used:

  1. Issue. The grantor custodian submits IssueAccessGrant as a tracked command. The authority evidence must be an available exact record version. Adapter ingress cannot issue or revoke grants.
  2. Demand. The grantee submits a demand with ResourceDemandSourcePolicyV1::Granted { grant_id, accounts }. The grant must be active and name the requester as grantee; the resource and unit must match; the demand’s due and expiry times must fall inside the grant window; every listed account must be open and custodied by the grantor.
  3. Allocate. Allocation checks the grant before it divides scarce supply among demands. A revoked or out-of-window grant supplies nothing, and no allocation exceeds the remaining cap. Supply comes only from the listed accounts.
  4. Debit. The grantee debits the allocation, by consumption or by starting a transfer or exchange leg, under its own completion lease. A lease held by anyone else, including the grantor, cannot debit a granted allocation. The debit settles only in a boundary whose time equals the lease’s certified time, and the grant window must contain that time.

Accounting: ResourceAccessGrantRecordV1 keeps cap_quantity = remaining + reserved + debited. Allocation moves quantity from remaining to reserved, and the debit moves it to debited, so each unit is charged once. A released or expired reservation returns its quantity to remaining. Expired reservations of a granted demand are released by the next allocation pass for the grantee, which the host queues with enqueue_resource_allocation.

Transfers: a transfer started from a granted allocation records its access_grant, and the grantee then controls its cancellation, return, and loss. Local acceptance still belongs to the destination custodian.

Revocation: the grantor submits RevokeAccessGrant with the expected grant revision. Once anything has been reserved or debited under the grant, revocation is refused, so choose the cap and window to fit the delegation.

Limits and reads: grants, including revoked ones, stay in hot state, up to 4,096 per resource state (MAX_RESOURCE_ACCESS_GRANTS). Control who may issue them as you control account creation. resource_access_grant_status(canwu, holder, grant_id) returns a grant and its accounting to its grantor or grantee.

For example, a requisition flow in your application could first record the population owner’s acceptance. The owner then issues a grant citing that record, the army submits a Granted demand, and the army’s own lease debits the allocation. The access-grant test issues grants, allocates under them, debits by transfer, and revokes them through the public API.

ResourceOperationRequestV1::RecordLoss(ResourceAccountLossRequestV1) records spoilage, theft, or any other loss directly on one account. The request carries a loss_id, the account’s expected revision, a quantity, a cause, allow_protected, a time, and a completion certificate.

  • It settles as a ResourceLoss with account: Some(..) and transfer: None, counted as admitted loss in the conservation totals.
  • The debit leaves reserved stock untouched and respects the protected floor unless allow_protected is set. A stale account revision is rejected.
  • As a tracked command it must come from the account’s custodian. Through adapter ingress, the cause must be the provider’s exact source record.
  • Holder reports include ResourceLossObservationV1 entries, visible under the same rules as transfer details.

ResourceOperationRequestV1::BeginExchange(ResourceExchangeStartRequestV1) starts two transfers together: both are created, or neither is. The single outcome lists both transfer IDs in cited_transfers, whether it applied or was rejected.

  1. The parties agree on ResourceExchangeTermsV1: the exchange operation key and, for each leg, its transfer ID, exact allocation, and destination.
  2. ResourceExchangeTermsV1::leg_operation_keys derives each leg’s operation key from the digest of those terms.
  3. Each leg’s source custodian (or grantee, for a granted leg) acquires a completion lease for that leg’s derived key. The lease therefore consents to the whole exchange and fits no other terms.
  4. The debit holder of leg_a submits the exchange as a tracked command.

The exchange has no certificate of its own. After the start, the two transfers settle independently, so one may be accepted while the other is lost or returned. Each side spends only stock it holds or may draw under a grant.

ResourceTransferDispositionV1::AcceptLocal { destination, expected_destination_revision, handover_evidence } settles a transfer without a transport execution, for example a handover inside one warehouse or town. It applies only while the transfer is PendingDispatch with no transport link, and only when both accounts declare the same ResourceAccount::place_scope.

  • The scenario declares place_scope when it installs an account, and the value stays fixed. Canwu does not infer it. The default is None, which leaves local acceptance unavailable for that account. A tracked CreateAccount command cannot set it.
  • A missing or mismatched scope is rejected as invalid_definition; a transfer with a transport link, as invalid_lifecycle.
  • The terminal certificate locks the exact handover record. As a tracked command, AcceptLocal must come from the destination custodian; through adapter ingress, the handover record must be the provider’s source.

ProductionOperation::CompleteExecution may carry realized_output_per_mille and realization_evidence. The ratio is in thousandths of the process’s nominal output; None means 1,000. At completion, every output settlement quantity is scaled by the ratio and rounded down, and both values are stored on the execution. The resource credit and the output acknowledgement then settle exactly the scaled quantities.

  • A ratio other than 1,000 needs an exact evidence record version of a kind listed in the process revision’s realization_evidence_kinds. The list is empty by default, so a default process accepts only nominal output. Evidence sent with the nominal ratio is rejected.
  • The ratio must be positive and must not scale any output leg to zero. To record a total loss, cancel the work order.
  • The ratio may not exceed the process revision’s max_realized_per_mille, which defaults to 1,000; set it higher to accept evidenced yields above nominal.
  • Holder, lifecycle, ratio, and kind rules run before the evidence record is looked up, so a rejection reveals nothing about whether another record exists.

After an execution completes, a phase-12 system in canwu-production pins the current production runtime record version on the execution as its output_source and schedules one output batch to canwu-resource. The resource plugin credits each output account through its production output batch ingress, which requires the credit to cite exactly that pinned source. The generic adapter ingress rejects production credits.

A consumer plugin of your own can consume allocations through the ResourceConsumptionIntentV1 contract:

  1. In your plugin’s own active domain record, keep a top-level resource_consumption_intents map. Each key is the sealed intent’s id. No callback or reference-provider name is needed.
  2. Pass that record kind to ResourcePlugin::new so the resource adapter may read the exact source.
  3. Before submitting, complete the allocation and completion-lease steps.
  4. Submit ResourceOperationRequestV1::Consume through adapter ingress (enqueue_resource_adapter_operation). Its consumer_evidence must equal the provider’s exact current source record, and the completion certificate must lock that source and use the same operation key. The usual holder, participant, time, revision, and lease checks apply.

The resource plugin accepts the consumption only when exactly one Authorized intent in the map matches the whole allocation leg, the demand and account revisions, the consumption ID, and the operation key. Consuming part of a leg is outside this contract. The map may hold at most max_operation_outcomes entries (from the resource state’s ResourceLimitsV1); a larger map is rejected before any entry is decoded. Missing or malformed maps, identity or digest mismatches, and retired, ambiguous, or mismatched intents are rejected, and nothing is debited. A sealed digest proves the intent’s content is consistent; the permission comes from your plugin authorizing it.

Your plugin owns authorizing and retiring intents and any local consequences after the resource receipt. canwu-resource owns the conserved balance and duplicate-operation handling. The force-supply reference consumer (canwu-force-supply-reference) keeps its own adapter, which also accepts an earlier version of its source record; custom consumers use the current-source contract above.

Pin exact versions of canwu-api, canwu-resource, canwu-production, and the plugin semantic hashes from one release line. Loading restores the saved Canwu snapshot and revalidates extension records against the plugins you pass. See save compatibility.

Continue with the production, resources, and military supply case and the mechanism decision.