@hyperscale0/udl 2.6.0 → 3.0.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 +2 -322
- package/LICENSING.md +1 -2
- package/README.md +3 -114
- package/TRADEMARKS.md +2 -2
- package/dist/diagnostics.d.ts +21 -201
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/diagnostics.js +29 -193
- package/dist/diagnostics.js.map +1 -1
- package/dist/evolution.d.ts +3 -132
- package/dist/evolution.d.ts.map +1 -1
- package/dist/evolution.js +29 -633
- package/dist/evolution.js.map +1 -1
- package/dist/finance.d.ts +5 -72
- package/dist/finance.d.ts.map +1 -1
- package/dist/finance.js +233 -737
- package/dist/finance.js.map +1 -1
- package/dist/index.d.ts +7 -18
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -12
- package/dist/index.js.map +1 -1
- package/dist/instrument-references.d.ts.map +1 -1
- package/dist/instrument-references.js +3 -2
- package/dist/instrument-references.js.map +1 -1
- package/dist/schema.d.ts +2079 -4564
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +270 -1609
- package/dist/schema.js.map +1 -1
- package/dist/validation.d.ts +8 -104
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +653 -3271
- package/dist/validation.js.map +1 -1
- package/docs/README.md +147 -14
- package/package.json +7 -11
- package/spec/README.md +151 -164
- package/spec/darb.udl.json +249 -0
- package/spec/udl.schema.json +4104 -3202
- package/src/diagnostics.ts +51 -246
- package/src/evolution.ts +39 -1045
- package/src/finance.ts +287 -1127
- package/src/index.ts +18 -142
- package/src/instrument-references.ts +3 -2
- package/src/schema.ts +285 -1843
- package/src/validation.ts +870 -5611
- package/conformance/README.md +0 -82
- package/conformance/evolution/action-contract.expected.json +0 -10
- package/conformance/evolution/action-contract.live.udl +0 -44
- package/conformance/evolution/action-contract.next.udl +0 -45
- package/conformance/evolution/product-identity.expected.json +0 -10
- package/conformance/evolution/product-identity.live.udl +0 -55
- package/conformance/evolution/product-identity.next.udl +0 -55
- package/conformance/evolution/version-required.expected.json +0 -10
- package/conformance/evolution/version-required.live.udl +0 -55
- package/conformance/evolution/version-required.next.udl +0 -58
- package/conformance/invalid/action-without-transition.expected.json +0 -10
- package/conformance/invalid/action-without-transition.udl +0 -60
- package/conformance/invalid/agent-description-too-long.expected.json +0 -10
- package/conformance/invalid/agent-description-too-long.udl +0 -57
- package/conformance/invalid/blank-title.expected.json +0 -10
- package/conformance/invalid/blank-title.udl +0 -54
- package/conformance/invalid/call-binds-results.expected.json +0 -10
- package/conformance/invalid/call-binds-results.udl +0 -314
- package/conformance/invalid/call-unknown-action.expected.json +0 -10
- package/conformance/invalid/call-unknown-action.udl +0 -314
- package/conformance/invalid/composition-dial-duplicate-key.expected.json +0 -10
- package/conformance/invalid/composition-dial-duplicate-key.udl +0 -73
- package/conformance/invalid/depth-budget.expected.json +0 -10
- package/conformance/invalid/depth-budget.udl +0 -49
- package/conformance/invalid/duplicate-subject.expected.json +0 -10
- package/conformance/invalid/duplicate-subject.udl +0 -230
- package/conformance/invalid/forged-effects.expected.json +0 -10
- package/conformance/invalid/forged-effects.udl +0 -76
- package/conformance/invalid/format-version.expected.json +0 -10
- package/conformance/invalid/format-version.udl +0 -54
- package/conformance/invalid/instrument-id-not-snake-case.expected.json +0 -10
- package/conformance/invalid/instrument-id-not-snake-case.udl +0 -54
- package/conformance/invalid/invalid-aggregate-gate-shape.expected.json +0 -10
- package/conformance/invalid/invalid-aggregate-gate-shape.udl +0 -1819
- package/conformance/invalid/invalid-check-duration.expected.json +0 -10
- package/conformance/invalid/invalid-check-duration.udl +0 -219
- package/conformance/invalid/invalid-dial-anchor.expected.json +0 -10
- package/conformance/invalid/invalid-dial-anchor.udl +0 -219
- package/conformance/invalid/invalid-exception-parent-ref.expected.json +0 -10
- package/conformance/invalid/invalid-exception-parent-ref.udl +0 -2487
- package/conformance/invalid/invalid-exposure-shape.expected.json +0 -10
- package/conformance/invalid/invalid-exposure-shape.udl +0 -1819
- package/conformance/invalid/invalid-journeys.expected.json +0 -10
- package/conformance/invalid/invalid-journeys.udl +0 -77
- package/conformance/invalid/invalid-remainder.expected.json +0 -10
- package/conformance/invalid/invalid-remainder.udl +0 -220
- package/conformance/invalid/invalid-schema-keyword.expected.json +0 -10
- package/conformance/invalid/invalid-schema-keyword.udl +0 -220
- package/conformance/invalid/invalid-utf8.expected.json +0 -10
- package/conformance/invalid/invalid-utf8.udl +0 -1
- package/conformance/invalid/leaf-effect-mismatch.expected.json +0 -10
- package/conformance/invalid/leaf-effect-mismatch.udl +0 -314
- package/conformance/invalid/malformed-json.expected.json +0 -10
- package/conformance/invalid/malformed-json.udl +0 -1
- package/conformance/invalid/missing-create-action.expected.json +0 -10
- package/conformance/invalid/missing-create-action.udl +0 -48
- package/conformance/invalid/missing-exception-amount-field.expected.json +0 -10
- package/conformance/invalid/missing-exception-amount-field.udl +0 -2487
- package/conformance/invalid/missing-exception-contract.expected.json +0 -14
- package/conformance/invalid/missing-exception-contract.udl +0 -2523
- package/conformance/invalid/missing-exception-reason-field.expected.json +0 -10
- package/conformance/invalid/missing-exception-reason-field.udl +0 -2487
- package/conformance/invalid/not-an-object.expected.json +0 -10
- package/conformance/invalid/not-an-object.udl +0 -1
- package/conformance/invalid/payout-reconcile-not-a-bank-debit.expected.json +0 -10
- package/conformance/invalid/payout-reconcile-not-a-bank-debit.udl +0 -259
- package/conformance/invalid/piece-plan-without-partition.expected.json +0 -10
- package/conformance/invalid/piece-plan-without-partition.udl +0 -305
- package/conformance/invalid/private-action-independent-approval.expected.json +0 -10
- package/conformance/invalid/private-action-independent-approval.udl +0 -314
- package/conformance/invalid/quote-freeze-set-incomplete.expected.json +0 -10
- package/conformance/invalid/quote-freeze-set-incomplete.udl +0 -261
- package/conformance/invalid/quote-named-reference-gate.expected.json +0 -10
- package/conformance/invalid/quote-named-reference-gate.udl +0 -50
- package/conformance/invalid/reconcile-named-reference-gate.expected.json +0 -10
- package/conformance/invalid/reconcile-named-reference-gate.udl +0 -50
- package/conformance/invalid/unfund-order-not-reversed.expected.json +0 -10
- package/conformance/invalid/unfund-order-not-reversed.udl +0 -314
- package/conformance/invalid/unknown-key.expected.json +0 -10
- package/conformance/invalid/unknown-key.udl +0 -55
- package/conformance/invalid/unknown-reference-gate-field.expected.json +0 -10
- package/conformance/invalid/unknown-reference-gate-field.udl +0 -1819
- package/conformance/invalid/unknown-required-field.expected.json +0 -10
- package/conformance/invalid/unknown-required-field.udl +0 -220
- package/conformance/invalid/unreachable-state.expected.json +0 -10
- package/conformance/invalid/unreachable-state.udl +0 -55
- package/conformance/invalid/wrong-exception-amount-field.expected.json +0 -10
- package/conformance/invalid/wrong-exception-amount-field.udl +0 -2487
- package/conformance/invalid/wrong-exception-reason-field.expected.json +0 -10
- package/conformance/invalid/wrong-exception-reason-field.udl +0 -2487
- package/conformance/valid/agent-description.expected.json +0 -6
- package/conformance/valid/agent-description.udl +0 -66
- package/conformance/valid/attested.expected.json +0 -6
- package/conformance/valid/attested.udl +0 -251
- package/conformance/valid/cards.expected.json +0 -6
- package/conformance/valid/cards.udl +0 -1579
- package/conformance/valid/commerce-escrow.expected.json +0 -6
- package/conformance/valid/commerce-escrow.udl +0 -1512
- package/conformance/valid/compiled-crowdfunding.expected.json +0 -6
- package/conformance/valid/compiled-crowdfunding.udl +0 -1843
- package/conformance/valid/compiled-watch-club.expected.json +0 -6
- package/conformance/valid/compiled-watch-club.udl +0 -2486
- package/conformance/valid/complete-contract.expected.json +0 -6
- package/conformance/valid/complete-contract.udl +0 -218
- package/conformance/valid/effect-signatures.expected.json +0 -6
- package/conformance/valid/effect-signatures.udl +0 -75
- package/conformance/valid/hand-edited.expected.json +0 -6
- package/conformance/valid/hand-edited.udl +0 -1
- package/conformance/valid/insured-car-marketplace.expected.json +0 -6
- package/conformance/valid/insured-car-marketplace.udl +0 -1050
- package/conformance/valid/insured-travel.expected.json +0 -6
- package/conformance/valid/insured-travel.udl +0 -3469
- package/conformance/valid/minimal.expected.json +0 -6
- package/conformance/valid/minimal.udl +0 -62
- package/conformance/valid/piece-plan-calls.expected.json +0 -6
- package/conformance/valid/piece-plan-calls.udl +0 -314
- package/conformance/valid/protection.expected.json +0 -6
- package/conformance/valid/protection.udl +0 -1551
- package/conformance/valid/string-escaping.expected.json +0 -6
- package/conformance/valid/string-escaping.udl +0 -54
- package/conformance/valid/vocabulary.expected.json +0 -6
- package/conformance/valid/vocabulary.udl +0 -2012
- package/dist/allocation.d.ts +0 -60
- package/dist/allocation.d.ts.map +0 -1
- package/dist/allocation.js +0 -177
- package/dist/allocation.js.map +0 -1
- package/dist/check-profiles.d.ts +0 -57
- package/dist/check-profiles.d.ts.map +0 -1
- package/dist/check-profiles.js +0 -62
- package/dist/check-profiles.js.map +0 -1
- package/dist/distribution.d.ts +0 -15
- package/dist/distribution.d.ts.map +0 -1
- package/dist/distribution.js +0 -49
- package/dist/distribution.js.map +0 -1
- package/dist/effects.d.ts +0 -58
- package/dist/effects.d.ts.map +0 -1
- package/dist/effects.js +0 -1126
- package/dist/effects.js.map +0 -1
- package/dist/reference.d.ts +0 -3
- package/dist/reference.d.ts.map +0 -1
- package/dist/reference.js +0 -28
- package/dist/reference.js.map +0 -1
- package/dist/vocabulary.d.ts +0 -23
- package/dist/vocabulary.d.ts.map +0 -1
- package/dist/vocabulary.js +0 -1052
- package/dist/vocabulary.js.map +0 -1
- package/docs/funding-custody.md +0 -165
- package/docs/guide/01-a-document.md +0 -37
- package/docs/guide/02-money-steps.md +0 -23
- package/docs/guide/03-laws.md +0 -18
- package/docs/guide/04-fees-and-remainder.md +0 -36
- package/docs/guide/05-checks-updates-dials.md +0 -7
- package/docs/guide/06-effects.md +0 -11
- package/docs/guide/07-evolution.md +0 -11
- package/docs/guide/08-implementing.md +0 -13
- package/docs/guide/09-schedules-and-allocation.md +0 -132
- package/docs/llms-full.txt +0 -2008
- package/docs/llms.txt +0 -14
- package/docs/piece-plans.md +0 -148
- package/docs/reference/canonical.md +0 -16
- package/docs/reference/clauses.md +0 -1585
- package/docs/reference/cli.md +0 -24
- package/docs/reference/diagnostics.md +0 -38
- package/skills/udl/SKILL.md +0 -28
- package/src/allocation.ts +0 -259
- package/src/check-profiles.ts +0 -80
- package/src/distribution.ts +0 -61
- package/src/effects.ts +0 -1920
- package/src/reference.ts +0 -31
- package/src/vocabulary.ts +0 -1635
package/docs/reference/cli.md
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
<!-- Generated by scripts/docs/build.ts from @hyperscale0/udl 2.6.0. Edit the source, not cli.md. -->
|
|
2
|
-
|
|
3
|
-
# Command reference
|
|
4
|
-
|
|
5
|
-
In the full repository checkout, run `bun open/udl/src/cli.ts validate <file.udl.json>` from the repository root. The entrypoint is `open/udl/src/cli.ts`; in a standalone package checkout it is `src/cli.ts`. Use this local validator for local HSX output.
|
|
6
|
-
|
|
7
|
-
The installed `udl` binary exposes the following commands and exit codes.
|
|
8
|
-
|
|
9
|
-
```text
|
|
10
|
-
udl - the Universal Domain Language toolchain
|
|
11
|
-
|
|
12
|
-
usage:
|
|
13
|
-
udl validate <file> parse <file> and report every issue found
|
|
14
|
-
udl fmt <file> [--write] print the canonical form, or rewrite the file
|
|
15
|
-
udl canon <file> [--digest] print canonical bytes, or their SHA-256 digest
|
|
16
|
-
udl diff <live> <next> check <next> against the append-only law
|
|
17
|
-
udl explain UDL#### explain one stable diagnostic code
|
|
18
|
-
udl help print this
|
|
19
|
-
|
|
20
|
-
exit codes:
|
|
21
|
-
0 the document is admissible, or the change is additive
|
|
22
|
-
1 the document was refused, or the change breaks the append-only law
|
|
23
|
-
2 the command line was wrong, or a file could not be read
|
|
24
|
-
```
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
<!-- Generated by scripts/docs/build.ts from @hyperscale0/udl 2.6.0. Edit the source, not diagnostics.md. -->
|
|
2
|
-
|
|
3
|
-
# Diagnostic reference
|
|
4
|
-
|
|
5
|
-
Codes are stable. Titles and messages may become clearer without changing the code. Apply the listed fix, then validate the whole document again.
|
|
6
|
-
|
|
7
|
-
| Code | Family | Category | Title | Fix |
|
|
8
|
-
| --------- | --------- | ----------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
|
9
|
-
| `UDL1001` | admission | invalid_utf8 | Invalid UTF-8 | Encode the document as valid UTF-8. |
|
|
10
|
-
| `UDL1002` | admission | invalid_json | Invalid JSON | Repair the JSON syntax before validation. |
|
|
11
|
-
| `UDL1003` | admission | invalid_shape | Invalid document shape | Match the published UDL JSON Schema. |
|
|
12
|
-
| `UDL1004` | admission | resource_limit | Resource limit exceeded | Reduce the source size, nesting, values, strings, references, or financial paths named by the message. |
|
|
13
|
-
| `UDL2001` | document | invalid_semantics | Duplicate declaration | Give each declaration a unique name. |
|
|
14
|
-
| `UDL2002` | document | invalid_semantics | Document law violation | Repair the declaration, subject contract, or derived effects named by the message. |
|
|
15
|
-
| `UDL2005` | document | invalid_semantics | Derived effects mismatch | Regenerate the action effects from its clauses. |
|
|
16
|
-
| `UDL2010` | document | invalid_semantics | Action graph violation | Order all actions and calls without cycles or collisions, within depth and count limits. |
|
|
17
|
-
| `UDL2011` | document | invalid_semantics | Action binding violation | Bind parameters to declared instance, piece, or caller variables without forward references or duplicate captures. |
|
|
18
|
-
| `UDL2012` | document | invalid_semantics | Action authority violation | Match principal, approval, and recovery policies and keep field paths within tenant boundary. |
|
|
19
|
-
| `UDL2013` | document | invalid_semantics | Action effect or evidence violation | Provide valid effect kind, matching effect signature, and non-empty evidence for every leaf. |
|
|
20
|
-
| `UDL3001` | lifecycle | invalid_semantics | Lifecycle is not closed | Declare every state and action transition, and make every state reachable. |
|
|
21
|
-
| `UDL4001` | finance | invalid_semantics | Money graph violation | Balance every funded amount and close every hold on each lifecycle path. |
|
|
22
|
-
| `UDL4002` | finance | invalid_semantics | Piece partition violation | Match piece plan total, amounts, and payees to required immutable fields and declared partition. |
|
|
23
|
-
| `UDL5001` | gates | invalid_semantics | Reference gate violation | Point the gate at a declared instrument, action, state, field, and reference. |
|
|
24
|
-
| `UDL5002` | gates | invalid_semantics | Check requirement violation | Use a declared check with compatible evidence and recurrence. |
|
|
25
|
-
| `UDL5003` | gates | invalid_semantics | Exposure gate violation | Use declared account and money fields for the exposure gate. |
|
|
26
|
-
| `UDL5004` | gates | invalid_semantics | Aggregate law violation | Point the aggregate at compatible parent and child fields. |
|
|
27
|
-
| `UDL5005` | gates | invalid_semantics | Settlement or payout violation | Use a declared settlement account and a compatible payout statement line. |
|
|
28
|
-
| `UDL5006` | gates | invalid_semantics | Quote and commit violation | Declare one complete quote freeze set and one matching commit action. |
|
|
29
|
-
| `UDL5007` | gates | invalid_semantics | Reconcile exception child violation | Name a declared child whose reference points back to this instrument. |
|
|
30
|
-
| `UDL5008` | gates | invalid_semantics | Action clause violation | Repair the clause fields and keep incompatible clauses separate. |
|
|
31
|
-
| `UDL5009` | gates | invalid_semantics | Reconcile exception amount field is missing or optional | Name the exception child's required money field in amountField. |
|
|
32
|
-
| `UDL5010` | gates | invalid_semantics | Reconcile exception amount field has the wrong type | Point amountField at a money field declared by the exception child. |
|
|
33
|
-
| `UDL5011` | gates | invalid_semantics | Reconcile exception reason field is missing or optional | Name the exception child's required text field in reasonField. |
|
|
34
|
-
| `UDL5012` | gates | invalid_semantics | Reconcile exception reason field has the wrong type | Point reasonField at a required plain text field declared by the exception child. |
|
|
35
|
-
| `UDL5013` | gates | invalid_semantics | Piece stage violation | Reference a declared piece plan and stage in the containing instrument. |
|
|
36
|
-
| `UDL6001` | schema | invalid_semantics | Unsupported JSON Schema | Use only the sealed UDL JSON Schema subset. |
|
|
37
|
-
| `UDL7001` | evolution | invalid_evolution | Stored contract changed | Keep stored identities and contracts unchanged, and add only allowed optional declarations. |
|
|
38
|
-
| `UDL7002` | evolution | invalid_evolution | Version was not increased | Increase the product version for every semantic change. |
|
package/skills/udl/SKILL.md
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: udl
|
|
3
|
-
description: Read, validate, explain, or write Universal Domain Language documents directly. Use for canonical UDL JSON and format-level diagnostics. SDK and MCP consumers should use their generated contracts instead.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# UDL
|
|
7
|
-
|
|
8
|
-
Use UDL when the task concerns the canonical product document, format conformance, or a validator implementation. Use HSX when authoring a product from reusable modules.
|
|
9
|
-
|
|
10
|
-
## Read first
|
|
11
|
-
|
|
12
|
-
1. Read `docs/guide/01-a-document.md` for the document frame.
|
|
13
|
-
2. Read `docs/guide/03-laws.md` before changing lifecycle, money, gates, or references.
|
|
14
|
-
3. Look up exact fields in `spec/udl.schema.json` and clauses in `docs/reference/clauses.md`.
|
|
15
|
-
|
|
16
|
-
## Edit loop
|
|
17
|
-
|
|
18
|
-
1. Change the smallest complete clause.
|
|
19
|
-
2. Run `udl validate <file>`.
|
|
20
|
-
3. Look up each `UDL####` code with `udl explain UDL####` or `docs/reference/diagnostics.md`.
|
|
21
|
-
4. Apply the listed fix. Validate the whole document again.
|
|
22
|
-
5. Run `udl canon <file> --digest` before pinning bytes or an identity.
|
|
23
|
-
|
|
24
|
-
Never hand-author `effects`. Derive them from action clauses. Never represent money as a JSON number. Use an integer minor-unit string and its declared currency. Never add a money move without checking every terminal lifecycle path for a matching drain or unwind.
|
|
25
|
-
|
|
26
|
-
For a stored definition, validate both documents before `udl diff <live> <next>`. Treat any `UDL7xxx` result as a refused change. A data migration does not make an incompatible document edit additive.
|
|
27
|
-
|
|
28
|
-
Validator work must run the complete `conformance/` contract and follow `docs/reference/canonical.md` byte for byte.
|
package/src/allocation.ts
DELETED
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
export type AllocationBucketKey = "principal" | "profit" | "cost" | "fine";
|
|
2
|
-
export interface AllocationBalance {
|
|
3
|
-
readonly key: AllocationBucketKey;
|
|
4
|
-
readonly assessmentId: string;
|
|
5
|
-
readonly destinationAccountId: string;
|
|
6
|
-
readonly amount: bigint;
|
|
7
|
-
readonly consumed: bigint;
|
|
8
|
-
}
|
|
9
|
-
export interface AllocationSlice {
|
|
10
|
-
readonly id: string;
|
|
11
|
-
readonly dueAt: string;
|
|
12
|
-
readonly position: number;
|
|
13
|
-
readonly paid: boolean;
|
|
14
|
-
readonly buckets: readonly AllocationBalance[];
|
|
15
|
-
}
|
|
16
|
-
export interface AllocationInput {
|
|
17
|
-
readonly asOf: string;
|
|
18
|
-
readonly earningRule: "per_slice_on_due" | "on_disbursement";
|
|
19
|
-
readonly mode: "payment" | "payoff" | "write_off";
|
|
20
|
-
readonly payment?: bigint | undefined;
|
|
21
|
-
readonly assessmentId?: string;
|
|
22
|
-
readonly slices: readonly AllocationSlice[];
|
|
23
|
-
}
|
|
24
|
-
export interface AllocationPosting {
|
|
25
|
-
readonly sliceId: string;
|
|
26
|
-
readonly assessmentId: string;
|
|
27
|
-
readonly bucket: AllocationBucketKey;
|
|
28
|
-
readonly destinationAccountId: string;
|
|
29
|
-
readonly amount: bigint;
|
|
30
|
-
}
|
|
31
|
-
export interface AllocationCancellation {
|
|
32
|
-
readonly sliceId: string;
|
|
33
|
-
readonly assessmentId: string;
|
|
34
|
-
readonly bucket: AllocationBucketKey;
|
|
35
|
-
readonly amount: bigint;
|
|
36
|
-
readonly reason: "unearned_profit" | "write_off";
|
|
37
|
-
}
|
|
38
|
-
function clock(value: string): number {
|
|
39
|
-
if (!/^\d{4}-\d\d-\d\d(?:T\d\d:\d\d:\d\d(?:\.\d+)?Z)?$/.test(value))
|
|
40
|
-
throw new Error("allocation needs an authoritative UTC time");
|
|
41
|
-
const result = Date.parse(value);
|
|
42
|
-
if (
|
|
43
|
-
!Number.isFinite(result) ||
|
|
44
|
-
new Date(`${value.slice(0, 10)}T00:00:00Z`).toISOString().slice(0, 10) !==
|
|
45
|
-
value.slice(0, 10)
|
|
46
|
-
)
|
|
47
|
-
throw new Error("invalid allocation time");
|
|
48
|
-
return result;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** Plan from locked balances. The engine owns identity claims and atomic posting. */
|
|
52
|
-
export function planAllocation(
|
|
53
|
-
contract: {
|
|
54
|
-
readonly buckets: readonly { readonly key: AllocationBucketKey }[];
|
|
55
|
-
},
|
|
56
|
-
input: AllocationInput,
|
|
57
|
-
) {
|
|
58
|
-
const priority = contract.buckets.map((bucket) => bucket.key);
|
|
59
|
-
const keys = new Set(priority);
|
|
60
|
-
if (
|
|
61
|
-
priority.length < 2 ||
|
|
62
|
-
priority.length > 4 ||
|
|
63
|
-
keys.size !== priority.length ||
|
|
64
|
-
!keys.has("principal") ||
|
|
65
|
-
!keys.has("profit") ||
|
|
66
|
-
priority.some(
|
|
67
|
-
(key) => !["principal", "profit", "cost", "fine"].includes(key),
|
|
68
|
-
)
|
|
69
|
-
)
|
|
70
|
-
throw new Error("invalid allocation priority");
|
|
71
|
-
if (
|
|
72
|
-
!["per_slice_on_due", "on_disbursement"].includes(input.earningRule) ||
|
|
73
|
-
!["payment", "payoff", "write_off"].includes(input.mode)
|
|
74
|
-
)
|
|
75
|
-
throw new Error("invalid earning rule or allocation mode");
|
|
76
|
-
if (!input.slices.length || input.slices.length > 366)
|
|
77
|
-
throw new Error("allocation requires 1 to 366 slices");
|
|
78
|
-
if (
|
|
79
|
-
input.assessmentId !== undefined &&
|
|
80
|
-
(input.mode !== "payment" || !input.assessmentId)
|
|
81
|
-
)
|
|
82
|
-
throw new Error("assessment selection requires payment mode");
|
|
83
|
-
const asOf = clock(input.asOf);
|
|
84
|
-
if (
|
|
85
|
-
input.mode === "write_off"
|
|
86
|
-
? input.payment !== undefined
|
|
87
|
-
: input.payment === undefined
|
|
88
|
-
? input.mode !== "payoff" && input.assessmentId === undefined
|
|
89
|
-
: typeof input.payment !== "bigint" ||
|
|
90
|
-
input.payment < 0n ||
|
|
91
|
-
(input.mode === "payment" && input.payment === 0n)
|
|
92
|
-
)
|
|
93
|
-
throw new Error("invalid allocation payment");
|
|
94
|
-
const ids = new Set<string>();
|
|
95
|
-
const positions = new Set<number>();
|
|
96
|
-
const assessments = new Set<string>();
|
|
97
|
-
const slices = input.slices
|
|
98
|
-
.map((slice) => {
|
|
99
|
-
const due = clock(slice.dueAt);
|
|
100
|
-
if (
|
|
101
|
-
!slice.id ||
|
|
102
|
-
ids.has(slice.id) ||
|
|
103
|
-
!Number.isInteger(slice.position) ||
|
|
104
|
-
slice.position < 1 ||
|
|
105
|
-
positions.has(slice.position) ||
|
|
106
|
-
typeof slice.paid !== "boolean" ||
|
|
107
|
-
slice.buckets.length > 256
|
|
108
|
-
)
|
|
109
|
-
throw new Error("invalid or duplicate slice");
|
|
110
|
-
ids.add(slice.id);
|
|
111
|
-
positions.add(slice.position);
|
|
112
|
-
for (const balance of slice.buckets) {
|
|
113
|
-
if (
|
|
114
|
-
!keys.has(balance.key) ||
|
|
115
|
-
!balance.assessmentId ||
|
|
116
|
-
assessments.has(balance.assessmentId) ||
|
|
117
|
-
!balance.destinationAccountId ||
|
|
118
|
-
typeof balance.amount !== "bigint" ||
|
|
119
|
-
typeof balance.consumed !== "bigint" ||
|
|
120
|
-
balance.amount < 0n ||
|
|
121
|
-
balance.consumed < 0n ||
|
|
122
|
-
balance.consumed > balance.amount
|
|
123
|
-
)
|
|
124
|
-
throw new Error("invalid or duplicate assessment balance");
|
|
125
|
-
assessments.add(balance.assessmentId);
|
|
126
|
-
}
|
|
127
|
-
return { slice, due };
|
|
128
|
-
})
|
|
129
|
-
.sort((a, b) => a.due - b.due || a.slice.position - b.slice.position);
|
|
130
|
-
const candidates: AllocationPosting[] = [];
|
|
131
|
-
const cancellations: AllocationCancellation[] = [];
|
|
132
|
-
for (const { slice, due } of slices) {
|
|
133
|
-
const earned =
|
|
134
|
-
input.earningRule === "on_disbursement" || due <= asOf || slice.paid;
|
|
135
|
-
for (const key of priority) {
|
|
136
|
-
// Multiple assessed charges of one kind have a deterministic identity order.
|
|
137
|
-
const balances = slice.buckets
|
|
138
|
-
.filter((balance) => balance.key === key)
|
|
139
|
-
.sort((a, b) =>
|
|
140
|
-
a.assessmentId < b.assessmentId
|
|
141
|
-
? -1
|
|
142
|
-
: a.assessmentId > b.assessmentId
|
|
143
|
-
? 1
|
|
144
|
-
: 0,
|
|
145
|
-
);
|
|
146
|
-
for (const balance of balances) {
|
|
147
|
-
const amount = balance.amount - balance.consumed;
|
|
148
|
-
if (
|
|
149
|
-
input.assessmentId !== undefined &&
|
|
150
|
-
balance.assessmentId !== input.assessmentId
|
|
151
|
-
)
|
|
152
|
-
continue;
|
|
153
|
-
if (amount === 0n) continue;
|
|
154
|
-
const unearned = key === "profit" && !earned;
|
|
155
|
-
if (
|
|
156
|
-
input.mode === "write_off" ||
|
|
157
|
-
(input.mode === "payoff" && unearned)
|
|
158
|
-
) {
|
|
159
|
-
cancellations.push({
|
|
160
|
-
sliceId: slice.id,
|
|
161
|
-
assessmentId: balance.assessmentId,
|
|
162
|
-
bucket: key,
|
|
163
|
-
amount,
|
|
164
|
-
reason: unearned ? "unearned_profit" : "write_off",
|
|
165
|
-
});
|
|
166
|
-
} else {
|
|
167
|
-
candidates.push({
|
|
168
|
-
sliceId: slice.id,
|
|
169
|
-
assessmentId: balance.assessmentId,
|
|
170
|
-
bucket: key,
|
|
171
|
-
destinationAccountId: balance.destinationAccountId,
|
|
172
|
-
amount,
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
const outstanding = candidates.reduce((sum, row) => sum + row.amount, 0n);
|
|
179
|
-
if (input.mode === "write_off")
|
|
180
|
-
return { postings: [] as AllocationPosting[], cancellations, amount: 0n };
|
|
181
|
-
if (input.assessmentId !== undefined && !assessments.has(input.assessmentId))
|
|
182
|
-
throw new Error("assessment is not in the allocation");
|
|
183
|
-
const payment = input.payment ?? outstanding;
|
|
184
|
-
if (input.mode === "payment" && payment === 0n)
|
|
185
|
-
throw new Error("assessment already consumed");
|
|
186
|
-
if (
|
|
187
|
-
payment > outstanding ||
|
|
188
|
-
(input.mode === "payoff" && payment !== outstanding)
|
|
189
|
-
)
|
|
190
|
-
throw new Error("payment does not match outstanding allocation");
|
|
191
|
-
let remaining = payment;
|
|
192
|
-
const postings: AllocationPosting[] = [];
|
|
193
|
-
for (const candidate of candidates) {
|
|
194
|
-
const amount = candidate.amount < remaining ? candidate.amount : remaining;
|
|
195
|
-
if (amount > 0n) postings.push({ ...candidate, amount });
|
|
196
|
-
remaining -= amount;
|
|
197
|
-
if (remaining === 0n) break;
|
|
198
|
-
}
|
|
199
|
-
return { postings, cancellations, amount: payment };
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/** Reverse recorded postings once, without reopening the consumed assessment. */
|
|
203
|
-
export function planAllocationRefund(
|
|
204
|
-
postings: readonly AllocationPosting[],
|
|
205
|
-
originalPayer: string,
|
|
206
|
-
refundedAssessments: readonly string[],
|
|
207
|
-
assessmentId?: string,
|
|
208
|
-
) {
|
|
209
|
-
if (!originalPayer || postings.length > 366 * 256)
|
|
210
|
-
throw new Error("invalid allocation receipt");
|
|
211
|
-
const selected = postings.filter(
|
|
212
|
-
(row) => assessmentId === undefined || row.assessmentId === assessmentId,
|
|
213
|
-
);
|
|
214
|
-
if (!selected.length)
|
|
215
|
-
throw new Error("assessment has no recorded allocation");
|
|
216
|
-
const seen = new Set<string>();
|
|
217
|
-
return selected.map((row) => {
|
|
218
|
-
if (
|
|
219
|
-
row.amount <= 0n ||
|
|
220
|
-
!row.destinationAccountId ||
|
|
221
|
-
!row.assessmentId ||
|
|
222
|
-
seen.has(row.assessmentId) ||
|
|
223
|
-
refundedAssessments.includes(row.assessmentId)
|
|
224
|
-
)
|
|
225
|
-
throw new Error("assessment already refunded or invalid receipt");
|
|
226
|
-
seen.add(row.assessmentId);
|
|
227
|
-
return {
|
|
228
|
-
assessmentId: row.assessmentId,
|
|
229
|
-
amount: row.amount,
|
|
230
|
-
sourceAccountId: row.destinationAccountId,
|
|
231
|
-
destinationAccountId: originalPayer,
|
|
232
|
-
};
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/** Evaluate a slice gate from the same locked consumption balances used to allocate. */
|
|
237
|
-
export function matchesAllocationConsumption(
|
|
238
|
-
clause: {
|
|
239
|
-
readonly buckets: readonly AllocationBucketKey[];
|
|
240
|
-
readonly check: "settled" | "outstanding";
|
|
241
|
-
},
|
|
242
|
-
slice: AllocationSlice,
|
|
243
|
-
): boolean {
|
|
244
|
-
if (
|
|
245
|
-
!clause.buckets.length ||
|
|
246
|
-
new Set(clause.buckets).size !== clause.buckets.length
|
|
247
|
-
)
|
|
248
|
-
throw new Error("allocation gate needs distinct buckets");
|
|
249
|
-
const settled = clause.buckets
|
|
250
|
-
.map((key) => {
|
|
251
|
-
const rows = slice.buckets.filter((bucket) => bucket.key === key);
|
|
252
|
-
for (const row of rows)
|
|
253
|
-
if (row.consumed < 0n || row.consumed > row.amount || row.amount < 0n)
|
|
254
|
-
throw new Error("invalid consumption");
|
|
255
|
-
return rows.every((row) => row.consumed === row.amount);
|
|
256
|
-
})
|
|
257
|
-
.every(Boolean);
|
|
258
|
-
return clause.check === "settled" ? settled : !settled;
|
|
259
|
-
}
|
package/src/check-profiles.ts
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Evidence profiles that an instrument action may require before it runs.
|
|
3
|
-
* The public grammar and the runtime both read this table. A pair absent here
|
|
4
|
-
* cannot gate an action.
|
|
5
|
-
*/
|
|
6
|
-
export interface UdlCheckEvidenceProfile {
|
|
7
|
-
readonly family: string;
|
|
8
|
-
readonly checkKind: string;
|
|
9
|
-
readonly kindField?: string;
|
|
10
|
-
readonly statusField: string;
|
|
11
|
-
readonly statuses: readonly string[];
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export const udlCheckEvidenceProfiles = [
|
|
15
|
-
{
|
|
16
|
-
family: "national_identity",
|
|
17
|
-
checkKind: "identity_verification",
|
|
18
|
-
kindField: "checkKind",
|
|
19
|
-
statusField: "verificationStatus",
|
|
20
|
-
statuses: [
|
|
21
|
-
"requested",
|
|
22
|
-
"waiting",
|
|
23
|
-
"completed",
|
|
24
|
-
"rejected",
|
|
25
|
-
"expired",
|
|
26
|
-
"error",
|
|
27
|
-
],
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
family: "national_identity",
|
|
31
|
-
checkKind: "contact_ownership_verification",
|
|
32
|
-
kindField: "checkKind",
|
|
33
|
-
statusField: "matchResult",
|
|
34
|
-
statuses: ["match", "no_match"],
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
family: "credit_bureau",
|
|
38
|
-
checkKind: "consumer_inquiry",
|
|
39
|
-
kindField: "inquiryType",
|
|
40
|
-
statusField: "bureauStatus",
|
|
41
|
-
statuses: ["returned", "acknowledged", "correction_required"],
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
family: "credit_bureau",
|
|
45
|
-
checkKind: "commercial_inquiry",
|
|
46
|
-
kindField: "inquiryType",
|
|
47
|
-
statusField: "bureauStatus",
|
|
48
|
-
statuses: ["returned", "acknowledged", "correction_required"],
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
family: "credit_bureau",
|
|
52
|
-
checkKind: "facility_report",
|
|
53
|
-
kindField: "inquiryType",
|
|
54
|
-
statusField: "bureauStatus",
|
|
55
|
-
statuses: ["returned", "acknowledged", "correction_required"],
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
family: "sanctions_screening",
|
|
59
|
-
checkKind: "screen",
|
|
60
|
-
kindField: "checkKind",
|
|
61
|
-
statusField: "screeningStatus",
|
|
62
|
-
statuses: ["clear", "hit", "manual_review"],
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
family: "enforcement_instrument",
|
|
66
|
-
checkKind: "note_request",
|
|
67
|
-
statusField: "requestStatus",
|
|
68
|
-
statuses: ["debtor_approved", "debtor_rejected", "auto_cancelled"],
|
|
69
|
-
},
|
|
70
|
-
] as const satisfies readonly UdlCheckEvidenceProfile[];
|
|
71
|
-
|
|
72
|
-
/** Returns the tenant-gateable evidence profile for one family and check. */
|
|
73
|
-
export function udlCheckEvidenceProfile(
|
|
74
|
-
family: string,
|
|
75
|
-
checkKind: string,
|
|
76
|
-
): UdlCheckEvidenceProfile | undefined {
|
|
77
|
-
return udlCheckEvidenceProfiles.find(
|
|
78
|
-
(profile) => profile.family === family && profile.checkKind === checkKind,
|
|
79
|
-
);
|
|
80
|
-
}
|
package/src/distribution.ts
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import type { UdlAction } from "./schema.js";
|
|
2
|
-
|
|
3
|
-
/** Arithmetic only. The host resolves and consumes the receipt and snapshot under locks. */
|
|
4
|
-
export function distributeReceiptAmounts(
|
|
5
|
-
clause: NonNullable<UdlAction["receiptDistribution"]>,
|
|
6
|
-
principal: bigint,
|
|
7
|
-
profit: bigint,
|
|
8
|
-
tickets: readonly { readonly id: string; readonly weight: bigint }[],
|
|
9
|
-
) {
|
|
10
|
-
if (principal < 0n || profit < 0n || tickets.length === 0)
|
|
11
|
-
throw new Error(
|
|
12
|
-
"distribution requires nonnegative receipt amounts and tickets",
|
|
13
|
-
);
|
|
14
|
-
if (
|
|
15
|
-
new Set(tickets.map((ticket) => ticket.id)).size !== tickets.length ||
|
|
16
|
-
tickets.some((ticket) => ticket.weight <= 0n)
|
|
17
|
-
)
|
|
18
|
-
throw new Error(
|
|
19
|
-
"snapshot tickets must have unique identities and positive weights",
|
|
20
|
-
);
|
|
21
|
-
const loss = clause.mode === "loss";
|
|
22
|
-
if (loss && (profit !== 0n || clause.feeBps !== 0 || clause.vatBps !== 0))
|
|
23
|
-
throw new Error("loss allocation contains only principal and no fees");
|
|
24
|
-
const totalWeight = tickets.reduce((sum, ticket) => sum + ticket.weight, 0n);
|
|
25
|
-
const fee = (profit * BigInt(clause.feeBps)) / 10000n;
|
|
26
|
-
const vat = (fee * BigInt(clause.vatBps)) / 10000n;
|
|
27
|
-
const amount = principal + profit - fee - vat;
|
|
28
|
-
if (amount < 0n) throw new Error("fee and VAT exceed the receipt");
|
|
29
|
-
const shares = tickets.map((ticket) => ({
|
|
30
|
-
id: ticket.id,
|
|
31
|
-
amount: (amount * ticket.weight) / totalWeight,
|
|
32
|
-
}));
|
|
33
|
-
let residual = amount - shares.reduce((sum, share) => sum + share.amount, 0n);
|
|
34
|
-
if (loss) {
|
|
35
|
-
// A loss cannot be paid to a residual cash beneficiary. Largest remainder
|
|
36
|
-
// assigns every lost minor unit to an investor, with identity breaking ties.
|
|
37
|
-
const ordered = tickets
|
|
38
|
-
.map((ticket, index) => ({
|
|
39
|
-
index,
|
|
40
|
-
id: ticket.id,
|
|
41
|
-
remainder: (amount * ticket.weight) % totalWeight,
|
|
42
|
-
}))
|
|
43
|
-
.sort((a, b) =>
|
|
44
|
-
a.remainder === b.remainder
|
|
45
|
-
? a.id < b.id
|
|
46
|
-
? -1
|
|
47
|
-
: a.id > b.id
|
|
48
|
-
? 1
|
|
49
|
-
: 0
|
|
50
|
-
: a.remainder > b.remainder
|
|
51
|
-
? -1
|
|
52
|
-
: 1,
|
|
53
|
-
);
|
|
54
|
-
for (const ticket of ordered) {
|
|
55
|
-
if (residual === 0n) break;
|
|
56
|
-
shares[ticket.index]!.amount += 1n;
|
|
57
|
-
residual -= 1n;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
return { fee, vat, residual, shares };
|
|
61
|
-
}
|