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 acceptance, 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. The scenario authors floors, and an account’s custodian can also set one at runtime; see protected floors.
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. A lease takes four operations, or one when the resource plugin is its only participant; see completion leases.
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. For a multi-leg execution the provider sends another AdvanceTransfer for each later leg or handoff, an intermediate leg; the transfer records each in order in its link history (link_history, at most 16 entries, 14 in transit).
  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.

Each operation has an operation key. Sending the same request again under its key returns the recorded outcome and settles nothing. A tracked command that reuses a key for a different request is refused at admission with IdempotencyConflict; if it was admitted before the first request settled, its packet is skipped with an operation_key_taken event and the outcome under the key names the request that owns it. Neither fails a boundary, and no packet that does not fit the resource root does either: it is skipped with an event and sent again when room returns, or, for work another plugin scheduled, deferred (see skipped packets and deferrals).

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 grain, production, and military supply case.

Every irreversible request (a consumption, a transfer start, a terminal transfer disposition, a credit, an external outflow, an account-level loss, a partial transfer loss) carries the activation certificate of a completion lease. Queue lease operations with enqueue_resource_completion_operation. A lease’s recipe may reserve a completion report for a holder: an Acquire, SingleStepLease, or GrantExternalParticipant that reserves one for a holder the engine refuses knowledge is a recorded invalid_authority rejection, and a reserved report is given up when its grant has no observation head, or the engine refuses the holder when the report falls due, so the participant archives without it and nothing wakes the report system again. To receive the mandatory report, the provider records the holder’s observation head at or after the completion’s revision, citing only current versions as its sources.

A lease can have several participants, each of which grants and prepares on its own, so the general path takes four operations: Acquire, Grant, Prepare, and Activate. Each leaves an operation outcome and a lease receipt. Use it when the lease needs a grant owned by another plugin, as an adapter operation does, or when another plugin coordinates the lease, as canwu-production and the force-supply reference do.

When the resource plugin is the only participant, take the lease in one operation, a single-step completion lease:

ResourceCompletionOperationV1::SingleStepLease(SingleStepCompletionLeaseV1 {
lease, // RequestCompletionLeaseV1, as for Acquire
grant_id, // the one grant, owned by canwu-resource
target_versions, // every exact target the lease locks
current_boundary,
})
  • When it applies. “Only participant” means exactly that lease.expected_participants is {"canwu-resource"}. That is the lease behind a tracked BeginTransfer, an exchange leg, CompleteTransfer with AcceptLocal, Lose, or Return, ExternalOutflow, external-inflow Credit, RecordLoss, and RecordTransferLoss. A single-step request that names any other participant set is refused with invalid_lifecycle.
  • Same checks, same certificate. The operation runs the four steps in order with the checks each has on its own. Its certificate equals the one the four steps produce for the same acquisition, grant, and targets at the same boundary, so the request that carries the certificate is unchanged.
  • Fewer records. It leaves one CompletionLease outcome, keyed resource:completion:lease:{acquisition}, and one receipt, where the four steps leave four of each. In the committed fixture, a same-place transfer leaves 15 terminal records and makes 9 archive lookups instead of 27 and 15.
  • How it enters. A tracked holder command cannot carry it, because an active lease does not expire and holds its target locks. Your host queues it, or a plugin that declares the resource completion ingress schedules it.
  • Refusals are recorded. A domain refusal is the operation’s rejected outcome, and it leaves no acquisition, grant, lock, or spent request token behind. A refused request has used its operation key, so retry under a new acquisition ID. A resend of the same request returns its recorded outcome and is not a second lease. Neither path resumes an acquisition the resource record already holds: a single-step request or an Acquire that names one is a recorded idempotency_conflict rejection. What the plugin finds against the simulation view (an eligibility time that is not the boundary time, an external record version that is not current) is a recorded rejection too, where the four-step path skips the packet.

Room is measured on the result, as for every resource operation. A refused single step needs room for its outcome only. An applied one takes 2 + 16 archive candidate slots and 1 + 16 hot outcome slots, beside the 16 of each that every active lease already reserves: with the canonical budget of 256 and no backlog, the fifteenth concurrently active lease is the last that fits. One that does not fit is not failed but skipped, with archive_backlog when an archive commit would make room and reserved_capacity when the room is held by the reservations of active leases, which only their completion, release, or expiry frees; send it again when room returns. A failed archive lookup (a missing or damaged archive object, never a miss) still fails the boundary.

Two things to watch:

  • An active lease never expires, and a single-step lease is active at once. If its operation is never issued, or is rejected, the lease keeps its target locks and its 16 reserved slots until your host sends Release.
  • A holder may still send a tracked Acquire. It must be eligible at the instant it is admitted and cite current evidence, and while that acquisition waits for its first grant, every single-step request that sorts after it in the fairness order is refused with invalid_lifecycle. A host that uses only single-step leases watches for such acquisitions and aborts them. If the holder’s own rejected Abort has already used the abort key, grant the stray acquisition and release that grant instead.

An ordinary request cannot use an operation key under the reserved resource:completion: prefix; one that does is skipped with reserved_operation_key. A holder can still take a completion key with the two completion operations it may send as tracked commands: an Acquire, and an Abort of a lease it holds. A packet whose key a different request already holds settles nothing on any ingress, and its boundary commits; the skip event names it with operation_key_taken, and the outcome under the key is the other request’s.

A completion lease’s targets say what its operation may touch, not which operation it is: an outflow, an inflow, and an account loss with a record cause all lock an account revision and one record version. The lease’s issuer (your host, or the plugin that coordinates the lease) can bind it to one operation: EligibilityEnvelopeV1::bound_to sets the envelope’s operation_binding (CompletionOperationBindingV1::new(kind, quantity), and for a CompleteTransfer lease optionally with_disposition). The acquisition stores the envelope’s digest and the activation certificate carries it, so a binding cannot change after the lease is requested. An unbound envelope encodes and digests as in 0.18.0.

  • Only the issuer binds. A holder’s tracked Acquire whose envelope carries a binding is a recorded invalid_authority rejection.

  • Every redemption is checked. Redeeming a bound lease for another kind, another disposition (when the binding names one), or another quantity is a recorded invalid_authority rejection. The bound quantity is the credit, outflow, or loss quantity; the allocation leg’s quantity for a consumption or a transfer start (each leg’s own for an exchange); the items’ legs together for a consumption batch; and the escrow a terminal transfer disposition settles.

  • Tracked commands are refused early. A tracked command is refused at admission, settling nothing and taking no operation key, when:

    • its lease includes another plugin’s participant grant or names another plugin as an expected participant (such a lease authorizes that plugin’s operation, which arrives through adapter ingress);
    • the request itself shows that the lease’s binding does not admit it; or
    • it is an external-inflow Credit with an unbound lease.

    The operation the lease was issued for can still settle under its own key. Settlement checks again: another plugin’s lease that appeared since admission is skipped with failed_settlement_checks and takes no key; a lease whose binding changed, a disposition whose escrow differs from its binding, and an adapter redemption the binding does not admit are recorded invalid_authority rejections, which take their key.

An account’s protected floor is the ProtectedFloorPolicyRevision that its protected_floor_policy names. A revision is immutable and is one of two kinds:

Kind Identity Who installs it How long it stays
Authored Chosen by the author The scenario, with ResourceState::install_protected_floor_policy Always
Content-addressed Derived from the content, as resource:protected-floor:{digest} The account’s custodian at runtime, or the scenario Only while an account or a demand refers to it

To set a floor of any quantity during a run, the custodian builds a content-addressed revision and sends its body in install and its ID in policy. SetProtectedFloor installs the revision, if the resource record does not hold it yet, and points the account at it, together or not at all:

let floor = ProtectedFloorPolicyRevision::content_addressed(
ResourceRevision::INITIAL,
180,
BTreeSet::new(),
)?;
ResourceOperationRequestV1::SetProtectedFloor(ResourceProtectedFloorRequestV1 {
operation_key,
account,
expected_account_revision,
policy: Some(floor.id.clone()),
install: Some(floor),
})
  • Always send the body. A content-addressed revision is named with its body every time. Its ID alone is refused with invalid_definition, whether or not the record holds the revision, so the answer tells a custodian nothing about what other accounts protect. policy alone names an authored revision, or None to clear the floor.
  • Authority. The operation needs no completion lease, leaves one SetProtectedFloor outcome, and advances the account revision. A tracked command must come from the account’s custodian. An access grant is consent to draw on stock, not custody, so a grantee cannot set the grantor’s floor.
  • One shared quota. The record holds at most 4,096 revisions of both kinds (MAX_PROTECTED_FLOOR_POLICY_REVISIONS). A runtime install past it settles as a recorded limit_exceeded rejection. The quota is shared by every custodian and is below the canonical 8,192 accounts, so if your game lets holders set floors, limit how many distinct floors each may hold.
  • Unused revisions are retired. Every applied SetProtectedFloor removes the content-addressed revisions that no account and no demand refers to. A floor that changes every day keeps one revision. Accounts that set equal content share one. A demand keeps the revision it cites until the demand is archived.
  • Keep the body if you need it later. Retirement removes the only copy of the body the engine holds. An archived demand keeps the ID of the floor it was admitted under and nothing else.
  • Open demands go stale. A floor change advances the account revision. An open demand cites the floor it was admitted under, so its allocation pass settles as a version_conflict rejection until the demand is amended to the floor in force.

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. It also cannot ask for less than what the demand already holds: fulfilled, actively reserved, and in flight together (a returning transfer does not count). Such an amendment is a recorded invalid_lifecycle 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 the exact current version of its record; an earlier version is rejected. 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 the resource root for its life, in live buckets, up to 16,384 per resource state (MAX_RESOURCE_ACCESS_GRANTS); they are not archived. 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::RecordTransferLoss(ResourceTransferLossRequestV1) records the loss of part of an open transfer’s escrow, such as grain that spoils on the way. The request carries a loss_id, the transfer, its expected revision, a quantity, a cause, a time, and a completion certificate.

  • The transfer keeps its state and the rest of its escrow: the quantity moves from escrow to lost, the transfer revision advances, and a ResourceLoss with transfer: Some(..) counts as admitted loss. It is allowed in every state that is not terminal, any number of times.
  • The quantity must be above zero (invalid_definition otherwise) and below the escrow (invalid_lifecycle). To lose everything that is left, end the transfer with CompleteTransfer and Lose, which loses whatever the partial losses left.
  • The certificate locks the transfer at the expected revision and, for a record cause, that record version. The loss completes only its own lease; the lease the transfer was started under stays until its terminal disposition. A lease that already locks the transfer’s current revision for a terminal disposition keeps the loss’s lease from locking it: release that lease, and take a new one for the revision the loss leaves.
  • Because the transfer revision advances, a queued advance or cancellation that expects the earlier revision is rejected with version_conflict.
  • As a tracked command it must come from the transfer’s controller, as for Lose: the source custodian, or the grantee of a transfer started under an access grant. Through adapter ingress, the cause must be the provider’s exact source record.
  • Acceptance credits the demand with what is accepted, not with the allocation: a transfer of 100 that lost 30 and is accepted records a fulfillment of 70, so the demand keeps 30 of remainder, which the next allocation pass may supply. An allocation pass counts what open transfers carry toward a demand as drawn, so escrow in flight is never allocated twice; a returning transfer is never accepted, so its escrow is needed again as soon as it turns back. A demand that ends while a transfer for it is in flight keeps its terminal status when the transfer is accepted, and its records stay in the live root until the transfer ends.
  • The transfer does not list its losses; each loss names the transfer, and lost totals them. A loss identity is used once for the run, even after its loss is archived.

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; afterwards only the host application changes it, with enqueue_resource_account_rescope, citing the current version of the record of the move. 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 rescope (ResourceRescopeAccountRequestV1 { operation_key, account, expected_account_revision, place_scope, evidence }) needs no completion lease and advances the account revision. Its evidence must be the current version of a record of a kind passed to ResourcePlugin::new; the engine checks only that the evidence is current and of an allowed kind, not that it shows the stock at the new place. It is refused while a transfer that is not terminal names the account or a lease locks the account’s current revision. Reservations, demands, and access grants that name the account carry over.
  • 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.
  • Both legs of an exchange may be accepted locally against one handover record, but not in one boundary: the first leg’s terminal certificate locks the record’s exact version from its activation until its completion. Settle the second leg in a further boundary at the same simulated instant (a host may settle several boundaries at one instant; take the second leg’s lease once the first acceptance has settled, as each holder’s lease cooldown allows), or give each leg its own handover record.

Credit with ResourceCreditSourceV1::ExternalInflow(DomainRecordVersionRef) books value from outside the modelled accounts, such as tribute or a purchase from abroad, the counterpart of ExternalOutflow. It counts in ConservationTotalsV1::external_inflow, and its applied outcome keeps the cited record version as evidence.

  • As a tracked command from the account’s custodian, it needs a single-step lease that locks the account revision and the inflow’s record version and is bound to exactly that credit: build the lease’s eligibility envelope with bound_to(CompletionOperationBindingV1::new(ResourceOperationKind::Credit, quantity)) and queue the lease with enqueue_resource_completion_operation. A tracked inflow whose lease is unbound, or bound to another kind or quantity, is refused at admission, so a custodian books only the quantity your host bound, once.
  • Through adapter ingress, a provider plugin may carry it with the record as its provider source, under its own participant grant. No binding is needed there, and a bound lease is checked as any other.
  • The engine sets no cap on inflow: what bounds a holder’s inflow is the leases your host issues. A production credit stays refused as a tracked command and on adapter ingress; it settles only through the production output batch.

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 evidence record 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 evidence must be the exact current version of its record. An earlier version is rejected even while the run still keeps it, so a run sealed with seal_evidence accepts the same completions as its exact replay.
  • 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, every credit or none. The generic adapter ingress rejects production credits. A batch that finds no room is deferred until an archive commit or a lease release makes room, and its acknowledgement carries settled_at, the instant the credits settled, which the execution records as output_settled_at and the archive rebuilds the request digests with. StartExecution refuses an output key that could never be credited (one under the reserved prefix, one another unsettled execution names, or one a resource outcome already holds), more than 64 outputs, and an output set the resource root can never hold, so an execution never starts into a batch that must be skipped.

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. In 0.20.0 the resource state format is 6 and both resource bucket formats are 2; a continuation checkpoint or seal taken while a completion lease is in progress loads. See save compatibility.

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