@hyperscale0/udl 1.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AUTHORS +8 -0
- package/CHANGELOG.md +36 -0
- package/LICENSE +661 -0
- package/LICENSING.md +51 -0
- package/README.md +163 -0
- package/SECURITY.md +42 -0
- package/TRADEMARKS.md +35 -0
- package/conformance/README.md +79 -0
- package/conformance/invalid/blank-title.expected.json +10 -0
- package/conformance/invalid/blank-title.udl +50 -0
- package/conformance/invalid/depth-budget.expected.json +10 -0
- package/conformance/invalid/depth-budget.udl +49 -0
- package/conformance/invalid/format-version.expected.json +10 -0
- package/conformance/invalid/format-version.udl +50 -0
- package/conformance/invalid/invalid-utf8.expected.json +10 -0
- package/conformance/invalid/invalid-utf8.udl +1 -0
- package/conformance/invalid/malformed-json.expected.json +10 -0
- package/conformance/invalid/malformed-json.udl +1 -0
- package/conformance/invalid/missing-create-verb.expected.json +10 -0
- package/conformance/invalid/missing-create-verb.udl +45 -0
- package/conformance/invalid/not-an-object.expected.json +10 -0
- package/conformance/invalid/not-an-object.udl +1 -0
- package/conformance/invalid/noun-id-not-snake-case.expected.json +10 -0
- package/conformance/invalid/noun-id-not-snake-case.udl +50 -0
- package/conformance/invalid/unknown-key.expected.json +10 -0
- package/conformance/invalid/unknown-key.udl +51 -0
- package/conformance/invalid/unreachable-state.expected.json +10 -0
- package/conformance/invalid/unreachable-state.udl +51 -0
- package/conformance/invalid/verb-without-transition.expected.json +10 -0
- package/conformance/invalid/verb-without-transition.udl +55 -0
- package/conformance/valid/cards.expected.json +5 -0
- package/conformance/valid/cards.udl +1092 -0
- package/conformance/valid/commerce-escrow.expected.json +5 -0
- package/conformance/valid/commerce-escrow.udl +1011 -0
- package/conformance/valid/hand-edited.expected.json +5 -0
- package/conformance/valid/hand-edited.udl +1 -0
- package/conformance/valid/insured-car-marketplace.expected.json +5 -0
- package/conformance/valid/insured-car-marketplace.udl +831 -0
- package/conformance/valid/insured-travel.expected.json +5 -0
- package/conformance/valid/insured-travel.udl +2621 -0
- package/conformance/valid/minimal.expected.json +5 -0
- package/conformance/valid/minimal.udl +50 -0
- package/conformance/valid/protection.expected.json +5 -0
- package/conformance/valid/protection.udl +1136 -0
- package/dist/canonical.d.ts +2 -0
- package/dist/canonical.d.ts.map +1 -0
- package/dist/canonical.js +35 -0
- package/dist/canonical.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +138 -0
- package/dist/cli.js.map +1 -0
- package/dist/duration.d.ts +3 -0
- package/dist/duration.d.ts.map +1 -0
- package/dist/duration.js +23 -0
- package/dist/duration.js.map +1 -0
- package/dist/evolution.d.ts +63 -0
- package/dist/evolution.d.ts.map +1 -0
- package/dist/evolution.js +345 -0
- package/dist/evolution.js.map +1 -0
- package/dist/finance.d.ts +53 -0
- package/dist/finance.d.ts.map +1 -0
- package/dist/finance.js +511 -0
- package/dist/finance.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/limits.d.ts +33 -0
- package/dist/limits.d.ts.map +1 -0
- package/dist/limits.js +33 -0
- package/dist/limits.js.map +1 -0
- package/dist/parser.d.ts +4 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +54 -0
- package/dist/parser.js.map +1 -0
- package/dist/schema.d.ts +757 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +281 -0
- package/dist/schema.js.map +1 -0
- package/dist/validation.d.ts +64 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +1323 -0
- package/dist/validation.js.map +1 -0
- package/package.json +84 -0
- package/spec/README.md +137 -0
- package/spec/udl.schema.json +1019 -0
- package/src/canonical.ts +41 -0
- package/src/cli.ts +148 -0
- package/src/duration.ts +26 -0
- package/src/evolution.ts +587 -0
- package/src/finance.ts +775 -0
- package/src/index.ts +59 -0
- package/src/limits.ts +32 -0
- package/src/parser.ts +61 -0
- package/src/schema.ts +339 -0
- package/src/validation.ts +2053 -0
package/LICENSING.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Licensing
|
|
2
|
+
|
|
3
|
+
The code in this repository is Copyright 2026 Hyperscale LLC and is licensed
|
|
4
|
+
under the GNU Affero General Public License version 3 only
|
|
5
|
+
(`AGPL-3.0-only`). The full text is in [`LICENSE`](./LICENSE).
|
|
6
|
+
|
|
7
|
+
## What the AGPL asks of you
|
|
8
|
+
|
|
9
|
+
Two obligations matter in practice.
|
|
10
|
+
|
|
11
|
+
**If you distribute a copy in object form**, modified or not, you make its
|
|
12
|
+
Corresponding Source available under the same license.
|
|
13
|
+
|
|
14
|
+
**If you run a modified copy as a network service**, section 13 applies: the
|
|
15
|
+
users interacting with it over the network get an offer of the source of the
|
|
16
|
+
version you are running. Running UDL unmodified behind your own service does
|
|
17
|
+
not trigger this. Changing the parser, the validator, or the canonical
|
|
18
|
+
serializer and then serving that change does.
|
|
19
|
+
|
|
20
|
+
Using `@hyperscale0/udl` as a library inside your own program is a combined
|
|
21
|
+
work under the AGPL. If that does not fit how you ship, take the commercial
|
|
22
|
+
license instead.
|
|
23
|
+
|
|
24
|
+
## Commercial license
|
|
25
|
+
|
|
26
|
+
Hyperscale LLC sells a commercial license to organisations that cannot accept
|
|
27
|
+
the AGPL, for the usual reasons: a proprietary product that links the library,
|
|
28
|
+
a hosted service you will not open, a procurement policy that refuses copyleft.
|
|
29
|
+
It grants the same code under ordinary commercial terms with no source
|
|
30
|
+
obligation.
|
|
31
|
+
|
|
32
|
+
Ask through <https://hyperscale0.ai>.
|
|
33
|
+
|
|
34
|
+
## The license covers code, not marks
|
|
35
|
+
|
|
36
|
+
"Hyperscale" and "UDL" are trademarks of Hyperscale LLC. A copyright license
|
|
37
|
+
says nothing about trademarks in either direction, so
|
|
38
|
+
[`TRADEMARKS.md`](./TRADEMARKS.md) draws that boundary.
|
|
39
|
+
|
|
40
|
+
## The format is not the implementation
|
|
41
|
+
|
|
42
|
+
UDL the format is separate from this implementation of it. The JSON Schema in
|
|
43
|
+
[`spec/`](./spec) and the conformance cases in [`conformance/`](./conformance)
|
|
44
|
+
are data describing a format, and anyone may write their own parser, validator,
|
|
45
|
+
or serializer against them, in any language, under any license, without
|
|
46
|
+
touching this code.
|
|
47
|
+
|
|
48
|
+
What you may then say about it is a trademark question, not a copyright one.
|
|
49
|
+
[`TRADEMARKS.md`](./TRADEMARKS.md) has the rule: claim compatibility with a UDL
|
|
50
|
+
version only when your implementation passes that version's published
|
|
51
|
+
conformance cases unmodified.
|
package/README.md
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# UDL
|
|
2
|
+
|
|
3
|
+
UDL is the Universal Domain Language: a JSON document format for describing
|
|
4
|
+
a financial product in business terms. One `.udl` file declares the product's
|
|
5
|
+
subjects, its nouns, each noun's lifecycle, the verbs that move instances
|
|
6
|
+
through that lifecycle, and how money moves while they do. Everything else,
|
|
7
|
+
SDKs, docs, tool surfaces, the running engine, is generated from it or checked
|
|
8
|
+
against it.
|
|
9
|
+
|
|
10
|
+
The language deliberately cannot say certain things. There is no statement
|
|
11
|
+
format, no file drop, no polling loop, no cutoff time, no scheme name, no
|
|
12
|
+
reconciliation vocabulary. Those are real and they are somebody's problem, but
|
|
13
|
+
they are not the product, so they are absorbed below the language and never
|
|
14
|
+
surface in a document. What is left is small enough to hold in your head.
|
|
15
|
+
|
|
16
|
+
This package is the reference implementation: parser, semantic validator,
|
|
17
|
+
canonical serializer, evolution diff, and the `udl` command. The format itself
|
|
18
|
+
is specified in [`spec/`](./spec/README.md) and pinned by
|
|
19
|
+
[`conformance/`](./conformance/README.md).
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install @hyperscale0/udl@alpha
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
There is no `latest` tag before 1.0.0, so a bare `npm install @hyperscale0/udl`
|
|
28
|
+
fails loudly rather than installing something you did not choose.
|
|
29
|
+
|
|
30
|
+
## Thirty seconds
|
|
31
|
+
|
|
32
|
+
`note.udl`, the smallest document the format accepts:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"nouns": [
|
|
37
|
+
{
|
|
38
|
+
"fields": { "reference": { "type": "string" } },
|
|
39
|
+
"id": "note",
|
|
40
|
+
"idPrefix": "note",
|
|
41
|
+
"lifecycle": {
|
|
42
|
+
"initial": "open",
|
|
43
|
+
"states": ["open", "closed"],
|
|
44
|
+
"transitions": { "close": { "from": ["open"], "to": "closed" } }
|
|
45
|
+
},
|
|
46
|
+
"required": ["reference"],
|
|
47
|
+
"summary": "A note a tenant files and later closes.",
|
|
48
|
+
"title": "Note",
|
|
49
|
+
"verbs": {
|
|
50
|
+
"close": { "moves": [], "steps": [], "summary": "Close the note." },
|
|
51
|
+
"create": { "moves": [], "steps": [], "summary": "File the note." }
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
],
|
|
55
|
+
"product": "minimal",
|
|
56
|
+
"subjects": [],
|
|
57
|
+
"title": "Minimal",
|
|
58
|
+
"udl": 1,
|
|
59
|
+
"version": 1
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
65
|
+
import {
|
|
66
|
+
diffUdlEvolution,
|
|
67
|
+
parseUdl,
|
|
68
|
+
serializeUdl,
|
|
69
|
+
validateUdl,
|
|
70
|
+
} from "@hyperscale0/udl";
|
|
71
|
+
|
|
72
|
+
const document = parseUdl(await readFile("note.udl"));
|
|
73
|
+
|
|
74
|
+
// One document, one byte sequence. Sorted keys, two-space indent, final LF.
|
|
75
|
+
await writeFile("note.udl", serializeUdl(document));
|
|
76
|
+
|
|
77
|
+
// Issues carry a stable code and a JSON path, never just a sentence.
|
|
78
|
+
const result = validateUdl(document);
|
|
79
|
+
if (!result.ok) console.error(result.issues);
|
|
80
|
+
|
|
81
|
+
// Is this change legal against the version that already has live instances?
|
|
82
|
+
const live = parseUdl(await readFile("note.live.udl"));
|
|
83
|
+
const violations = diffUdlEvolution(live, document);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`parseUdl` takes a string or a `Uint8Array`; bytes are decoded as strict UTF-8.
|
|
87
|
+
`serializeUdl` validates before it writes, so an invalid document has no
|
|
88
|
+
canonical form.
|
|
89
|
+
|
|
90
|
+
## The command
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
udl validate product.udl # parse and report every issue found
|
|
94
|
+
udl fmt product.udl # print the canonical form
|
|
95
|
+
udl fmt product.udl --write # rewrite the file in place
|
|
96
|
+
udl diff live.udl product.udl # is the change additive, or does it break?
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Exit codes: `0` the document is admissible or the change is additive, `1` the
|
|
100
|
+
document was refused or the change breaks the append-only law, `2` the command
|
|
101
|
+
line was wrong or a file could not be read. That split matters in CI: a broken
|
|
102
|
+
document and a broken invocation are different failures.
|
|
103
|
+
|
|
104
|
+
## The two things worth knowing
|
|
105
|
+
|
|
106
|
+
**Evolution is append-only.** Once a definition has live instances, you may add
|
|
107
|
+
states, transitions, optional fields, and verbs. You may not remove, rename,
|
|
108
|
+
tighten, or change a money step. `udl diff` is not advice; it is the same
|
|
109
|
+
function the compiler runs before it will accept a new version.
|
|
110
|
+
|
|
111
|
+
**Money moves through four instructions and no others.**
|
|
112
|
+
`internal_transfer.create`, `.reserve`, `.post`, `.void`. Three account
|
|
113
|
+
instructions complete the sealed set. A noun cannot invent a fifth money path,
|
|
114
|
+
which is why a document can be checked for stranded value before anything runs.
|
|
115
|
+
|
|
116
|
+
## The spec
|
|
117
|
+
|
|
118
|
+
- [`spec/README.md`](./spec/README.md) is the specification: the ten laws, the
|
|
119
|
+
canonical form, the issue codes, and what the schema deliberately cannot say.
|
|
120
|
+
- [`spec/udl.schema.json`](./spec/udl.schema.json) is JSON Schema 2020-12,
|
|
121
|
+
generated from the grammar. Where prose and schema disagree, the schema wins.
|
|
122
|
+
- [`conformance/`](./conformance/README.md) is the semantic spec: `.udl` inputs
|
|
123
|
+
with expected verdicts, canonical bytes, and issue codes, runnable from any
|
|
124
|
+
language.
|
|
125
|
+
|
|
126
|
+
## Versioning
|
|
127
|
+
|
|
128
|
+
Two numbers move independently.
|
|
129
|
+
|
|
130
|
+
**Format version** is the literal `"udl": 1` inside a document. Format 1 is the
|
|
131
|
+
only format that exists.
|
|
132
|
+
|
|
133
|
+
**Package version** is this package's semver, currently `1.0.0-alpha.1`,
|
|
134
|
+
published only under the `alpha` dist-tag.
|
|
135
|
+
|
|
136
|
+
Format 1 is unstable until the package reaches 1.0.0. Until then an alpha
|
|
137
|
+
release may change what format 1 accepts, and every such change is listed in
|
|
138
|
+
[`CHANGELOG.md`](./CHANGELOG.md). After 1.0.0, format 1 is frozen and an
|
|
139
|
+
incompatible change bumps the literal to `2`.
|
|
140
|
+
|
|
141
|
+
## Status
|
|
142
|
+
|
|
143
|
+
Alpha. The format is in use, the API surface is settled enough to build on, and
|
|
144
|
+
the version number is honest about the rest. Breaking changes go in the
|
|
145
|
+
changelog, not in a footnote.
|
|
146
|
+
|
|
147
|
+
## Contributing
|
|
148
|
+
|
|
149
|
+
Issues only. Hyperscale makes the changes to the format and the package; you
|
|
150
|
+
propose them in an issue carrying the use case and the conformance case it
|
|
151
|
+
would add. [`CONTRIBUTING.md`](./CONTRIBUTING.md) has that model in full, plus
|
|
152
|
+
the dev setup, the test commands, and how to regenerate the spec. Conduct:
|
|
153
|
+
[`CODE_OF_CONDUCT.md`](./CODE_OF_CONDUCT.md). Vulnerabilities:
|
|
154
|
+
[`SECURITY.md`](./SECURITY.md).
|
|
155
|
+
|
|
156
|
+
## License
|
|
157
|
+
|
|
158
|
+
AGPL-3.0-only, with a commercial license available from Hyperscale LLC for
|
|
159
|
+
organisations that cannot accept the AGPL. See [`LICENSE`](./LICENSE) for the
|
|
160
|
+
text and [`LICENSING.md`](./LICENSING.md) for which one you want and how to ask
|
|
161
|
+
for the commercial one. The marks are not covered by either; see
|
|
162
|
+
[`TRADEMARKS.md`](./TRADEMARKS.md), which also carries the rule for claiming
|
|
163
|
+
UDL compatibility.
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
## Reporting a vulnerability
|
|
4
|
+
|
|
5
|
+
Report privately through GitHub, using this repository's
|
|
6
|
+
[private vulnerability reporting form](https://github.com/hyperscale0/hyperscale-udl/security/advisories/new).
|
|
7
|
+
That is the only intake. There is no security email address, and nothing
|
|
8
|
+
security-sensitive belongs in an issue, a pull request, a discussion, or a
|
|
9
|
+
commit message.
|
|
10
|
+
|
|
11
|
+
A report we can act on names the affected version, describes the surface, and
|
|
12
|
+
gives us something to run: a `.udl` file, an input, a snippet. If you can shape
|
|
13
|
+
it as a conformance case, do that; it goes straight into the fix.
|
|
14
|
+
|
|
15
|
+
## What counts
|
|
16
|
+
|
|
17
|
+
This package parses untrusted documents, so the interesting failures are the
|
|
18
|
+
ones a document can cause:
|
|
19
|
+
|
|
20
|
+
- A document that gets past `validateUdl` but should not, especially one that
|
|
21
|
+
breaks a money-graph law.
|
|
22
|
+
- A document that makes the parser or validator burn unbounded time or memory.
|
|
23
|
+
Admission budgets (source bytes, nesting depth, node count, string length,
|
|
24
|
+
regex search space) exist precisely to make this impossible; a way around one
|
|
25
|
+
is a vulnerability.
|
|
26
|
+
- A document that makes `serializeUdl` produce bytes that reparse into a
|
|
27
|
+
different document.
|
|
28
|
+
- An evolution diff that reports a breaking change as additive.
|
|
29
|
+
|
|
30
|
+
Out of scope: the `udl` command reading a file you told it to read, and
|
|
31
|
+
anything that requires already controlling the machine running it.
|
|
32
|
+
|
|
33
|
+
## Supported versions
|
|
34
|
+
|
|
35
|
+
Alpha releases are supported at the newest published `alpha` version only.
|
|
36
|
+
Fixes land there; there is no backport branch before 1.0.0.
|
|
37
|
+
|
|
38
|
+
## Disclosure
|
|
39
|
+
|
|
40
|
+
We will confirm receipt, tell you what we found, and agree a disclosure date
|
|
41
|
+
with you before publishing an advisory. If a fix is not straightforward we will
|
|
42
|
+
say so rather than go quiet.
|
package/TRADEMARKS.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Trademarks
|
|
2
|
+
|
|
3
|
+
"Hyperscale" and "UDL" are trademarks of Hyperscale LLC.
|
|
4
|
+
|
|
5
|
+
The AGPL covers the code in this repository and nothing else. It grants no
|
|
6
|
+
rights to the marks, and it never mentions them: a copyright license says
|
|
7
|
+
nothing about trademarks either way. This file draws that boundary so nobody
|
|
8
|
+
has to guess where it sits.
|
|
9
|
+
|
|
10
|
+
What that means in practice:
|
|
11
|
+
|
|
12
|
+
- **Yes.** Say your project uses UDL, reads UDL, or is compatible with UDL.
|
|
13
|
+
Say it in your README, your docs, your talk, and your package description.
|
|
14
|
+
Fork this repository and keep the notices intact.
|
|
15
|
+
- **Yes.** Publish a UDL implementation in another language, and name it in a
|
|
16
|
+
way that describes what it does: `udl-rs`, `udl-parser`, `python-udl`.
|
|
17
|
+
- **No.** Name your project, company, or product in a way that suggests
|
|
18
|
+
Hyperscale LLC published it or endorses it. `@hyperscale0/*` on npm and the
|
|
19
|
+
`hyperscale0` GitHub organisation are ours.
|
|
20
|
+
- **No.** Use the marks or our logo in a way that implies affiliation,
|
|
21
|
+
sponsorship, or certification we have not given.
|
|
22
|
+
|
|
23
|
+
## Claiming compatibility
|
|
24
|
+
|
|
25
|
+
An independent implementation may say it "implements UDL version X" or "passes
|
|
26
|
+
the UDL conformance suite version X" only while it passes the published
|
|
27
|
+
conformance cases for that version, unmodified. The cases in
|
|
28
|
+
[`conformance/`](./conformance) are the whole test: no skipped case, no edited
|
|
29
|
+
expectation, no local fork of the fixtures.
|
|
30
|
+
|
|
31
|
+
That claim is a statement about your implementation, so keep the marks out of
|
|
32
|
+
its name and off its logo, and do not present it as endorsement or
|
|
33
|
+
certification by Hyperscale LLC. We certify nothing; the suite does.
|
|
34
|
+
|
|
35
|
+
If you are unsure, open an issue and ask. Nobody has ever regretted asking.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# The UDL conformance suite
|
|
2
|
+
|
|
3
|
+
Every file here is data. Nothing in this directory imports the reference
|
|
4
|
+
implementation, so a UDL implementation in any language can run the suite by
|
|
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.
|
|
9
|
+
|
|
10
|
+
## Layout
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
conformance/
|
|
14
|
+
valid/ documents the format accepts, each with its canonical form
|
|
15
|
+
invalid/ documents the format refuses, each with the issues it must report
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Every `<case>.udl` has a sibling `<case>.expected.json`. A file without its
|
|
19
|
+
sibling is a case that silently stopped running, so the runner fails on
|
|
20
|
+
orphans in either direction.
|
|
21
|
+
|
|
22
|
+
## Expected files
|
|
23
|
+
|
|
24
|
+
A valid case:
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"canonical": "minimal.udl",
|
|
29
|
+
"summary": "Compact, reverse-ordered bytes canonicalize to minimal.udl.",
|
|
30
|
+
"verdict": "valid"
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`canonical` names the file in `valid/` holding the canonical bytes for this
|
|
35
|
+
input. A document that is already canonical names itself.
|
|
36
|
+
|
|
37
|
+
An invalid case:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"issues": [{ "code": "invalid_semantics", "path": "$.nouns[0].verbs" }],
|
|
42
|
+
"summary": "Every noun declares create: nothing else can bring an instance into being.",
|
|
43
|
+
"verdict": "invalid"
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`summary` is for humans reading a failure. Nothing asserts against it beyond
|
|
48
|
+
requiring it to be present and non-blank.
|
|
49
|
+
|
|
50
|
+
## The three levels
|
|
51
|
+
|
|
52
|
+
An implementation claims conformance at the highest level it passes.
|
|
53
|
+
|
|
54
|
+
**Level 1, verdict.** Read the file as bytes. Every `valid/` case is admitted
|
|
55
|
+
and every `invalid/` case is refused. This is the whole contract for a
|
|
56
|
+
validator.
|
|
57
|
+
|
|
58
|
+
**Level 2, canonical bytes.** For each `valid/` case, serialize the parsed
|
|
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.
|
|
62
|
+
|
|
63
|
+
**Level 3, issues.** For each `invalid/` case, every listed `code` and `path`
|
|
64
|
+
pair appears among the reported issues. Extra issues are allowed, because
|
|
65
|
+
implementations legitimately differ on how many problems they report before
|
|
66
|
+
giving up. Messages are never compared; they are prose and they are free to
|
|
67
|
+
change.
|
|
68
|
+
|
|
69
|
+
## Adding a case
|
|
70
|
+
|
|
71
|
+
Write the `.udl` and its `.expected.json`, then run `bun test`. Keep new cases
|
|
72
|
+
small and single-purpose: the point of `invalid/blank-title.udl` is that it
|
|
73
|
+
differs from `valid/minimal.udl` in exactly one key. `valid/minimal.udl` is the
|
|
74
|
+
smallest document both the grammar and the semantic laws accept, and it is the
|
|
75
|
+
right base to mutate.
|
|
76
|
+
|
|
77
|
+
The five domain documents in `valid/` are the real thing, projected from a
|
|
78
|
+
shipped product catalog. They are large on purpose: they are what catches a
|
|
79
|
+
regression the minimal case cannot see.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"nouns": [
|
|
3
|
+
{
|
|
4
|
+
"fields": {
|
|
5
|
+
"reference": {
|
|
6
|
+
"type": "string"
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
"id": "note",
|
|
10
|
+
"idPrefix": "note",
|
|
11
|
+
"lifecycle": {
|
|
12
|
+
"initial": "open",
|
|
13
|
+
"states": [
|
|
14
|
+
"open",
|
|
15
|
+
"closed"
|
|
16
|
+
],
|
|
17
|
+
"transitions": {
|
|
18
|
+
"close": {
|
|
19
|
+
"from": [
|
|
20
|
+
"open"
|
|
21
|
+
],
|
|
22
|
+
"to": "closed"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"required": [
|
|
27
|
+
"reference"
|
|
28
|
+
],
|
|
29
|
+
"summary": "A note a tenant files and later closes.",
|
|
30
|
+
"title": "Note",
|
|
31
|
+
"verbs": {
|
|
32
|
+
"close": {
|
|
33
|
+
"moves": [],
|
|
34
|
+
"steps": [],
|
|
35
|
+
"summary": "Close the note."
|
|
36
|
+
},
|
|
37
|
+
"create": {
|
|
38
|
+
"moves": [],
|
|
39
|
+
"steps": [],
|
|
40
|
+
"summary": "File the note."
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
"product": "minimal",
|
|
46
|
+
"subjects": [],
|
|
47
|
+
"title": " ",
|
|
48
|
+
"udl": 1,
|
|
49
|
+
"version": 1
|
|
50
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"issues": [
|
|
3
|
+
{
|
|
4
|
+
"code": "resource_limit",
|
|
5
|
+
"path": "$.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested"
|
|
6
|
+
}
|
|
7
|
+
],
|
|
8
|
+
"summary": "Admission budgets are part of the format: nesting past 24 levels is refused before validation.",
|
|
9
|
+
"verdict": "invalid"
|
|
10
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"nested": {
|
|
3
|
+
"nested": {
|
|
4
|
+
"nested": {
|
|
5
|
+
"nested": {
|
|
6
|
+
"nested": {
|
|
7
|
+
"nested": {
|
|
8
|
+
"nested": {
|
|
9
|
+
"nested": {
|
|
10
|
+
"nested": {
|
|
11
|
+
"nested": {
|
|
12
|
+
"nested": {
|
|
13
|
+
"nested": {
|
|
14
|
+
"nested": {
|
|
15
|
+
"nested": {
|
|
16
|
+
"nested": {
|
|
17
|
+
"nested": {
|
|
18
|
+
"nested": {
|
|
19
|
+
"nested": {
|
|
20
|
+
"nested": {
|
|
21
|
+
"nested": {
|
|
22
|
+
"nested": {
|
|
23
|
+
"nested": {
|
|
24
|
+
"nested": {
|
|
25
|
+
"nested": {}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"nouns": [
|
|
3
|
+
{
|
|
4
|
+
"fields": {
|
|
5
|
+
"reference": {
|
|
6
|
+
"type": "string"
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
"id": "note",
|
|
10
|
+
"idPrefix": "note",
|
|
11
|
+
"lifecycle": {
|
|
12
|
+
"initial": "open",
|
|
13
|
+
"states": [
|
|
14
|
+
"open",
|
|
15
|
+
"closed"
|
|
16
|
+
],
|
|
17
|
+
"transitions": {
|
|
18
|
+
"close": {
|
|
19
|
+
"from": [
|
|
20
|
+
"open"
|
|
21
|
+
],
|
|
22
|
+
"to": "closed"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"required": [
|
|
27
|
+
"reference"
|
|
28
|
+
],
|
|
29
|
+
"summary": "A note a tenant files and later closes.",
|
|
30
|
+
"title": "Note",
|
|
31
|
+
"verbs": {
|
|
32
|
+
"close": {
|
|
33
|
+
"moves": [],
|
|
34
|
+
"steps": [],
|
|
35
|
+
"summary": "Close the note."
|
|
36
|
+
},
|
|
37
|
+
"create": {
|
|
38
|
+
"moves": [],
|
|
39
|
+
"steps": [],
|
|
40
|
+
"summary": "File the note."
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
"product": "minimal",
|
|
46
|
+
"subjects": [],
|
|
47
|
+
"title": "Minimal",
|
|
48
|
+
"udl": 2,
|
|
49
|
+
"version": 1
|
|
50
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"udl": 1, "title": "�"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"udl": 1,
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"nouns": [
|
|
3
|
+
{
|
|
4
|
+
"fields": {
|
|
5
|
+
"reference": {
|
|
6
|
+
"type": "string"
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
"id": "note",
|
|
10
|
+
"idPrefix": "note",
|
|
11
|
+
"lifecycle": {
|
|
12
|
+
"initial": "open",
|
|
13
|
+
"states": [
|
|
14
|
+
"open",
|
|
15
|
+
"closed"
|
|
16
|
+
],
|
|
17
|
+
"transitions": {
|
|
18
|
+
"close": {
|
|
19
|
+
"from": [
|
|
20
|
+
"open"
|
|
21
|
+
],
|
|
22
|
+
"to": "closed"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"required": [
|
|
27
|
+
"reference"
|
|
28
|
+
],
|
|
29
|
+
"summary": "A note a tenant files and later closes.",
|
|
30
|
+
"title": "Note",
|
|
31
|
+
"verbs": {
|
|
32
|
+
"close": {
|
|
33
|
+
"moves": [],
|
|
34
|
+
"steps": [],
|
|
35
|
+
"summary": "Close the note."
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"product": "minimal",
|
|
41
|
+
"subjects": [],
|
|
42
|
+
"title": "Minimal",
|
|
43
|
+
"udl": 1,
|
|
44
|
+
"version": 1
|
|
45
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"a string"
|