@hyperscale0/udl 2.1.0 → 2.3.0
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 +19 -0
- package/dist/effects.d.ts +3 -7
- package/dist/effects.d.ts.map +1 -1
- package/dist/effects.js +117 -21
- package/dist/effects.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/validation.d.ts +13 -1
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +252 -56
- package/dist/validation.js.map +1 -1
- package/docs/README.md +3 -0
- package/docs/llms-full.txt +4 -1
- package/docs/llms.txt +1 -1
- package/docs/piece-plans.md +148 -0
- package/docs/reference/clauses.md +1 -1
- package/docs/reference/cli.md +1 -1
- package/docs/reference/diagnostics.md +1 -1
- package/package.json +2 -2
- package/src/effects.ts +159 -22
- package/src/index.ts +1 -0
- package/src/validation.ts +341 -62
package/docs/README.md
CHANGED
|
@@ -11,4 +11,7 @@ UDL is the versioned JSON contract between a product definition and an engine th
|
|
|
11
11
|
7. [Evolution](guide/07-evolution.md)
|
|
12
12
|
8. [Implementing UDL](guide/08-implementing.md)
|
|
13
13
|
|
|
14
|
+
[Piece plans and private action composition](piece-plans.md) is a worked
|
|
15
|
+
instrument fragment for `piecePlan`, `pieceStage`, `calls` and `actionLibrary`.
|
|
16
|
+
|
|
14
17
|
The [clause reference](reference/clauses.md) and [diagnostic reference](reference/diagnostics.md) come from the package tables. The [command reference](reference/cli.md) comes from the CLI usage text. Do not edit those generated files by hand.
|
package/docs/llms-full.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- Generated by scripts/docs/build.ts from @hyperscale0/udl 2.
|
|
1
|
+
<!-- Generated by scripts/docs/build.ts from @hyperscale0/udl 2.3.0. Edit the source, not llms-full.txt. -->
|
|
2
2
|
|
|
3
3
|
# UDL complete reference
|
|
4
4
|
|
|
@@ -17,6 +17,9 @@ UDL is the versioned JSON contract between a product definition and an engine th
|
|
|
17
17
|
7. [Evolution](guide/07-evolution.md)
|
|
18
18
|
8. [Implementing UDL](guide/08-implementing.md)
|
|
19
19
|
|
|
20
|
+
[Piece plans and private action composition](piece-plans.md) is a worked
|
|
21
|
+
instrument fragment for `piecePlan`, `pieceStage`, `calls` and `actionLibrary`.
|
|
22
|
+
|
|
20
23
|
The [clause reference](reference/clauses.md) and [diagnostic reference](reference/diagnostics.md) come from the package tables. The [command reference](reference/cli.md) comes from the CLI usage text. Do not edit those generated files by hand.
|
|
21
24
|
|
|
22
25
|
<!-- source: guide/01-a-document.md -->
|
package/docs/llms.txt
CHANGED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Piece plans and private action composition
|
|
2
|
+
|
|
3
|
+
UDL preserves the authored graph and the order needed to interpret it.
|
|
4
|
+
`piecePlan` declares a finite partition, `pieceStage` names a stage, `calls`
|
|
5
|
+
binds private actions, and `actionLibrary` supplies their definitions. HSX
|
|
6
|
+
spells these clauses `piece_plan`, `piece_stage`, `calls` and `action_library`.
|
|
7
|
+
The shapes live in [`schema.ts`](../src/schema.ts); resolution and finance
|
|
8
|
+
validation live in [`validation.ts`](../src/validation.ts). The
|
|
9
|
+
[clause reference](reference/clauses.md) describes the complete vocabulary.
|
|
10
|
+
|
|
11
|
+
## Worked sale example
|
|
12
|
+
|
|
13
|
+
The following decoded instrument fragment uses the sale settlement's price
|
|
14
|
+
partition and refund action. Add it to an instrument with its required
|
|
15
|
+
immutable fields, matching partition, lifecycle and captured escrow account.
|
|
16
|
+
It omits the surrounding document and other actions. It is formatted for
|
|
17
|
+
reading; canonical serialization sorts object keys and retains array order.
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"piecePlan": {
|
|
22
|
+
"id": "price",
|
|
23
|
+
"total": "price",
|
|
24
|
+
"pieces": [
|
|
25
|
+
{
|
|
26
|
+
"id": "seller",
|
|
27
|
+
"amount": "piece1Amount",
|
|
28
|
+
"release_to": "sellerAccountId",
|
|
29
|
+
"refund_to": "buyerAccountId"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": "platform_fee",
|
|
33
|
+
"amount": "piece2Amount",
|
|
34
|
+
"release_to": "platformAccountId",
|
|
35
|
+
"refund_to": "buyerAccountId"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"id": "seller_cancel_fee",
|
|
39
|
+
"amount": "piece3Amount",
|
|
40
|
+
"release_to": "platformAccountId",
|
|
41
|
+
"refund_to": "sellerAccountId"
|
|
42
|
+
}
|
|
43
|
+
],
|
|
44
|
+
"fund_order": ["seller", "platform_fee", "seller_cancel_fee"],
|
|
45
|
+
"release_order": ["platform_fee", "seller_cancel_fee"],
|
|
46
|
+
"refund_order": ["platform_fee", "seller_cancel_fee"],
|
|
47
|
+
"unfund_order": ["seller_cancel_fee", "platform_fee", "seller"]
|
|
48
|
+
},
|
|
49
|
+
"actionLibrary": {
|
|
50
|
+
"settlement_piece": {
|
|
51
|
+
"actionOrder": ["move"],
|
|
52
|
+
"actions": {
|
|
53
|
+
"move": {
|
|
54
|
+
"parameters": {
|
|
55
|
+
"piece": { "kind": "piece" },
|
|
56
|
+
"source": { "kind": "account" },
|
|
57
|
+
"destination": { "kind": "account" }
|
|
58
|
+
},
|
|
59
|
+
"principal": "api_key",
|
|
60
|
+
"approval": "inherit",
|
|
61
|
+
"recovery": "local",
|
|
62
|
+
"order": ["transfer"],
|
|
63
|
+
"calls": [],
|
|
64
|
+
"leaves": [
|
|
65
|
+
{
|
|
66
|
+
"id": "transfer",
|
|
67
|
+
"operation": "internal_transfer.create",
|
|
68
|
+
"bind": {
|
|
69
|
+
"amount": "$piece.amount",
|
|
70
|
+
"currency": "$piece.currency",
|
|
71
|
+
"sourceAccountId": "$source",
|
|
72
|
+
"destinationAccountId": "$destination"
|
|
73
|
+
},
|
|
74
|
+
"effects": [
|
|
75
|
+
{ "kind": "moves", "signature": "moves.transfer.internal" }
|
|
76
|
+
],
|
|
77
|
+
"evidence": "transferId"
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"actions": {
|
|
85
|
+
"refund_piece": {
|
|
86
|
+
"pieceStage": { "plan": "price", "stage": "refund" },
|
|
87
|
+
"calls": [
|
|
88
|
+
{
|
|
89
|
+
"id": "move_piece",
|
|
90
|
+
"action": "settlement_piece.move",
|
|
91
|
+
"bind": {
|
|
92
|
+
"piece": "$piece",
|
|
93
|
+
"source": "$instance.refs.escrowAccountId",
|
|
94
|
+
"destination": "$piece.refund_to"
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
],
|
|
98
|
+
"steps": [],
|
|
99
|
+
"summary": "Refund a piece of a sale settlement"
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The containing instrument's `actionOrder` includes `refund_piece` at its
|
|
106
|
+
authored position. Both library `actionOrder` and private action `order` must
|
|
107
|
+
be exact permutations of their members. The stage derives a required
|
|
108
|
+
`pieceId` enum, here `platform_fee` or `seller_cancel_fee`. The selector does
|
|
109
|
+
not add another transfer; the private leaf moves its amount once.
|
|
110
|
+
|
|
111
|
+
For the sale's example amounts, `piece1Amount` is `245000`, `piece2Amount` is
|
|
112
|
+
`3750`, and `piece3Amount` is `1250`, all SAR minor units. They partition
|
|
113
|
+
`price` of `250000`. Cancellation's separate decision action returns the seller
|
|
114
|
+
piece to the buyer. The refund stage then returns `3750` to the buyer and pays
|
|
115
|
+
`1250` to the seller. Unfund follows its reverse order and returns every funded
|
|
116
|
+
piece to the buyer instead. The service fee stays outside the price partition.
|
|
117
|
+
|
|
118
|
+
`resolveUdlActionPlans` returns plans and issues. A plan identifies its action
|
|
119
|
+
and optional piece ID, with resolved effects and leaves. Each leaf retains
|
|
120
|
+
`originPath`, step, effects and evidence. Resolution expands static calls; it
|
|
121
|
+
does not replace the source graph in canonical UDL. A private library entry is
|
|
122
|
+
not a public action. [`refactor-clauses.spec.ts`](../test/refactor-clauses.spec.ts)
|
|
123
|
+
checks piece constraints, graph order, cycles, bindings, authority and evidence.
|
|
124
|
+
|
|
125
|
+
## Conservation and diagnostics
|
|
126
|
+
|
|
127
|
+
UDL 2.3.0 checks funded amounts over piece progress. A partially funded path
|
|
128
|
+
must return only the pieces actually funded; selecting one piece is not proof
|
|
129
|
+
that a whole stage completed. For a piece plan, `expandPieceProgress` bounds the reachable lifecycle/progress
|
|
130
|
+
states at 256. Instruments without a piece plan use the separate action-plan
|
|
131
|
+
combination bound. These bounds belong to the proof, not to a runtime retry
|
|
132
|
+
loop. [`udl.spec.ts`](../test/udl.spec.ts) includes the stranded-funded-piece
|
|
133
|
+
refusal; [`validation.ts`](../src/validation.ts) owns the progress expansion.
|
|
134
|
+
|
|
135
|
+
[`diagnostics.ts`](../src/diagnostics.ts) defines the refusal codes:
|
|
136
|
+
|
|
137
|
+
| Code | Refusal | Repair |
|
|
138
|
+
| --------- | ---------------------- | ---------------------------------------------------------------------------------------- |
|
|
139
|
+
| `UDL4001` | Money graph violation | Balance each funded amount and close every hold on each lifecycle path |
|
|
140
|
+
| `UDL5013` | Piece stage violation | Select a declared plan/stage, valid ordered piece IDs and a call-based stage action |
|
|
141
|
+
| `UDL2010` | Action graph violation | Resolve targets and remove cycles, collisions or invalid order within depth/count limits |
|
|
142
|
+
|
|
143
|
+
A partition mismatch or incompatible immutable field is `UDL4002`. A typed
|
|
144
|
+
binding failure is `UDL2011`; an authority boundary is `UDL2012`; incomplete
|
|
145
|
+
effects or evidence are `UDL2013`. These failures are not permission to skip
|
|
146
|
+
the independent conservation oracle. Keep actions that need another principal,
|
|
147
|
+
independent approval or external recovery as separate calls at the public
|
|
148
|
+
boundary, not flattened private work.
|
package/docs/reference/cli.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperscale0/udl",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "The Universal Domain Language: format spec, parser, validator, canonical serializer, and evolution diff.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"udl",
|
|
@@ -84,5 +84,5 @@
|
|
|
84
84
|
"Amir Ayub",
|
|
85
85
|
"Sara AlBakaawi"
|
|
86
86
|
],
|
|
87
|
-
"gitHead": "
|
|
87
|
+
"gitHead": "ed8866f5e5201aad938ad0219da06e430a513467"
|
|
88
88
|
}
|
package/src/effects.ts
CHANGED
|
@@ -66,6 +66,26 @@ function boundPath(move: Movement, endpoint: string): string | undefined {
|
|
|
66
66
|
* External collection, deposit, and payout operations carry their role in the
|
|
67
67
|
* operation family because their remote endpoint is not a UDL account binding.
|
|
68
68
|
*/
|
|
69
|
+
/**
|
|
70
|
+
* The typed kind of an instance ref a call binding may name. Refs exist only
|
|
71
|
+
* through step and move captures on the same instrument; a capture of a leaf's
|
|
72
|
+
* `accountId` output is an account, every other capture is opaque text.
|
|
73
|
+
*/
|
|
74
|
+
function capturedRefKind(
|
|
75
|
+
instrument: UdlInstrument,
|
|
76
|
+
refName: string,
|
|
77
|
+
): "account" | "text" | undefined {
|
|
78
|
+
for (const action of Object.values(instrument.actions)) {
|
|
79
|
+
for (const step of [...action.steps, ...action.moves]) {
|
|
80
|
+
const output = step.capture?.[refName];
|
|
81
|
+
if (output !== undefined) {
|
|
82
|
+
return output === "accountId" ? "account" : "text";
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
|
|
69
89
|
export function movementClass(move: Movement): UdlMovementClass {
|
|
70
90
|
if (move.operation.startsWith("internal_transfer.")) {
|
|
71
91
|
const source = boundPath(move, "sourceAccountId");
|
|
@@ -176,7 +196,11 @@ export interface ResolvedActionPlanLeaf {
|
|
|
176
196
|
}[];
|
|
177
197
|
readonly evidence: string;
|
|
178
198
|
readonly originPath: readonly string[];
|
|
179
|
-
readonly step:
|
|
199
|
+
readonly step:
|
|
200
|
+
| UdlMove
|
|
201
|
+
| (Omit<UdlStep, "operation"> & {
|
|
202
|
+
readonly operation: UdlStep["operation"] | "payout.create";
|
|
203
|
+
});
|
|
180
204
|
}
|
|
181
205
|
|
|
182
206
|
export interface ResolvedActionPlan {
|
|
@@ -227,6 +251,35 @@ function encodeOriginPathKey(originPath: readonly string[]): string {
|
|
|
227
251
|
return `k_${parts.join("_")}`;
|
|
228
252
|
}
|
|
229
253
|
|
|
254
|
+
function sameEffectSignatures(
|
|
255
|
+
left: readonly { readonly kind: UdlEffectKind; readonly signature: string }[],
|
|
256
|
+
right: readonly {
|
|
257
|
+
readonly kind: UdlEffectKind;
|
|
258
|
+
readonly signature: string;
|
|
259
|
+
}[],
|
|
260
|
+
): boolean {
|
|
261
|
+
const count = (
|
|
262
|
+
effects: readonly {
|
|
263
|
+
readonly kind: UdlEffectKind;
|
|
264
|
+
readonly signature: string;
|
|
265
|
+
}[],
|
|
266
|
+
) => {
|
|
267
|
+
const counts = new Map<string, number>();
|
|
268
|
+
for (const eff of effects) {
|
|
269
|
+
const key = `${eff.kind}:${eff.signature}`;
|
|
270
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
271
|
+
}
|
|
272
|
+
return counts;
|
|
273
|
+
};
|
|
274
|
+
const leftCounts = count(left);
|
|
275
|
+
const rightCounts = count(right);
|
|
276
|
+
if (leftCounts.size !== rightCounts.size) return false;
|
|
277
|
+
for (const [key, n] of leftCounts) {
|
|
278
|
+
if (rightCounts.get(key) !== n) return false;
|
|
279
|
+
}
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
|
|
230
283
|
function expectedLeafEffects(
|
|
231
284
|
step: UdlStep | UdlMove,
|
|
232
285
|
): readonly { readonly kind: UdlEffectKind; readonly signature: string }[] {
|
|
@@ -921,6 +974,35 @@ export function resolveUdlActionPlans(
|
|
|
921
974
|
path: parts[1],
|
|
922
975
|
};
|
|
923
976
|
}
|
|
977
|
+
if (parts[1] === "refs") {
|
|
978
|
+
if (parts.length !== 3) {
|
|
979
|
+
issues.push(
|
|
980
|
+
issue(
|
|
981
|
+
"UDL2011",
|
|
982
|
+
leafPath,
|
|
983
|
+
`invalid trailing member access on instance ref: ${rawBind}`,
|
|
984
|
+
),
|
|
985
|
+
);
|
|
986
|
+
return undefined;
|
|
987
|
+
}
|
|
988
|
+
const refName = parts[2]!;
|
|
989
|
+
const captured = capturedRefKind(instrument, refName);
|
|
990
|
+
if (!captured) {
|
|
991
|
+
issues.push(
|
|
992
|
+
issue(
|
|
993
|
+
"UDL2011",
|
|
994
|
+
leafPath,
|
|
995
|
+
`referenced ref ${refName} is not captured by any step or move on instrument`,
|
|
996
|
+
),
|
|
997
|
+
);
|
|
998
|
+
return undefined;
|
|
999
|
+
}
|
|
1000
|
+
return {
|
|
1001
|
+
binding: { from: "instance", path: `refs.${refName}` },
|
|
1002
|
+
kind: captured,
|
|
1003
|
+
path: `refs.${refName}`,
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
924
1006
|
issues.push(
|
|
925
1007
|
issue(
|
|
926
1008
|
"UDL2011",
|
|
@@ -1271,27 +1353,17 @@ export function resolveUdlActionPlans(
|
|
|
1271
1353
|
consumedSources.add(holdKey);
|
|
1272
1354
|
}
|
|
1273
1355
|
|
|
1356
|
+
// A library leaf is written once and expanded under several callers,
|
|
1357
|
+
// so its declaration names the operation's generic class (the class
|
|
1358
|
+
// with no bindings). Each expansion then carries the class its
|
|
1359
|
+
// resolved bindings select, exactly as an inline move would: a buyer
|
|
1360
|
+
// funding escrow is a pay-in, the same leaf paying out is internal.
|
|
1274
1361
|
const expectedEffects = expectedLeafEffects(step);
|
|
1275
|
-
const
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
const expectedCounts = new Map<string, number>();
|
|
1281
|
-
for (const eff of expectedEffects) {
|
|
1282
|
-
const key = `${eff.kind}:${eff.signature}`;
|
|
1283
|
-
expectedCounts.set(key, (expectedCounts.get(key) ?? 0) + 1);
|
|
1284
|
-
}
|
|
1285
|
-
let effectsMatch = actualCounts.size === expectedCounts.size;
|
|
1286
|
-
if (effectsMatch) {
|
|
1287
|
-
for (const [k, count] of actualCounts.entries()) {
|
|
1288
|
-
if (expectedCounts.get(k) !== count) {
|
|
1289
|
-
effectsMatch = false;
|
|
1290
|
-
break;
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
if (!effectsMatch) {
|
|
1362
|
+
const genericEffects = expectedLeafEffects({ ...step, bind: {} });
|
|
1363
|
+
if (
|
|
1364
|
+
!sameEffectSignatures(leaf.effects, expectedEffects) &&
|
|
1365
|
+
!sameEffectSignatures(leaf.effects, genericEffects)
|
|
1366
|
+
) {
|
|
1295
1367
|
issues.push(
|
|
1296
1368
|
issue(
|
|
1297
1369
|
"UDL2013",
|
|
@@ -1302,7 +1374,7 @@ export function resolveUdlActionPlans(
|
|
|
1302
1374
|
}
|
|
1303
1375
|
|
|
1304
1376
|
expandedLeaves.push({
|
|
1305
|
-
effects:
|
|
1377
|
+
effects: expectedEffects,
|
|
1306
1378
|
evidence: leaf.evidence,
|
|
1307
1379
|
originPath,
|
|
1308
1380
|
step,
|
|
@@ -1750,6 +1822,71 @@ export function resolveUdlActionPlans(
|
|
|
1750
1822
|
effects,
|
|
1751
1823
|
leaves: expandedLeaves,
|
|
1752
1824
|
});
|
|
1825
|
+
} else {
|
|
1826
|
+
// Ordinary actions already are flat. Preserve the executor's order and
|
|
1827
|
+
// authored step bytes; origin paths belong to the plan, not the ABI.
|
|
1828
|
+
const effects = deriveUdlActionEffects(
|
|
1829
|
+
action as Readonly<Record<string, unknown>>,
|
|
1830
|
+
udlClauseVocabulary,
|
|
1831
|
+
);
|
|
1832
|
+
const leafEffects = (source: string): ResolvedActionPlanLeaf["effects"] =>
|
|
1833
|
+
Object.entries(effects).flatMap(([kind, rows]) =>
|
|
1834
|
+
rows
|
|
1835
|
+
.filter((row) => row.source === source)
|
|
1836
|
+
.map((row) => ({
|
|
1837
|
+
kind: kind as UdlEffectKind,
|
|
1838
|
+
signature: row.signature,
|
|
1839
|
+
})),
|
|
1840
|
+
);
|
|
1841
|
+
const leaves: ResolvedActionPlanLeaf[] = action.steps.map(
|
|
1842
|
+
(step, index) => ({
|
|
1843
|
+
step,
|
|
1844
|
+
originPath: [actionKey, "steps", String(index)],
|
|
1845
|
+
effects: leafEffects(`steps[${index}]`),
|
|
1846
|
+
evidence: "parent_receipt",
|
|
1847
|
+
}),
|
|
1848
|
+
);
|
|
1849
|
+
if (action.payout) {
|
|
1850
|
+
const intent = action.payout;
|
|
1851
|
+
leaves.push({
|
|
1852
|
+
step: {
|
|
1853
|
+
operation: "payout.create",
|
|
1854
|
+
bind: {
|
|
1855
|
+
amount: { from: "instance", path: intent.amount },
|
|
1856
|
+
beneficiaryId: {
|
|
1857
|
+
from: "instance",
|
|
1858
|
+
path: `fields.${intent.beneficiaryField}`,
|
|
1859
|
+
},
|
|
1860
|
+
currency: {
|
|
1861
|
+
from: "instance",
|
|
1862
|
+
path: `fields.${intent.currencyField}`,
|
|
1863
|
+
},
|
|
1864
|
+
sourceAccountId: {
|
|
1865
|
+
from: "instance",
|
|
1866
|
+
path: `fields.${intent.sourceAccountField}`,
|
|
1867
|
+
},
|
|
1868
|
+
speed: { from: "const", value: intent.speed },
|
|
1869
|
+
},
|
|
1870
|
+
capture: { [intent.capture]: "payoutId" },
|
|
1871
|
+
},
|
|
1872
|
+
originPath: [actionKey, "payout"],
|
|
1873
|
+
effects: leafEffects("payout"),
|
|
1874
|
+
evidence: "parent_receipt",
|
|
1875
|
+
});
|
|
1876
|
+
}
|
|
1877
|
+
leaves.push(
|
|
1878
|
+
...action.moves.map((step, index) => ({
|
|
1879
|
+
step,
|
|
1880
|
+
originPath: [actionKey, "moves", String(index)],
|
|
1881
|
+
effects: leafEffects(`moves[${index}]`),
|
|
1882
|
+
evidence: "parent_receipt",
|
|
1883
|
+
})),
|
|
1884
|
+
);
|
|
1885
|
+
plans.push({
|
|
1886
|
+
action: actionKey,
|
|
1887
|
+
leaves,
|
|
1888
|
+
effects,
|
|
1889
|
+
});
|
|
1753
1890
|
}
|
|
1754
1891
|
}
|
|
1755
1892
|
|