gdp-wired 0.3.0 → 0.5.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/README.md +109 -113
- package/dist/core/dsl.d.ts +210 -9
- package/dist/core/dsl.js +799 -78
- package/dist/core/watch.d.ts +14 -0
- package/dist/core/watch.js +78 -0
- package/dist/kernel/kernel.d.ts +7 -0
- package/dist/kernel/kernel.js +121 -11
- package/dist/kernel/prove.d.ts +3 -0
- package/dist/kernel/prove.js +190 -51
- package/dist/lean-kernel/emit.d.ts +2 -10
- package/dist/lean-kernel/emit.js +164 -201
- package/dist/lean-kernel/library.js +5 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,13 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
# gdp-wired
|
|
4
4
|
|
|
5
|
-
An automatic prover for stateful rules: budgets, caps, rate limits, workflows, access policies.
|
|
6
|
-
laws it must keep
|
|
7
|
-
(
|
|
8
|
-
|
|
9
|
-
Proofs are checked by a small trusted kernel; the prover that writes them is untrusted, and the kernel's Lean twin is
|
|
10
|
-
proven sound. On the three rules in [`compare/`](compare/README.md), the proof is a one- or two-line call and checks in
|
|
11
|
-
25–44 ms, against 74–117 lines of hand proof and 137–161 ms in Bend, and 8–30 lines and 488–571 ms in Lean.
|
|
5
|
+
An automatic prover for stateful rules: budgets, caps, rate limits, workflows, access policies. You write the rule and
|
|
6
|
+
the laws it must keep. gdp-wired proves them with no proof to write, shows a real run that breaks a law when it's false
|
|
7
|
+
(found by searching on the GPU), has every proof checked again by Lean, and ships the rule as JavaScript.
|
|
12
8
|
|
|
13
9
|
```sh
|
|
14
10
|
npm install gdp-wired # or bun add gdp-wired; plain JavaScript with types, Node 20+, Bun or a browser
|
|
@@ -34,85 +30,76 @@ Forget the cap (`s.plus(c)`) and the kernel refuses the proof; a search from the
|
|
|
34
30
|
and `examples/domains/` holds 92 rules from nine domains (finance, access control, public services, operations,
|
|
35
31
|
protocols, commerce, supply chains, games, education), each checked three ways.
|
|
36
32
|
|
|
37
|
-
##
|
|
33
|
+
## Where it fits
|
|
38
34
|
|
|
39
|
-
gdp-wired
|
|
40
|
-
of names, logs summed over time, steps in a row, runs that go on forever), proven without a proof to write, with the
|
|
41
|
-
rule shipped as code. With Lean beside it, it says what Bend says; where a proof needs more than its kernel decides, the
|
|
42
|
-
proof is Lean's, as a fact about a function the rule reads:
|
|
35
|
+
<img src="https://raw.githubusercontent.com/monotykamary/gdp-wired/main/static/venn.svg" alt="What Lean, Bend and gdp-wired each cover, and where they overlap" width="640" />
|
|
43
36
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
**
|
|
60
|
-
|
|
61
|
-
- **
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
- **
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
so they stay in Bend, or go to Lean as functions with proven facts. `compare/` puts three rules side by side in gdp-wired, Bend and plain Lean, with line counts and
|
|
85
|
-
check times.
|
|
86
|
-
|
|
87
|
-
## What's trusted
|
|
37
|
+
Lean proves anything you write the proof for. Bend runs programs that carry their proofs. gdp-wired proves one kind of
|
|
38
|
+
claim, laws about stateful rules, with no proof to write, and hands every proof to Lean as a theorem. Where a proof needs
|
|
39
|
+
more than it decides, the proof is Lean's: a function you define and prove there, which the rule then uses.
|
|
40
|
+
[`docs/comparison.md`](docs/comparison.md) goes through each region, compares the three side by side, and measures them
|
|
41
|
+
on the same rules.
|
|
42
|
+
|
|
43
|
+
## What it proves
|
|
44
|
+
|
|
45
|
+
All of these are proven automatically, and each proof is also a Lean theorem:
|
|
46
|
+
|
|
47
|
+
- **Invariants**: a law that holds after every sequence of requests ("spend stays within the budget").
|
|
48
|
+
- **Maps, sets and lists of names**: laws at every key ("no day passes its cap"), sets a request carries, conditions
|
|
49
|
+
about every key, lists kept in order (`examples/sets.ts`, `examples/lists.ts`).
|
|
50
|
+
- **Step laws**: what one step does ("a balance only goes down", "a grant adds one"), and laws over several steps in a
|
|
51
|
+
row.
|
|
52
|
+
- **Histories**: sums and queries over a log of requests, like a sliding-window rate limit.
|
|
53
|
+
- **Orderings at any distance**: "a refund never follows a release", through ghost state that watches the rule.
|
|
54
|
+
- **Liveness**: "every ticket issued is served", in every run that keeps sending the requests that help, including under
|
|
55
|
+
weak fairness (`examples/liveness.ts`).
|
|
56
|
+
- **Refinement**: an implementation behaves like a simpler spec, step for step (`examples/composition.ts`).
|
|
57
|
+
- **Functions**: division, remainder, and your own functions, known by their facts; a function defined in Lean brings
|
|
58
|
+
facts proven there (`examples/functions.ts`).
|
|
59
|
+
- **Pure functions and two-run laws**: laws about what a function returns, and laws comparing two calls, like "the
|
|
60
|
+
secret never changes the answer". `examples/jev-fabric/` ports a Bend project's policy core and proves all 59 laws its
|
|
61
|
+
Bend proofs state.
|
|
62
|
+
|
|
63
|
+
When a law is false, the GPU search finds a real run that breaks it. When a law needs an invariant nobody wrote, the GPU
|
|
64
|
+
synthesizer finds one and the kernel proves it.
|
|
65
|
+
|
|
66
|
+
**What it leaves to Lean or Bend.** Two things, and neither is a gap the next feature closes: proofs the prover would
|
|
67
|
+
have to invent (induction over a recursive structure whose proof needs a lemma nobody stated, and arithmetic with
|
|
68
|
+
products of unknowns beyond multiplying two known facts; integer arithmetic with induction or with products is
|
|
69
|
+
undecidable), and shipping an arbitrary algorithm as its proved source (gdp-wired ships the rule as JavaScript; Bend
|
|
70
|
+
compiles the proved source itself). The full list of what is and isn't in scope is in
|
|
71
|
+
[`docs/comparison.md`](docs/comparison.md#gdp-wireds-scope-in-detail).
|
|
72
|
+
|
|
73
|
+
## Why you can trust a proof
|
|
74
|
+
|
|
75
|
+
A small kernel checks every proof; the prover that finds them is untrusted, and the kernel's twin in Lean is proven
|
|
76
|
+
sound.
|
|
88
77
|
|
|
89
78
|
| Part | Trusted? | What it does |
|
|
90
79
|
| --- | --- | --- |
|
|
91
|
-
| `kernel/kernel.ts`, `core/check.ts` | **Yes** | Turns a
|
|
92
|
-
| `kernel/prove.ts` | No |
|
|
93
|
-
| `lean-kernel/` | Optional | The kernel's twin in Lean 4.34.0,
|
|
94
|
-
| `search/`, `synth/`, `hw/` | No | GPU
|
|
95
|
-
| `core/dsl.ts`, `core/compose.ts`, the runtimes | No | The builders and combinators compile to the kernel's plain values; the
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
- **
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
- **
|
|
106
|
-
|
|
107
|
-
- **
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
- **Fuzz**: the kernel never accepts a refutation of something with a model (`kernel/kernel.test.ts`), and laws proven
|
|
115
|
-
over histories hold on every run (`kernel/history.test.ts`).
|
|
80
|
+
| `kernel/kernel.ts`, `core/check.ts` | **Yes** | Turns a rule and its laws into goals, and checks a proof tree for each: case splits on linear facts, then a linear refutation at each leaf |
|
|
81
|
+
| `kernel/prove.ts` | No | Finds the proof trees. If it's wrong, the kernel rejects the tree |
|
|
82
|
+
| `lean-kernel/` | Optional | The kernel's twin in Lean 4.34.0, every rule proven sound there with the standard axioms only. The exporter sends the kernel's real trees; tampered ones compute to false |
|
|
83
|
+
| `research/search/`, `research/synth/`, `research/hw/` | No | The GPU searches for breaking runs, and invariant and rank synthesis. Every hit is replayed or proven before it counts |
|
|
84
|
+
| `core/dsl.ts`, `core/compose.ts`, the runtimes | No | The builders and combinators compile to the kernel's plain values; the runtimes are held to a reference evaluator |
|
|
85
|
+
|
|
86
|
+
The untrusted parts are held to the trusted ones by the test suite:
|
|
87
|
+
|
|
88
|
+
- **Builders**: every law, step law, flag, update and decision in the examples is run again on concrete runs, its
|
|
89
|
+
callback's answer computed by the builders' own arithmetic (each term carries its value beside its expression) against
|
|
90
|
+
what the interpreter makes of the expression that was proven (`rule.crossCheck`, `crossCheckDecision`). A builder that
|
|
91
|
+
writes a different expression than it means is caught, even where the kernel would still prove the law it wrote.
|
|
92
|
+
- **Encodings**: `fun.compare(reference)` and `rule.compare({init, step})` run the rule against the code it describes,
|
|
93
|
+
on drawn and edge inputs (strings, decimals and choices as themselves), field by field.
|
|
94
|
+
- **Types**: strict TypeScript and a lint that denies warnings; every switch over operations is exhaustive, so a new
|
|
95
|
+
operation fails to compile wherever it's missing.
|
|
96
|
+
- **Conformance**: random terms through every evaluator (interpreter, generated JavaScript, the kernel, the GPU search,
|
|
97
|
+
Lean's own evaluator) against the reference one.
|
|
98
|
+
- **Examples**: every law proven and re-checked in Lean, every rule run at random, every broken variant broken by a real
|
|
99
|
+
run.
|
|
100
|
+
- **Tampering and mutation**: broken proofs must be rejected; every one-operator change to the kernel is run against the
|
|
101
|
+
tests, and the few that survive are equivalent (`kernel/MUTANTS.md`).
|
|
102
|
+
- **Fuzz**: the kernel never accepts a refutation of something that has a model.
|
|
116
103
|
|
|
117
104
|
## Writing laws
|
|
118
105
|
|
|
@@ -157,9 +144,21 @@ const failsClosed = trial.decide('a missing fact means no trial', ({spent}) => s
|
|
|
157
144
|
({plan, spent, allowance}) => plan.match({free: () => spent.match({none: () => yes, some: s => allowance.match({none: () => yes, some: a => s.atLeast(a)})}), pro: () => yes}))
|
|
158
145
|
```
|
|
159
146
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
147
|
+
|
|
148
|
+
- **Kinds**: `nat`, `upTo(k)`, `bool`, `oneOf(...)`, `maybe(kind)`, `int`, `decimal(places)`, `str({like, maxLength})`;
|
|
149
|
+
their assumptions (a bit is 0 or 1, a choice is in range) go to the kernel by themselves, and
|
|
150
|
+
`encodeInput`/`encodeParams` turn runtime values into the numbers the machine reads.
|
|
151
|
+
- **State of any kind**: `.state({open: bool.from(true), status: oneOf('new', 'done').from('new'), owner: str().from(''), note: maybe(nat).from(null)})`;
|
|
152
|
+
a step writes each kind's own values, `when(c, a, b)` chooses between any two values of a kind, and `rule.decode(state)`
|
|
153
|
+
reads them back as runtime values.
|
|
154
|
+
- **Integers and decimals**: `int` and `decimal(places)` inputs, `.ints({...})` and `.decimals(places, {...})` state;
|
|
155
|
+
`plus`, `minus`, `negate`, `times(k)` (k may be negative), `min`, `max`, `abs`, `isNegative`, comparisons with numbers
|
|
156
|
+
(a decimal's literals are scaled: `price.atMost(19.99)`). Each is two naturals underneath, so the kernel and the Lean
|
|
157
|
+
twin see nothing new; `rule.decode(state)` reads them back as numbers.
|
|
158
|
+
- **Strings**: `str({like: ['*.internal'], maxLength: 64})`. A string is its interned name, its length and a bit per
|
|
159
|
+
declared pattern (Cedar's glob), all computed from the string itself when the input is encoded. `s.like(pattern)`,
|
|
160
|
+
`s.eq('admin')` (which carries the literal's length and pattern bits, so laws reason from either), `s.isEmpty`,
|
|
161
|
+
`s.length`; sets of strings are carried maps keyed by `s.id`.
|
|
163
162
|
- **Arithmetic** over the naturals: `plus`, `minus` (truncated), `times(k)`, `mul` (a product of two terms), `min`,
|
|
164
163
|
`max`, `when(cond, a, b)`, `count(cond)`; conditions `lt`, `atMost`, `eq`, `and`, `or`, `not`, `implies`.
|
|
165
164
|
- **Sets and quantifiers**: `.carries(...)` for sets a request brings, `everyKey(k => ...)` (and `has`, `subsetOf`,
|
|
@@ -174,10 +173,16 @@ const failsClosed = trial.decide('a missing fact means no trial', ({spent}) => s
|
|
|
174
173
|
helpful request lowers in its region. `rank` may return several terms (compared in order) and `helpful` may be a list
|
|
175
174
|
of `{when, request}`; a request names only the inputs it fixes (`examples/liveness.ts`: batches, a history with a
|
|
176
175
|
query, a machine with sets).
|
|
176
|
+
- **Pure functions** (`fun(name).inputs(...).returns(env => ({...}))`): each field of the result a term or a condition;
|
|
177
|
+
`f.law(name, given, then)` is a decision about the terms it returns and `f.run(values)` runs those terms compiled. A
|
|
178
|
+
decision assumes the functions' facts where it reads them.
|
|
179
|
+
- **Two runs** (`r.relate(name, (a, b) => given, (a, b) => then)`, or `f.relate(...)` with both results): a decision
|
|
180
|
+
about two requests under the same params, for noninterference ("requests that differ only in a secret are decided
|
|
181
|
+
alike"); the second request's inputs show as `b.<input>` in a counterexample.
|
|
177
182
|
- **Loops** (`loop(...)`): state, `while`, `body`, a `rank` (or a tuple of ranks) that must drop each turn.
|
|
178
183
|
- **Lean** (`gdp-wired/lean`): `toLean([{id, machine, laws, stepLaws}, ...])` writes the proofs as a Lean file, and
|
|
179
184
|
`leanProject(file)` a Lake project with the twin, so `lake build` checks them. Each law comes as the twin's theorem and
|
|
180
|
-
as a plain statement over named variables, e.g. `theorem budget.law0 (h :
|
|
185
|
+
as a plain statement over named variables, e.g. `theorem budget.law0 (h : SReach budget_machine ρ μ) : ρ «s» ≤
|
|
181
186
|
(ρ «b» + ρ «m»)`, for a Lean development to cite.
|
|
182
187
|
- **Composition** (`gdp-wired/compose`): `product(a, b)`, `interleave(a, b)`, `refinement(impl, spec, {relation, witness, stutter})`,
|
|
183
188
|
`steps(spec, k)` (k spec steps as one, each skippable) and `ghost(m, {name: ever(...) | count(...) | total(m, map, name)})`
|
|
@@ -197,33 +202,25 @@ On Modal CPUs (2 vCPU), generated JavaScript, the answers checked against the in
|
|
|
197
202
|
|
|
198
203
|
| Machine | 1k | 10k | 100k | 1M |
|
|
199
204
|
| --- | --- | --- | --- | --- |
|
|
200
|
-
| Per-key caps (`perf/grant-scale.ts`), requests | 0.3 ms | 2.5 ms | 29 ms | 267 ms |
|
|
201
|
-
| Window limit, pruned (`perf/window-scale.ts`), events | 0.8 ms | 3.1 ms | 16 ms | 136 ms |
|
|
205
|
+
| Per-key caps (`research/perf/grant-scale.ts`), requests | 0.3 ms | 2.5 ms | 29 ms | 267 ms |
|
|
206
|
+
| Window limit, pruned (`research/perf/window-scale.ts`), events | 0.8 ms | 3.1 ms | 16 ms | 136 ms |
|
|
202
207
|
|
|
203
208
|
Pruned, the window limit holds about 35 records whatever the event count; unpruned, each request scans every record kept
|
|
204
209
|
(76 ms at 10k events). The interpreter copies its maps each step (it's the reference, and searches branch from it):
|
|
205
210
|
1.6 s for 10k requests of the caps rule.
|
|
206
211
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
L4 ours takes 5 ms warm; Bend's CUDA build compiled, but its GPU runs hung in our tests (its CPU builds ran: 109 ms
|
|
211
|
-
parallel).
|
|
212
|
+
Measured against Bend and Lean on the same three rules ([`compare/`](compare/README.md)), gdp-wired's proofs are one
|
|
213
|
+
or two lines and check in 25–44 ms, against 74–117 lines of hand proof and 137–161 ms in Bend and 8–30 lines and
|
|
214
|
+
488–571 ms in Lean. Running each tool's proved code over 100,000 requests (whole command, Modal, 2 CPUs):
|
|
212
215
|
|
|
213
|
-
|
|
216
|
+
| | budget | caps | window |
|
|
217
|
+
| --- | --- | --- | --- |
|
|
218
|
+
| gdp-wired | 44 ms | 59 ms | 2.6 s |
|
|
219
|
+
| Bend | 92 s | 75 s | over 300 s |
|
|
220
|
+
| Lean | 8 ms | 205 ms | 8.7 s |
|
|
214
221
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
| | Rule lines | Law lines | Proof lines | Check (median, whole command) |
|
|
219
|
-
| --- | --- | --- | --- | --- |
|
|
220
|
-
| gdp-wired | 5–7 | 1–4 | 1–2 (`proveRule`) | 25–44 ms |
|
|
221
|
-
| Bend 2 | 39–98 (25–50 of them proof evidence in the rule) | 5–26 | 74–117 | 137–161 ms (`--verdict` 167–178 ms) |
|
|
222
|
-
| Lean 4 (by hand) | 4–15 | 1–4 | 8–30 | 488–571 ms |
|
|
223
|
-
|
|
224
|
-
Most of each time is the tool's startup (17, 134 and 453 ms). gdp-wired's kernel trees for the same rules re-check in
|
|
225
|
-
its Lean twin in 1.35 s. What each proves, what each ships at runtime, and where each is the better tool are in
|
|
226
|
-
`compare/README.md`.
|
|
222
|
+
The proved Bend and Lean rules keep lists and scan them; gdp-wired runs maps and a log (unpruned in this window rule).
|
|
223
|
+
Lean, compiled natively, wins where the state stays small. Details in [`docs/comparison.md`](docs/comparison.md).
|
|
227
224
|
|
|
228
225
|
## Layout
|
|
229
226
|
|
|
@@ -231,19 +228,18 @@ its Lean twin in 1.35 s. What each proves, what each ships at runtime, and where
|
|
|
231
228
|
| --- | --- |
|
|
232
229
|
| `core/` | Machines, the interpreter and JS compiler, the builders, composition (`compose.ts`), the linear solver and its checker, Houdini invariants, models, the first Lean export |
|
|
233
230
|
| `kernel/` | The trusted kernel and the untrusted prover, with soundness fuzz (`kernel.test.ts`, `history.test.ts`) |
|
|
234
|
-
| `lean
|
|
235
|
-
| `
|
|
236
|
-
| `
|
|
237
|
-
| `next/` | Guards synthesized from laws, relational laws (migrations, tenant isolation, through `core/compose.ts`), a ReBAC audit |
|
|
238
|
-
| `sat/` | Bit-vector laws: bit-blasted, solved, LRAT-checked in TypeScript, optionally by Lean |
|
|
239
|
-
| `enum/`, `perf/` | The enumeration, and the benchmarks |
|
|
240
|
-
| `examples/` | The builders at work: `domains/` (92 rules across nine domains, each with laws and broken variants), `harness.ts` (the triple check), `readme.ts`, `budget.ts`, `composition.ts`, `sets.ts`, `quantifiers.ts`, `functions.ts`, `lists.ts`, `sizes.ts`, `liveness.ts` |
|
|
231
|
+
| `lean-kernel/` | The kernel's Lean twin, its soundness proofs, and the exported trees |
|
|
232
|
+
| `examples/` | The builders at work: `domains/` (92 rules across nine domains, each with laws and broken variants), `harness.ts` (the triple check), `readme.ts`, `budget.ts`, `composition.ts`, `sets.ts`, `quantifiers.ts`, `functions.ts`, `lists.ts`, `sizes.ts`, `liveness.ts`, `steps.ts` (laws over up to 16 steps), and `jev-fabric/` (a Bend project's 59 proven laws, ported) |
|
|
233
|
+
| `docs/` | The comparison with Bend and Lean, and the full scope |
|
|
241
234
|
| `compare/` | The same three rules in gdp-wired, Bend and plain Lean, checked and measured |
|
|
242
|
-
| `
|
|
235
|
+
| `research/` | Everything around the library ([`research/README.md`](research/README.md)): the GPU searches (`search/`, `hw/`, `modal-gpu/`), invariant and rank synthesis (`synth/`), relational laws and guard synthesis (`next/`), bit-vector laws (`sat/`), the enumeration and benchmarks (`enum/`, `gpu/`, `perf/`), the first Lean checker (`lean/`) and the first prototype, the audit path (`audit/`) |
|
|
243
236
|
| `modal/` | Everything above as Modal jobs: `suite.json` is the regression suite |
|
|
237
|
+
| `scripts/` | The package build, the smoke test of the packed tarball, the Lean library generator, the diagram |
|
|
244
238
|
| `vendor/handwired` | [monotykamary/handwired](https://github.com/monotykamary/handwired), branch `u32-words`: Cheng Lou's Handwired with exact u32 word circuits and word stages in loop programs |
|
|
245
239
|
| `vendor/gdp-ts` | [rauchg/gdp-ts](https://github.com/rauchg/gdp-ts) |
|
|
246
240
|
|
|
241
|
+
The npm package is built from `core/`, `kernel/` and `lean-kernel/` alone; nothing in it imports `research/`.
|
|
242
|
+
|
|
247
243
|
## Running
|
|
248
244
|
|
|
249
245
|
The npm package is the library: the builders (`gdp-wired`), the runtimes (`gdp-wired/machine`), composition
|