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 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. Write the rule and the
6
- laws it must keep; gdp-wired proves them with no proof to write, shows a real run that breaks them when they're false
7
- (searched on the GPU), checks every proof again in Lean, and ships the rule as JavaScript.
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
- ## Scope: what it proves by itself, and where the wall is
33
+ ## Where it fits
38
34
 
39
- gdp-wired is an automatic prover, not a language: laws about **stateful rules** (counters, totals, maps, sets and lists
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
- | It expresses and proves | It does not (today) |
45
- | --- | --- |
46
- | Step machines over naturals: scalars, maps from naturals to naturals (a write at one key, or at every key, per map per step), named step conditions, preconditions on params, assumptions per request | Strings, and lists as values (a list in a map's cell, lists of lists) |
47
- | Lists of names (`.lists(...)`): positions and a length, `push`, `pop`, `set`, and `insertAt`/`removeAt` that keep the order (positions shift: a write at every key reading its key at k ∸ 1 or k + 1, which the runtimes decide on breakpoints); laws about every two positions or more (`forall((i, j) => ...)`: "strictly increasing", so no name twice; `forall((i, j, k) => ...)`: "any three cells repeat a value"), and step laws that an insert puts its name in, keeps every name, and a removal takes it out (`examples/lists.ts`, a scope kept in normal form) | |
48
- | Sets of names, as maps of 0/1 cells over names interned to numbers: a request carrying whole sets (`.carries`), conditions about every key (`everyKey`, `has`, `subsetOf`, `disjointFrom`, `sameAs`) anywhere in a step (each named as a flag of its own) and inside laws (pulled out as the law's key), the key read alone or plus or less a term and compared (`k < n`, `M[k + 1]`), writes at every key in one step (`each`); laws, step laws and k-step laws over them, each also a Lean theorem; a set's size over a universe of names (`within(n)`, `sizeBelow(n)`: the sum of its cells, exact for names interned below n; `examples/sizes.ts`) | A set's size over unbounded names (Lean's maps have no finite support, so no sum over every key) |
49
- | Some key (`someKey`): "something was added, so some name is in", proven through a witness, a term naming the key, that the prover finds among the state, the params, the law's key and the request (or the one the law names) | A witness no term of the state names (which a ghost can keep: the last key added, say) |
50
- | Functions from outside the rule (`fn`, and `divBy`, `modBy` for division and remainder by a constant): read like maps, known to the kernel only by their facts, which every goal may assume; the Lean export states the facts as the hypothesis they are, and proves them in Lean where the function has a Lean definition (`divBy` and `modBy` by `omega`); the GPU search and the GPU invariant synthesis tabulate each function over the keys they can read it at | Facts proven by gdp-wired (they are Lean's to prove) |
51
- | Invariants of the state and params, laws about every key of a map; step laws relating one step's before and after (`s'`, `M'[k]`: "only increases", "stays retired"), and k-step laws about k steps in a row ("two grants on one day add two", up to 16 steps); orderings at any distance through ghost state ("a refund never follows a release": `ghost` with `ever`, `count`); liveness under fairness (`leadsTo`: "every ticket issued is served", in every run that sends each helpful request infinitely often, by a rank that drops: one natural or several compared in order; several helpful requests, each in the region where it helps; a request naming some inputs and leaving the rest, and the sets it carries, free), for machines with maps, with sets, and with a history (its queries free, within its relations); a Lean theorem over runs, and under weak fairness too (each helpful request taken whenever it stays allowed, proven allowed in its region: its free inputs at any value, or at a witness the exporter finds among 0, the state and the params, a history's query among them; the sets it carries empty) | |
52
- | Composition: rules side by side (`product`), taking turns (`interleave`), and one rule refining another (`refinement`: a forward simulation, the spec allowing every request it is sent, steps the spec doesn't see as `stutter`, and `steps(spec, k)` where the spec takes several steps for one of the implementation's: a transfer at once refines a withdrawal then a deposit), each built as a plain machine so every law above applies; what a refinement means is a Lean theorem (`refines`: every reachable state of the implementation beside a reachable state of the spec, related by every law), for machines with maps and flags and for machines with sets (`srefines`: the sets a request carries shared by name, writes at every key, flags at every key) | Refinement where the number of spec steps depends on the state (beyond a bound k with skips) |
53
- | Laws about one key of a map at a time, or about scalars; totals across a map's keys through a ghost total ("in use equals what the clients hold") | Sums across a map's keys other than through a ghost total |
54
- | Histories: sums and queries over a log of requests, relations between them proven one record at a time, pruning | Quantifiers over sequences other than a machine's own history |
55
- | Decisions over facts: flags, one-of choices, optional facts, bounded naturals (`match` covers every case) | Datatypes beyond those encodings (except as a function's arguments: a list as a number a Lean function decodes); reals |
56
- | Loops that halt by a rank or a tuple of ranks; folds over binary trees; progress laws ("some request makes this happen"); recursion defined in Lean, entered as a function with its facts | Induction the prover would have to invent (the wall below) |
57
- | Arithmetic: `+`, truncated `−`, `×` by a constant, `min`, `max`, `if`, products of two terms (proven by the kernel, less well by the prover), division and remainder by a constant (`divBy`, `modBy`) | Generated code for decisions alone (`compileJs` emits machines; a predicate emitter is planned) |
58
-
59
- **The wall.** What stays outside is one of two things, and neither is a gap the next construct closes:
60
-
61
- - **Induction the prover would have to invent.** A property of a recursive structure you define (a tree, every prefix
62
- of a list of lists) whose proof needs a lemma nobody stated. No automatic prover proves all of these: arithmetic with
63
- induction is undecidable. Here the proof is written in Lean, and gdp-wired uses it: define the function in Lean,
64
- prove its facts there, and give the rule `fn(arity, js, {facts, lean})`. The steps, the state, the maps and the laws
65
- around it stay automatic, and the Lean export takes the facts as the hypothesis they are.
66
- - **An arbitrary algorithm shipped as its proved source.** gdp-wired ships the rule as JavaScript and runs a function's
67
- own JavaScript beside its Lean definition (held to it by tests, not by a compiler). Bend compiles the proved source
68
- itself.
69
-
70
- The engineering list is closed. Each other "does not" above has a route already: a ghost (a witness no term names, a
71
- sum across a map's keys), a bound with skips (spec steps that vary), an encoding (strings, datatypes as numbers a Lean
72
- function decodes), or Lean itself (a function you define and prove there). Use gdp-wired where the rule is a budget, a cap, a
73
- window, a workflow, a policy decision, an authority over sets of names or a list kept in normal form
74
- (`examples/sets.ts`, `examples/lists.ts`): it proves those without proof files, finds counterexample runs when they're
75
- false, and checks its own kernel in Lean. Keep Bend (or Lean) where the proof is the induction above.
76
-
77
- For a sense of the split: in one real codebase with 98 Bend laws and about 1,600 lines of hand proofs, the policy
78
- decisions, the compare-and-swap storage plans and the state/provider plans fit gdp-wired as it is (with names interned
79
- to numbers), and gdp-wired can prove more about storage than its Bend proofs state (no revision reuse across keys); the
80
- lifecycle machine's before/after laws are step laws (as in `release lifecycle` in `examples/`); the authority checks,
81
- built on lists of names used as sets, fit as sets of names (all ten of its authority laws proven automatically, and the
82
- rule agrees with the compiled Bend on all 7,654 outcomes over four names); the resource checks, which normalize scopes
83
- that are lists of paths (lists of lists of names) and compare their prefixes, are induction over nested lists: the wall,
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 machine and its laws into goals and checks a proof tree for each: splits on linear atoms, a Fourier–Motzkin derivation at each leaf. Every rule (step machines with maps and flags, step laws and k-step laws, loops with ranks, progress, folds over trees) is a few moves of one small algebra of runs: conditions holding where a run is, a step taken from there, a conclusion read at the end or across the run. Histories become plain machines first. Refuses names a machine doesn't declare |
92
- | `kernel/prove.ts` | No | Grows the trees. If it's wrong, the kernel rejects the tree |
93
- | `lean-kernel/` | Optional | The kernel's twin in Lean 4.34.0, each rule proven sound (`Kern.sound`, `machine_sound`, `mmachine_sound`, `fmmachine_sound`, `loop_sound`, `fold_sound`, `progress_sound`, `steplaw_sound`, `kstep_sound`, `hmachine_sound`, `smachine_sound` with functions' facts and laws about several keys, `skstep_sound`, `leads_sound`, `leads_wf_sound` and `sleads_sound` for liveness over runs), what a ghost means (`ghost_laws`, `ghost_run`, `ever_meaning`) and what a refinement means (`refines`, `srefines`), axioms `propext`, `Classical.choice`, `Quot.sound` only. The exporter sends the kernel's real trees; tampered ones compute to false |
94
- | `search/`, `synth/`, `hw/` | No | GPU work: counterexample search over every short history (for laws, step laws and k-step laws, machines with a log of requests too), and invariant/rank synthesis. Every hit is replayed by the interpreter or proven by the kernel before it counts |
95
- | `core/dsl.ts`, `core/compose.ts`, the runtimes | No | The builders and combinators compile to the kernel's plain values; the interpreter and generated JavaScript are held to the reference evaluator by the conformance test, and the combinators to runs of their parts |
96
-
97
- How the untrusted parts are held to the trusted ones, automatically:
98
-
99
- - **Types**: the whole repo type-checks strictly (`noImplicitReturns`, `noFallthroughCasesInSwitch`,
100
- `noUncheckedIndexedAccess`), and a type-aware lint denies warnings; every switch over term and condition operations is
101
- exhaustive and ends in `unreachable()`, so a new operation fails to compile wherever it's missing.
102
- - **Conformance** (`core/conformance.test.ts`): random terms over every operation through every consumer (interpreter,
103
- generated JS, the kernel on fixed values, the GPU search's u32 twin, the old symbolic prover) against the reference
104
- evaluator, and the same cases through Lean's own `evalE` (`lean-kernel/conformance.ts`).
105
- - **Examples** (`examples/`): every law proven by the kernel and re-checked by Lean, every rule run at random through
106
- both runtimes, and every broken variant broken by a real run (a refused proof alone could be the prover's weakness).
107
- - **One description, three readings**: a law or step law is proven by the kernel, read on one run by `core/watch.ts` (the
108
- examples' runs, every replay), and searched for a breaking run by the Handwired search; `hw/check-steps.ts` holds the
109
- search to the watcher on whole spaces, and all three to each other on random machines and laws.
110
- - **Tampering and mutation** (`kernel/tamper.test.ts`, `kernel/refusals.test.ts`, `kernel/mutants.ts`): real proofs
111
- broken structurally or offered for weakened goals that have a model must be rejected, every refusal the kernel makes is
112
- exercised, and every one-operator mutation of `kernel.ts` and `check.ts` is run against the tests to find what they
113
- don't notice: 239 of 261 mutants are caught, and the 22 left are equivalent (`kernel/MUTANTS.md`).
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
- - **Kinds**: `nat`, `upTo(k)`, `bool`, `oneOf(...)`, `maybe(kind)`; their assumptions (a bit is 0 or 1, a choice is in
161
- range) go to the kernel by themselves, and `encodeInput`/`encodeParams` turn runtime values into the numbers the
162
- machine reads.
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 : FMReach budget_machine ρ μ) : ρ «s» ≤
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
- A brute-force enumeration of 2^31 worlds (`enum/`, `perf/bend-enum/`), the same predicate as Bend programs and as a
208
- Handwired word program: on an M2, Bend takes 985 ms sequential, 221 ms parallel and 153 ms on Metal (whole process,
209
- about 130 ms of it device setup); ours takes 33 ms warm on WebGPU, with about 115 ms of device setup first. On an NVIDIA
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
- ## Compared with Bend and Lean
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
- `compare/` writes three rules (a budget, per-key caps, a sliding-window limit) in gdp-wired, in Bend 2 and in plain Lean
216
- 4, each checked by its own tool on Modal, each also handed the same bug to reject. From `compare/README.md`:
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/`, `lean-kernel/` | Lean: the derivation checker `Cert.lean`, and the kernel's twin with its exported trees |
235
- | `search/`, `hw/`, `modal-gpu/` | Counterexample search on the GPU: a hand-written WGSL kernel, and the same through the Handwired fork's word stages |
236
- | `synth/` | Invariants and ranks found on the GPU, proven by the kernel |
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
- | `spec.ts`, `gdp.ts`, `circuit.ts`, `laws.ts` | The audit path: gdp-ts predicates written once, as trusted provers per request and as a Handwired circuit that decides every (user, project) pair at once |
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