@hyperscale0/udl 1.0.0-beta.1 → 1.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 +78 -25
- package/README.md +37 -99
- package/conformance/README.md +44 -41
- package/conformance/evolution/action-contract.expected.json +10 -0
- package/conformance/evolution/action-contract.live.udl +44 -0
- package/conformance/evolution/action-contract.next.udl +45 -0
- package/conformance/evolution/product-identity.expected.json +10 -0
- package/conformance/evolution/product-identity.live.udl +55 -0
- package/conformance/evolution/product-identity.next.udl +55 -0
- package/conformance/evolution/version-required.expected.json +10 -0
- package/conformance/evolution/version-required.live.udl +55 -0
- package/conformance/evolution/version-required.next.udl +58 -0
- package/conformance/invalid/action-without-transition.expected.json +10 -0
- package/conformance/invalid/{verb-without-transition.udl → action-without-transition.udl} +7 -2
- package/conformance/invalid/agent-description-too-long.expected.json +10 -0
- package/conformance/invalid/agent-description-too-long.udl +57 -0
- package/conformance/invalid/blank-title.expected.json +1 -1
- package/conformance/invalid/blank-title.udl +6 -2
- package/conformance/invalid/composition-dial-duplicate-key.expected.json +10 -0
- package/conformance/invalid/composition-dial-duplicate-key.udl +73 -0
- package/conformance/invalid/depth-budget.expected.json +1 -1
- package/conformance/invalid/duplicate-subject.expected.json +10 -0
- package/conformance/invalid/duplicate-subject.udl +230 -0
- package/conformance/invalid/forged-effects.expected.json +10 -0
- package/conformance/invalid/forged-effects.udl +76 -0
- package/conformance/invalid/format-version.expected.json +1 -1
- package/conformance/invalid/format-version.udl +6 -2
- package/conformance/invalid/instrument-id-not-snake-case.expected.json +10 -0
- package/conformance/invalid/{noun-id-not-snake-case.udl → instrument-id-not-snake-case.udl} +6 -2
- package/conformance/invalid/invalid-aggregate-gate-shape.expected.json +10 -0
- package/conformance/invalid/invalid-aggregate-gate-shape.udl +1819 -0
- package/conformance/invalid/invalid-check-duration.expected.json +10 -0
- package/conformance/invalid/invalid-check-duration.udl +219 -0
- package/conformance/invalid/invalid-dial-anchor.expected.json +10 -0
- package/conformance/invalid/invalid-dial-anchor.udl +219 -0
- package/conformance/invalid/invalid-exception-parent-ref.expected.json +10 -0
- package/conformance/invalid/invalid-exception-parent-ref.udl +2487 -0
- package/conformance/invalid/invalid-exposure-shape.expected.json +10 -0
- package/conformance/invalid/invalid-exposure-shape.udl +1819 -0
- package/conformance/invalid/invalid-remainder.expected.json +10 -0
- package/conformance/invalid/invalid-remainder.udl +220 -0
- package/conformance/invalid/invalid-schema-keyword.expected.json +10 -0
- package/conformance/invalid/invalid-schema-keyword.udl +220 -0
- package/conformance/invalid/invalid-utf8.expected.json +1 -1
- package/conformance/invalid/malformed-json.expected.json +1 -1
- package/conformance/invalid/missing-create-action.expected.json +10 -0
- package/conformance/invalid/{missing-create-verb.udl → missing-create-action.udl} +5 -2
- package/conformance/invalid/missing-exception-amount-field.expected.json +10 -0
- package/conformance/invalid/missing-exception-amount-field.udl +2487 -0
- package/conformance/invalid/missing-exception-contract.expected.json +14 -0
- package/conformance/invalid/missing-exception-contract.udl +2523 -0
- package/conformance/invalid/missing-exception-reason-field.expected.json +10 -0
- package/conformance/invalid/missing-exception-reason-field.udl +2487 -0
- package/conformance/invalid/not-an-object.expected.json +1 -1
- package/conformance/invalid/payout-reconcile-not-a-bank-debit.expected.json +10 -0
- package/conformance/invalid/payout-reconcile-not-a-bank-debit.udl +259 -0
- package/conformance/invalid/quote-freeze-set-incomplete.expected.json +10 -0
- package/conformance/invalid/quote-freeze-set-incomplete.udl +261 -0
- package/conformance/invalid/quote-named-reference-gate.expected.json +10 -0
- package/conformance/invalid/quote-named-reference-gate.udl +50 -0
- package/conformance/invalid/reconcile-named-reference-gate.expected.json +10 -0
- package/conformance/invalid/reconcile-named-reference-gate.udl +50 -0
- package/conformance/invalid/unknown-key.expected.json +1 -1
- package/conformance/invalid/unknown-key.udl +6 -2
- package/conformance/invalid/unknown-reference-gate-field.expected.json +10 -0
- package/conformance/invalid/unknown-reference-gate-field.udl +1819 -0
- package/conformance/invalid/unknown-required-field.expected.json +10 -0
- package/conformance/invalid/unknown-required-field.udl +220 -0
- package/conformance/invalid/unreachable-state.expected.json +2 -2
- package/conformance/invalid/unreachable-state.udl +6 -2
- package/conformance/invalid/wrong-exception-amount-field.expected.json +10 -0
- package/conformance/invalid/wrong-exception-amount-field.udl +2487 -0
- package/conformance/invalid/wrong-exception-reason-field.expected.json +10 -0
- package/conformance/invalid/wrong-exception-reason-field.udl +2487 -0
- package/conformance/valid/agent-description.expected.json +6 -0
- package/conformance/valid/agent-description.udl +66 -0
- package/conformance/valid/cards.expected.json +1 -0
- package/conformance/valid/cards.udl +843 -356
- package/conformance/valid/commerce-escrow.expected.json +1 -0
- package/conformance/valid/commerce-escrow.udl +792 -291
- package/conformance/valid/compiled-crowdfunding.expected.json +6 -0
- package/conformance/valid/compiled-crowdfunding.udl +1843 -0
- package/conformance/valid/compiled-watch-club.expected.json +6 -0
- package/conformance/valid/compiled-watch-club.udl +2486 -0
- package/conformance/valid/complete-contract.expected.json +6 -0
- package/conformance/valid/complete-contract.udl +218 -0
- package/conformance/valid/effect-signatures.expected.json +6 -0
- package/conformance/valid/effect-signatures.udl +75 -0
- package/conformance/valid/hand-edited.expected.json +1 -0
- package/conformance/valid/hand-edited.udl +1 -1
- package/conformance/valid/insured-car-marketplace.expected.json +1 -0
- package/conformance/valid/insured-car-marketplace.udl +534 -315
- package/conformance/valid/insured-travel.expected.json +2 -1
- package/conformance/valid/insured-travel.udl +1489 -641
- package/conformance/valid/minimal.expected.json +1 -0
- package/conformance/valid/minimal.udl +18 -14
- package/conformance/valid/protection.expected.json +1 -0
- package/conformance/valid/protection.udl +719 -304
- package/conformance/valid/string-escaping.expected.json +6 -0
- package/conformance/valid/string-escaping.udl +54 -0
- package/dist/canonical.d.ts +2 -0
- package/dist/canonical.d.ts.map +1 -1
- package/dist/canonical.js +7 -0
- package/dist/canonical.js.map +1 -1
- package/dist/check-profiles.d.ts +57 -0
- package/dist/check-profiles.d.ts.map +1 -0
- package/dist/check-profiles.js +62 -0
- package/dist/check-profiles.js.map +1 -0
- package/dist/cli.js +48 -4
- package/dist/cli.js.map +1 -1
- package/dist/diagnostics.d.ts +168 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +164 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/effects.d.ts +41 -0
- package/dist/effects.d.ts.map +1 -0
- package/dist/effects.js +110 -0
- package/dist/effects.js.map +1 -0
- package/dist/evolution.d.ts +51 -18
- package/dist/evolution.d.ts.map +1 -1
- package/dist/evolution.js +244 -102
- package/dist/evolution.js.map +1 -1
- package/dist/finance.d.ts +17 -13
- package/dist/finance.d.ts.map +1 -1
- package/dist/finance.js +165 -109
- package/dist/finance.js.map +1 -1
- package/dist/index.d.ts +12 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -5
- package/dist/index.js.map +1 -1
- package/dist/limits.d.ts +2 -2
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +4 -3
- package/dist/limits.js.map +1 -1
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +6 -16
- package/dist/parser.js.map +1 -1
- package/dist/schema.d.ts +2059 -135
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +759 -73
- package/dist/schema.js.map +1 -1
- package/dist/validation.d.ts +22 -9
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +1463 -457
- package/dist/validation.js.map +1 -1
- package/docs/README.md +14 -0
- package/docs/guide/01-a-document.md +37 -0
- package/docs/guide/02-money-steps.md +23 -0
- package/docs/guide/03-laws.md +18 -0
- package/docs/guide/04-fees-and-remainder.md +15 -0
- package/docs/guide/05-checks-updates-dials.md +7 -0
- package/docs/guide/06-effects.md +11 -0
- package/docs/guide/07-evolution.md +11 -0
- package/docs/guide/08-implementing.md +13 -0
- package/docs/llms-full.txt +1241 -0
- package/docs/llms.txt +14 -0
- package/docs/reference/canonical.md +16 -0
- package/docs/reference/clauses.md +986 -0
- package/docs/reference/cli.md +22 -0
- package/docs/reference/diagnostics.md +32 -0
- package/package.json +6 -2
- package/skills/udl/SKILL.md +28 -0
- package/spec/README.md +82 -51
- package/spec/udl.schema.json +1191 -119
- package/src/canonical.ts +11 -0
- package/src/check-profiles.ts +80 -0
- package/src/cli.ts +49 -3
- package/src/diagnostics.ts +221 -0
- package/src/effects.ts +163 -0
- package/src/evolution.ts +375 -125
- package/src/finance.ts +255 -160
- package/src/index.ts +50 -14
- package/src/limits.ts +4 -3
- package/src/parser.ts +15 -18
- package/src/schema.ts +855 -81
- package/src/validation.ts +2666 -560
- package/conformance/invalid/missing-create-verb.expected.json +0 -10
- package/conformance/invalid/noun-id-not-snake-case.expected.json +0 -10
- package/conformance/invalid/verb-without-transition.expected.json +0 -10
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,58 @@ here under the release that made it.
|
|
|
10
10
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
|
+
## [1.0.0] - 2026-09-04
|
|
14
|
+
|
|
15
|
+
This release is byte-identical to 1.0.0-rc.1. Later changes bump under the contract-diff tiers.
|
|
16
|
+
|
|
17
|
+
## [1.0.0-rc.1] - 2026-09-02
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- Stable `UDL####` diagnostics include a category, fix, message, and JSON path.
|
|
22
|
+
- Evolution comparison returns coded issues instead of strings.
|
|
23
|
+
- `canonicalDigest` returns a promise for SHA-256 over canonical UTF-8 bytes.
|
|
24
|
+
- `udl canon` prints canonical bytes or their digest, and `udl explain` prints
|
|
25
|
+
the catalog entry for one code.
|
|
26
|
+
- The conformance runner has evolution pairs and pins canonical digests, issue
|
|
27
|
+
codes, and paths.
|
|
28
|
+
- Eight guides, generated clause, diagnostic, and command references,
|
|
29
|
+
`llms.txt`, `llms-full.txt`, and an agent skill now ship with the package.
|
|
30
|
+
|
|
31
|
+
- `reconcile` declares one expectation about money an action cannot see yet:
|
|
32
|
+
the amount path, currency field, direction, the ref naming the provider-side
|
|
33
|
+
row, exactly one evidence source, a match law, and a window. The match law is
|
|
34
|
+
`exact`, `tolerance` bounded by a `reconcile_tolerance` dial, or `window`. An
|
|
35
|
+
expectation that never matches by the end of its window is carried by the
|
|
36
|
+
named exception child, capped by `maxOpen`. The child is declared and
|
|
37
|
+
validated at admission, not materialized: the transition refuses with
|
|
38
|
+
`state_conflict` naming it and the due sweep re-arms the expectation.
|
|
39
|
+
`UdlReconcile` exposes the clause type.
|
|
40
|
+
- An action may declare a `quote` offer and exactly one other action may spend
|
|
41
|
+
it through `commit`. Both the base money field and the net destination are
|
|
42
|
+
mandatory members of `fixes`, and `fixes` and `updates` may not overlap.
|
|
43
|
+
|
|
44
|
+
### Removed
|
|
45
|
+
|
|
46
|
+
- `requiresSettlement` and `UdlRequiresSettlement` are gone. A payout-owning
|
|
47
|
+
instrument now declares exactly one action carrying `reconcile` against a
|
|
48
|
+
debit statement line, under any match law. There is no wrapper and no alias.
|
|
49
|
+
|
|
50
|
+
### Changed
|
|
51
|
+
|
|
52
|
+
- The npm package includes `docs/` and `skills/`.
|
|
53
|
+
- Derived effect rows for `quote`, `commit`, and `reconcile` change the canonical
|
|
54
|
+
bytes of stored documents that carry `effects` for those clauses.
|
|
55
|
+
- Format 1 now carries every instrument contract clause. Instrument metadata
|
|
56
|
+
includes archetype template, navigation, visibility, dials, caller-parked
|
|
57
|
+
states, and extensible subjects. Actions include check gates, remainder
|
|
58
|
+
arithmetic, field updates, principal policy, and sandbox failure points.
|
|
59
|
+
- Remainder clauses declare their computed money refs, so computed money has
|
|
60
|
+
one authored source.
|
|
61
|
+
- Evolution snapshots protect lifted clauses that affect composition,
|
|
62
|
+
admission, or execution. Navigation, examples, and parked-state reason text
|
|
63
|
+
remain editable presentation.
|
|
64
|
+
|
|
13
65
|
## [1.0.0-beta.1] - 2026-08-29
|
|
14
66
|
|
|
15
67
|
- This is the first beta and has no package behavior changes from
|
|
@@ -19,35 +71,34 @@ here under the release that made it.
|
|
|
19
71
|
|
|
20
72
|
### Added
|
|
21
73
|
|
|
22
|
-
-
|
|
23
|
-
name while the
|
|
24
|
-
due
|
|
74
|
+
- Actions may declare a camelCase `publicAction` as their author-approved public
|
|
75
|
+
name while the action key remains the lifecycle and execution identity. System
|
|
76
|
+
due actions must omit it. `udlPublicActionSchema` is exported for consumers
|
|
25
77
|
that admit the same name outside a complete document.
|
|
26
|
-
- `captureInput` maps declared
|
|
27
|
-
Declare only input properties from that
|
|
28
|
-
in the
|
|
78
|
+
- `captureInput` maps declared action input fields into durable receipt refs.
|
|
79
|
+
Declare only input properties from that action and allocate each captured key
|
|
80
|
+
in the instrument's shared ref namespace.
|
|
29
81
|
- `signedSum` computes stored add and subtract subtotals over typed child money,
|
|
30
|
-
then captures one net amount for exactly one payout or
|
|
82
|
+
then captures one net amount for exactly one payout or instrument transfer. Authors
|
|
31
83
|
must declare the child reference, money field, currency, admitted statuses,
|
|
32
84
|
and explicit negative and zero policies.
|
|
33
85
|
- `requiresExposure` gates a child amount against a stored cap, with an optional
|
|
34
86
|
anchor-specific cap, and `setsAt.marker` records occurrence timestamps that
|
|
35
87
|
cannot drive a due condition or deadline. Use these clauses for bounded
|
|
36
88
|
installment writes and per-anchor occurrence markers.
|
|
37
|
-
- `distribute` allocates one parent money field or computed money ref
|
|
38
|
-
typed children selected by status and stored weight.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
- Nouns may declare up to four `derivedAmounts`. Each rule computes a declared
|
|
89
|
+
- `distribute` allocates one parent money field or remainder-computed money ref
|
|
90
|
+
across typed children selected by status and stored weight. The validator
|
|
91
|
+
resolves the parent, weight, statuses, and pool before admitting the document.
|
|
92
|
+
- Instruments may declare up to four `derivedAmounts`. Each rule computes a declared
|
|
42
93
|
money field as 1 through 9,999 basis points of another declared money field
|
|
43
94
|
with floor rounding. Callers supply neither the result nor a fixed or tiered
|
|
44
95
|
rule, and a rule may not derive a field from itself.
|
|
45
|
-
-
|
|
96
|
+
- Actions may declare one `payout` intent that reads stored money, currency,
|
|
46
97
|
source-account, and beneficiary values and captures the payout reference.
|
|
47
98
|
This does not add an operation to the seven-instruction kernel.
|
|
48
99
|
- A system-only `requiresSettlement` transition may read a captured payout
|
|
49
100
|
reference and capture the durable evidence record that matched it. The
|
|
50
|
-
validator rejects caller input, decision ports, public
|
|
101
|
+
validator rejects caller input, decision ports, public actions, due and
|
|
51
102
|
deadline triggers, kernel steps, and money moves on that transition.
|
|
52
103
|
- `validateUdlJsonSchema` validates one schema against UDL's sealed JSON Schema
|
|
53
104
|
subset without applying it to a value. `UdlPayout` and
|
|
@@ -55,12 +106,12 @@ here under the release that made it.
|
|
|
55
106
|
|
|
56
107
|
### Changed
|
|
57
108
|
|
|
58
|
-
- Evolution snapshots freeze public
|
|
59
|
-
distribution rules, exposure gates, signed sums, computed money
|
|
60
|
-
derived-amount arithmetic once a
|
|
109
|
+
- Evolution snapshots freeze public actions, captured receipt input,
|
|
110
|
+
distribution rules, exposure gates, signed sums, computed money clauses, and
|
|
111
|
+
derived-amount arithmetic once a instrument has live instances. Older snapshots
|
|
61
112
|
remain readable when those keys are absent.
|
|
62
113
|
- Evolution snapshots freeze both payout intents and settlement evidence gates.
|
|
63
|
-
- Receipt refs written by the new clauses share the
|
|
114
|
+
- Receipt refs written by the new clauses share the instrument ref namespace with
|
|
64
115
|
kernel captures, input captures, signed sums, subject refs, and unwind refs.
|
|
65
116
|
|
|
66
117
|
## [1.0.0-alpha.4] - 2026-08-26
|
|
@@ -83,16 +134,16 @@ here under the release that made it.
|
|
|
83
134
|
refuses now throws a `UdlError` carrying that document's issues where
|
|
84
135
|
alpha.2 returned evolution violations. Measured on
|
|
85
136
|
`conformance/valid/protection.udl` with the product renamed to
|
|
86
|
-
`protection_v2` and the live `claim`
|
|
87
|
-
violations, naming the rename, the removed live
|
|
137
|
+
`protection_v2` and the live `claim` instrument dropped: alpha.2 returned three
|
|
138
|
+
violations, naming the rename, the removed live instrument, and the version that
|
|
88
139
|
did not move; this release throws `invalid_semantics` at
|
|
89
|
-
`$.
|
|
140
|
+
`$.instruments[0].aggregateInvariants[0].childInstrumentId`, because another instrument's
|
|
90
141
|
aggregate still references `claim`.
|
|
91
142
|
|
|
92
143
|
Read the throw as a refusal to judge, not as a verdict of no violations.
|
|
93
144
|
A `catch` that treats it as a schema problem and carries on has skipped the
|
|
94
145
|
append-only check entirely, and the candidate above is exactly the kind that
|
|
95
|
-
then sails through: dropping a live
|
|
146
|
+
then sails through: dropping a live instrument is the headline violation the law
|
|
96
147
|
exists to catch. Fix the document, or use `diffValidatedUdlEvolution`.
|
|
97
148
|
|
|
98
149
|
The parameters are `unknown` because the function now accepts input nobody
|
|
@@ -111,8 +162,8 @@ here under the release that made it.
|
|
|
111
162
|
- The comparison key behind every diff carries the same depth budget the
|
|
112
163
|
validator applies to a document (`UDL_LIMITS.maxDepth`, 24 levels; the
|
|
113
164
|
deepest conformance document reaches 12). This is what protects
|
|
114
|
-
`
|
|
115
|
-
closes the case where `
|
|
165
|
+
`diffInstrumentEvolution`, which takes snapshots no validator has seen, and it
|
|
166
|
+
closes the case where `snapshotUdlInstrument` returns a cyclic snapshot that only
|
|
116
167
|
detonates when something later stringifies it.
|
|
117
168
|
|
|
118
169
|
### Added
|
|
@@ -157,7 +208,9 @@ First public release. Format version 1.
|
|
|
157
208
|
- `bun run spec:check`, which fails when the committed schema drifts from the
|
|
158
209
|
grammar it was generated from.
|
|
159
210
|
|
|
160
|
-
[Unreleased]: https://github.com/hyperscale0/hyperscale-udl/compare/v1.0.0
|
|
211
|
+
[Unreleased]: https://github.com/hyperscale0/hyperscale-udl/compare/v1.0.0...HEAD
|
|
212
|
+
[1.0.0]: https://github.com/hyperscale0/hyperscale-udl/compare/v1.0.0-rc.1...v1.0.0
|
|
213
|
+
[1.0.0-rc.1]: https://github.com/hyperscale0/hyperscale-udl/compare/v1.0.0-beta.1...v1.0.0-rc.1
|
|
161
214
|
[1.0.0-beta.1]: https://github.com/hyperscale0/hyperscale-udl/compare/v1.0.0-alpha.5...v1.0.0-beta.1
|
|
162
215
|
[1.0.0-alpha.5]: https://github.com/hyperscale0/hyperscale-udl/compare/v1.0.0-alpha.4...v1.0.0-alpha.5
|
|
163
216
|
[1.0.0-alpha.4]: https://github.com/hyperscale0/hyperscale-udl/compare/v1.0.0-alpha.3...v1.0.0-alpha.4
|
package/README.md
CHANGED
|
@@ -1,22 +1,10 @@
|
|
|
1
1
|
# UDL
|
|
2
2
|
|
|
3
|
-
UDL is the Universal Domain Language
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
The language deliberately cannot say certain things. There is no statement
|
|
10
|
-
format, no file drop, no polling loop, no cutoff time, no scheme name, no
|
|
11
|
-
reconciliation vocabulary. Those are real and they are somebody's problem, but
|
|
12
|
-
they are not the product, so they are absorbed below the language and never
|
|
13
|
-
surface in a document. What is left is small enough to hold in your head.
|
|
14
|
-
|
|
15
|
-
This package is the reference implementation: parser, semantic validator,
|
|
16
|
-
canonical serializer, append-only evolution diff, and the `udl` command. It
|
|
17
|
-
does not compose a company or execute operations. The format itself is
|
|
18
|
-
specified in [`spec/`](./spec/README.md) and pinned by
|
|
19
|
-
[`conformance/`](./conformance/README.md).
|
|
3
|
+
UDL is the Universal Domain Language, the canonical JSON contract for a financial product. One `.udl` file declares subjects, instruments, lifecycles, actions, and money movement. An engine can admit that document without reading the source language that produced it.
|
|
4
|
+
|
|
5
|
+
UDL keeps provider machinery below the format. It has no file drops, polling loops, cutoff jobs, scheme messages, or provider statement schemas. The `reconcile` clause names settlement evidence against a declared provider-side row. It does not model the provider file or transport.
|
|
6
|
+
|
|
7
|
+
This package contains the parser, semantic validator, canonical serializer, append-only evolution diff, JSON Schema, and conformance corpus.
|
|
20
8
|
|
|
21
9
|
## Install
|
|
22
10
|
|
|
@@ -24,18 +12,17 @@ specified in [`spec/`](./spec/README.md) and pinned by
|
|
|
24
12
|
npm install @hyperscale0/udl
|
|
25
13
|
```
|
|
26
14
|
|
|
27
|
-
|
|
28
|
-
a bare install gets it. Pin an exact version if you need one: until 1.0.0 a
|
|
29
|
-
change to the surface ships as a minor bump, not a major.
|
|
15
|
+
`1.0.0` freezes format 1. Pin version 1.0.0 while testing another implementation.
|
|
30
16
|
|
|
31
17
|
## Thirty seconds
|
|
32
18
|
|
|
33
|
-
`note.udl
|
|
19
|
+
`note.udl` is the smallest admitted document.
|
|
34
20
|
|
|
35
21
|
```json
|
|
36
22
|
{
|
|
37
|
-
"
|
|
23
|
+
"instruments": [
|
|
38
24
|
{
|
|
25
|
+
"actionOrder": ["close", "create"],
|
|
39
26
|
"fields": { "reference": { "type": "string" } },
|
|
40
27
|
"id": "note",
|
|
41
28
|
"idPrefix": "note",
|
|
@@ -47,7 +34,7 @@ change to the surface ships as a minor bump, not a major.
|
|
|
47
34
|
"required": ["reference"],
|
|
48
35
|
"summary": "A note a tenant files and later closes.",
|
|
49
36
|
"title": "Note",
|
|
50
|
-
"
|
|
37
|
+
"actions": {
|
|
51
38
|
"close": { "moves": [], "steps": [], "summary": "Close the note." },
|
|
52
39
|
"create": { "moves": [], "steps": [], "summary": "File the note." }
|
|
53
40
|
}
|
|
@@ -64,6 +51,7 @@ change to the surface ships as a minor bump, not a major.
|
|
|
64
51
|
```ts
|
|
65
52
|
import { readFile, writeFile } from "node:fs/promises";
|
|
66
53
|
import {
|
|
54
|
+
canonicalDigest,
|
|
67
55
|
diffValidatedUdlEvolution,
|
|
68
56
|
parseUdl,
|
|
69
57
|
serializeUdl,
|
|
@@ -71,102 +59,52 @@ import {
|
|
|
71
59
|
} from "@hyperscale0/udl";
|
|
72
60
|
|
|
73
61
|
const document = parseUdl(await readFile("note.udl"));
|
|
74
|
-
|
|
75
|
-
// One document, one byte sequence. Sorted keys, two-space indent, final LF.
|
|
76
62
|
await writeFile("note.udl", serializeUdl(document));
|
|
63
|
+
console.log(await canonicalDigest(document));
|
|
77
64
|
|
|
78
|
-
// Issues carry a stable code and a JSON path, never just a sentence.
|
|
79
65
|
const result = validateUdl(document);
|
|
80
66
|
if (!result.ok) console.error(result.issues);
|
|
81
67
|
|
|
82
|
-
// Is this change legal against the frozen version with existing instances?
|
|
83
68
|
const previous = parseUdl(await readFile("note.previous.udl"));
|
|
84
69
|
const violations = diffValidatedUdlEvolution(previous, document);
|
|
85
70
|
```
|
|
86
71
|
|
|
87
|
-
`
|
|
88
|
-
`serializeUdl` validates before it writes, so an invalid document has no
|
|
89
|
-
canonical form.
|
|
72
|
+
Every issue has a stable `UDL####` code, a category, a JSON path, a message, and a fix. Messages may become clearer. Codes do not change once published.
|
|
90
73
|
|
|
91
|
-
`
|
|
92
|
-
already been admitted. Validate first: an evolution verdict on a document
|
|
93
|
-
`validateUdl` refuses is not a verdict at all, and a candidate that drops a
|
|
94
|
-
referenced noun sails through a diff that never checked the reference graph.
|
|
95
|
-
The `UdlDocument` parameter types keep the compiler on your side of that rule.
|
|
74
|
+
`parseUdl` accepts a string or `Uint8Array` and rejects malformed UTF-8. `serializeUdl` validates before writing. It sorts object keys by UTF-16 code unit, keeps array order, uses two-space indentation, and writes one final line feed. `canonicalDigest` hashes those UTF-8 bytes with SHA-256 and returns a promise for the lowercase hexadecimal digest.
|
|
96
75
|
|
|
97
|
-
|
|
76
|
+
The seven kernel operations are `internal_transfer.create`, `internal_transfer.reserve`, `internal_transfer.post`, `internal_transfer.void`, `account.escrow.provision`, `account.freeze`, and `account.unfreeze`. A `payout` is an execution intent, not another kernel operation.
|
|
98
77
|
|
|
99
|
-
|
|
100
|
-
udl validate product.udl # parse and report every issue found
|
|
101
|
-
udl fmt product.udl # print the canonical form
|
|
102
|
-
udl fmt product.udl --write # rewrite the file in place
|
|
103
|
-
udl diff frozen.udl product.udl # is the change additive, or does it break?
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
Exit codes: `0` the document is admissible or the change is additive, `1` the
|
|
107
|
-
document was refused or the change breaks the append-only law, `2` the command
|
|
108
|
-
line was wrong or a file could not be read. That split matters in CI: a broken
|
|
109
|
-
document and a broken invocation are different failures.
|
|
78
|
+
The compiler derives action `effects` from clauses. The validator rejects a supplied effects object unless every row and its order match. Derived effects do not consume the authored node budget.
|
|
110
79
|
|
|
111
|
-
##
|
|
80
|
+
## Command line
|
|
112
81
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
82
|
+
```bash
|
|
83
|
+
udl validate product.udl
|
|
84
|
+
udl fmt product.udl --write
|
|
85
|
+
udl canon product.udl
|
|
86
|
+
udl canon product.udl --digest
|
|
87
|
+
udl diff frozen.udl product.udl
|
|
88
|
+
udl explain UDL5001
|
|
89
|
+
```
|
|
117
90
|
|
|
118
|
-
|
|
119
|
-
`internal_transfer.create`, `.reserve`, `.post`, `.void`. Three account
|
|
120
|
-
instructions complete the sealed set. A noun cannot invent a fifth money path,
|
|
121
|
-
which is why a document can be checked for stranded value before anything runs.
|
|
122
|
-
The separate `payout` intent carries a stored amount to the execution core and
|
|
123
|
-
captures its durable reference. A later `requiresSettlement` transition stays
|
|
124
|
-
system-only and runs only after that payout matches durable settlement evidence.
|
|
91
|
+
Exit code `0` means success. Exit code `1` means the validator or evolution law refused the document. Exit code `2` means the invocation or file read failed.
|
|
125
92
|
|
|
126
|
-
##
|
|
93
|
+
## Documentation
|
|
127
94
|
|
|
128
|
-
- [
|
|
129
|
-
|
|
130
|
-
- [
|
|
131
|
-
|
|
132
|
-
- [
|
|
133
|
-
|
|
134
|
-
language.
|
|
95
|
+
- [Guide and reading order](docs/README.md)
|
|
96
|
+
- [Format specification](spec/README.md)
|
|
97
|
+
- [Canonical bytes law](docs/reference/canonical.md)
|
|
98
|
+
- [Stable diagnostics](docs/reference/diagnostics.md)
|
|
99
|
+
- [Conformance runner contract](conformance/README.md)
|
|
100
|
+
- [Agent skill](skills/udl/SKILL.md)
|
|
135
101
|
|
|
136
102
|
## Versioning
|
|
137
103
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
**Format version** is the literal `"udl": 1` inside a document. Format 1 is the
|
|
141
|
-
only format that exists.
|
|
142
|
-
|
|
143
|
-
**Package version** is this package's semver, in `package.json`.
|
|
144
|
-
|
|
145
|
-
Format 1 is unstable until the package reaches 1.0.0. Until then an alpha
|
|
146
|
-
release may change what format 1 accepts, and every such change is listed in
|
|
147
|
-
[`CHANGELOG.md`](./CHANGELOG.md). After 1.0.0, format 1 is frozen and an
|
|
148
|
-
incompatible change bumps the literal to `2`.
|
|
149
|
-
|
|
150
|
-
## Status
|
|
151
|
-
|
|
152
|
-
Alpha. The format is in use, the API surface is settled enough to build on, and
|
|
153
|
-
the version number is honest about the rest. Breaking changes go in the
|
|
154
|
-
changelog, not in a footnote.
|
|
155
|
-
|
|
156
|
-
## Contributing
|
|
104
|
+
The literal `"udl": 1` is the format version. The version in `package.json` is the package version. They move independently. After package version `1.0.0`, an incompatible format change uses a new format literal and keeps an explicit reader for stored format 1 documents during its stated support window.
|
|
157
105
|
|
|
158
|
-
|
|
159
|
-
propose them in an issue carrying the use case and the conformance case it
|
|
160
|
-
would add. [`CONTRIBUTING.md`](./CONTRIBUTING.md) has that model in full, plus
|
|
161
|
-
the dev setup, the test commands, and how to regenerate the spec. Conduct:
|
|
162
|
-
[`CODE_OF_CONDUCT.md`](./CODE_OF_CONDUCT.md). Vulnerabilities:
|
|
163
|
-
[`SECURITY.md`](./SECURITY.md).
|
|
106
|
+
## Contributing and license
|
|
164
107
|
|
|
165
|
-
|
|
108
|
+
Hyperscale accepts format proposals as issues with a use case and the conformance case they would add. See [CONTRIBUTING.md](CONTRIBUTING.md) for setup and proof commands.
|
|
166
109
|
|
|
167
|
-
AGPL-3.0-only
|
|
168
|
-
organisations that cannot accept the AGPL. See [`LICENSE`](./LICENSE) for the
|
|
169
|
-
text and [`LICENSING.md`](./LICENSING.md) for which one you want and how to ask
|
|
170
|
-
for the commercial one. The marks are not covered by either; see
|
|
171
|
-
[`TRADEMARKS.md`](./TRADEMARKS.md), which also carries the rule for claiming
|
|
172
|
-
UDL compatibility.
|
|
110
|
+
UDL is AGPL-3.0-only. Hyperscale LLC also offers a commercial license. See [LICENSING.md](LICENSING.md) and [TRADEMARKS.md](TRADEMARKS.md).
|
package/conformance/README.md
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
# The UDL conformance suite
|
|
2
2
|
|
|
3
|
-
Every file here is data.
|
|
4
|
-
|
|
5
|
-
reading files and comparing bytes.
|
|
6
|
-
|
|
7
|
-
`../test/conformance.spec.ts` is the reference runner. Read it if a rule below
|
|
8
|
-
is ambiguous.
|
|
3
|
+
Every file here is data. An implementation in any language can run the suite
|
|
4
|
+
without importing the TypeScript reference implementation.
|
|
9
5
|
|
|
10
6
|
## Layout
|
|
11
7
|
|
|
12
8
|
```
|
|
13
9
|
conformance/
|
|
14
|
-
valid
|
|
15
|
-
|
|
10
|
+
valid/<case>.udl
|
|
11
|
+
valid/<case>.expected.json
|
|
12
|
+
invalid/<case>.udl
|
|
13
|
+
invalid/<case>.expected.json
|
|
14
|
+
evolution/<case>.live.udl
|
|
15
|
+
evolution/<case>.next.udl
|
|
16
|
+
evolution/<case>.expected.json
|
|
16
17
|
```
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
orphans in either direction.
|
|
19
|
+
The runner fails if either half of a case is missing. An evolution case must
|
|
20
|
+
have all three named files.
|
|
21
21
|
|
|
22
22
|
## Expected files
|
|
23
23
|
|
|
@@ -26,54 +26,57 @@ A valid case:
|
|
|
26
26
|
```json
|
|
27
27
|
{
|
|
28
28
|
"canonical": "minimal.udl",
|
|
29
|
-
"
|
|
29
|
+
"digest": "c21d198d69e9d4cadb33aaacf37581bb388e4a8964b7fc284606cfa9f65d35bb",
|
|
30
|
+
"summary": "The smallest admitted document.",
|
|
30
31
|
"verdict": "valid"
|
|
31
32
|
}
|
|
32
33
|
```
|
|
33
34
|
|
|
34
|
-
`
|
|
35
|
-
|
|
35
|
+
`digest` is lowercase SHA-256 over the canonical UTF-8 bytes. A document that
|
|
36
|
+
is already canonical names itself.
|
|
36
37
|
|
|
37
38
|
An invalid case:
|
|
38
39
|
|
|
39
40
|
```json
|
|
40
41
|
{
|
|
41
|
-
"issues": [{ "code": "
|
|
42
|
-
"summary": "Every
|
|
42
|
+
"issues": [{ "code": "UDL3001", "path": "$.instruments[0].actions" }],
|
|
43
|
+
"summary": "Every instrument declares create.",
|
|
43
44
|
"verdict": "invalid"
|
|
44
45
|
}
|
|
45
46
|
```
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
requiring it to be present and non-blank.
|
|
48
|
+
The runner requires a non-blank `summary`. It never compares messages.
|
|
49
49
|
|
|
50
|
-
##
|
|
50
|
+
## Conformance levels
|
|
51
51
|
|
|
52
|
-
An implementation claims
|
|
52
|
+
An implementation claims the highest level it passes.
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
1. Verdict. Admit every `valid/` document and refuse every `invalid/` document.
|
|
55
|
+
2. Canonical bytes. Serialize every valid document byte for byte as its named
|
|
56
|
+
canonical file, including the trailing line feed, and match its digest.
|
|
57
|
+
3. Diagnostics. Report every listed code and path for each invalid document.
|
|
58
|
+
The implementation may report extra issues.
|
|
59
|
+
4. Evolution. Admit both documents, then compare the live and next definitions.
|
|
60
|
+
Report every listed UDL7xxx code and path. The implementation may report
|
|
61
|
+
extra issues.
|
|
57
62
|
|
|
58
|
-
|
|
59
|
-
document and compare it byte for byte against the file named by `canonical`.
|
|
60
|
-
Any implementation that writes UDL must pass this, or two tools will disagree
|
|
61
|
-
about what the same document is.
|
|
63
|
+
## Path grammar
|
|
62
64
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
Issue paths use `$` for the document root, `.name` for an object member, and
|
|
66
|
+
`[n]` for a zero-based array index. Evolution paths use the instrument index
|
|
67
|
+
from the live document and continue to the changed member, such as
|
|
68
|
+
`$.instruments[3].actions.reconcile`. A direct `diffInstrumentEvolution` call
|
|
69
|
+
has no document index, so its paths start at `$.instruments`.
|
|
68
70
|
|
|
69
|
-
##
|
|
71
|
+
## Runner exit rule
|
|
70
72
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
The runner exits 0 only if every case at the claimed level passes. A missing
|
|
74
|
+
file, malformed expected file, unexpected verdict, byte mismatch, digest
|
|
75
|
+
mismatch, or missing issue pair makes it exit nonzero.
|
|
76
|
+
|
|
77
|
+
## Adding a case
|
|
76
78
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
Start from the smallest admitted document that carries the needed clause.
|
|
80
|
+
Change one law where possible. Pin codes and paths, never messages. Valid cases
|
|
81
|
+
must keep one example of every target in `udlClauseVocabulary`, including
|
|
82
|
+
quote, commit, reconcile, checks, updates, dials, effects, and money clauses.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"instruments": [
|
|
3
|
+
{
|
|
4
|
+
"actionOrder": ["close", "create"],
|
|
5
|
+
"actions": {
|
|
6
|
+
"close": {
|
|
7
|
+
"moves": [],
|
|
8
|
+
"steps": [],
|
|
9
|
+
"summary": "Close the note."
|
|
10
|
+
},
|
|
11
|
+
"create": {
|
|
12
|
+
"moves": [],
|
|
13
|
+
"steps": [],
|
|
14
|
+
"summary": "File the note."
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"fields": {
|
|
18
|
+
"reference": {
|
|
19
|
+
"type": "string"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"id": "note",
|
|
23
|
+
"idPrefix": "note",
|
|
24
|
+
"lifecycle": {
|
|
25
|
+
"initial": "open",
|
|
26
|
+
"states": ["open", "closed"],
|
|
27
|
+
"transitions": {
|
|
28
|
+
"close": {
|
|
29
|
+
"from": ["open"],
|
|
30
|
+
"to": "closed"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"required": ["reference"],
|
|
35
|
+
"summary": "A note a tenant files and later closes.",
|
|
36
|
+
"title": "Note"
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
"product": "minimal",
|
|
40
|
+
"subjects": [],
|
|
41
|
+
"title": "Minimal",
|
|
42
|
+
"udl": 1,
|
|
43
|
+
"version": 1
|
|
44
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"instruments": [
|
|
3
|
+
{
|
|
4
|
+
"actionOrder": ["close", "create"],
|
|
5
|
+
"actions": {
|
|
6
|
+
"close": {
|
|
7
|
+
"eventName": "note.closed",
|
|
8
|
+
"moves": [],
|
|
9
|
+
"steps": [],
|
|
10
|
+
"summary": "Close the note."
|
|
11
|
+
},
|
|
12
|
+
"create": {
|
|
13
|
+
"moves": [],
|
|
14
|
+
"steps": [],
|
|
15
|
+
"summary": "File the note."
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"fields": {
|
|
19
|
+
"reference": {
|
|
20
|
+
"type": "string"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"id": "note",
|
|
24
|
+
"idPrefix": "note",
|
|
25
|
+
"lifecycle": {
|
|
26
|
+
"initial": "open",
|
|
27
|
+
"states": ["open", "closed"],
|
|
28
|
+
"transitions": {
|
|
29
|
+
"close": {
|
|
30
|
+
"from": ["open"],
|
|
31
|
+
"to": "closed"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"required": ["reference"],
|
|
36
|
+
"summary": "A note a tenant files and later closes.",
|
|
37
|
+
"title": "Note"
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"product": "minimal",
|
|
41
|
+
"subjects": [],
|
|
42
|
+
"title": "Minimal",
|
|
43
|
+
"udl": 1,
|
|
44
|
+
"version": 2
|
|
45
|
+
}
|