生产、资源与运输的职责划分
当你的游戏或模型里有会耗尽的存量,比如粮食、布匹、弹药或建材,就读这一页。它说明每一步由哪个 crate 负责、各步骤的先后顺序,以及需求来源、动用授权、损耗、交换、本地验收、生产产出和自定义消费者的规则。
这些能力由可选的 canwu-resource 和 canwu-production 扩展提供。模拟内核保持通用:粮食、工厂、价格、历史矿点和军队都放在扩展、内容和你的应用中。
| 负责方 | 负责内容 |
|---|---|
| 模拟内核 | 命令准入、确定性时间、阶段顺序、原子提交、回滚、可见性、存档与重放 |
canwu-resource |
资源与单位版本、账户(ResourceAccount)、受保护存量底线、需求(ResourceDemand)、资源预留(ResourceReservation)、分配腿、转移、消费、损耗和履约 |
canwu-production |
工艺、生产地点、设施、产能、工单、在制品、执行、项目和产出结算。它使用资源结果和技术证据,自己不保存库存。 |
canwu-transport 与 canwu-movement |
行程、路段、保管交接和交付完成;canwu-movement 负责移动执行和运力池的生命周期。货物余额、损耗、验收、退回和目的地入账仍由 canwu-resource 负责。 |
| 你的人口、军事等领域 | 形成需求,并对履约结果作出反应 |
| 你的应用 | 货币、市场、工资、建筑界面、战斗公式和历史内容 |
canwu-resource 是库存余额的唯一写入者。其他领域提交资源操作并读取结果,不得直接写余额,也不得改写其他领域的记录。这些 crate 都建立在对外 canwu-api 之上。
| 术语 | 含义 |
|---|---|
| 账户 | 某一确切资源与单位版本的一份库存余额(balance),由保管人持有;保管人即对这份库存负责的持有人。 |
| 受保护存量底线 | 账户不参与普通分配的保留量,例如种粮(ProtectedFloorPolicyRevision)。需求的 protection_override_class 若列在该政策中,可以动用底线以下的存量。底线可以由场景预先编写,也可以由账户的保管人在运行中设定,见受保护存量底线。 |
| 需求 | 申请者对一定数量的请求,带有最低有用量、部分履约策略、到期和失效时间、优先级以及来源策略。 |
| 资源预留与分配腿 | 分配的结果:在某个账户中为某项需求留出的数量。预留被释放或过期后,这部分数量重新可用。 |
| 转移托管量 | 已离开来源账户、正在途中的存量。在被验收、损耗或退回之前,它一直计入总量。 |
| 完成租约 | 持有人执行一项不可逆操作的权利,以操作键标识。激活租约得到 CompletionLeaseActivationCertificateV1,它固定操作键、时间和操作所依赖的精确记录。不可逆请求都要携带这份凭证。取得租约要四个操作;资源插件是唯一参与方时,一个操作即可,见完成租约。 |
| 适配器输入 | 提供方插件使用的输入路径(enqueue_resource_adapter_operation)。每项操作都要引用证明其合法的提供方精确记录版本。 |
带请求标识的命令指通过 CommandRequest(含请求标识和预期修订号)提交的命令。资源命令用 resource_command(&ResourceCommandV1 { subject, request }) 构建,签发者必须能代表 subject。
查看图表源码
flowchart TB
Demand["需求:申请者提交 SubmitDemand 命令"] --> Alloc["分配:上层应用排入 enqueue_resource_allocation,canwu-resource 预留存量"]
Alloc --> Consume["消费:消费者插件经适配器输入"]
Alloc --> Begin["BeginTransfer:来源保管人或被授权方提交命令,存量进入托管"]
Begin --> Move["运输:canwu-transport 与 canwu-movement"]
Move --> Accept["经适配器输入 Accept"]
Begin -. "相同 place_scope" .-> Local["AcceptLocal:目的地保管人"]
Move --> LoseReturn["Lose 或 Return:转移控制方"]
Accept --> Credit["目的地账户入账"]
Local --> Credit
Credit --> Later["之后的结算边界:消费者使用存量"]
- 需求。 申请者以带请求标识的资源命令提交
SubmitDemand,之后可用AmendDemand或CancelDemand修改或取消。适配器输入不能提交或修改需求。 - 分配。 上层应用用
enqueue_resource_allocation为某个申请者排入一次分配。canwu-resource依据受保护存量底线、最低有用量、部分履约策略、到期时间、优先级和稳定的 tie-break 键做确定性分配,并记录资源预留和分配腿。 - 扣减。 消费者插件经适配器输入消费分配结果(
Consume),或者由来源保管人(授权下的分配则为被授权方)发起转移(BeginTransfer),把相应数量移入托管。 - 运输。
canwu-transport和canwu-movement负责运送。转移进度(AdvanceTransfer)经适配器输入提交,并引用运输证据。多段运输执行中,提供方为之后的每个路段或交接(中间路段)再发送一次AdvanceTransfer;转移按顺序把它们记入运输关联历史(link_history,最多 16 条,其中在途的最多 14 条)。 - 到达。
CompleteTransfer结束转移:经适配器输入的Accept为目的地入账;AcceptLocal处理同地交接;其余情况用Lose或Return结束。 - 使用。 生产、军事补给等消费者在之后的结算边界使用已入账的存量。它们引用精确的分配与履约证据,只写自己的记录。
转移的每一步都要给出 expected_transfer_revision,过期或重复的步骤会被拒绝,同一次到货不会入账两次。守恒、版本或权限校验任一失败,该操作的全部变化一起回滚。
每个操作都有一个操作键。用同一个键重发同一个请求,会拿回已记录的结果,什么也不结算。带请求标识的命令如果用已有的键提交内容不同的请求,会在准入时以 IdempotencyConflict 被拒绝;如果它在第一个请求结算之前就已准入,它的数据包会以 operation_key_taken 事件被跳过,该键之下的操作结果会写明键属于哪个请求。两种情况都不会让边界失败;放不进资源记录的数据包也不会,它会带着事件被跳过,空间恢复后重发,其他插件安排的工作则被延后(见跳过的数据包与延后结算)。
canwu-resource 用 ResourceState::validate_conservation 按 ConservationTotalsV1 检查守恒:
期末余额 + 期末转移托管量 = 期初余额 + 期初托管量 + 已准入产出 + 外部流入 - 已准入消费 - 已准入损耗 - 外部流出只有 balance 是存储值;可用量、预留量和受保护量都是派生出来的(ResourceState::account_quantities)。路线估算、到货验收和消费是三件独立的事,各自需要单独的步骤。
可运行的十四个月粮食循环:执行 cargo run -p canwu-economy-reference --example grain_loop,并参阅粮食、生产与军事补给案例。
每个不可逆请求(消费、发起转移、转移的终态处置、入账、对外流出、账户损耗、转移部分损耗)都要携带一份完成租约的激活凭证。租约操作用 enqueue_resource_completion_operation 排入队列。租约的配方可以为持有人预留一份完成报告:为引擎不接受知识的持有人预留报告的 Acquire、SingleStepLease 或 GrantExternalParticipant 记为 invalid_authority 拒绝;预留的报告在授权没有观察头,或持有人到期时被引擎拒绝时放弃,参与者不带报告归档,也不会再唤醒报告系统。要拿到这份强制报告,提供方在完成的修订号或之后记录持有人的观察头,观察头引用的来源必须是其记录的当前版本。
一份租约可以有多个参与方,各自授权、各自准备,所以通用路径要四个操作:Acquire、Grant、Prepare 和 Activate,每个操作都留下一条操作结果和一条租约凭证。租约需要其他插件拥有的授权时(例如适配器操作),或者租约由其他插件协调时(例如 canwu-production 和军事补给参考消费者),走这条路径。
如果资源插件是唯一的参与方,一个操作就够了,这就是一步式完成租约:
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,})- 适用场合。 “唯一的参与方”的确切含义是:
lease.expected_participants正好是{"canwu-resource"}。带请求标识的BeginTransfer、交换的一条腿、以AcceptLocal、Lose或Return处置的CompleteTransfer、ExternalOutflow、外部流入的Credit、RecordLoss和RecordTransferLoss,背后都是这种租约。写了其他参与方的一步式请求会以invalid_lifecycle被拒绝。 - 检查相同,凭证相同。 这个操作依次执行四个步骤,每一步的检查与单独执行时一样。对同一个申请、同一份授权和同一组目标,只要激活发生在同一个边界,它得到的凭证与四步路径完全相同,所以携带凭证的请求不需要任何改动。
- 记录更少。 它只留下一条
CompletionLease操作结果(键为resource:completion:lease:{acquisition})和一条凭证,四步路径则各留四条。在仓库的测试用例中,一次同地转移留下 15 条终态记录、做 9 次归档查找,而四步路径是 27 条和 15 次。 - 进入方式。 带请求标识的持有人命令不能携带它,因为已激活的租约不会过期,并且一直锁着目标。它由上层应用排入队列,或者由声明了资源完成租约输入的插件安排。
- 拒绝会被记录。 领域层面的拒绝记为这个操作的被拒绝结果,不会留下任何申请、授权、锁或已消耗的请求额度。被拒绝的请求已经用掉了它的操作键,重试时要换一个新的申请 ID。重发同一个请求会拿回已记录的结果,不会产生第二份租约。两条路径都不会接着处理资源记录里已有的申请:一步式请求或
Acquire如果写了已有的申请 ID,会记为一条idempotency_conflict拒绝。插件对照模拟视图发现的问题(可用时间不是边界时间、外部记录版本不是当前版本)同样记为拒绝;在四步路径上,这样的数据包则被跳过。
和每个资源操作一样,空间按结果计算。被拒绝的一步式租约只需要它结果的空间。已应用的占用 2 + 16 个归档候选名额和 1 + 16 个热操作结果名额,此外每份已激活的租约还各自预留着 16 个:默认预算 256、没有积压时,同时处于激活状态的租约最多 15 份。放不下的租约不会让边界失败,而是被跳过:归档提交能腾出空间时原因是 archive_backlog,空间被已激活租约的预留占着时原因是 reserved_capacity,后者只有租约完成、释放或过期才能释放;空间恢复后重发即可。归档查找失败(归档对象缺失或损坏,绝不会被当成“没有归档过”)仍然会让边界失败。
还有两点需要留意:
- 已激活的租约不会过期,而一步式租约一结算就处于激活状态。如果它授权的操作一直没有发出,或者被拒绝了,租约会一直占着目标锁和 16 个预留名额,直到上层应用发送
Release。 - 持有人仍然可以用带请求标识的命令发送
Acquire。它在准入时就必须已经可用,并且引用当前的证据;这样的申请在等待第一份授权期间,公平排序中排在它后面的所有一步式请求都会以invalid_lifecycle被拒绝。只使用一步式租约的上层应用要留意这种申请,并把它中止。如果持有人自己那条被拒绝的Abort已经占用了中止操作的键,就改为给这份申请授权,再释放该授权。
普通请求不能使用以保留前缀 resource:completion: 开头的操作键,用了的会以 reserved_operation_key 被跳过。持有人仍然可以通过它能以带请求标识的命令发送的两种完成操作占用完成操作的键:Acquire,以及对自己持有的租约的 Abort。键已被另一个请求占用的数据包,在任何入口都什么也不结算,所在边界照常提交;跳过事件以 operation_key_taken 指明它,该键下的操作结果属于另一个请求。
租约操作绑定
Section titled “租约操作绑定”完成租约的目标只说明它的操作可以动哪些记录,不说明是哪一个操作:外部流出、外部流入,以及以记录为原因的账户损耗,锁定的都是一个账户修订号和一个记录版本。租约的签发方(上层应用,或协调这份租约的插件)可以把租约绑定到一个操作上:EligibilityEnvelopeV1::bound_to 设置资格信封的 operation_binding(CompletionOperationBindingV1::new(kind, quantity);CompleteTransfer 的租约还可以用 with_disposition 指定处置方式)。申请保存信封的摘要,激活凭证也带着它,所以租约申请之后,绑定就无法再改。未绑定的信封,编码和摘要都与 0.18.0 相同。
-
只有签发方能绑定。 持有人以带请求标识的命令发送的
Acquire如果信封带有绑定,会记为一条invalid_authority拒绝。 -
每次兑现都要检查。 把已绑定的租约用于另一种操作、另一种处置方式(绑定写明了处置方式时)或另一个数量,会记为一条
invalid_authority拒绝。绑定的数量是:入账、流出或损耗的数量;消费或发起转移时分配腿的数量(交换则是每条腿各自的数量);消费批次各项分配腿的合计;以及终态转移处置所结算的托管量。 -
带请求标识的命令提前被拒。 出现以下情况时,带请求标识的命令在准入时就被拒绝,什么也不结算,也不占用操作键:
- 它的租约包含其他插件持有的参与方授权,或者把其他插件列为预期参与方(这样的租约授权的是那个插件的操作,该操作经适配器输入到达);
- 从请求本身就能看出租约的绑定不接受它;
- 它是外部流入的
Credit,而租约没有绑定。
租约原本授权的操作仍可以用它自己的键结算。结算时还会再检查一次:准入之后才出现的其他插件的租约会以
failed_settlement_checks被跳过,不占用操作键;绑定有变化的租约、所结算的托管量与绑定不符的处置,以及绑定不接受的适配器兑现,都会记为invalid_authority拒绝,并占用各自的操作键。
受保护存量底线
Section titled “受保护存量底线”账户的受保护存量底线,就是它的 protected_floor_policy 指向的那个 ProtectedFloorPolicyRevision。政策版本不可变,分为两种:
| 种类 | 身份 | 由谁安装 | 保留多久 |
|---|---|---|---|
| 预先编写 | 由编写者指定 | 场景,通过 ResourceState::install_protected_floor_policy |
始终保留 |
| 按内容寻址 | 由内容推导,形如 resource:protected-floor:{digest} |
账户的保管人在运行中安装,场景也可以安装 | 只在仍有账户或需求引用它时保留 |
要在运行中设定任意数量的底线,保管人先构造一个按内容寻址的版本,把版本本体放进 install,把它的 ID 放进 policy。SetProtectedFloor 会在资源记录里还没有这个版本时安装它,并让账户指向它,两件事要么都发生,要么都不发生:
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),})- 每次都要带上本体。 按内容寻址的版本每次都必须连同本体一起给出。只给 ID 会以
invalid_definition被拒绝,不管资源记录里有没有这个版本,这样保管人就无法借此探知别的账户保护了多少。只填policy表示指向一个预先编写的版本;填None则取消底线。 - 权限。 这个操作不需要完成租约,只留下一条
SetProtectedFloor操作结果,并让账户修订号前进一次。带请求标识的命令必须来自账户的保管人。动用授权只是同意对方动用存量,并不转移保管权,所以被授权方不能替授权方设定底线。 - 名额是共用的。 资源记录最多保存 4,096 个政策版本,两种合计(
MAX_PROTECTED_FLOOR_POLICY_REVISIONS)。运行中的安装如果超出上限,会结算为一条被记录的limit_exceeded拒绝。这个名额由所有保管人共用,而且低于默认的 8,192 个账户上限,所以如果你的游戏允许持有人自行设定底线,要限制每个持有人能保有多少种不同的底线。 - 没人引用的版本会被退役。 每次成功应用
SetProtectedFloor,都会移除没有任何账户或需求引用的按内容寻址版本。每天都改的底线,资源记录里始终只留一个版本;内容相同的账户共用一个版本。需求引用的版本会一直保留到需求被归档。 - 以后还要用,就自己留一份本体。 退役会移除引擎手里唯一的一份本体。已归档的需求只保留它准入时所依据的底线的 ID。
- 未结的需求要跟着修订。 改动底线会让账户修订号前进。未结的需求引用的还是它准入时的底线,所以它的分配会结算为
version_conflict拒绝,直到把需求修订为当前生效的底线。
需求来源策略
Section titled “需求来源策略”每个 ResourceDemand 都保存一个 source_policy(ResourceDemandSourcePolicyV1),决定哪些账户可以为它供给:
| 策略 | 可供给的账户 |
|---|---|
Pooled(默认) |
资源版本和单位版本都匹配的所有开放账户,按账户 ID 顺序访问 |
ExactAccounts(accounts) |
只限列出的账户。每个账户必须存在、未关闭、两个版本都匹配,且保管人是申请者本人。 |
Granted { grant_id, accounts } |
只限列出的账户。每个账户都必须由所指动用授权的授权方保管,且申请者必须是该授权的被授权方(见动用授权)。 |
规则:
ExactAccounts和Granted的列表含 1 到 256 个账户(MAX_DEMAND_SOURCE_ACCOUNTS),ID 严格递增、不能重复。列表无效时,命令在任何存量变化之前就被拒绝;分配时还会再检查一次。- 分配只根据所选账户计算可用量、最低有用量和部分履约。列出的账户不够时,需求只能拿到这些账户里的存量。受保护存量底线以及之后的转移、消费校验照常适用。
- 在需求第一次分配之前,可以凭预期需求修订号修改策略。一旦有了任何资源预留(包括已消费、但仍对应在途转移的预留)或任何履约,策略就固定下来;要换策略,请取消需求再提交新需求。取消需求不会影响它已经发起的转移。
AmendDemand不能修改需求的状态、拒绝原因或申请者,这类修改会被记录为拒绝。它也不能把请求量改得少于需求已经占用的数量,即已履约、已预留和在途数量之和(退回中的转移不计在内);这样的修改会记为一条invalid_lifecycle拒绝。已终止的需求不能修改。
权限:Pooled 和 ExactAccounts 只决定从哪里取得供给,不赋予动用其他保管人存量的权利。由你的应用控制谁可以提交资源池需求、以哪个申请者身份提交。要让一个保管人在限额和时间窗口内动用另一方的存量,请使用动用授权和 Granted 策略。任命、用途以及谁可以授权给谁,仍由你的应用决定。
持久化:策略进入请求摘要、运行时快照、精确重放和已终止需求的归档。持有人相对报告不会公开来源列表。缺少 source_policy 的 JSON 需求会反序列化为 Pooled。
动用授权(ResourceAccessGrantV1)让被授权方无需先转移,就能动用授权方保管的存量。它记录:
grantor_custodian(授权方保管人)和grantee(被授权方);- 精确的
resource_revision与unit_revision; cap_quantity,这份授权最多能供给的总量;- 有效期
valid_from..valid_until(不含终点); authority_evidence,证明这份授权成立的精确记录版本,例如应用中已被接受的征发记录。
授权方以带请求标识的命令签发授权:
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, // 一个 ResourceAccessGrantV1 }),})?;canwu.enqueue_command( now, 0, CommandRequest::new( CommandRequestId::new(41), canwu.revision(), CommandEnvelope::new(Issuer::Actor(grantor_person), command).at_time(now), ),)?;授权的使用过程:
- 签发。 授权方保管人以带请求标识的命令提交
IssueAccessGrant。权限证据必须是其记录的精确当前版本;较早的版本会被拒绝。适配器输入不能签发或撤销授权。 - 提出需求。 被授权方提交来源策略为
ResourceDemandSourcePolicyV1::Granted { grant_id, accounts }的需求。授权必须有效且被授权方就是申请者;资源和单位必须匹配;需求的到期和失效时间必须落在授权有效期内;列出的每个账户都必须开放并由授权方保管。 - 分配。 分配在多个需求之间分摊紧缺存量之前,先检查授权。已撤销或不在有效期内的授权不提供任何供给,任何分配都不会超过剩余额度。供给只来自列出的账户。
- 扣减。 被授权方在自己的完成租约下扣减分配结果,方式可以是消费,也可以是发起转移或交换的一条腿。其他任何人(包括授权方)持有的租约都不能扣减授权下的分配。扣减只在时间等于租约认证时间的结算边界结算,且该时间必须位于授权有效期内。
额度核算:ResourceAccessGrantRecordV1 始终满足 cap_quantity = remaining + reserved + debited。分配把数量从 remaining 移到 reserved,扣减再移到 debited,每个单位只计一次。预留被释放或过期时,数量回到 remaining。授权需求中过期的预留,会在为被授权方运行的下一次分配中释放;这次分配由上层应用通过 enqueue_resource_allocation 排入。
转移:由授权下的分配发起的转移会记录其 access_grant,之后由被授权方控制它的取消、退回和损耗。本地验收仍归目的地保管人。
撤销:授权方凭预期授权修订号提交 RevokeAccessGrant。授权下一旦有过任何预留或扣减,撤销就会被拒绝,所以额度和有效期要按实际委托来定。
上限与读取:授权(包括已撤销的)在资源根的整个生命期内都保存在活动桶中,每个资源状态最多 16,384 份(MAX_RESOURCE_ACCESS_GRANTS),不会归档。要像控制账户创建一样控制谁能签发授权。resource_access_grant_status(canwu, holder, grant_id) 向授权方或被授权方返回授权内容及其额度核算。
举个例子:应用中的征发流程先记录人口所有者的同意;所有者再引用这条记录签发授权,军队提交 Granted 需求,并用自己的租约扣减分配结果。动用授权测试通过对外 API 演示了签发授权、在授权下分配、以转移方式扣减以及撤销授权。
ResourceOperationRequestV1::RecordLoss(ResourceAccountLossRequestV1) 直接在单个账户上记录腐坏、失窃或其他损耗。请求携带 loss_id、账户的预期修订号、数量、cause、allow_protected、时间和完成凭证。
- 它结算为
account: Some(..)、transfer: None的ResourceLoss,在守恒总量中计为已准入损耗。 - 扣减不动用已预留的存量;除非设置了
allow_protected,否则遵守受保护存量底线。账户修订号过期会被拒绝。 - 作为带请求标识的命令时,必须来自账户保管人。经适配器输入时,
cause必须是提供方的精确来源记录。 - 持有人报告会包含
ResourceLossObservationV1条目,可见性规则与转移详情相同。
转移部分损耗
Section titled “转移部分损耗”ResourceOperationRequestV1::RecordTransferLoss(ResourceTransferLossRequestV1) 记录在途转移的一部分托管量的损耗,例如途中腐坏的粮食。请求携带 loss_id、转移、转移的预期修订号、数量、cause、时间和完成凭证。
- 转移保持原来的状态和其余的托管量:这部分数量从
escrow移到lost,转移修订号前进,并记下一条transfer: Some(..)的ResourceLoss,计为已准入损耗。转移处于任何非终态时都可以记录,次数不限。 - 数量必须大于零(否则以
invalid_definition拒绝),并且小于托管量(否则以invalid_lifecycle拒绝)。要让剩下的全部损耗,请用CompleteTransfer的Lose结束转移,部分损耗之后剩下的数量都会记为损耗。 - 凭证锁定处于预期修订号的转移;原因是领域记录时,还锁定那个记录版本。这次损耗只完成它自己的租约,发起转移时的租约一直保留到终态处置。如果已有一份租约为终态处置锁定了转移的当前修订号,损耗的租约就锁不上:请先释放那份租约,再按损耗之后的修订号取一份新的。
- 由于转移修订号前进,仍按旧修订号排队的推进或取消会以
version_conflict被拒绝。 - 作为带请求标识的命令时,它必须来自转移的控制方,与
Lose相同:来源保管人,或者授权下发起的转移的被授权方。经适配器输入时,cause必须是提供方的精确来源记录。 - 验收按实际验收的数量为需求记履约,而不是按分配:一项 100 的转移损耗 30 之后被验收,记下 70 的履约,需求还剩 30,下一次分配可以补上。分配时把在途转移为需求运送的数量计为已动用,所以在途的托管量不会被分配两次;退回中的转移永远不会被验收,所以它一开始退回,这部分托管量就重新需要供给。为需求运送的转移还在途中时需求已经终止的,转移被验收时需求保持原来的终态,它的记录留在活动根里,直到转移结束。
- 转移不列出自己的损耗;每条损耗都写明所属的转移,
lost是它们的合计。损耗标识在整个运行期内只能用一次,损耗归档之后也一样。
ResourceOperationRequestV1::BeginExchange(ResourceExchangeStartRequestV1) 同时发起两项转移:要么都创建,要么都不创建。无论成功还是被拒绝,唯一的结果都会在 cited_transfers 中列出两个转移 ID。
- 双方先约定
ResourceExchangeTermsV1:交换的操作键,以及每条腿的转移 ID、精确分配和目的地。 ResourceExchangeTermsV1::leg_operation_keys根据这些条款的摘要派生每条腿的操作键。- 每条腿的来源保管人(授权下的腿则为被授权方)为该腿的派生键取得完成租约。这份租约因此代表对整个交换的同意,换成其他条款就用不上。
leg_a的扣减方把交换作为带请求标识的命令提交。
交换本身没有单独的凭证。发起之后,两项转移各自结算,可能一项被验收、另一项损耗或退回。每一方动用的都只是自己保管的、或经授权可以动用的存量。
ResourceTransferDispositionV1::AcceptLocal { destination, expected_destination_revision, handover_evidence } 不经运输执行就结算一项转移,例如同一仓库或同一城镇内的交接。它只适用于仍处于 PendingDispatch、没有运输关联的转移,且两个账户声明了相同的 ResourceAccount::place_scope。
place_scope由场景在安装账户时声明,之后只有上层应用可以用enqueue_resource_account_rescope更改,并须引用记载这次搬迁的那条记录的当前版本;参伍不做推断。默认值None表示该账户不能使用本地验收。带请求标识的CreateAccount命令不能设置它。- 更改地点范围的请求(
ResourceRescopeAccountRequestV1 { operation_key, account, expected_account_revision, place_scope, evidence })不需要完成租约,会让账户修订号前进。它的证据必须是某条记录的当前版本,记录种类必须是传给ResourcePlugin::new的种类之一;引擎只检查证据是否为当前版本、种类是否允许,不检查它是否表明存量已在新地点。有非终态的转移指向该账户、或有租约锁定账户的当前修订号时,请求会被拒绝。引用该账户的资源预留、需求和动用授权都保持不变。 - 地点范围缺失或不一致时以
invalid_definition拒绝;已有运输关联的转移以invalid_lifecycle拒绝。 - 终结凭证会锁定精确的交接记录。作为带请求标识的命令时,
AcceptLocal必须来自目的地保管人;经适配器输入时,交接记录必须是提供方的来源记录。 - 交换的两条腿可以都对同一条交接记录做本地验收,但不能在同一个边界中:第一条腿的终结凭证从激活到完成一直锁定这条记录的精确版本。可以在同一模拟时刻的下一个边界结算第二条腿(上层应用可以在同一时刻结算多个边界;等第一次验收结算之后,在各持有人的租约冷却允许时再为第二条腿取租约),也可以为每条腿各用一条交接记录。
外部流入入账
Section titled “外部流入入账”来源为 ResourceCreditSourceV1::ExternalInflow(DomainRecordVersionRef) 的 Credit 把来自模型账户之外的价值记入账户,例如贡赋或从外地购入的物资,它与 ExternalOutflow 相对应。它计入 ConservationTotalsV1::external_inflow,已应用的结果把所引用的记录版本保留为证据。
- 作为带请求标识的命令由账户保管人发送时,需要一份一步式完成租约:锁定账户修订号和流入所引用的记录版本,并且恰好绑定这笔入账。用
bound_to(CompletionOperationBindingV1::new(ResourceOperationKind::Credit, quantity))构建租约的资格信封,再用enqueue_resource_completion_operation排入这份租约。租约未绑定、或绑定到另一种操作或另一个数量时,带请求标识的外部流入在准入时就被拒绝,所以保管人只能按上层应用绑定的数量入账一次。 - 经适配器输入时,提供方插件可以把这条记录作为提供方来源,在自己的参与方授权下发送。这条路径不需要绑定,带绑定的租约照常检查。
- 引擎不限制流入的数量:限制持有人流入的是上层应用签发的租约。生产入账仍然不能作为带请求标识的命令或经适配器输入提交,只能经生产产出批次结算。
ProductionOperation::CompleteExecution 可以携带 realized_output_per_mille 和 realization_evidence。比例以工艺名义产出的千分之一为单位,None 表示 1,000。执行完成时,每项产出结算数量按该比例缩放并向下取整,两个值都保存在生产执行上;资源入账和产出确认随后按缩放后的数量精确结算。
- 比例不等于 1,000 时,需要一条证据记录,其种类必须列在工艺版本的
realization_evidence_kinds中。该列表默认为空,所以默认工艺只接受名义产出。名义比例附带证据会被拒绝。 - 证据必须是其记录的精确当前版本。较早的版本即使仍保留在模拟运行中也会被拒绝,因此用
seal_evidence封存过的模拟运行与其精确重放接受相同的完成操作。 - 比例必须为正,且不能把任何一条产出腿缩放为零。要记录完全损失,请取消工单。
- 比例不能超过工艺版本的
max_realized_per_mille,默认值为 1,000;调高它即可接受有证据支持的超名义产出。 - 持有人、生命周期、比例和种类规则都在查找证据记录之前检查,因此拒绝结果不会透露其他记录是否存在。
产出如何结算
Section titled “产出如何结算”执行完成后,canwu-production 的一个第 12 阶段系统把当前生产运行时记录的版本固定为该执行的 output_source,并向 canwu-resource 排入一个产出批次。资源插件通过生产产出批次输入为各产出账户入账,并要求入账引用的正是这个固定来源,要么全部入账,要么一项也不入账。通用适配器输入会拒绝生产入账。放不下的批次会被延后,直到归档提交或租约释放腾出空间;它的确认带有 settled_at,即入账结算的时刻,执行把它记为 output_settled_at,归档也用它重建请求摘要。StartExecution 会拒绝永远无法入账的产出键(落在保留前缀之下、被另一个未结算执行占用、或已被某个资源操作结果持有)、超过 64 项的产出,以及资源记录永远放不下的产出集合,所以执行不会带着注定被跳过的批次开始。
授权自定义消费者
Section titled “授权自定义消费者”你自己的消费者插件可以通过 ResourceConsumptionIntentV1 契约消费分配结果:
- 在插件自己的有效领域记录中,保存一个顶层
resource_consumption_intents映射,每个键等于对应已封存意图的id。无需回调,也无需借用参考提供方的名称。 - 把这个记录种类传给
ResourcePlugin::new,让资源适配器可以读取精确来源。 - 提交之前,先完成分配和完成租约的各个步骤。
- 经适配器输入(
enqueue_resource_adapter_operation)提交ResourceOperationRequestV1::Consume。其consumer_evidence必须等于提供方当前的精确来源记录,完成凭证必须锁定该来源并使用同一操作键。持有人、参与者、时间、修订号和租约的常规校验照常进行。
只有当映射中恰好一个 Authorized 意图与完整的分配腿、需求和账户修订号、消费 ID 以及操作键全部匹配时,资源插件才接受这次消费。只消费一条分配腿的一部分,不在这个契约范围内。映射最多容纳 max_operation_outcomes 个条目(取自资源状态的 ResourceLimitsV1),超出时在解码任何条目之前就被拒绝。映射缺失或格式错误、标识或摘要不符,以及意图已退役、有歧义或不匹配时,都会被拒绝,不发生任何扣减。封存摘要只证明意图内容前后一致;许可来自你的插件对意图的授权。
意图的授权与退役,以及资源回执之后的本领域后果,都由你的插件负责;守恒余额和重复操作的处理由 canwu-resource 负责。军事补给参考消费者(canwu-force-supply-reference)保留自己的专用适配器,也接受其来源记录的较早版本;自定义消费者使用上面的当前来源契约。
请从同一发布线精确固定 canwu-api、canwu-resource、canwu-production 的版本和插件语义哈希。加载时恢复存档中的参伍快照,并按你传入的插件重新验证扩展记录。0.20.0 的资源状态格式为 6、两类资源桶的格式为 2;在完成租约进行中写出的接续检查点或封存可以加载。另见存档兼容性。
继续阅读粮食、生产与军事补给案例与机制决议。