@hyperscale0/udl 2.2.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 +12 -0
- package/dist/effects.d.ts +0 -6
- package/dist/effects.d.ts.map +1 -1
- package/dist/effects.js +61 -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 +89 -21
- package/src/index.ts +1 -0
- package/src/validation.ts +340 -61
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");
|
|
@@ -231,6 +251,35 @@ function encodeOriginPathKey(originPath: readonly string[]): string {
|
|
|
231
251
|
return `k_${parts.join("_")}`;
|
|
232
252
|
}
|
|
233
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
|
+
|
|
234
283
|
function expectedLeafEffects(
|
|
235
284
|
step: UdlStep | UdlMove,
|
|
236
285
|
): readonly { readonly kind: UdlEffectKind; readonly signature: string }[] {
|
|
@@ -925,6 +974,35 @@ export function resolveUdlActionPlans(
|
|
|
925
974
|
path: parts[1],
|
|
926
975
|
};
|
|
927
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
|
+
}
|
|
928
1006
|
issues.push(
|
|
929
1007
|
issue(
|
|
930
1008
|
"UDL2011",
|
|
@@ -1275,27 +1353,17 @@ export function resolveUdlActionPlans(
|
|
|
1275
1353
|
consumedSources.add(holdKey);
|
|
1276
1354
|
}
|
|
1277
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.
|
|
1278
1361
|
const expectedEffects = expectedLeafEffects(step);
|
|
1279
|
-
const
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
const expectedCounts = new Map<string, number>();
|
|
1285
|
-
for (const eff of expectedEffects) {
|
|
1286
|
-
const key = `${eff.kind}:${eff.signature}`;
|
|
1287
|
-
expectedCounts.set(key, (expectedCounts.get(key) ?? 0) + 1);
|
|
1288
|
-
}
|
|
1289
|
-
let effectsMatch = actualCounts.size === expectedCounts.size;
|
|
1290
|
-
if (effectsMatch) {
|
|
1291
|
-
for (const [k, count] of actualCounts.entries()) {
|
|
1292
|
-
if (expectedCounts.get(k) !== count) {
|
|
1293
|
-
effectsMatch = false;
|
|
1294
|
-
break;
|
|
1295
|
-
}
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
if (!effectsMatch) {
|
|
1362
|
+
const genericEffects = expectedLeafEffects({ ...step, bind: {} });
|
|
1363
|
+
if (
|
|
1364
|
+
!sameEffectSignatures(leaf.effects, expectedEffects) &&
|
|
1365
|
+
!sameEffectSignatures(leaf.effects, genericEffects)
|
|
1366
|
+
) {
|
|
1299
1367
|
issues.push(
|
|
1300
1368
|
issue(
|
|
1301
1369
|
"UDL2013",
|
|
@@ -1306,7 +1374,7 @@ export function resolveUdlActionPlans(
|
|
|
1306
1374
|
}
|
|
1307
1375
|
|
|
1308
1376
|
expandedLeaves.push({
|
|
1309
|
-
effects:
|
|
1377
|
+
effects: expectedEffects,
|
|
1310
1378
|
evidence: leaf.evidence,
|
|
1311
1379
|
originPath,
|
|
1312
1380
|
step,
|