@hyperscale0/hsx 1.0.0-beta.1 → 1.0.0-rc.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 +41 -9
- package/LICENSING.md +3 -3
- package/README.md +37 -149
- package/TRADEMARKS.md +4 -4
- package/dist/src/ast.d.ts +129 -10
- package/dist/src/ast.d.ts.map +1 -1
- package/dist/src/cli.d.ts +2 -1
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +148 -12
- package/dist/src/cli.js.map +1 -1
- package/dist/src/compile.d.ts +41 -4
- package/dist/src/compile.d.ts.map +1 -1
- package/dist/src/compile.js +82 -15
- package/dist/src/compile.js.map +1 -1
- package/dist/src/cost.d.ts +78 -0
- package/dist/src/cost.d.ts.map +1 -0
- package/dist/src/cost.js +369 -0
- package/dist/src/cost.js.map +1 -0
- package/dist/src/diagnostics.d.ts +11 -0
- package/dist/src/diagnostics.d.ts.map +1 -0
- package/dist/src/diagnostics.js +420 -0
- package/dist/src/diagnostics.js.map +1 -0
- package/dist/src/emit.d.ts +35 -0
- package/dist/src/emit.d.ts.map +1 -0
- package/dist/src/emit.js +359 -0
- package/dist/src/emit.js.map +1 -0
- package/dist/src/entry-overrides.d.ts +17 -3
- package/dist/src/entry-overrides.d.ts.map +1 -1
- package/dist/src/entry-overrides.js +180 -35
- package/dist/src/entry-overrides.js.map +1 -1
- package/dist/src/format.d.ts +10 -0
- package/dist/src/format.d.ts.map +1 -0
- package/dist/src/format.js +183 -0
- package/dist/src/format.js.map +1 -0
- package/dist/src/index.d.ts +10 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +8 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/ir.d.ts +66 -0
- package/dist/src/ir.d.ts.map +1 -0
- package/dist/src/ir.js +15 -0
- package/dist/src/ir.js.map +1 -0
- package/dist/src/lex.d.ts +9 -1
- package/dist/src/lex.d.ts.map +1 -1
- package/dist/src/lex.js +50 -4
- package/dist/src/lex.js.map +1 -1
- package/dist/src/limits.d.ts +2 -3
- package/dist/src/limits.d.ts.map +1 -1
- package/dist/src/limits.js +2 -3
- package/dist/src/limits.js.map +1 -1
- package/dist/src/modules.d.ts +36 -0
- package/dist/src/modules.d.ts.map +1 -0
- package/dist/src/modules.js +372 -0
- package/dist/src/modules.js.map +1 -0
- package/dist/src/parse.d.ts.map +1 -1
- package/dist/src/parse.js +590 -17
- package/dist/src/parse.js.map +1 -1
- package/dist/src/typecheck.d.ts +8 -0
- package/dist/src/typecheck.d.ts.map +1 -0
- package/dist/src/typecheck.js +2958 -0
- package/dist/src/typecheck.js.map +1 -0
- package/dist/src/version.d.ts +4 -15
- package/dist/src/version.d.ts.map +1 -1
- package/dist/src/version.js +4 -15
- package/dist/src/version.js.map +1 -1
- package/docs/README.md +23 -0
- package/docs/guide/01-first-program.md +24 -0
- package/docs/guide/02-money.md +25 -0
- package/docs/guide/03-instruments.md +22 -0
- package/docs/guide/04-lifecycles.md +25 -0
- package/docs/guide/05-fees-and-splits.md +45 -0
- package/docs/guide/06-schedules.md +22 -0
- package/docs/guide/07-composition.md +46 -0
- package/docs/guide/08-writing-a-module.md +26 -0
- package/docs/guide/09-cost.md +24 -0
- package/docs/guide/10-diagnostics.md +27 -0
- package/docs/llms-full.txt +1738 -0
- package/docs/llms.txt +45 -0
- package/docs/reference/cli.md +33 -0
- package/docs/reference/diagnostics.md +619 -0
- package/docs/reference/grammar.md +39 -0
- package/docs/reference/std/advance.md +35 -0
- package/docs/reference/std/cancellable_booking.md +35 -0
- package/docs/reference/std/captured_payment.md +43 -0
- package/docs/reference/std/conditional_disbursement.md +33 -0
- package/docs/reference/std/credit_facility.md +35 -0
- package/docs/reference/std/held_payment.md +64 -0
- package/docs/reference/std/instant_transfer.md +30 -0
- package/docs/reference/std/metered.md +27 -0
- package/docs/reference/std/pooled_split.md +26 -0
- package/docs/reference/std/premium_forward.md +39 -0
- package/docs/reference/std/reconciled_payout.md +33 -0
- package/docs/reference/std/recurring_collection.md +25 -0
- package/docs/reference/std/rotating_pool.md +42 -0
- package/docs/reference/std/scheduled.md +51 -0
- package/docs/reference/std/security_deposit.md +46 -0
- package/docs/reference/std/settlement_batch.md +42 -0
- package/docs/reference/std/swap.md +46 -0
- package/docs/reference/std/threshold_pool.md +47 -0
- package/docs/reference/std/weighted_distribution.md +39 -0
- package/docs/reference/types.md +20 -0
- package/docs/reference/udl-output.md +14 -0
- package/docs/sessions/2026-09-02-two-instruments.hsx +21 -0
- package/docs/sessions/2026-09-02-two-instruments.md +41 -0
- package/examples/01-first-program/README.md +45 -0
- package/examples/01-first-program/tip-jar.hsx +17 -0
- package/examples/02-imports-and-archetypes/README.md +99 -0
- package/examples/02-imports-and-archetypes/photo-booth.hsx +37 -0
- package/examples/03-diagnostics/README.md +68 -0
- package/examples/03-diagnostics/corner-shop-fixed.hsx +22 -0
- package/examples/03-diagnostics/corner-shop.hsx +19 -0
- package/examples/04-complete-product/README.md +87 -0
- package/examples/04-complete-product/study-hall.hsx +67 -0
- package/examples/05-watch-club/README.md +9 -0
- package/examples/05-watch-club/watch-club.hsx +124 -0
- package/examples/README.md +18 -0
- package/examples/advance/README.md +3 -0
- package/examples/advance/advance.hsx +13 -0
- package/examples/advance/advance.udl +339 -0
- package/examples/cancellable_booking/README.md +3 -0
- package/examples/cancellable_booking/cancellable_booking.hsx +14 -0
- package/examples/cancellable_booking/cancellable_booking.udl +391 -0
- package/examples/captured_payment/README.md +3 -0
- package/examples/captured_payment/captured_payment.hsx +21 -0
- package/examples/captured_payment/captured_payment.udl +677 -0
- package/examples/conditional_disbursement/README.md +3 -0
- package/examples/conditional_disbursement/conditional_disbursement.hsx +17 -0
- package/examples/conditional_disbursement/conditional_disbursement.udl +412 -0
- package/examples/cost-table.json +386 -0
- package/examples/credit_facility/README.md +3 -0
- package/examples/credit_facility/credit_facility.hsx +27 -0
- package/examples/credit_facility/credit_facility.udl +1373 -0
- package/examples/held_payment/README.md +3 -0
- package/examples/held_payment/held_payment.hsx +13 -0
- package/examples/held_payment/held_payment.udl +507 -0
- package/examples/instant_transfer/README.md +3 -0
- package/examples/instant_transfer/instant_transfer.hsx +16 -0
- package/examples/instant_transfer/instant_transfer.udl +364 -0
- package/examples/metered/README.md +3 -0
- package/examples/metered/metered.hsx +13 -0
- package/examples/metered/metered.udl +247 -0
- package/examples/pooled_split/README.md +3 -0
- package/examples/pooled_split/pooled_split.hsx +15 -0
- package/examples/pooled_split/pooled_split.udl +425 -0
- package/examples/premium_forward/README.md +3 -0
- package/examples/premium_forward/premium_forward.hsx +19 -0
- package/examples/premium_forward/premium_forward.udl +594 -0
- package/examples/reconciled_payout/README.md +3 -0
- package/examples/reconciled_payout/reconciled_payout.hsx +13 -0
- package/examples/reconciled_payout/reconciled_payout.udl +303 -0
- package/examples/recurring_collection/README.md +3 -0
- package/examples/recurring_collection/recurring_collection.hsx +21 -0
- package/examples/recurring_collection/recurring_collection.udl +1130 -0
- package/examples/rotating_pool/README.md +3 -0
- package/examples/rotating_pool/rotating_pool.hsx +18 -0
- package/examples/rotating_pool/rotating_pool.udl +4352 -0
- package/examples/scheduled/README.md +3 -0
- package/examples/scheduled/scheduled.hsx +12 -0
- package/examples/scheduled/scheduled.udl +335 -0
- package/examples/security_deposit/README.md +3 -0
- package/examples/security_deposit/security_deposit.hsx +21 -0
- package/examples/security_deposit/security_deposit.udl +293 -0
- package/examples/settlement_batch/README.md +3 -0
- package/examples/settlement_batch/settlement_batch.hsx +19 -0
- package/examples/settlement_batch/settlement_batch.udl +753 -0
- package/examples/swap/README.md +3 -0
- package/examples/swap/swap.hsx +19 -0
- package/examples/swap/swap.udl +876 -0
- package/examples/threshold_pool/README.md +3 -0
- package/examples/threshold_pool/threshold_pool.hsx +16 -0
- package/examples/threshold_pool/threshold_pool.udl +667 -0
- package/examples/weighted_distribution/README.md +3 -0
- package/examples/weighted_distribution/weighted_distribution.hsx +20 -0
- package/examples/weighted_distribution/weighted_distribution.udl +337 -0
- package/package.json +19 -6
- package/skills/hsx/SKILL.md +483 -0
- package/src/ast.ts +163 -9
- package/src/cli.ts +164 -12
- package/src/compile.ts +160 -20
- package/src/cost.ts +579 -0
- package/src/diagnostics.ts +442 -0
- package/src/emit.ts +447 -0
- package/src/entry-overrides.ts +263 -48
- package/src/format.ts +261 -0
- package/src/index.ts +32 -4
- package/src/ir.ts +99 -0
- package/src/lex.ts +59 -4
- package/src/limits.ts +2 -3
- package/src/modules.ts +444 -0
- package/src/parse.ts +655 -21
- package/src/typecheck.ts +3876 -0
- package/src/version.ts +4 -16
- package/std/SEMANTICS.md +130 -0
- package/std/settlements/advance.hsx +177 -0
- package/std/settlements/cancellable_booking.hsx +87 -0
- package/std/settlements/captured_payment.hsx +144 -0
- package/std/settlements/conditional_disbursement.hsx +185 -0
- package/std/settlements/credit_facility.hsx +167 -0
- package/std/settlements/held_payment.hsx +578 -0
- package/std/settlements/index.hsx +3 -0
- package/std/settlements/instant_transfer.hsx +190 -0
- package/std/settlements/metered.hsx +67 -0
- package/std/settlements/pooled_split.hsx +68 -0
- package/std/settlements/premium_forward.hsx +257 -0
- package/std/settlements/reconciled_payout.hsx +77 -0
- package/std/settlements/recurring_collection.hsx +27 -0
- package/std/settlements/rotating_pool.hsx +252 -0
- package/std/settlements/scheduled.hsx +641 -0
- package/std/settlements/security_deposit.hsx +166 -0
- package/std/settlements/settlement_batch.hsx +122 -0
- package/std/settlements/swap.hsx +1114 -0
- package/std/settlements/threshold_pool.hsx +221 -0
- package/std/settlements/weighted_distribution.hsx +141 -0
- package/dist/src/archetypes.d.ts +0 -17
- package/dist/src/archetypes.d.ts.map +0 -1
- package/dist/src/archetypes.js +0 -327
- package/dist/src/archetypes.js.map +0 -1
- package/dist/src/check.d.ts +0 -12
- package/dist/src/check.d.ts.map +0 -1
- package/dist/src/check.js +0 -1910
- package/dist/src/check.js.map +0 -1
- package/dist/src/lower.d.ts +0 -146
- package/dist/src/lower.d.ts.map +0 -1
- package/dist/src/lower.js +0 -4570
- package/dist/src/lower.js.map +0 -1
- package/dist/src/model.d.ts +0 -467
- package/dist/src/model.d.ts.map +0 -1
- package/dist/src/model.js +0 -15
- package/dist/src/model.js.map +0 -1
- package/spec/hsx-ir.schema.json +0 -920
- package/src/archetypes.ts +0 -340
- package/src/check.ts +0 -2938
- package/src/lower.ts +0 -5416
- package/src/model.ts +0 -519
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
module std.settlements.security_deposit
|
|
2
|
+
|
|
3
|
+
export instrument security_deposit<C>(payer: party, holder: party, amount: money<C>, claim: optional<condition>, return: optional<condition>, claim_amount: optional<block>, deadline: optional<date>, claim_to: optional<party>, return_to: optional<party>, memo: optional<text>, claim_input: optional<block>, claim_capture: optional<block>, fund_failure_point: optional<text>, id_prefix_override: optional<text>) {
|
|
4
|
+
let(claim_name): claim;
|
|
5
|
+
let(return_name): return;
|
|
6
|
+
let(payer_account_field): camel(concat(payer, "_account_id"));
|
|
7
|
+
let(holder_account_field): camel(concat(holder, "_account_id"));
|
|
8
|
+
let(claim_actor): at(claim_allowed, 1);
|
|
9
|
+
let(claim_role): if_eq(claim_actor, payer, "payer", "beneficiary");
|
|
10
|
+
let(claim_field): get(claim_amount, "field");
|
|
11
|
+
let(claim_bound): get(claim_amount, "bound");
|
|
12
|
+
let(claim_remainder): get(claim_amount, "remainder");
|
|
13
|
+
let(deadline_field): concat(deadline);
|
|
14
|
+
let(memo_field): concat(memo);
|
|
15
|
+
let(claim_to_role): if_eq(claim_to, payer, "payer", "beneficiary");
|
|
16
|
+
let(return_to_role): if_eq(return_to, payer, "payer", "beneficiary");
|
|
17
|
+
agent_description: "Reach for security deposit when money is reserved against the {payer} in the {holder}'s favor and later claimed or returned. The funds stay reserved and move only on the claim. Pick captured payment when the holder takes the money in slices, and instant transfer when nothing is held.";
|
|
18
|
+
summary: "Refundable deposit from {payer} held for {holder}";
|
|
19
|
+
title: sentence(instrument);
|
|
20
|
+
when(id_prefix_override) { id_prefix: id_prefix_override; }
|
|
21
|
+
when_not(deadline) {
|
|
22
|
+
when_not(claim_amount) {
|
|
23
|
+
description: concat("Deposit: the ", amount, " is reserved against the ", payer, "'s account in the ", holder, "'s favor, then claimed or returned in full");
|
|
24
|
+
}
|
|
25
|
+
when(claim_amount) {
|
|
26
|
+
description: concat("Deposit: the ", amount, " is reserved against the ", payer, "'s account in the ", holder, "'s favor, then ", claim_field, " is claimed and the exact remainder returned, or the deposit is returned in full");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
fields {
|
|
30
|
+
when_not(deadline) {
|
|
31
|
+
amount { type: money<C>; description: "The deposit amount in {C} minor units, reserved in full and fully accounted on claim or return"; }
|
|
32
|
+
}
|
|
33
|
+
when(deadline) {
|
|
34
|
+
let(reference_filter_key): "x-hyperscale-reference-filter";
|
|
35
|
+
amount { type: money<C>; description: "Positive minor-unit integer amount serialized as a string (at most 18 digits)"; }
|
|
36
|
+
[deadline_field] { type: date; }
|
|
37
|
+
[holder_account_field] { type: account<C>; description: "Hyperscale account ID"; pattern: "^acct_(sandbox|live)_[a-z0-9]{8,64}$"; [reference_filter_key]: { column: role; values: [customer_balance]; }; }
|
|
38
|
+
[payer_account_field] { type: account<C>; description: "Hyperscale account ID"; pattern: "^acct_(sandbox|live)_[a-z0-9]{8,64}$"; [reference_filter_key]: { column: role; values: [customer_balance]; }; }
|
|
39
|
+
when(memo) { [memo_field] { type: text; max_length: 180; min_length: 1; optional: true; } }
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
lifecycle {
|
|
43
|
+
when_not(deadline) {
|
|
44
|
+
states created held claimed returned;
|
|
45
|
+
initial created;
|
|
46
|
+
on place_deposit: created -> held;
|
|
47
|
+
on [claim_name]: held -> claimed;
|
|
48
|
+
on [return_name]: held -> returned;
|
|
49
|
+
}
|
|
50
|
+
when(deadline) {
|
|
51
|
+
states created held claimed returned expired canceled;
|
|
52
|
+
initial created;
|
|
53
|
+
on cancel: created -> canceled;
|
|
54
|
+
on claim: held -> claimed;
|
|
55
|
+
on expire: held -> expired;
|
|
56
|
+
on fund: created -> held;
|
|
57
|
+
on return: held -> returned;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
parties {
|
|
61
|
+
payer: payer;
|
|
62
|
+
beneficiary: holder;
|
|
63
|
+
}
|
|
64
|
+
when_not(deadline) {
|
|
65
|
+
action create {
|
|
66
|
+
agent_description: "Open the deposit row before anything is reserved. The caller supplies the {amount}, the currency, and the {payer} and {holder} accounts. Nothing is reserved yet, call place deposit next.";
|
|
67
|
+
summary: concat("Create a ", words(instrument));
|
|
68
|
+
moves: [];
|
|
69
|
+
steps: [];
|
|
70
|
+
}
|
|
71
|
+
action place_deposit {
|
|
72
|
+
agent_description: "Reserve the {amount} against the {payer}'s account in the {holder}'s favor. The deposit must be created. The money is held, not paid: the claim posts it to the {holder} and the return releases it back.";
|
|
73
|
+
let(metadata_instrument): "metadata.instrumentId";
|
|
74
|
+
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
75
|
+
let(metadata_phase): "metadata.phase";
|
|
76
|
+
summary: "Reserve the {amount} against the {payer}'s account";
|
|
77
|
+
moves: [{ key: reservation; operation: internal_transfer.reserve; bind: { amount: { from: instance; path: concat("fields.", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", holder_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: place_deposit; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { placeDepositReservationTransferId: transferId; }; }];
|
|
78
|
+
steps: [];
|
|
79
|
+
}
|
|
80
|
+
action [claim_name] {
|
|
81
|
+
agent_description: "Claim the held deposit for the {holder}. The deposit must be in held. The reservation settles here, whole or up to the decided amount with the exact remainder released back to the {payer}. There is no return afterwards.";
|
|
82
|
+
summary: "Claim the deposit for the {holder} through {claim}";
|
|
83
|
+
when_not(claim_amount) {
|
|
84
|
+
moves: [{ key: post; operation: internal_transfer.post; bind: { transferId: { from: instance; path: refs.placeDepositReservationTransferId; }; }; }];
|
|
85
|
+
}
|
|
86
|
+
when(claim_amount) {
|
|
87
|
+
let(decided_clause): "decided amount";
|
|
88
|
+
input {
|
|
89
|
+
type: object;
|
|
90
|
+
additional_properties: false;
|
|
91
|
+
properties {
|
|
92
|
+
for input_name in keys(claim_fields) {
|
|
93
|
+
[input_name] { type: string; description: "Required decision reference retained in the operation receipt"; max_length: 180; min_length: 1; }
|
|
94
|
+
}
|
|
95
|
+
[claim_field] { type: string; description: "Caller-decided amount in minor units"; pattern: "^(0|[1-9][0-9]{0,17})$"; }
|
|
96
|
+
}
|
|
97
|
+
required: keys(claim_fields);
|
|
98
|
+
}
|
|
99
|
+
[decided_clause]: { field: claim_field; bound_field: claim_bound; remainder_action: return_name; };
|
|
100
|
+
port { allowed_parties: [claim_role]; };
|
|
101
|
+
moves: [{ key: post; operation: internal_transfer.post; bind: { amount: { from: input; path: claim_field; }; currency: { from: instance; path: fields.currency; }; postMode: { from: const; value: partial_only; }; transferId: { from: instance; path: refs.placeDepositReservationTransferId; }; }; }, { key: remainder; operation: internal_transfer.void; bind: { reason: { from: const; value: "Return the unclaimed deposit remainder"; }; transferId: { from: instance; path: refs.placeDepositReservationTransferId; }; }; }];
|
|
102
|
+
}
|
|
103
|
+
steps: [];
|
|
104
|
+
}
|
|
105
|
+
action [return_name] {
|
|
106
|
+
agent_description: "Return the held deposit to the {payer}. The deposit must be in held and unclaimed. This releases the reservation whole and the {holder} receives nothing.";
|
|
107
|
+
summary: "Return the deposit to the {payer} through {return}";
|
|
108
|
+
moves: [{ key: void; operation: internal_transfer.void; bind: { reason: { from: const; value: "Deposit returned in full"; }; transferId: { from: instance; path: refs.placeDepositReservationTransferId; }; }; }];
|
|
109
|
+
steps: [];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
when(deadline) {
|
|
113
|
+
required: [payer_account_field, holder_account_field, amount, currency, deadline_field];
|
|
114
|
+
caller_parked_states: { created: "funding or canceling an unfunded hold is the payer's decision"; };
|
|
115
|
+
distinct_parties: true;
|
|
116
|
+
dials: [{ field: deadline_field; key: holding_window; kind: window; maxOffset: "P90D"; summary: "Maximum time before the machine returns an unclaimed hold."; title: "Holding window"; }];
|
|
117
|
+
action create {
|
|
118
|
+
agent_description: "Open the hold by recording the {payer}, the {holder}, the {amount}, and the deadline. No money moves here, so a created hold costs the {payer} nothing until fund runs. The deadline is when the machine returns anything still unclaimed.";
|
|
119
|
+
deadline: { field: deadline_field; };
|
|
120
|
+
summary: concat("Create a ", words(instrument));
|
|
121
|
+
moves: [];
|
|
122
|
+
steps: [];
|
|
123
|
+
}
|
|
124
|
+
action fund {
|
|
125
|
+
agent_description: "Reserve the whole {amount} out of the {payer}'s balance for the {holder}. This moves money and the {payer} can no longer spend it. Only a created hold funds. The reservation carries no expiry of its own, so the hold's deadline governs when it comes back.";
|
|
126
|
+
deadline: { field: deadline_field; };
|
|
127
|
+
moves: [{ key: reservation; operation: internal_transfer.reserve; bind: { amount: { from: instance; path: concat("fields.", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat("fields.", holder_account_field); }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat("fields.", payer_account_field); }; }; capture: { holdTransferId: transferId; }; }];
|
|
128
|
+
when(fund_failure_point) { sandbox_failure_point: fund_failure_point; }
|
|
129
|
+
steps: [];
|
|
130
|
+
summary: "Fund the security deposit";
|
|
131
|
+
}
|
|
132
|
+
action claim {
|
|
133
|
+
agent_description: "Post the {holder}'s decided amount out of the reservation and void the remainder back to the {payer} in one linked group. This moves money and does not reverse. Only a funded hold claims, and the claimed amount cannot exceed the held {amount}.";
|
|
134
|
+
deadline: { field: deadline_field; };
|
|
135
|
+
when(claim_capture) { capture_input: claim_capture; }
|
|
136
|
+
when(claim_amount) {
|
|
137
|
+
decided_amount: { field: claim_field; bound_field: claim_bound; remainder_action: claim_remainder; };
|
|
138
|
+
}
|
|
139
|
+
when(claim_input) { input: claim_input; }
|
|
140
|
+
when(claim_to) { port { allowed_parties: [claim_to_role]; }; }
|
|
141
|
+
moves: [{ key: "claim"; operation: internal_transfer.post; bind: { amount: { from: input; path: claim_field; }; currency: { from: instance; path: fields.currency; }; postMode: { from: const; value: partial_only; }; transferId: { from: instance; path: refs.holdTransferId; }; }; }, { key: remainder; operation: internal_transfer.void; bind: { reason: { from: const; value: "Return unclaimed deposit remainder"; }; transferId: { from: instance; path: refs.holdTransferId; }; }; }];
|
|
142
|
+
steps: [];
|
|
143
|
+
summary: "Claim the security deposit";
|
|
144
|
+
}
|
|
145
|
+
action return {
|
|
146
|
+
agent_description: "Void the whole reservation so the {payer} gets every unit back. Use this when the {holder} decides to keep nothing. This moves money and does not reverse. Only a funded hold returns.";
|
|
147
|
+
deadline: { field: deadline_field; };
|
|
148
|
+
when(return_to) { port { allowed_parties: [return_to_role]; }; }
|
|
149
|
+
moves: [{ key: "return"; operation: internal_transfer.void; bind: { reason: { from: const; value: "Deposit returned in full"; }; transferId: { from: instance; path: refs.holdTransferId; }; }; }];
|
|
150
|
+
steps: [];
|
|
151
|
+
summary: "Return the security deposit";
|
|
152
|
+
}
|
|
153
|
+
action expire {
|
|
154
|
+
due: { field: deadline_field; };
|
|
155
|
+
moves: [{ key: expire; operation: internal_transfer.void; bind: { reason: { from: const; value: "Deposit hold expired"; }; transferId: { from: instance; path: refs.holdTransferId; }; }; }];
|
|
156
|
+
steps: [];
|
|
157
|
+
summary: "Expire the security deposit";
|
|
158
|
+
}
|
|
159
|
+
action cancel {
|
|
160
|
+
agent_description: "Close a hold before anyone funds it. No money has moved, so this is a lifecycle transition only. Only a created hold cancels. A funded hold refuses, because its money is already reserved. Claim it or return it instead.";
|
|
161
|
+
moves: [];
|
|
162
|
+
steps: [];
|
|
163
|
+
summary: "Cancel the security deposit";
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
module std.settlements.settlement_batch
|
|
2
|
+
|
|
3
|
+
export instrument settlement_batch<C>(
|
|
4
|
+
settlement_account: party,
|
|
5
|
+
source_capture_refs: text,
|
|
6
|
+
fee_entries: text,
|
|
7
|
+
external_reversal_offsets: text,
|
|
8
|
+
close_trigger: date,
|
|
9
|
+
payout_destination: party,
|
|
10
|
+
negative_position: text,
|
|
11
|
+
payout_acknowledgement: condition,
|
|
12
|
+
payout_beneficiary_ref: text,
|
|
13
|
+
) {
|
|
14
|
+
fields {
|
|
15
|
+
close_trigger { type: date; description: "Date the open batch freezes against later entries"; }
|
|
16
|
+
currency { type: text; description: "ISO 4217 currency code"; minLength: 3; maxLength: 3; pattern: "^[A-Z]{3}$"; }
|
|
17
|
+
payout_beneficiary_ref { type: text; description: "Beneficiary ID for the payout instruction"; pattern: "^ben_(sandbox|live)_[a-z0-9]{8,64}$"; }
|
|
18
|
+
[payout_destination]AccountId { type: account<C>; description: concat("The ", words(payout_destination), " account"); pattern: "^acct_(sandbox|live)_[a-z0-9]{8,64}$"; "x-hyperscale-reference-filter": { column: role; values: [customer_balance]; }; }
|
|
19
|
+
[settlement_account]AccountId { type: account<C>; description: concat("The ", words(settlement_account), " account"); pattern: "^acct_(sandbox|live)_[a-z0-9]{8,64}$"; "x-hyperscale-reference-filter": { column: role; values: [customer_balance]; }; }
|
|
20
|
+
}
|
|
21
|
+
lifecycle {
|
|
22
|
+
states open closed calculated approved instructed acknowledged reconciled;
|
|
23
|
+
initial open;
|
|
24
|
+
on close: open -> closed;
|
|
25
|
+
on calculate: closed -> calculated;
|
|
26
|
+
on approve: calculated -> approved;
|
|
27
|
+
on instruct: approved -> instructed;
|
|
28
|
+
on [payout_acknowledgement]: instructed -> acknowledged;
|
|
29
|
+
on reconcile: instructed|acknowledged -> reconciled;
|
|
30
|
+
}
|
|
31
|
+
parties { beneficiary: concat(camel(payout_destination), "AccountId"); payer: concat(camel(settlement_account), "AccountId"); }
|
|
32
|
+
required: [concat(camel(payout_destination), "AccountId"), concat(camel(settlement_account), "AccountId"), close_trigger, currency, payout_beneficiary_ref];
|
|
33
|
+
title: sentence(instrument);
|
|
34
|
+
id_prefix: prefix(instrument);
|
|
35
|
+
agent_description: "Reach for settlement batch when many charges over a period roll up into one payout. Capture entries and adjustments accrue against an open batch, the batch freezes on its close date, calculate proves one signed net payable, and instruct sends a single external payout. Weighted distribution goes the other way, one amount out to many recipients.";
|
|
36
|
+
summary: concat("Payout batch from ", words(settlement_account), " to ", words(payout_destination));
|
|
37
|
+
description: "Immutable batch of capture lineage and signed adjustments that creates one payout";
|
|
38
|
+
action create { agent_description: "Open a batch that capture entries and adjustments can accrue into. No money moves. The batch keeps taking entries until its close date freezes it."; summary: concat("Open a ", words(instrument)); moves: []; steps: []; }
|
|
39
|
+
action close { summary: "Freeze the batch and stop all new entries"; due { field: close_trigger; } moves: []; steps: []; }
|
|
40
|
+
action calculate {
|
|
41
|
+
agent_description: "Prove and freeze the one net payable from gross captures plus credit adjustments minus debit adjustments. The batch must be closed first. A zero or negative net is refused, so a batch in deficit never reaches a payout. No money moves.";
|
|
42
|
+
summary: "Prove and freeze the one signed net payable amount";
|
|
43
|
+
computes signed_sum {
|
|
44
|
+
amountRef: netPayable; onNegative: refuse; onZero: refuse;
|
|
45
|
+
sources: [
|
|
46
|
+
{ amountField: amount; instrumentId: concat(instrument, "_capture_entry"); refField: concat(camel(instrument), "Id"); sign: add; statuses: [accrued]; subtotalRef: grossCaptureAmount; },
|
|
47
|
+
{ amountField: amount; instrumentId: concat(instrument, "_credit_adjustment"); refField: concat(camel(instrument), "Id"); sign: add; statuses: [applied]; subtotalRef: creditAdjustmentAmount; },
|
|
48
|
+
{ amountField: amount; instrumentId: concat(instrument, "_debit_adjustment"); refField: concat(camel(instrument), "Id"); sign: subtract; statuses: [applied]; subtotalRef: debitAdjustmentAmount; },
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
moves: []; steps: [];
|
|
52
|
+
}
|
|
53
|
+
action approve { agent_description: "Sign off on the frozen net payable. The batch must already be calculated. Nothing is recomputed and no money moves. This is the gate instruct waits on."; summary: "Approve the frozen payable without recomputing it"; moves: []; steps: []; }
|
|
54
|
+
action instruct {
|
|
55
|
+
agent_description: "Create one external payout for the frozen net payable, drawn on the settlement account and sent to the stored beneficiary. The batch must be approved. This moves money off the platform and is idempotent, so a retry never pays twice.";
|
|
56
|
+
summary: "Create one idempotent payout from the frozen net payable";
|
|
57
|
+
payout { amount: "refs.netPayable"; beneficiaryField: payout_beneficiary_ref; beneficiaryPartyField: concat(camel(payout_destination), "AccountId"); capture: payoutId; currencyField: currency; sourceAccountField: concat(camel(settlement_account), "AccountId"); speed: standard; }
|
|
58
|
+
moves: []; steps: [];
|
|
59
|
+
}
|
|
60
|
+
action [payout_acknowledgement] {
|
|
61
|
+
agent_description: "Record the beneficiary confirmation that the payout landed, with the reference kept in the receipt. The batch must already be instructed. Only the payout destination may call it. No money moves.";
|
|
62
|
+
summary: "Record the tenant's payout acknowledgement in the receipt";
|
|
63
|
+
port { allowedParties: [beneficiary]; }
|
|
64
|
+
input { type: object; properties: { acknowledgementReference: { type: string; description: "Required decision reference retained in the operation receipt"; minLength: 1; maxLength: 180; }; }; required: [acknowledgementReference]; additionalProperties: false; }
|
|
65
|
+
capture_input { acknowledgementReference: acknowledgementReference; }
|
|
66
|
+
moves: []; steps: [];
|
|
67
|
+
}
|
|
68
|
+
action reconcile {
|
|
69
|
+
summary: "Match durable evidence that the payout settled";
|
|
70
|
+
due { field: close_trigger; offset: "P30D"; }
|
|
71
|
+
reconcile {
|
|
72
|
+
amount: "refs.netPayable"; capture: settlementEvidenceId; counterpartyRef: payoutId;
|
|
73
|
+
currencyField: currency; direction: debit; evidence: statement_line;
|
|
74
|
+
exception { amountField: amount; childInstrumentId: concat(instrument, "_debit_adjustment"); maxOpen: 1; reasonField: adjustmentReference; refField: concat(camel(instrument), "Id"); }
|
|
75
|
+
match { law: exact; }
|
|
76
|
+
within { offset: "P30D"; }
|
|
77
|
+
}
|
|
78
|
+
moves: []; steps: [];
|
|
79
|
+
}
|
|
80
|
+
instruments {
|
|
81
|
+
instrument {
|
|
82
|
+
let(parent_name): instrument;
|
|
83
|
+
id: concat(parent_name, "_capture_entry");
|
|
84
|
+
id_prefix: concat(prefix(parent_name), "ce");
|
|
85
|
+
fields {
|
|
86
|
+
amount { type: money<C>; description: "Gross captured amount in minor units"; }
|
|
87
|
+
[source_capture_refs] { type: text; description: "Immutable source capture reference"; minLength: 1; maxLength: 180; }
|
|
88
|
+
currency { type: text; description: "ISO 4217 currency code"; minLength: 3; maxLength: 3; pattern: "^[A-Z]{3}$"; }
|
|
89
|
+
[parent_name]Id { type: ref<parent_name>; description: "Open batch this capture entry accrues into"; pattern: concat("^", prefix(parent_name), "_(sandbox|live)_[a-z0-9]{8,64}$"); }
|
|
90
|
+
}
|
|
91
|
+
lifecycle { states created accrued; initial created; on accrue: created -> accrued; }
|
|
92
|
+
required: [amount, currency, concat(camel(parent_name), "Id"), source_capture_refs];
|
|
93
|
+
agent_description: "One gross capture that belongs to an open batch. Record one per source capture so the net payable keeps its lineage back to the original charge.";
|
|
94
|
+
title: concat(sentence(parent_name), " Capture Entry"); summary: "Gross capture lineage entry"; description: "One gross capture entry linked to an open payout batch";
|
|
95
|
+
action create { agent_description: "Record one capture with its source reference against an open batch. The batch must be open and share the currency. No money moves, and the entry counts toward the net payable only once it accrues."; summary: "Create a capture lineage entry on an open batch"; requires refs { field: concat(camel(parent_name), "Id"); statuses: [open]; match: { "fields.currency": "fields.currency"; }; } moves: []; steps: []; }
|
|
96
|
+
action accrue { agent_description: "Count this capture toward the batch gross total. The entry must be created and its batch still open, because a closed batch admits nothing new. No money moves."; summary: "Accrue the capture entry into the open batch"; requires refs { field: concat(camel(parent_name), "Id"); statuses: [open]; match: { "fields.currency": "fields.currency"; }; } moves: []; steps: []; }
|
|
97
|
+
}
|
|
98
|
+
for adjustment_kind in [credit, debit] {
|
|
99
|
+
instrument {
|
|
100
|
+
let(parent_name): instrument;
|
|
101
|
+
id: concat(parent_name, "_", adjustment_kind, "_adjustment");
|
|
102
|
+
id_prefix: concat(prefix(parent_name), if_eq(adjustment_kind, "credit", "ca", "da"));
|
|
103
|
+
fields {
|
|
104
|
+
adjustmentReference { type: text; description: "Immutable explicit adjustment reference"; minLength: 1; maxLength: 180; }
|
|
105
|
+
amount { type: money<C>; description: concat(title(adjustment_kind), " adjustment amount in minor units"); }
|
|
106
|
+
[source_capture_refs] { type: text; description: "Original capture reference that this adjustment corrects"; minLength: 1; maxLength: 180; }
|
|
107
|
+
currency { type: text; description: "ISO 4217 currency code"; minLength: 3; maxLength: 3; pattern: "^[A-Z]{3}$"; }
|
|
108
|
+
[external_reversal_offsets] { type: text; description: "Optional externally decided reversal reference"; minLength: 1; maxLength: 180; optional: true; }
|
|
109
|
+
[fee_entries] { type: text; description: "Optional fee entry reference"; minLength: 1; maxLength: 180; optional: true; }
|
|
110
|
+
[parent_name]Id { type: ref<parent_name>; description: "Open batch this adjustment applies to"; pattern: concat("^", prefix(parent_name), "_(sandbox|live)_[a-z0-9]{8,64}$"); }
|
|
111
|
+
}
|
|
112
|
+
lifecycle { states created applied; initial created; on adjust: created -> applied; on correct: created -> applied; }
|
|
113
|
+
required: [amount, currency, adjustmentReference, concat(camel(parent_name), "Id"), source_capture_refs];
|
|
114
|
+
agent_description: concat("One ", adjustment_kind, " adjustment against an open batch, carrying the capture reference it corrects. Credit adjustments add to the net payable and debit adjustments subtract from it, so a late correction lands on the current open batch instead of reopening a closed one.");
|
|
115
|
+
title: concat(sentence(parent_name), " ", title(adjustment_kind), " Adjustment"); summary: concat(title(adjustment_kind), " adjustment with capture lineage"); description: concat("One ", adjustment_kind, " adjustment linked to an open payout batch; closed batches stay unchanged");
|
|
116
|
+
action create { agent_description: concat("Record a ", adjustment_kind, " adjustment on an open batch, naming the capture reference it corrects. The batch must be open and share the currency. No money moves, and the adjustment counts only once it is applied."); summary: concat("Create a ", adjustment_kind, " adjustment on an open batch"); requires refs { field: concat(camel(parent_name), "Id"); statuses: [open]; match: { "fields.currency": "fields.currency"; }; } moves: []; steps: []; }
|
|
117
|
+
action adjust { agent_description: concat("Apply the ", adjustment_kind, " adjustment so calculate counts it. The adjustment must be created and its batch still open. No money moves. The effect shows up in the batch net payable."); summary: concat("Apply the ", adjustment_kind, " adjustment to the open batch"); requires refs { field: concat(camel(parent_name), "Id"); statuses: [open]; match: { "fields.currency": "fields.currency"; }; } moves: []; steps: []; }
|
|
118
|
+
action correct { agent_description: "Apply this adjustment as a correction on the open batch it names. Use it when the batch carrying the original entry has already closed, because a closed batch never changes. The adjustment must be created and no money moves."; summary: "Record a later correction on this open batch instead of changing the closed source batch"; requires refs { field: concat(camel(parent_name), "Id"); statuses: [open]; match: { "fields.currency": "fields.currency"; }; } moves: []; steps: []; }
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|