@hyperscale0/hsx 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/dist/src/cli.d.ts +1 -1
- package/dist/src/diagnostics.d.ts.map +1 -1
- package/dist/src/diagnostics.js +7 -0
- package/dist/src/diagnostics.js.map +1 -1
- package/dist/src/std-bundle.js +1 -1
- package/dist/src/std-bundle.js.map +1 -1
- package/dist/src/typecheck.js +89 -1
- package/dist/src/typecheck.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/docs/guide/09-cost.md +1 -1
- package/docs/llms-full.txt +25 -5
- package/docs/llms.txt +2 -2
- package/docs/reference/cli.md +2 -2
- package/docs/reference/diagnostics.md +21 -1
- package/docs/reference/grammar.md +1 -1
- package/docs/reference/std/advance.md +1 -1
- package/docs/reference/std/cancellable_booking.md +1 -1
- package/docs/reference/std/captured_payment.md +1 -1
- package/docs/reference/std/conditional_disbursement.md +1 -1
- package/docs/reference/std/credit_facility.md +1 -1
- package/docs/reference/std/held_payment.md +3 -3
- package/docs/reference/std/instant_transfer.md +1 -1
- package/docs/reference/std/metered.md +1 -1
- package/docs/reference/std/pooled_split.md +1 -1
- package/docs/reference/std/premium_forward.md +1 -1
- package/docs/reference/std/reconciled_payout.md +1 -1
- package/docs/reference/std/recurring_collection.md +1 -1
- package/docs/reference/std/rotating_pool.md +1 -1
- package/docs/reference/std/scheduled.md +1 -1
- package/docs/reference/std/security_deposit.md +1 -1
- package/docs/reference/std/settlement_batch.md +1 -1
- package/docs/reference/std/swap.md +1 -1
- package/docs/reference/std/threshold_pool.md +1 -1
- package/docs/reference/std/weighted_distribution.md +1 -1
- package/docs/reference/types.md +1 -1
- package/docs/reference/udl-output.md +1 -1
- package/examples/advance/advance.udl +6 -6
- package/examples/cancellable_booking/cancellable_booking.udl +10 -10
- package/examples/captured_payment/captured_payment.udl +6 -6
- package/examples/conditional_disbursement/conditional_disbursement.udl +2 -2
- package/examples/cost-table.json +18 -78
- package/examples/credit_facility/credit_facility.udl +10 -10
- package/examples/held_payment/held_payment.udl +23 -12
- package/examples/instant_transfer/instant_transfer.udl +6 -6
- package/examples/metered/metered.udl +4 -4
- package/examples/pooled_split/pooled_split.udl +10 -10
- package/examples/premium_forward/premium_forward.udl +12 -12
- package/examples/recurring_collection/recurring_collection.udl +8 -8
- package/examples/rotating_pool/rotating_pool.udl +78 -78
- package/examples/scheduled/scheduled.udl +6 -6
- package/examples/security_deposit/security_deposit.udl +2 -2
- package/examples/swap/swap.udl +22 -22
- package/examples/threshold_pool/threshold_pool.udl +10 -10
- package/examples/weighted_distribution/weighted_distribution.udl +2 -2
- package/package.json +3 -3
- package/src/diagnostics.ts +10 -0
- package/src/std-bundle.ts +1 -1
- package/src/typecheck.ts +105 -1
- package/src/version.ts +1 -1
- package/std/money_flows/held_payment.hsx +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperscale0/hsx",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "The strictly typed HSX language: compile total financial programs into canonical UDL.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"hsx",
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"typecheck": "tsc -p tsconfig.json"
|
|
86
86
|
},
|
|
87
87
|
"dependencies": {
|
|
88
|
-
"@hyperscale0/udl": "2.0.
|
|
88
|
+
"@hyperscale0/udl": "2.0.1"
|
|
89
89
|
},
|
|
90
90
|
"devDependencies": {
|
|
91
91
|
"bun-types": "1.4.2",
|
|
@@ -96,5 +96,5 @@
|
|
|
96
96
|
"Amir Ayub",
|
|
97
97
|
"Sara AlBakaawi"
|
|
98
98
|
],
|
|
99
|
-
"gitHead": "
|
|
99
|
+
"gitHead": "c675a558defe426bd3d349934fb4f83d211878c9"
|
|
100
100
|
}
|
package/src/diagnostics.ts
CHANGED
|
@@ -355,6 +355,16 @@ action pay { computes remainder rest { amount_ref: total; on_zero: refuse; total
|
|
|
355
355
|
fix: "Move all money in one currency; a program bills in one ledger currency.",
|
|
356
356
|
example: instrument("", "amount: money<SAR>; fee: money<USD>;"),
|
|
357
357
|
},
|
|
358
|
+
{
|
|
359
|
+
code: "HSX1306",
|
|
360
|
+
stage: "typecheck",
|
|
361
|
+
title: "Fixed-currency amount moved in another currency",
|
|
362
|
+
fix: "Bind the currency of a money<CUR> move or account step as the constant CUR; the compiler pins a binding that reads the instrument's currency field.",
|
|
363
|
+
example: instrument(
|
|
364
|
+
`action pay { agent_description: "Pay the amount."; moves: [{ "bind": { "amount": { "from": "instance"; "path": "fields.amount"; }; "currency": { "from": "const"; "value": "USD"; }; "destinationAccountId": { "from": "instance"; "path": "fields.payeeAccountId"; }; "sourceAccountId": { "from": "instance"; "path": "fields.payerAccountId"; }; }; "key": "transfer"; "operation": "internal_transfer.create"; }]; steps: []; }`,
|
|
365
|
+
"amount: money<SAR>; payeeAccountId: account<SAR>; payerAccountId: account<SAR>;",
|
|
366
|
+
),
|
|
367
|
+
},
|
|
358
368
|
{
|
|
359
369
|
code: "HSX1401",
|
|
360
370
|
stage: "typecheck",
|
package/src/std-bundle.ts
CHANGED
|
@@ -30,7 +30,7 @@ export const BUNDLED_STD_FILES: ReadonlyMap<string, string> = new Map<
|
|
|
30
30
|
],
|
|
31
31
|
[
|
|
32
32
|
"money_flows/held_payment.hsx",
|
|
33
|
-
'module std.money_flows.held_payment\n\nexport instrument held_payment<C>(payer: party, payee: party, amount: money<C>, release: condition, fees: optional<block>, on_cancel: optional<block>, derived_amount: optional<block>, release_to: optional<party>, whole_amount: optional<block>, release_action: optional<text>, whole_fee: optional<money<C>>, reference: optional<text>, upstream: optional<ref>, id_prefix_override: optional<text>) {\n let(release_name): release;\n let(release_actor_text): if_eq(len(release_allowed), 1, words(at(release_allowed, 1)), concat("a party allowed by ", release));\n let(payer_account_field): camel(concat(payer, "_account_id"));\n let(payee_account_field): camel(concat(payee, "_account_id"));\n let(release_to_account_field): camel(concat(release_to, "_account_id"));\n let(payer_fee): get(fees, payer);\n let(payee_fee): get(fees, payee);\n let(payer_fee_kind): kind(payer_fee);\n let(payee_fee_kind): kind(payee_fee);\n when_eq(payee_fee_kind, binding) {\n when(on_cancel) {\n unsupported {\n code: HSX1110;\n message: "exact payee fees cannot combine with on_cancel yet";\n fix: "remove on_cancel or use a percentage payee fee";\n }\n }\n }\n let(payer_bps): basis_points(payer_fee);\n let(payee_bps): basis_points(payee_fee);\n let(net_bps): sub(10000, payee_fee);\n let(net_percent): percent_text(net_bps);\n let(cancel_payer): get(on_cancel, payer);\n let(cancel_payee): get(on_cancel, payee);\n let(cancel_payer_bps): basis_points(cancel_payer);\n let(cancel_payee_bps): basis_points(cancel_payee);\n let(cancel_splits): lt(1, len(on_cancel));\n let(piece1_bps): min(net_bps, cancel_payer_bps);\n let(piece3_bps): min(payee_bps, cancel_payee_bps);\n let(piece2_bps): sub(sub(10000, piece1_bps), piece3_bps);\n let(piece1_percent): percent_text(piece1_bps);\n let(piece2_percent): percent_text(piece2_bps);\n let(piece3_percent): percent_text(piece3_bps);\n let(carved_spans): lt(cancel_payee_bps, payee_bps);\n let(derived_field): get(derived_amount, "field");\n let(derived_source): get(derived_amount, "source");\n let(derived_rule): get(derived_amount, "rule");\n let(derived_bearer): get(derived_amount, "bearer");\n agent_description: concat("Reach for ", words(instrument), " when the ", words(payer), " must commit money up front and nobody is paid until ", release, " fires. Funding moves ", amount, " into this settlement\'s own escrow account, where it stays until a release or an unwind pays it out. A straight-through payment with no custody step is a different shape.");\n when_not(whole_amount) {\n summary: concat("Escrow-held payment from ", words(payer), " to ", words(payee));\n title: sentence(instrument);\n when_not(release_to) { description: concat("Held payment: the ", words(payer), " funds ", amount, " into this settlement\'s own escrow; ", release_actor_text, " confirms through ", release, " to release"); }\n when(release_to) { description: concat("Held payment: the ", words(payer), " funds ", amount, " into this settlement\'s own escrow; ", release_actor_text, " confirms through ", release, " to release to the ", words(release_to), ", whose advance the ", words(payee), " repays out of it"); }\n fields {\n let(fee_collection_key): "x-hyperscale-fee-collection-port";\n let(reference_filter_key): "x-hyperscale-reference-filter";\n when(release_to) {\n [release_to_account_field] { type: account<C>; description: concat("The ", words(release_to), " account"); [reference_filter_key]: { column: role; values: [customer_balance]; }; }\n }\n platformAccountId {\n type: account<C>;\n description: "The product fee collection account";\n [fee_collection_key]: true;\n [reference_filter_key]: { column: role; values: [customer_balance, product_revenue]; };\n }\n when_not(payee_fee) {\n when_not(cancel_splits) { amount { type: money<C>; description: "The held amount in {C} minor units, funded and paid out whole"; } }\n when(cancel_splits) {\n amount { type: money<C>; description: "The gross held amount in {C} minor units; the piece fields below partition it exactly"; }\n piece1Amount { type: money<C>; description: concat(cancel_payer, " of ", amount, " (carries the integer-division remainder): released to the ", payee, "; on cancellation to the ", payer, ". Computed as floor(", amount, " * ", cancel_payer_bps, " / 10000) in ", C, " minor units"); pattern: "^[1-9][0-9]{0,17}$"; }\n piece2Amount { type: money<C>; description: concat(cancel_payee, " of ", amount, ": released to the ", payee, "; on cancellation to the ", payee, ". Computed as floor(", amount, " * ", cancel_payee_bps, " / 10000) in ", C, " minor units"); pattern: "^[1-9][0-9]{0,17}$"; }\n }\n }\n when(payee_fee) {\n amount { type: money<C>; description: "The gross held amount in {C} minor units; the piece fields below partition it exactly"; }\n when_not(on_cancel) {\n when_eq(payee_fee_kind, percent) {\n piece1Amount { type: money<C>; description: concat(net_percent, " of ", amount, " (carries the integer-division remainder): released to the ", payee, ". Computed as floor(", amount, " * ", net_bps, " / 10000) in ", C, " minor units"); pattern: "^[1-9][0-9]{0,17}$"; }\n piece2Amount { type: money<C>; description: concat(payee_fee, " of ", amount, ": released to the platform. Computed as floor(", amount, " * ", payee_bps, " / 10000) in ", C, " minor units"); pattern: "^[1-9][0-9]{0,17}$"; optional: true; }\n }\n when_eq(payee_fee_kind, binding) {\n piece1Amount { type: money<C>; description: concat("The remainder of ", amount, " after the exact ", payee_fee, " fee; released to the ", payee); pattern: "^[1-9][0-9]{0,17}$"; }\n [payee_fee] { type: payee_fee; description: concat("Immutable exact ", payee, " fee in ", C, " minor units; released to the platform"); pattern: "^[1-9][0-9]{0,17}$"; }\n }\n }\n when(on_cancel) {\n piece1Amount { type: money<C>; description: concat(piece1_percent, " of ", amount, " (carries the integer-division remainder): released to the ", payee, "; on cancellation to the ", payer, ". Computed as floor(", amount, " * ", piece1_bps, " / 10000) in ", C, " minor units"); pattern: "^[1-9][0-9]{0,17}$"; }\n when(carved_spans) {\n piece2Amount { type: money<C>; description: concat(piece2_percent, " of ", amount, ": released to the platform; on cancellation to the ", payer, ". Computed as floor(", amount, " * ", piece2_bps, " / 10000) in ", C, " minor units"); pattern: "^[1-9][0-9]{0,17}$"; }\n piece3Amount { type: money<C>; description: concat(piece3_percent, " of ", amount, ": released to the platform; on cancellation to the ", payee, ". Computed as floor(", amount, " * ", piece3_bps, " / 10000) in ", C, " minor units"); pattern: "^[1-9][0-9]{0,17}$"; }\n carvedFeeAmount { type: money<C>; description: "Machine-owned total of the 2 carved platform fee pieces"; pattern: "^[1-9][0-9]{0,17}$"; optional: true; }\n }\n when_not(carved_spans) {\n piece2Amount { type: money<C>; description: concat(piece2_percent, " of ", amount, ": released to the ", payee, "; on cancellation to the ", payee, ". Computed as floor(", amount, " * ", piece2_bps, " / 10000) in ", C, " minor units"); pattern: "^[1-9][0-9]{0,17}$"; }\n piece3Amount { type: money<C>; description: concat(piece3_percent, " of ", amount, ": released to the platform; on cancellation to the ", payee, ". Computed as floor(", amount, " * ", piece3_bps, " / 10000) in ", C, " minor units"); pattern: "^[1-9][0-9]{0,17}$"; optional: true; }\n }\n }\n }\n when_eq(payer_fee_kind, percent) { serviceFeeAmount { type: money<C>; description: concat("Machine-owned ", payer_fee, " service fee computed from ", amount, "; charged on top at funding"); pattern: "^[1-9][0-9]{0,17}$"; optional: true; } }\n when_eq(payer_fee_kind, binding) { [payer_fee] { type: payer_fee; description: concat("Immutable exact ", payer, " fee in ", C, " minor units; charged on top at funding"); pattern: "^[1-9][0-9]{0,17}$"; } }\n when(release_deadline) {\n when_not(release_to) { [release_deadline] { type: date; description: concat("The date an undecided hold releases to the ", words(payee), " on; ", release, " and cancellation decide only before it"); } }\n when(release_to) { [release_deadline] { type: date; description: concat("The date an undecided hold releases to the ", words(release_to), " on; ", release, " and cancellation decide only before it"); } }\n }\n when(derived_amount) { [derived_field] { type: money<C>; description: "Machine-computed {derived_rule} of {derived_source}; callers never supply it"; pattern: "^[1-9][0-9]{0,17}$"; optional: true; } }\n }\n lifecycle {\n when_not(payee_fee) {\n when_not(cancel_splits) {\n states created funded disputed released;\n when(on_cancel) { states cancelled; }\n states abandoned;\n }\n when(cancel_splits) { states created funding_1 funded disputed releasing_1 released cancelling_1 cancelled abandoned; }\n }\n when(payee_fee) {\n when_not(on_cancel) { states created funding_1 funded disputed releasing_1 released abandoned; }\n when(on_cancel) {\n when_eq(payer_fee_kind, percent) { states created funding_1 funding_2 funding_3 funded disputed releasing_1 releasing_2 released cancelling_1 cancelling_2 cancelled abandoning_1 abandoned abandoning_2; }\n when_eq(payer_fee_kind, none) { states created funding_1 funding_2 funded disputed releasing_1 releasing_2 released cancelling_1 cancelling_2 cancelled abandoning_1 abandoned; }\n when_eq(payer_fee_kind, binding) { states created funding_1 funding_2 funded disputed releasing_1 releasing_2 released cancelling_1 cancelling_2 cancelled abandoning_1 abandoned; }\n }\n }\n initial created;\n when_not(payee_fee) {\n when_not(cancel_splits) {\n on fund_piece_1: created -> funded;\n on [release_name]: funded -> released;\n when(release_deadline) { on release_on_deadline: funded -> released; }\n when(on_cancel) { on cancel: funded -> cancelled; }\n }\n when(cancel_splits) {\n on fund_piece_1: created -> funding_1;\n on fund_piece_2: funding_1 -> funded;\n on [release_name]: funded -> releasing_1;\n on release_piece_2: releasing_1 -> released;\n on cancel: funded -> cancelling_1;\n on refund_piece_2: cancelling_1 -> cancelled;\n on unfund_piece_1: funding_1 -> abandoned;\n }\n }\n when(payee_fee) {\n when_not(on_cancel) {\n on fund_piece_1: created -> funding_1;\n on fund_piece_2: funding_1 -> funded;\n on [release_name]: funded -> releasing_1;\n when(release_deadline) { on release_on_deadline: funded -> releasing_1; }\n on release_piece_2: releasing_1 -> released;\n on unfund_piece_1: funding_1 -> abandoned;\n }\n when(on_cancel) {\n on fund_piece_1: created -> funding_1;\n on fund_piece_2: funding_1 -> funding_2;\n when_eq(payer_fee_kind, percent) {\n on fund_piece_3: funding_2 -> funding_3;\n on collect_service_fee: funding_3 -> funded;\n }\n when_eq(payer_fee_kind, none) { on fund_piece_3: funding_2 -> funded; }\n when_eq(payer_fee_kind, binding) { on fund_piece_3: funding_2 -> funded; }\n on [release_name]: funded -> releasing_1;\n on release_piece_2: releasing_1 -> releasing_2;\n on release_piece_3: releasing_2 -> released;\n on cancel: funded -> cancelling_1;\n on refund_piece_2: cancelling_1 -> cancelling_2;\n on refund_piece_3: cancelling_2 -> cancelled;\n when_eq(payer_fee_kind, percent) {\n on unfund_piece_3: funding_3 -> abandoning_2;\n on unfund_piece_2: funding_2 | abandoning_2 -> abandoning_1;\n }\n when_eq(payer_fee_kind, none) { on unfund_piece_2: funding_2 -> abandoning_1; }\n when_eq(payer_fee_kind, binding) { on unfund_piece_2: funding_2 -> abandoning_1; }\n on unfund_piece_1: funding_1 | abandoning_1 -> abandoned;\n }\n }\n on dispute: funded -> disputed;\n on resume: disputed -> funded;\n on abandon: created -> abandoned;\n }\n parties {\n payer: payer;\n beneficiary: payee;\n }\n when_eq(payer_fee_kind, percent) {\n computes fees: { amount_field: serviceFeeAmount; base_field: amount; bearer_field: payer_account_field; position: on_top; rule: { kind: bps; bps: payer_bps; }; };\n }\n when_eq(payer_fee_kind, binding) {\n computes fees: { amount_field: camel(payer_fee); base_field: amount; bearer_field: payer_account_field; position: on_top; rule: { kind: exact; field: camel(payer_fee); currency_field: currency; }; };\n }\n when(payee_fee) {\n when(on_cancel) {\n action fund_piece_2 {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Move piece 2 of the split amount from the ", words(payer), " into escrow. Piece 1 must be in escrow first. It debits the ", words(payer), " for real, and the hold is still short of funded until piece 3 lands.");\n summary: "Fund piece 2 of the held amount into escrow";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.escrowAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { fundPiece2TransferTransferId: transferId; }; }];\n sandbox_failure_point: funding;\n steps: [];\n }\n action fund_piece_3 {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Move piece 3 of the split amount from the ", words(payer), " into escrow. Pieces 1 and 2 must be in escrow first. It debits the ", words(payer), " for real. Where the product charges a percentage payer fee, one more call collects that fee before the hold counts as funded.");\n summary: "Fund piece 3 of the held amount into escrow";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece3Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.escrowAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_3; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { fundPiece3TransferTransferId: transferId; }; }];\n sandbox_failure_point: funding;\n steps: [];\n }\n when_eq(payer_fee_kind, percent) {\n action collect_service_fee {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Charge the ", words(payer), " the percentage service fee on top of the held amount and pay it to the platform account. All three funding pieces must be in escrow first. It debits the ", words(payer), " for real and it is the last step of funding.");\n summary: "Collect the payer-side service fee";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.serviceFeeAmount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: collect_service_fee; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { collectServiceFeeTransferTransferId: transferId; }; }];\n steps: [];\n }\n }\n action release_piece_2 {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Pay piece 2 out of escrow once the release decision has run. Its destination follows the product fee split, either the platform account or the ", words(payee), ". Money leaves escrow on this call and does not come back.");\n summary: "Release piece 2 of the held amount";\n when(carved_spans) { let(release_piece_2_destination): "fields.platformAccountId"; }\n when_not(carved_spans) { let(release_piece_2_destination): concat("fields.", payee_account_field); }\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: release_piece_2_destination; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { releasePiece2TransferTransferId: transferId; }; }];\n steps: [];\n }\n action release_piece_3 {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Pay piece 3 out of escrow to the platform account, which closes the release. Piece 2 must be released first. Money leaves escrow on this call and does not come back.");\n summary: "Release piece 3 of the held amount";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece3Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_piece_3; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { releasePiece3TransferTransferId: transferId; }; }];\n steps: [];\n }\n action refund_piece_2 {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Return piece 2 out of escrow once cancel has run. Its destination follows the product fee split, either the ", words(payer), " or the ", words(payee), ". Money leaves escrow on this call and does not come back.");\n summary: "Return piece 2 on cancellation";\n when(carved_spans) { let(refund_piece_2_destination): concat("fields.", payer_account_field); }\n when_not(carved_spans) { let(refund_piece_2_destination): concat("fields.", payee_account_field); }\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refund_piece_2_destination; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: refund_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { refundPiece2TransferTransferId: transferId; }; }];\n steps: [];\n }\n action refund_piece_3 {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Return piece 3 out of escrow to the ", words(payee), ", which closes the cancellation. Piece 2 must be returned first. Money leaves escrow on this call and does not come back.");\n summary: "Return piece 3 on cancellation";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece3Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payee_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: refund_piece_3; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { refundPiece3TransferTransferId: transferId; }; }];\n steps: [];\n }\n action unfund_piece_1 {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Return piece 1 from escrow to the ", words(payer), " and abandon the ", words(instrument), ". It runs when only piece 1 was ever funded, or after the later pieces have already gone back. The settlement ends abandoned.");\n summary: "Return piece 1 to the {payer} on abandonment";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payer_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: unfund_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { unfundPiece1TransferTransferId: transferId; }; }];\n steps: [];\n }\n action unfund_piece_2 {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Return piece 2 from escrow to the ", words(payer), " while abandoning a part-funded ", words(instrument), ". Funding must have stopped at piece 2, or piece 3 must already be back. Piece 1 still has to be returned after this.");\n summary: "Return piece 2 to the {payer} on abandonment";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payer_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: unfund_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { unfundPiece2TransferTransferId: transferId; }; }];\n steps: [];\n }\n when_eq(payer_fee_kind, percent) {\n action unfund_piece_3 {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Return piece 3 from escrow to the ", words(payer), " while abandoning a ", words(instrument), " funded as far as piece 3. Pieces 2 and 1 still have to be returned after this, in that order.");\n summary: "Return piece 3 to the {payer} on abandonment";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece3Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payer_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: unfund_piece_3; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { unfundPiece3TransferTransferId: transferId; }; }];\n steps: [];\n }\n }\n }\n when_not(on_cancel) {\n when_eq(payee_fee_kind, percent) {\n computes fees: { amount_field: piece2Amount; base_field: amount; bearer_field: payee_account_field; position: carved; rule: { kind: bps; bps: payee_bps; }; };\n partitions: { total_field: amount; piece_fields: [piece1Amount, piece2Amount]; };\n }\n when_eq(payee_fee_kind, binding) {\n computes fees: { amount_field: camel(payee_fee); base_field: amount; bearer_field: payee_account_field; position: carved; rule: { kind: exact; field: camel(payee_fee); currency_field: currency; }; };\n partitions: { total_field: amount; piece_fields: [piece1Amount, camel(payee_fee)]; };\n }\n }\n when(on_cancel) {\n when_eq(payee_fee_kind, percent) {\n when(carved_spans) {\n computes fees: { amount_field: carvedFeeAmount; base_field: amount; bearer_field: payee_account_field; position: carved; rule: { kind: bps; bps: payee_bps; }; };\n }\n when_not(carved_spans) {\n computes fees: { amount_field: piece3Amount; base_field: amount; bearer_field: payee_account_field; position: carved; rule: { kind: bps; bps: payee_bps; }; };\n }\n }\n partitions: { total_field: amount; piece_fields: [piece1Amount, piece2Amount, piece3Amount]; };\n when(carved_spans) { partitions: { total_field: carvedFeeAmount; piece_fields: [piece2Amount, piece3Amount]; }; }\n }\n }\n when_not(payee_fee) {\n when(cancel_splits) { partitions: { total_field: amount; piece_fields: [piece1Amount, piece2Amount]; }; }\n }\n when(derived_amount) {\n computes derived: { field: derived_field; source_field: derived_source; rounding: floor; rule: { kind: percentage_of; bps: derived_rule; }; };\n }\n action create {\n let(owner_id): "owner.id";\n let(owner_type): "owner.type";\n agent_description: concat("Open the ", words(instrument), " and provision the escrow account that holds the funds. This is the first call, and every funding and release action moves money through that account. It does not move the held amount itself.");\n summary: concat("Create a ", words(instrument), " settlement");\n when_not(derived_amount) { moves: []; }\n when(derived_amount) {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n moves: [{ key: "derived_amount"; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", derived_field); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: create; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", camel(derived_bearer), "AccountId"); }; }; capture: { createDerivedAmountTransferId: transferId; }; }];\n }\n steps: [{ operation: "account.escrow.provision"; bind: { currency: { from: instance; path: fields.currency; }; [owner_id]: { from: instance; path: productId; }; [owner_type]: { from: const; value: product; }; productId: { from: instance; path: productId; }; role: { from: const; value: product_escrow; }; }; capture: { escrowAccountId: accountId; }; }];\n }\n action fund_piece_1 {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Move the ", words(payer), "\'s money into this settlement\'s escrow. Call it after create and before any release. It debits the ", words(payer), " for real, and where the product charges a payer-side fee that fee is taken on top in the same call. Only a later release or unwind moves the money again.");\n summary: "Fund piece 1 of the held amount into escrow";\n when_not(payee_fee) {\n when_not(cancel_splits) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.escrowAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { fundPiece1TransferTransferId: transferId; }; }]; }\n when(cancel_splits) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.escrowAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { fundPiece1TransferTransferId: transferId; }; }]; }\n }\n when(payee_fee) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.escrowAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { fundPiece1TransferTransferId: transferId; }; }]; }\n when_eq(payer_fee_kind, percent) {\n when_not(payee_fee) { moves: [{ key: service_fee; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.serviceFeeAmount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { fundPiece1ServiceFeeTransferId: transferId; }; }]; }\n when(payee_fee) {\n when_not(on_cancel) { moves: [{ key: service_fee; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.serviceFeeAmount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { fundPiece1ServiceFeeTransferId: transferId; }; }]; }\n }\n }\n when_eq(payer_fee_kind, binding) { moves: [{ key: service_fee; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", payer_fee); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { fundPiece1ServiceFeeTransferId: transferId; }; }]; }\n sandbox_failure_point: funding;\n steps: [];\n }\n action [release_name] {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n let(release_capture): camel(concat(release_name, "_transfer_transfer_id"));\n agent_description: concat("Confirm ", release, " and start paying the held money out of escrow. The ", words(instrument), " must be funded first, and the caller must be ", release_actor_text, ". Where the product requires a decision reference, supply it here. Money leaves escrow on this call and does not come back.");\n summary: concat("Confirm through ", release, " and start the release payout");\n when(release_deadline) { deadline { field: release_deadline; }; }\n when(release_fields) {\n input {\n type: object;\n additional_properties: false;\n properties {\n for input_name in keys(release_fields) {\n [input_name]: get(release_fields, input_name);\n }\n }\n required: keys(release_fields);\n }\n }\n port { allowed_parties: release_allowed; };\n when_not(payee_fee) {\n when_not(cancel_splits) {\n when_not(release_to) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payee_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_name; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { [release_capture]: transferId; }; }]; }\n when(release_to) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", release_to_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_name; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { [release_capture]: transferId; }; }]; }\n }\n when(cancel_splits) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payee_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_name; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { [release_capture]: transferId; }; }]; }\n }\n when(payee_fee) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payee_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_name; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { [release_capture]: transferId; }; }]; }\n sandbox_failure_point: "release";\n steps: [];\n }\n when(release_deadline) {\n action release_on_deadline {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n when_not(release_to) { summary: "Release to the {payee} when {release_deadline} arrives undecided"; }\n when(release_to) { summary: "Release to the {release_to} when {release_deadline} arrives undecided"; }\n due { field: release_deadline; };\n when_not(payee_fee) {\n when_not(release_to) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payee_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_on_deadline; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { releaseOnDeadlineTransferTransferId: transferId; }; }]; }\n when(release_to) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", release_to_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_on_deadline; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { releaseOnDeadlineTransferTransferId: transferId; }; }]; }\n }\n when(payee_fee) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payee_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_on_deadline; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { releaseOnDeadlineTransferTransferId: transferId; }; }]; }\n steps: [];\n }\n }\n when(on_cancel) {\n action cancel {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Cancel the ", words(instrument), " and start returning the held money to the ", words(payer), ". It must be funded and not yet released. Cancellation is final, and a cancelled settlement never releases. Where the product sets a release deadline, this call is refused once that date passes.");\n summary: "Cancel the settlement and start the unwind";\n when(release_deadline) { deadline { field: release_deadline; }; }\n when_not(payee_fee) {\n when_not(cancel_splits) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payer_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: cancel; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { cancelTransferTransferId: transferId; }; }]; }\n when(cancel_splits) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payer_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: cancel; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { cancelTransferTransferId: transferId; }; }]; }\n }\n when(payee_fee) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payer_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: cancel; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { cancelTransferTransferId: transferId; }; }]; }\n steps: [];\n }\n }\n when(payee_fee) {\n when_not(on_cancel) {\n action fund_piece_2 {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Move the carved platform fee piece from the ", words(payer), " into escrow, which completes funding. Piece 1 must be in escrow first. It debits the ", words(payer), " for real, and the whole held amount then sits in escrow.");\n summary: "Fund piece 2 of the held amount into escrow";\n when_eq(payee_fee_kind, percent) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.escrowAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { fundPiece2TransferTransferId: transferId; }; }]; }\n when_eq(payee_fee_kind, binding) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", payee_fee); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.escrowAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { fundPiece2TransferTransferId: transferId; }; }]; }\n sandbox_failure_point: funding;\n steps: [];\n }\n action release_piece_2 {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Pay the carved platform fee out of escrow to the platform account, which closes the release. The release decision must have run first. Money leaves escrow on this call and does not come back.");\n summary: "Release piece 2 of the held amount";\n when_eq(payee_fee_kind, percent) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { releasePiece2TransferTransferId: transferId; }; }]; }\n when_eq(payee_fee_kind, binding) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", payee_fee); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { releasePiece2TransferTransferId: transferId; }; }]; }\n steps: [];\n }\n action unfund_piece_1 {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Return piece 1 from escrow to the ", words(payer), " and abandon the ", words(instrument), ". It runs only while funding stopped after piece 1, before the release. The settlement ends abandoned.");\n summary: "Return piece 1 to the {payer} on abandonment";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payer_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: unfund_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { unfundPiece1TransferTransferId: transferId; }; }];\n steps: [];\n }\n }\n }\n when_not(payee_fee) {\n when(cancel_splits) {\n action fund_piece_2 {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Move piece 2 of the split amount from the ", words(payer), " into escrow, which completes funding. Piece 1 must be in escrow first. It debits the ", words(payer), " for real, and the whole held amount then sits in escrow.");\n summary: "Fund piece 2 of the held amount into escrow";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.escrowAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { fundPiece2TransferTransferId: transferId; }; }];\n sandbox_failure_point: funding;\n steps: [];\n }\n action release_piece_2 {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Pay piece 2 out of escrow to the ", words(payee), ", which closes the release. The release decision must have run first. Money leaves escrow on this call and does not come back.");\n summary: "Release piece 2 of the held amount";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payee_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { releasePiece2TransferTransferId: transferId; }; }];\n steps: [];\n }\n action refund_piece_2 {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Return piece 2 out of escrow to the ", words(payee), ", which closes the cancellation. Cancel must have run first. Money leaves escrow on this call and does not come back.");\n summary: "Return piece 2 on cancellation";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payee_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: refund_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { refundPiece2TransferTransferId: transferId; }; }];\n steps: [];\n }\n action unfund_piece_1 {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Return piece 1 from escrow to the ", words(payer), " and abandon the ", words(instrument), ". It runs only while funding stopped after piece 1, before the release. The settlement ends abandoned.");\n summary: "Return piece 1 to the {payer} on abandonment";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payer_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: unfund_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { unfundPiece1TransferTransferId: transferId; }; }];\n steps: [];\n }\n }\n }\n action abandon {\n agent_description: concat("Close a ", words(instrument), " that was never funded. The escrow account must be empty, so this refuses once any piece is in it. It moves no money and the settlement ends here with no way back.");\n summary: "Abandon the settlement before any money is held";\n requires drained: { path: refs.escrowAccountId; };\n moves: [];\n steps: [];\n }\n action dispute {\n agent_description: concat("Freeze a funded ", words(instrument), " so neither release nor refund can run until the parties settle the disagreement. No money moves and the whole balance stays put. Only a funded ", words(instrument), " disputes.");\n description: concat("Freezes funded ", words(instrument), ", preserving the entire balance while the parties decide what happens next.");\n summary: concat("Dispute a ", words(instrument), " settlement");\n moves: [];\n steps: [];\n }\n action resume {\n agent_description: concat("Lift the dispute hold and put the ", words(instrument), " back where it was, funded and ready to release. No money moves and the balance was never touched. Only a disputed ", words(instrument), " resumes. Use this when the parties settled without a refund.");\n description: concat("Ends the dispute hold without moving money and returns the fully funded ", words(instrument), " to its first milestone.");\n summary: concat("Resume a ", words(instrument), " settlement");\n moves: [];\n steps: [];\n }\n }\n when(whole_amount) {\n let(fee_collection_key): "x-hyperscale-fee-collection-port";\n let(reference_filter_key): "x-hyperscale-reference-filter";\n let(whole_fee_field): camel(whole_fee);\n title: sentence(instrument);\n id_prefix: if_eq(id_prefix_override, false, prefix(instrument), id_prefix_override);\n summary: concat("Escrow-held payment from ", words(payer), " to ", words(payee));\n description: concat("Held payment: the ", words(payer), " funds ", amount, " into this settlement\'s own escrow; ", release_actor_text, " confirms through ", release, " to release");\n distinct_parties: true;\n caller_parked_states: {\n created: "The tenant either funds the hold or abandons it before any money moves.";\n disputed: "Dispute resolution is a human judgment; the parties resume the hold to continue.";\n };\n fields {\n currency { type: text; description: "One currency for the hold and its fee"; const: C; }\n amount { type: money<C>; description: "Amount held in custody, in {C} minor units"; }\n [payee]AccountId { type: account<C>; description: "Party the hold protects and pays on release"; [reference_filter_key]: { column: role; values: [customer_balance]; }; }\n [payer]AccountId { type: account<C>; description: "Party whose money is held until the hold resolves"; [reference_filter_key]: { column: role; values: [customer_balance]; }; }\n platformAccountId { type: account<C>; description: "Product revenue or customer balance account collecting the custody fee"; [fee_collection_key]: true; [reference_filter_key]: { column: role; values: [customer_balance, product_revenue]; }; }\n when(reference) { reference { type: text; description: "The tenant\'s own identifier for what is held: invoice number, project code, contract reference"; optional: true; max_length: 180; min_length: 1; } }\n [release_deadline] { type: date; description: "Date the hold releases to the payee on its own; fixed at creation and the cutoff for both the release port and cancellation"; }\n whole_fee { type: money<C>; description: "Custody fee charged on top at funding and never placed in escrow"; }\n when(upstream) { upstream { type: ref<instrument>; description: "Hold this one is carved out of, when a chain of holds passes the same money down"; optional: true; pattern: concat("^", id_prefix_override, "_(sandbox|live)_[a-z0-9]{8,64}$"); } }\n }\n lifecycle {\n states created funded disputed released cancelled abandoned;\n initial created;\n on abandon: created -> abandoned;\n on cancel: funded -> cancelled;\n on dispute: funded -> disputed;\n on fund: created -> funded;\n on [release_action]: funded -> released;\n on release_on_deadline: funded -> released;\n on resume: disputed -> funded;\n }\n parties { beneficiary: payee_account_field; payer: payer_account_field; }\n required: [payer_account_field, payee_account_field, platformAccountId, amount, whole_fee_field, currency, release_deadline];\n action create {\n let(owner_id): "owner.id";\n let(owner_type): "owner.type";\n agent_description: concat("Open the ", words(instrument), " and provision the escrow account that holds the money. No money moves on this call. ", release_deadline, " is fixed here and never moves again, so one date governs the early release, the cancellation, and the automatic payout.");\n summary: concat("Create a ", words(instrument), " settlement");\n moves: [];\n steps: [{ operation: "account.escrow.provision"; bind: { currency: { from: instance; path: fields.currency; }; [owner_id]: { from: instance; path: productId; }; [owner_type]: { from: const; value: product; }; productId: { from: instance; path: productId; }; role: { from: const; value: product_escrow; }; }; capture: { escrowAccountId: accountId; }; }];\n }\n action fund {\n agent_description: concat("Move ", amount, " from the ", words(payer), " into escrow and charge ", whole_fee_field, " on top to the platform account, as one linked batch. This moves money and the ", words(payer), " pays both. Only a created hold funds. The fee never enters escrow, so it is non-refundable however the hold ends.");\n summary: "Fund the hold and collect the custody fee";\n moves: [\n { key: principal; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.escrowAccountId; }; "metadata.instrumentId": { from: const; value: instrument; }; "metadata.instrumentInstanceId": { from: instance; path: instrumentInstanceId; }; "metadata.phase": { from: const; value: fund; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { fundingTransferId: transferId; }; },\n { key: service_fee; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", whole_fee_field); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; "metadata.instrumentId": { from: const; value: instrument; }; "metadata.instrumentInstanceId": { from: instance; path: instrumentInstanceId; }; "metadata.phase": { from: const; value: service_fee; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { serviceFeeTransferId: transferId; }; }\n ];\n sandbox_failure_point: funding;\n steps: [];\n }\n action [release_action] {\n agent_description: concat("Pay the ", words(payee), " the whole ", amount, " out of escrow ahead of ", release_deadline, ". This moves money and does not reverse. Only a funded hold releases, the caller must act as ", words(at(release_allowed, 1)), ", and the call is refused once ", release_deadline, " passes. The custody fee stays posted.");\n summary: concat("Release the hold to the ", words(payee), " early");\n deadline: { field: release_deadline; };\n port: { allowed_parties: [at(release_allowed, 1)]; };\n moves: [{ key: release_action; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payee_account_field); }; "metadata.instrumentId": { from: const; value: instrument; }; "metadata.instrumentInstanceId": { from: instance; path: instrumentInstanceId; }; "metadata.phase": { from: const; value: release_action; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { releaseTransferId: transferId; }; }];\n sandbox_failure_point: "release";\n steps: [];\n }\n action release_on_deadline {\n summary: concat("Release the hold to the ", words(payee), " on its release date");\n due: { field: release_deadline; };\n event_name: concat(instrument, ".released_on_deadline");\n moves: [{ key: release_action; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payee_account_field); }; "metadata.instrumentId": { from: const; value: instrument; }; "metadata.instrumentInstanceId": { from: instance; path: instrumentInstanceId; }; "metadata.phase": { from: const; value: deadline_release; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { deadlineReleaseTransferId: transferId; }; }];\n steps: [];\n }\n action cancel {\n agent_description: concat("Return the whole ", amount, " from escrow to the ", words(payer), " and end the hold. This moves money and does not reverse. Only a funded hold cancels, and only before ", release_deadline, " passes. The custody fee stays posted because the custody it paid for was already provided.");\n summary: "Cancel the hold and return the held amount";\n deadline: { field: release_deadline; };\n event_name: concat(instrument, ".cancelled");\n moves: [{ key: refund; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payer_account_field); }; "metadata.instrumentId": { from: const; value: instrument; }; "metadata.instrumentInstanceId": { from: instance; path: instrumentInstanceId; }; "metadata.phase": { from: const; value: cancel_refund; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { cancelRefundTransferId: transferId; }; }];\n steps: [];\n }\n action abandon {\n agent_description: concat("Close a ", words(instrument), " that was opened and never funded. No money has moved and the escrow must be empty, so this is a lifecycle transition only. A funded hold refuses. Release it to the ", words(payee), " or cancel it back to the ", words(payer), " instead.");\n summary: "Abandon the hold before it is funded";\n moves: [];\n requires_drained: { path: refs.escrowAccountId; };\n steps: [];\n }\n action dispute {\n agent_description: concat("Freeze a funded ", words(instrument), " so neither release nor refund can run until the parties settle the disagreement. No money moves and the whole balance stays put. Only a funded ", words(instrument), " disputes.");\n description: concat("Freezes funded ", words(instrument), ", preserving the entire balance while the parties decide what happens next.");\n summary: concat("Dispute a ", words(instrument), " settlement");\n moves: [];\n steps: [];\n }\n action resume {\n agent_description: concat("Lift the dispute hold and put the ", words(instrument), " back where it was, funded and ready to release. No money moves and the balance was never touched. Only a disputed ", words(instrument), " resumes. Use this when the parties settled without a refund.");\n description: concat("Ends the dispute hold without moving money and returns the fully funded ", words(instrument), " to its first milestone.");\n summary: concat("Resume a ", words(instrument), " settlement");\n moves: [];\n steps: [];\n }\n }\n}\n',
|
|
33
|
+
'module std.money_flows.held_payment\n\nexport instrument held_payment<C>(payer: party, payee: party, amount: money<C>, release: condition, fees: optional<block>, on_cancel: optional<block>, derived_amount: optional<block>, release_to: optional<party>, whole_amount: optional<block>, release_action: optional<text>, whole_fee: optional<money<C>>, reference: optional<text>, upstream: optional<ref>, id_prefix_override: optional<text>) {\n let(release_name): release;\n let(release_actor_text): if_eq(len(release_allowed), 1, words(at(release_allowed, 1)), concat("a party allowed by ", release));\n let(payer_account_field): camel(concat(payer, "_account_id"));\n let(payee_account_field): camel(concat(payee, "_account_id"));\n let(release_to_account_field): camel(concat(release_to, "_account_id"));\n let(payer_fee): get(fees, payer);\n let(payee_fee): get(fees, payee);\n let(payer_fee_kind): kind(payer_fee);\n let(payee_fee_kind): kind(payee_fee);\n when_eq(payee_fee_kind, binding) {\n when(on_cancel) {\n unsupported {\n code: HSX1110;\n message: "exact payee fees cannot combine with on_cancel yet";\n fix: "remove on_cancel or use a percentage payee fee";\n }\n }\n }\n let(payer_bps): basis_points(payer_fee);\n let(payee_bps): basis_points(payee_fee);\n let(net_bps): sub(10000, payee_fee);\n let(net_percent): percent_text(net_bps);\n let(cancel_payer): get(on_cancel, payer);\n let(cancel_payee): get(on_cancel, payee);\n let(cancel_payer_bps): basis_points(cancel_payer);\n let(cancel_payee_bps): basis_points(cancel_payee);\n let(cancel_splits): lt(1, len(on_cancel));\n let(piece1_bps): min(net_bps, cancel_payer_bps);\n let(piece3_bps): min(payee_bps, cancel_payee_bps);\n let(piece2_bps): sub(sub(10000, piece1_bps), piece3_bps);\n let(piece1_percent): percent_text(piece1_bps);\n let(piece2_percent): percent_text(piece2_bps);\n let(piece3_percent): percent_text(piece3_bps);\n let(carved_spans): lt(cancel_payee_bps, payee_bps);\n let(derived_field): get(derived_amount, "field");\n let(derived_source): get(derived_amount, "source");\n let(derived_rule): get(derived_amount, "rule");\n let(derived_bearer): get(derived_amount, "bearer");\n agent_description: concat("Reach for ", words(instrument), " when the ", words(payer), " must commit money up front and nobody is paid until ", release, " fires. Funding moves ", amount, " into this settlement\'s own escrow account, where it stays until a release or an unwind pays it out. A straight-through payment with no custody step is a different shape.");\n when_not(whole_amount) {\n summary: concat("Escrow-held payment from ", words(payer), " to ", words(payee));\n title: sentence(instrument);\n when_not(release_to) { description: concat("Held payment: the ", words(payer), " funds ", amount, " into this settlement\'s own escrow; ", release_actor_text, " confirms through ", release, " to release"); }\n when(release_to) { description: concat("Held payment: the ", words(payer), " funds ", amount, " into this settlement\'s own escrow; ", release_actor_text, " confirms through ", release, " to release to the ", words(release_to), ", whose advance the ", words(payee), " repays out of it"); }\n fields {\n let(fee_collection_key): "x-hyperscale-fee-collection-port";\n let(reference_filter_key): "x-hyperscale-reference-filter";\n when(release_to) {\n [release_to_account_field] { type: account<C>; description: concat("The ", words(release_to), " account"); [reference_filter_key]: { column: role; values: [customer_balance]; }; }\n }\n platformAccountId {\n type: account<C>;\n description: "The product fee collection account";\n [fee_collection_key]: true;\n [reference_filter_key]: { column: role; values: [customer_balance, product_revenue]; };\n }\n when_not(payee_fee) {\n when_not(cancel_splits) { amount { type: money<C>; description: "The held amount in {C} minor units, funded and paid out whole"; } }\n when(cancel_splits) {\n amount { type: money<C>; description: "The gross held amount in {C} minor units; the piece fields below partition it exactly"; }\n piece1Amount { type: money<C>; description: concat(cancel_payer, " of ", amount, " (carries the integer-division remainder): released to the ", payee, "; on cancellation to the ", payer, ". Computed as floor(", amount, " * ", cancel_payer_bps, " / 10000) in ", C, " minor units"); pattern: "^[1-9][0-9]{0,17}$"; }\n piece2Amount { type: money<C>; description: concat(cancel_payee, " of ", amount, ": released to the ", payee, "; on cancellation to the ", payee, ". Computed as floor(", amount, " * ", cancel_payee_bps, " / 10000) in ", C, " minor units"); pattern: "^[1-9][0-9]{0,17}$"; }\n }\n }\n when(payee_fee) {\n amount { type: money<C>; description: "The gross held amount in {C} minor units; the piece fields below partition it exactly"; }\n when_not(on_cancel) {\n when_eq(payee_fee_kind, percent) {\n piece1Amount { type: money<C>; description: concat(net_percent, " of ", amount, " (carries the integer-division remainder): released to the ", payee, ". Computed as floor(", amount, " * ", net_bps, " / 10000) in ", C, " minor units"); pattern: "^[1-9][0-9]{0,17}$"; }\n piece2Amount { type: money<C>; description: concat(payee_fee, " of ", amount, ": released to the platform. Computed as floor(", amount, " * ", payee_bps, " / 10000) in ", C, " minor units"); pattern: "^[1-9][0-9]{0,17}$"; optional: true; }\n }\n when_eq(payee_fee_kind, binding) {\n piece1Amount { type: money<C>; description: concat("The remainder of ", amount, " after the exact ", payee_fee, " fee; released to the ", payee); pattern: "^[1-9][0-9]{0,17}$"; }\n [payee_fee] { type: payee_fee; description: concat("Immutable exact ", payee, " fee in ", C, " minor units; released to the platform"); pattern: "^[1-9][0-9]{0,17}$"; }\n }\n }\n when(on_cancel) {\n piece1Amount { type: money<C>; description: concat(piece1_percent, " of ", amount, " (carries the integer-division remainder): released to the ", payee, "; on cancellation to the ", payer, ". Computed as floor(", amount, " * ", piece1_bps, " / 10000) in ", C, " minor units"); pattern: "^[1-9][0-9]{0,17}$"; }\n when(carved_spans) {\n piece2Amount { type: money<C>; description: concat(piece2_percent, " of ", amount, ": released to the platform; on cancellation to the ", payer, ". Computed as floor(", amount, " * ", piece2_bps, " / 10000) in ", C, " minor units"); pattern: "^[1-9][0-9]{0,17}$"; }\n piece3Amount { type: money<C>; description: concat(piece3_percent, " of ", amount, ": released to the platform; on cancellation to the ", payee, ". Computed as floor(", amount, " * ", piece3_bps, " / 10000) in ", C, " minor units"); pattern: "^[1-9][0-9]{0,17}$"; }\n carvedFeeAmount { type: money<C>; description: "Machine-owned total of the 2 carved platform fee pieces"; pattern: "^[1-9][0-9]{0,17}$"; optional: true; }\n }\n when_not(carved_spans) {\n piece2Amount { type: money<C>; description: concat(piece2_percent, " of ", amount, ": released to the ", payee, "; on cancellation to the ", payee, ". Computed as floor(", amount, " * ", piece2_bps, " / 10000) in ", C, " minor units"); pattern: "^[1-9][0-9]{0,17}$"; }\n piece3Amount { type: money<C>; description: concat(piece3_percent, " of ", amount, ": released to the platform; on cancellation to the ", payee, ". Computed as floor(", amount, " * ", piece3_bps, " / 10000) in ", C, " minor units"); pattern: "^[1-9][0-9]{0,17}$"; optional: true; }\n }\n }\n }\n when_eq(payer_fee_kind, percent) { serviceFeeAmount { type: money<C>; description: concat("Machine-owned ", payer_fee, " service fee computed from ", amount, "; charged on top at funding"); pattern: "^[1-9][0-9]{0,17}$"; optional: true; } }\n when_eq(payer_fee_kind, binding) { [payer_fee] { type: payer_fee; description: concat("Immutable exact ", payer, " fee in ", C, " minor units; charged on top at funding"); pattern: "^[1-9][0-9]{0,17}$"; } }\n when(release_deadline) {\n when_not(release_to) { [release_deadline] { type: date; description: concat("The date an undecided hold releases to the ", words(payee), " on; ", release, " and cancellation decide only before it"); } }\n when(release_to) { [release_deadline] { type: date; description: concat("The date an undecided hold releases to the ", words(release_to), " on; ", release, " and cancellation decide only before it"); } }\n }\n when(derived_amount) { [derived_field] { type: money<C>; description: "Machine-computed {derived_rule} of {derived_source}; callers never supply it"; pattern: "^[1-9][0-9]{0,17}$"; optional: true; } }\n }\n lifecycle {\n when_not(payee_fee) {\n when_not(cancel_splits) {\n states created funded disputed released;\n when(on_cancel) { states cancelled; }\n states abandoned;\n }\n when(cancel_splits) { states created funding_1 funded disputed releasing_1 released cancelling_1 cancelled abandoned; }\n }\n when(payee_fee) {\n when_not(on_cancel) { states created funding_1 funded disputed releasing_1 released abandoned; }\n when(on_cancel) {\n when_eq(payer_fee_kind, percent) { states created funding_1 funding_2 funding_3 funded disputed releasing_1 releasing_2 released cancelling_1 cancelling_2 cancelled abandoning_1 abandoned abandoning_2; }\n when_eq(payer_fee_kind, none) { states created funding_1 funding_2 funded disputed releasing_1 releasing_2 released cancelling_1 cancelling_2 cancelled abandoning_1 abandoned; }\n when_eq(payer_fee_kind, binding) { states created funding_1 funding_2 funded disputed releasing_1 releasing_2 released cancelling_1 cancelling_2 cancelled abandoning_1 abandoned; }\n }\n }\n initial created;\n when_not(payee_fee) {\n when_not(cancel_splits) {\n on fund_piece_1: created -> funded;\n on [release_name]: funded -> released;\n when(release_deadline) { on release_on_deadline: funded -> released; }\n when(on_cancel) { on cancel: funded -> cancelled; }\n }\n when(cancel_splits) {\n on fund_piece_1: created -> funding_1;\n on fund_piece_2: funding_1 -> funded;\n on [release_name]: funded -> releasing_1;\n on release_piece_2: releasing_1 -> released;\n on cancel: funded -> cancelling_1;\n on refund_piece_2: cancelling_1 -> cancelled;\n on unfund_piece_1: funding_1 -> abandoned;\n }\n }\n when(payee_fee) {\n when_not(on_cancel) {\n on fund_piece_1: created -> funding_1;\n on fund_piece_2: funding_1 -> funded;\n on [release_name]: funded -> releasing_1;\n when(release_deadline) { on release_on_deadline: funded -> releasing_1; }\n on release_piece_2: releasing_1 -> released;\n on unfund_piece_1: funding_1 -> abandoned;\n }\n when(on_cancel) {\n on fund_piece_1: created -> funding_1;\n on fund_piece_2: funding_1 -> funding_2;\n when_eq(payer_fee_kind, percent) {\n on fund_piece_3: funding_2 -> funding_3;\n on collect_service_fee: funding_3 -> funded;\n }\n when_eq(payer_fee_kind, none) { on fund_piece_3: funding_2 -> funded; }\n when_eq(payer_fee_kind, binding) { on fund_piece_3: funding_2 -> funded; }\n on [release_name]: funded -> releasing_1;\n on release_piece_2: releasing_1 -> releasing_2;\n on release_piece_3: releasing_2 -> released;\n on cancel: funded -> cancelling_1;\n on refund_piece_2: cancelling_1 -> cancelling_2;\n on refund_piece_3: cancelling_2 -> cancelled;\n when_eq(payer_fee_kind, percent) {\n on unfund_piece_3: funding_3 -> abandoning_2;\n on unfund_piece_2: funding_2 | abandoning_2 -> abandoning_1;\n }\n when_eq(payer_fee_kind, none) { on unfund_piece_2: funding_2 -> abandoning_1; }\n when_eq(payer_fee_kind, binding) { on unfund_piece_2: funding_2 -> abandoning_1; }\n on unfund_piece_1: funding_1 | abandoning_1 -> abandoned;\n }\n }\n on dispute: funded -> disputed;\n on resume: disputed -> funded;\n on abandon: created -> abandoned;\n }\n parties {\n payer: payer;\n beneficiary: payee;\n }\n when_eq(payer_fee_kind, percent) {\n computes fees: { amount_field: serviceFeeAmount; base_field: amount; bearer_field: payer_account_field; position: on_top; rule: { kind: bps; bps: payer_bps; }; };\n }\n when_eq(payer_fee_kind, binding) {\n computes fees: { amount_field: camel(payer_fee); base_field: amount; bearer_field: payer_account_field; position: on_top; rule: { kind: exact; field: camel(payer_fee); currency_field: currency; }; };\n }\n when(payee_fee) {\n when(on_cancel) {\n action fund_piece_2 {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Move piece 2 of the split amount from the ", words(payer), " into escrow. Piece 1 must be in escrow first. It debits the ", words(payer), " for real, and the hold is still short of funded until piece 3 lands.");\n summary: "Fund piece 2 of the held amount into escrow";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.escrowAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { fundPiece2TransferTransferId: transferId; }; }];\n sandbox_failure_point: funding;\n steps: [];\n }\n action fund_piece_3 {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Move piece 3 of the split amount from the ", words(payer), " into escrow. Pieces 1 and 2 must be in escrow first. It debits the ", words(payer), " for real. Where the product charges a percentage payer fee, one more call collects that fee before the hold counts as funded.");\n summary: "Fund piece 3 of the held amount into escrow";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece3Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.escrowAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_3; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { fundPiece3TransferTransferId: transferId; }; }];\n sandbox_failure_point: funding;\n steps: [];\n }\n when_eq(payer_fee_kind, percent) {\n action collect_service_fee {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Charge the ", words(payer), " the percentage service fee on top of the held amount and pay it to the platform account. All three funding pieces must be in escrow first. It debits the ", words(payer), " for real and it is the last step of funding.");\n summary: "Collect the payer-side service fee";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.serviceFeeAmount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: collect_service_fee; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { collectServiceFeeTransferTransferId: transferId; }; }];\n steps: [];\n }\n }\n action release_piece_2 {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Pay piece 2 out of escrow once the release decision has run. Its destination follows the product fee split, either the platform account or the ", words(payee), ". Money leaves escrow on this call and does not come back.");\n summary: "Release piece 2 of the held amount";\n when(carved_spans) { let(release_piece_2_destination): "fields.platformAccountId"; }\n when_not(carved_spans) { let(release_piece_2_destination): concat("fields.", payee_account_field); }\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: release_piece_2_destination; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { releasePiece2TransferTransferId: transferId; }; }];\n steps: [];\n }\n action release_piece_3 {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Pay piece 3 out of escrow to the platform account, which closes the release. Piece 2 must be released first. Money leaves escrow on this call and does not come back.");\n summary: "Release piece 3 of the held amount";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece3Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_piece_3; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { releasePiece3TransferTransferId: transferId; }; }];\n steps: [];\n }\n action refund_piece_2 {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Return piece 2 out of escrow once cancel has run. Its destination follows the product fee split, either the ", words(payer), " or the ", words(payee), ". Money leaves escrow on this call and does not come back.");\n summary: "Return piece 2 on cancellation";\n when(carved_spans) { let(refund_piece_2_destination): concat("fields.", payer_account_field); }\n when_not(carved_spans) { let(refund_piece_2_destination): concat("fields.", payee_account_field); }\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refund_piece_2_destination; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: refund_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { refundPiece2TransferTransferId: transferId; }; }];\n steps: [];\n }\n action refund_piece_3 {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Return piece 3 out of escrow to the ", words(payee), ", which closes the cancellation. Piece 2 must be returned first. Money leaves escrow on this call and does not come back.");\n summary: "Return piece 3 on cancellation";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece3Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payee_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: refund_piece_3; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { refundPiece3TransferTransferId: transferId; }; }];\n steps: [];\n }\n action unfund_piece_1 {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Return piece 1 from escrow to the ", words(payer), " and abandon the ", words(instrument), ". It runs when only piece 1 was ever funded, or after the later pieces have already gone back. The settlement ends abandoned.");\n summary: "Return piece 1 to the {payer} on abandonment";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payer_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: unfund_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { unfundPiece1TransferTransferId: transferId; }; }];\n steps: [];\n }\n action unfund_piece_2 {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Return piece 2 from escrow to the ", words(payer), " while abandoning a part-funded ", words(instrument), ". Funding must have stopped at piece 2, or piece 3 must already be back. Piece 1 still has to be returned after this.");\n summary: "Return piece 2 to the {payer} on abandonment";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payer_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: unfund_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { unfundPiece2TransferTransferId: transferId; }; }];\n steps: [];\n }\n when_eq(payer_fee_kind, percent) {\n action unfund_piece_3 {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Return piece 3 from escrow to the ", words(payer), " while abandoning a ", words(instrument), " funded as far as piece 3. Pieces 2 and 1 still have to be returned after this, in that order.");\n summary: "Return piece 3 to the {payer} on abandonment";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece3Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payer_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: unfund_piece_3; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { unfundPiece3TransferTransferId: transferId; }; }];\n steps: [];\n }\n }\n }\n when_not(on_cancel) {\n when_eq(payee_fee_kind, percent) {\n computes fees: { amount_field: piece2Amount; base_field: amount; bearer_field: payee_account_field; position: carved; rule: { kind: bps; bps: payee_bps; }; };\n partitions: { total_field: amount; piece_fields: [piece1Amount, piece2Amount]; };\n }\n when_eq(payee_fee_kind, binding) {\n computes fees: { amount_field: camel(payee_fee); base_field: amount; bearer_field: payee_account_field; position: carved; rule: { kind: exact; field: camel(payee_fee); currency_field: currency; }; };\n partitions: { total_field: amount; piece_fields: [piece1Amount, camel(payee_fee)]; };\n }\n }\n when(on_cancel) {\n when_eq(payee_fee_kind, percent) {\n when(carved_spans) {\n computes fees: { amount_field: carvedFeeAmount; base_field: amount; bearer_field: payee_account_field; position: carved; rule: { kind: bps; bps: payee_bps; }; };\n }\n when_not(carved_spans) {\n computes fees: { amount_field: piece3Amount; base_field: amount; bearer_field: payee_account_field; position: carved; rule: { kind: bps; bps: payee_bps; }; };\n }\n }\n partitions: { total_field: amount; piece_fields: [piece1Amount, piece2Amount, piece3Amount]; };\n when(carved_spans) { partitions: { total_field: carvedFeeAmount; piece_fields: [piece2Amount, piece3Amount]; }; }\n }\n }\n when_not(payee_fee) {\n when(cancel_splits) { partitions: { total_field: amount; piece_fields: [piece1Amount, piece2Amount]; }; }\n }\n when(derived_amount) {\n computes derived: { field: derived_field; source_field: derived_source; rounding: floor; rule: { kind: percentage_of; bps: derived_rule; }; };\n }\n action create {\n let(owner_id): "owner.id";\n let(owner_type): "owner.type";\n agent_description: concat("Open the ", words(instrument), " and provision the escrow account that holds the funds. This is the first call, and every funding and release action moves money through that account. It does not move the held amount itself.");\n summary: concat("Create a ", words(instrument), " settlement");\n when_not(derived_amount) { moves: []; }\n when(derived_amount) {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n moves: [{ key: "derived_amount"; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", derived_field); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: create; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", camel(derived_bearer), "AccountId"); }; }; capture: { createDerivedAmountTransferId: transferId; }; }];\n }\n steps: [{ operation: "account.escrow.provision"; bind: { currency: { from: instance; path: fields.currency; }; [owner_id]: { from: instance; path: productId; }; [owner_type]: { from: const; value: product; }; productId: { from: instance; path: productId; }; role: { from: const; value: product_escrow; }; }; capture: { escrowAccountId: accountId; }; }];\n }\n action fund_piece_1 {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Move the ", words(payer), "\'s money into this settlement\'s escrow. Call it after create and before any release. It debits the ", words(payer), " for real, and where the product charges a payer-side fee that fee is taken on top in the same call. Only a later release or unwind moves the money again.");\n summary: "Fund piece 1 of the held amount into escrow";\n when_not(payee_fee) {\n when_not(cancel_splits) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.escrowAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { fundPiece1TransferTransferId: transferId; }; }]; }\n when(cancel_splits) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.escrowAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { fundPiece1TransferTransferId: transferId; }; }]; }\n }\n when(payee_fee) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.escrowAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { fundPiece1TransferTransferId: transferId; }; }]; }\n when_eq(payer_fee_kind, percent) {\n when_not(payee_fee) { moves: [{ key: service_fee; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.serviceFeeAmount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { fundPiece1ServiceFeeTransferId: transferId; }; }]; }\n when(payee_fee) {\n when_not(on_cancel) { moves: [{ key: service_fee; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.serviceFeeAmount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { fundPiece1ServiceFeeTransferId: transferId; }; }]; }\n }\n }\n when_eq(payer_fee_kind, binding) { moves: [{ key: service_fee; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", payer_fee); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { fundPiece1ServiceFeeTransferId: transferId; }; }]; }\n sandbox_failure_point: funding;\n steps: [];\n }\n action [release_name] {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n let(release_capture): camel(concat(release_name, "_transfer_transfer_id"));\n agent_description: concat("Confirm ", release, " and start paying the held money out of escrow. The ", words(instrument), " must be funded first, and the caller must be ", release_actor_text, ". Where the product requires a decision reference, supply it here. Money leaves escrow on this call and does not come back.");\n summary: concat("Confirm through ", release, " and start the release payout");\n when(release_deadline) { deadline { field: release_deadline; }; }\n when(release_fields) {\n input {\n type: object;\n additional_properties: false;\n properties {\n for input_name in keys(release_fields) {\n [input_name]: get(release_fields, input_name);\n }\n }\n required: keys(release_fields);\n }\n }\n port { allowed_parties: release_allowed; };\n when_not(payee_fee) {\n when_not(cancel_splits) {\n when_not(release_to) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payee_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_name; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { [release_capture]: transferId; }; }]; }\n when(release_to) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", release_to_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_name; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { [release_capture]: transferId; }; }]; }\n }\n when(cancel_splits) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payee_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_name; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { [release_capture]: transferId; }; }]; }\n }\n when(payee_fee) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payee_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_name; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { [release_capture]: transferId; }; }]; }\n sandbox_failure_point: "release";\n steps: [];\n }\n when(release_deadline) {\n action release_on_deadline {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n when_not(release_to) { summary: "Release to the {payee} when {release_deadline} arrives undecided"; }\n when(release_to) { summary: "Release to the {release_to} when {release_deadline} arrives undecided"; }\n due { field: release_deadline; };\n when_not(payee_fee) {\n when_not(release_to) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payee_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_on_deadline; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { releaseOnDeadlineTransferTransferId: transferId; }; }]; }\n when(release_to) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", release_to_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_on_deadline; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { releaseOnDeadlineTransferTransferId: transferId; }; }]; }\n }\n when(payee_fee) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payee_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_on_deadline; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { releaseOnDeadlineTransferTransferId: transferId; }; }]; }\n steps: [];\n }\n }\n when(on_cancel) {\n action cancel {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Cancel the ", words(instrument), " and start returning the held money to the ", words(payer), ". It must be funded and not yet released. Cancellation is final, and a cancelled settlement never releases. Where the product sets a release deadline, this call is refused once that date passes.");\n summary: "Cancel the settlement and start the unwind";\n when(release_deadline) { deadline { field: release_deadline; }; }\n when_not(payee_fee) {\n when_not(cancel_splits) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payer_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: cancel; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { cancelTransferTransferId: transferId; }; }]; }\n when(cancel_splits) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payer_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: cancel; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { cancelTransferTransferId: transferId; }; }]; }\n }\n when(payee_fee) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payer_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: cancel; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { cancelTransferTransferId: transferId; }; }]; }\n steps: [];\n }\n }\n when(payee_fee) {\n when_not(on_cancel) {\n action fund_piece_2 {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Move the carved platform fee piece from the ", words(payer), " into escrow, which completes funding. Piece 1 must be in escrow first. It debits the ", words(payer), " for real, and the whole held amount then sits in escrow.");\n summary: "Fund piece 2 of the held amount into escrow";\n when_eq(payee_fee_kind, percent) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.escrowAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { fundPiece2TransferTransferId: transferId; }; }]; }\n when_eq(payee_fee_kind, binding) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", payee_fee); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.escrowAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { fundPiece2TransferTransferId: transferId; }; }]; }\n sandbox_failure_point: funding;\n steps: [];\n }\n action release_piece_2 {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Pay the carved platform fee out of escrow to the platform account, which closes the release. The release decision must have run first. Money leaves escrow on this call and does not come back.");\n summary: "Release piece 2 of the held amount";\n when_eq(payee_fee_kind, percent) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { releasePiece2TransferTransferId: transferId; }; }]; }\n when_eq(payee_fee_kind, binding) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", payee_fee); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { releasePiece2TransferTransferId: transferId; }; }]; }\n steps: [];\n }\n action unfund_piece_1 {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Return piece 1 from escrow to the ", words(payer), " and abandon the ", words(instrument), ". It runs only while funding stopped after piece 1, before the release. The settlement ends abandoned.");\n summary: "Return piece 1 to the {payer} on abandonment";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payer_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: unfund_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { unfundPiece1TransferTransferId: transferId; }; }];\n steps: [];\n }\n }\n }\n when_not(payee_fee) {\n when(cancel_splits) {\n action fund_piece_2 {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Move piece 2 of the split amount from the ", words(payer), " into escrow, which completes funding. Piece 1 must be in escrow first. It debits the ", words(payer), " for real, and the whole held amount then sits in escrow.");\n summary: "Fund piece 2 of the held amount into escrow";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.escrowAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { fundPiece2TransferTransferId: transferId; }; }];\n sandbox_failure_point: funding;\n steps: [];\n }\n action release_piece_2 {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Pay piece 2 out of escrow to the ", words(payee), ", which closes the release. The release decision must have run first. Money leaves escrow on this call and does not come back.");\n summary: "Release piece 2 of the held amount";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payee_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { releasePiece2TransferTransferId: transferId; }; }];\n steps: [];\n }\n action refund_piece_2 {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Return piece 2 out of escrow to the ", words(payee), ", which closes the cancellation. Cancel must have run first. Money leaves escrow on this call and does not come back.");\n summary: "Return piece 2 on cancellation";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payee_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: refund_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { refundPiece2TransferTransferId: transferId; }; }];\n steps: [];\n }\n action unfund_piece_1 {\n let(metadata_instrument): "metadata.instrumentId";\n let(metadata_instance): "metadata.instrumentInstanceId";\n let(metadata_phase): "metadata.phase";\n agent_description: concat("Return piece 1 from escrow to the ", words(payer), " and abandon the ", words(instrument), ". It runs only while funding stopped after piece 1, before the release. The settlement ends abandoned.");\n summary: "Return piece 1 to the {payer} on abandonment";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payer_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: unfund_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { unfundPiece1TransferTransferId: transferId; }; }];\n steps: [];\n }\n }\n }\n action abandon {\n agent_description: concat("Close a ", words(instrument), " that was never funded. The escrow account must be empty, so this refuses once any piece is in it. It moves no money and the settlement ends here with no way back.");\n summary: "Abandon the settlement before any money is held";\n requires drained: { path: refs.escrowAccountId; };\n moves: [];\n steps: [];\n }\n action dispute {\n agent_description: concat("Freeze a funded ", words(instrument), " so neither release nor refund can run until the parties settle the disagreement. No money moves and the whole balance stays put. Only a funded ", words(instrument), " disputes.");\n description: concat("Freezes funded ", words(instrument), ", preserving the entire balance while the parties decide what happens next.");\n summary: concat("Dispute a ", words(instrument), " settlement");\n when(release_deadline) { deadline { field: release_deadline; }; }\n moves: [];\n steps: [];\n }\n action resume {\n agent_description: concat("Lift the dispute hold and put the ", words(instrument), " back where it was, funded and ready to release. No money moves and the balance was never touched. Only a disputed ", words(instrument), " resumes. Use this when the parties settled without a refund.");\n description: concat("Ends the dispute hold without moving money and returns the fully funded ", words(instrument), " to its first milestone.");\n summary: concat("Resume a ", words(instrument), " settlement");\n moves: [];\n steps: [];\n }\n }\n when(whole_amount) {\n let(fee_collection_key): "x-hyperscale-fee-collection-port";\n let(reference_filter_key): "x-hyperscale-reference-filter";\n let(whole_fee_field): camel(whole_fee);\n title: sentence(instrument);\n id_prefix: if_eq(id_prefix_override, false, prefix(instrument), id_prefix_override);\n summary: concat("Escrow-held payment from ", words(payer), " to ", words(payee));\n description: concat("Held payment: the ", words(payer), " funds ", amount, " into this settlement\'s own escrow; ", release_actor_text, " confirms through ", release, " to release");\n distinct_parties: true;\n caller_parked_states: {\n created: "The tenant either funds the hold or abandons it before any money moves.";\n disputed: "Dispute resolution is a human judgment; the parties resume the hold to continue.";\n };\n fields {\n currency { type: text; description: "One currency for the hold and its fee"; const: C; }\n amount { type: money<C>; description: "Amount held in custody, in {C} minor units"; }\n [payee]AccountId { type: account<C>; description: "Party the hold protects and pays on release"; [reference_filter_key]: { column: role; values: [customer_balance]; }; }\n [payer]AccountId { type: account<C>; description: "Party whose money is held until the hold resolves"; [reference_filter_key]: { column: role; values: [customer_balance]; }; }\n platformAccountId { type: account<C>; description: "Product revenue or customer balance account collecting the custody fee"; [fee_collection_key]: true; [reference_filter_key]: { column: role; values: [customer_balance, product_revenue]; }; }\n when(reference) { reference { type: text; description: "The tenant\'s own identifier for what is held: invoice number, project code, contract reference"; optional: true; max_length: 180; min_length: 1; } }\n [release_deadline] { type: date; description: "Date the hold releases to the payee on its own; fixed at creation and the cutoff for both the release port and cancellation"; }\n whole_fee { type: money<C>; description: "Custody fee charged on top at funding and never placed in escrow"; }\n when(upstream) { upstream { type: ref<instrument>; description: "Hold this one is carved out of, when a chain of holds passes the same money down"; optional: true; pattern: concat("^", id_prefix_override, "_(sandbox|live)_[a-z0-9]{8,64}$"); } }\n }\n lifecycle {\n states created funded disputed released cancelled abandoned;\n initial created;\n on abandon: created -> abandoned;\n on cancel: funded -> cancelled;\n on dispute: funded -> disputed;\n on fund: created -> funded;\n on [release_action]: funded -> released;\n on release_on_deadline: funded -> released;\n on resume: disputed -> funded;\n }\n parties { beneficiary: payee_account_field; payer: payer_account_field; }\n required: [payer_account_field, payee_account_field, platformAccountId, amount, whole_fee_field, currency, release_deadline];\n action create {\n let(owner_id): "owner.id";\n let(owner_type): "owner.type";\n agent_description: concat("Open the ", words(instrument), " and provision the escrow account that holds the money. No money moves on this call. ", release_deadline, " is fixed here and never moves again, so one date governs the early release, the cancellation, and the automatic payout.");\n summary: concat("Create a ", words(instrument), " settlement");\n moves: [];\n steps: [{ operation: "account.escrow.provision"; bind: { currency: { from: instance; path: fields.currency; }; [owner_id]: { from: instance; path: productId; }; [owner_type]: { from: const; value: product; }; productId: { from: instance; path: productId; }; role: { from: const; value: product_escrow; }; }; capture: { escrowAccountId: accountId; }; }];\n }\n action fund {\n agent_description: concat("Move ", amount, " from the ", words(payer), " into escrow and charge ", whole_fee_field, " on top to the platform account, as one linked batch. This moves money and the ", words(payer), " pays both. Only a created hold funds. The fee never enters escrow, so it is non-refundable however the hold ends.");\n summary: "Fund the hold and collect the custody fee";\n moves: [\n { key: principal; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.escrowAccountId; }; "metadata.instrumentId": { from: const; value: instrument; }; "metadata.instrumentInstanceId": { from: instance; path: instrumentInstanceId; }; "metadata.phase": { from: const; value: fund; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { fundingTransferId: transferId; }; },\n { key: service_fee; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", whole_fee_field); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; "metadata.instrumentId": { from: const; value: instrument; }; "metadata.instrumentInstanceId": { from: instance; path: instrumentInstanceId; }; "metadata.phase": { from: const; value: service_fee; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { serviceFeeTransferId: transferId; }; }\n ];\n sandbox_failure_point: funding;\n steps: [];\n }\n action [release_action] {\n agent_description: concat("Pay the ", words(payee), " the whole ", amount, " out of escrow ahead of ", release_deadline, ". This moves money and does not reverse. Only a funded hold releases, the caller must act as ", words(at(release_allowed, 1)), ", and the call is refused once ", release_deadline, " passes. The custody fee stays posted.");\n summary: concat("Release the hold to the ", words(payee), " early");\n deadline: { field: release_deadline; };\n port: { allowed_parties: [at(release_allowed, 1)]; };\n moves: [{ key: release_action; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payee_account_field); }; "metadata.instrumentId": { from: const; value: instrument; }; "metadata.instrumentInstanceId": { from: instance; path: instrumentInstanceId; }; "metadata.phase": { from: const; value: release_action; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { releaseTransferId: transferId; }; }];\n sandbox_failure_point: "release";\n steps: [];\n }\n action release_on_deadline {\n summary: concat("Release the hold to the ", words(payee), " on its release date");\n due: { field: release_deadline; };\n event_name: concat(instrument, ".released_on_deadline");\n moves: [{ key: release_action; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payee_account_field); }; "metadata.instrumentId": { from: const; value: instrument; }; "metadata.instrumentInstanceId": { from: instance; path: instrumentInstanceId; }; "metadata.phase": { from: const; value: deadline_release; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { deadlineReleaseTransferId: transferId; }; }];\n steps: [];\n }\n action cancel {\n agent_description: concat("Return the whole ", amount, " from escrow to the ", words(payer), " and end the hold. This moves money and does not reverse. Only a funded hold cancels, and only before ", release_deadline, " passes. The custody fee stays posted because the custody it paid for was already provided.");\n summary: "Cancel the hold and return the held amount";\n deadline: { field: release_deadline; };\n event_name: concat(instrument, ".cancelled");\n moves: [{ key: refund; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat("fields.", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", payer_account_field); }; "metadata.instrumentId": { from: const; value: instrument; }; "metadata.instrumentInstanceId": { from: instance; path: instrumentInstanceId; }; "metadata.phase": { from: const; value: cancel_refund; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { cancelRefundTransferId: transferId; }; }];\n steps: [];\n }\n action abandon {\n agent_description: concat("Close a ", words(instrument), " that was opened and never funded. No money has moved and the escrow must be empty, so this is a lifecycle transition only. A funded hold refuses. Release it to the ", words(payee), " or cancel it back to the ", words(payer), " instead.");\n summary: "Abandon the hold before it is funded";\n moves: [];\n requires_drained: { path: refs.escrowAccountId; };\n steps: [];\n }\n action dispute {\n agent_description: concat("Freeze a funded ", words(instrument), " so neither release nor refund can run until the parties settle the disagreement. No money moves and the whole balance stays put. Only a funded ", words(instrument), " disputes.");\n description: concat("Freezes funded ", words(instrument), ", preserving the entire balance while the parties decide what happens next.");\n summary: concat("Dispute a ", words(instrument), " settlement");\n deadline: { field: release_deadline; };\n moves: [];\n steps: [];\n }\n action resume {\n agent_description: concat("Lift the dispute hold and put the ", words(instrument), " back where it was, funded and ready to release. No money moves and the balance was never touched. Only a disputed ", words(instrument), " resumes. Use this when the parties settled without a refund.");\n description: concat("Ends the dispute hold without moving money and returns the fully funded ", words(instrument), " to its first milestone.");\n summary: concat("Resume a ", words(instrument), " settlement");\n moves: [];\n steps: [];\n }\n }\n}\n',
|
|
34
34
|
],
|
|
35
35
|
[
|
|
36
36
|
"money_flows/index.hsx",
|
package/src/typecheck.ts
CHANGED
|
@@ -3147,7 +3147,8 @@ function checkInstrument(
|
|
|
3147
3147
|
},
|
|
3148
3148
|
},
|
|
3149
3149
|
};
|
|
3150
|
-
})
|
|
3150
|
+
})
|
|
3151
|
+
.map((action) => pinFixedCurrencyBindings(action, fields, diagnostics));
|
|
3151
3152
|
for (const action of actions) {
|
|
3152
3153
|
for (const declared of ports.get(action.name) ?? []) {
|
|
3153
3154
|
const allowed = entry(declared.body, "allowed")?.value;
|
|
@@ -3885,6 +3886,109 @@ function isJsonObject(value: JsonValue): value is Record<string, JsonValue> {
|
|
|
3885
3886
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3886
3887
|
}
|
|
3887
3888
|
|
|
3889
|
+
/**
|
|
3890
|
+
* A money<SAR> field moves SAR, whatever the caller wrote in the instance
|
|
3891
|
+
* currency field. Every move of a fixed-currency amount, and every account
|
|
3892
|
+
* step of an instrument whose money is fixed, binds that currency as a
|
|
3893
|
+
* constant: a binding that reads one of the instrument's own text fields is
|
|
3894
|
+
* pinned here, and one the compiler cannot pin (an input, a ref, or a
|
|
3895
|
+
* constant naming another currency) is refused as HSX1306.
|
|
3896
|
+
*/
|
|
3897
|
+
function pinFixedCurrencyBindings(
|
|
3898
|
+
action: TypedAction,
|
|
3899
|
+
fields: readonly TypedField[],
|
|
3900
|
+
diagnostics: GeneralDiagnostic[],
|
|
3901
|
+
): TypedAction {
|
|
3902
|
+
const fieldByName = new Map(fields.map((field) => [field.name, field]));
|
|
3903
|
+
const fixedCurrencyOf = (field: TypedField | undefined) =>
|
|
3904
|
+
field?.type.kind === "money" &&
|
|
3905
|
+
field.type.currency &&
|
|
3906
|
+
CURRENCY.test(field.type.currency)
|
|
3907
|
+
? field.type.currency
|
|
3908
|
+
: undefined;
|
|
3909
|
+
const fixed = new Set(
|
|
3910
|
+
fields.flatMap((field) => fixedCurrencyOf(field) ?? []),
|
|
3911
|
+
);
|
|
3912
|
+
// Two fixed currencies is HSX1305's refusal; one decides here, none skips.
|
|
3913
|
+
const ledger = fixed.size === 1 ? [...fixed][0] : undefined;
|
|
3914
|
+
if (!ledger) return action;
|
|
3915
|
+
const instanceField = (
|
|
3916
|
+
binding: JsonValue | undefined,
|
|
3917
|
+
): TypedField | undefined =>
|
|
3918
|
+
binding !== undefined &&
|
|
3919
|
+
isJsonObject(binding) &&
|
|
3920
|
+
binding.from === "instance" &&
|
|
3921
|
+
typeof binding.path === "string" &&
|
|
3922
|
+
binding.path.startsWith("fields.")
|
|
3923
|
+
? fieldByName.get(binding.path.slice("fields.".length))
|
|
3924
|
+
: undefined;
|
|
3925
|
+
const pin = (
|
|
3926
|
+
entry: JsonValue,
|
|
3927
|
+
currency: string,
|
|
3928
|
+
subject: string,
|
|
3929
|
+
): JsonValue => {
|
|
3930
|
+
if (!isJsonObject(entry)) return entry;
|
|
3931
|
+
const bind = entry.bind;
|
|
3932
|
+
if (bind === undefined || !isJsonObject(bind)) return entry;
|
|
3933
|
+
const binding = bind.currency;
|
|
3934
|
+
if (binding === undefined) return entry;
|
|
3935
|
+
const bound =
|
|
3936
|
+
isJsonObject(binding) && binding.from === "const"
|
|
3937
|
+
? String(binding.value)
|
|
3938
|
+
: undefined;
|
|
3939
|
+
if (bound === currency) return entry;
|
|
3940
|
+
if (bound === undefined && instanceField(binding)?.type.kind === "text") {
|
|
3941
|
+
return {
|
|
3942
|
+
...entry,
|
|
3943
|
+
bind: { ...bind, currency: { from: "const", value: currency } },
|
|
3944
|
+
};
|
|
3945
|
+
}
|
|
3946
|
+
diagnostics.push({
|
|
3947
|
+
code: "HSX1306",
|
|
3948
|
+
fix: `bind currency as { from: const; value: ${currency}; }`,
|
|
3949
|
+
message: `action ${action.name} binds the currency of ${subject} to ${bound ?? "a caller-supplied value"}; a money<${currency}> amount moves in ${currency}`,
|
|
3950
|
+
severity: "error",
|
|
3951
|
+
span: action.origin,
|
|
3952
|
+
});
|
|
3953
|
+
return entry;
|
|
3954
|
+
};
|
|
3955
|
+
const moves = action.slots.moves ?? [];
|
|
3956
|
+
const steps = action.slots.steps ?? [];
|
|
3957
|
+
return {
|
|
3958
|
+
...action,
|
|
3959
|
+
slots: {
|
|
3960
|
+
...action.slots,
|
|
3961
|
+
moves: Array.isArray(moves)
|
|
3962
|
+
? moves.map((move) => {
|
|
3963
|
+
if (!isJsonObject(move)) return move;
|
|
3964
|
+
// A post settles a reservation; the ledger refuses a post whose
|
|
3965
|
+
// currency differs from the reserve, which is pinned here.
|
|
3966
|
+
if (move.operation === "internal_transfer.post") return move;
|
|
3967
|
+
const bind = move.bind;
|
|
3968
|
+
const amount =
|
|
3969
|
+
bind !== undefined && isJsonObject(bind)
|
|
3970
|
+
? instanceField(bind.amount)
|
|
3971
|
+
: undefined;
|
|
3972
|
+
return pin(
|
|
3973
|
+
move,
|
|
3974
|
+
fixedCurrencyOf(amount) ?? ledger,
|
|
3975
|
+
amount ? `fields.${amount.name}` : "the moved amount",
|
|
3976
|
+
);
|
|
3977
|
+
})
|
|
3978
|
+
: moves,
|
|
3979
|
+
steps: Array.isArray(steps)
|
|
3980
|
+
? steps.map((step) =>
|
|
3981
|
+
isJsonObject(step) &&
|
|
3982
|
+
typeof step.operation === "string" &&
|
|
3983
|
+
step.operation.startsWith("account.")
|
|
3984
|
+
? pin(step, ledger, `the ${step.operation} account`)
|
|
3985
|
+
: step,
|
|
3986
|
+
)
|
|
3987
|
+
: steps,
|
|
3988
|
+
},
|
|
3989
|
+
};
|
|
3990
|
+
}
|
|
3991
|
+
|
|
3888
3992
|
function bindFieldReferences(
|
|
3889
3993
|
slots: Readonly<Record<string, JsonValue>>,
|
|
3890
3994
|
fields: ReadonlySet<string>,
|
package/src/version.ts
CHANGED
|
@@ -497,6 +497,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
497
497
|
agent_description: concat("Freeze a funded ", words(instrument), " so neither release nor refund can run until the parties settle the disagreement. No money moves and the whole balance stays put. Only a funded ", words(instrument), " disputes.");
|
|
498
498
|
description: concat("Freezes funded ", words(instrument), ", preserving the entire balance while the parties decide what happens next.");
|
|
499
499
|
summary: concat("Dispute a ", words(instrument), " settlement");
|
|
500
|
+
when(release_deadline) { deadline { field: release_deadline; }; }
|
|
500
501
|
moves: [];
|
|
501
502
|
steps: [];
|
|
502
503
|
}
|
|
@@ -598,6 +599,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
598
599
|
agent_description: concat("Freeze a funded ", words(instrument), " so neither release nor refund can run until the parties settle the disagreement. No money moves and the whole balance stays put. Only a funded ", words(instrument), " disputes.");
|
|
599
600
|
description: concat("Freezes funded ", words(instrument), ", preserving the entire balance while the parties decide what happens next.");
|
|
600
601
|
summary: concat("Dispute a ", words(instrument), " settlement");
|
|
602
|
+
deadline: { field: release_deadline; };
|
|
601
603
|
moves: [];
|
|
602
604
|
steps: [];
|
|
603
605
|
}
|