gdp-wired 0.2.0 → 0.3.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 +75 -32
- package/dist/core/compose.d.ts +15 -1
- package/dist/core/compose.js +132 -8
- package/dist/core/dsl.d.ts +84 -13
- package/dist/core/dsl.js +380 -23
- package/dist/core/machine.d.ts +34 -6
- package/dist/core/machine.js +194 -22
- package/dist/kernel/kernel.d.ts +29 -2
- package/dist/kernel/kernel.js +306 -48
- package/dist/kernel/prove.d.ts +33 -9
- package/dist/kernel/prove.js +298 -24
- package/dist/lean-kernel/emit.d.ts +132 -0
- package/dist/lean-kernel/emit.js +936 -0
- package/dist/lean-kernel/lean-terms.d.ts +5 -0
- package/dist/lean-kernel/lean-terms.js +44 -0
- package/dist/lean-kernel/library.d.ts +1 -0
- package/dist/lean-kernel/library.js +13 -0
- package/package.json +11 -4
package/README.md
CHANGED
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
# gdp-wired
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
8
|
|
|
9
|
-
|
|
10
|
-
|
|
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.
|
|
11
12
|
|
|
12
13
|
```sh
|
|
13
14
|
npm install gdp-wired # or bun add gdp-wired; plain JavaScript with types, Node 20+, Bun or a browser
|
|
@@ -33,34 +34,54 @@ Forget the cap (`s.plus(c)`) and the kernel refuses the proof; a search from the
|
|
|
33
34
|
and `examples/domains/` holds 92 rules from nine domains (finance, access control, public services, operations,
|
|
34
35
|
protocols, commerce, supply chains, games, education), each checked three ways.
|
|
35
36
|
|
|
36
|
-
## Scope:
|
|
37
|
+
## Scope: what it proves by itself, and where the wall is
|
|
37
38
|
|
|
38
|
-
gdp-wired is
|
|
39
|
-
|
|
40
|
-
code.
|
|
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:
|
|
41
43
|
|
|
42
44
|
| It expresses and proves | It does not (today) |
|
|
43
45
|
| --- | --- |
|
|
44
|
-
| Step machines over naturals: scalars, maps from naturals to naturals (
|
|
45
|
-
|
|
|
46
|
-
|
|
|
47
|
-
|
|
|
48
|
-
|
|
|
49
|
-
|
|
|
50
|
-
|
|
|
51
|
-
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
that
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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.
|
|
58
76
|
|
|
59
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
|
|
60
78
|
decisions, the compare-and-swap storage plans and the state/provider plans fit gdp-wired as it is (with names interned
|
|
61
79
|
to numbers), and gdp-wired can prove more about storage than its Bend proofs state (no revision reuse across keys); the
|
|
62
|
-
lifecycle machine's before/after laws are step laws (as in `release lifecycle` in `examples/`); the
|
|
63
|
-
|
|
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
|
|
64
85
|
check times.
|
|
65
86
|
|
|
66
87
|
## What's trusted
|
|
@@ -69,8 +90,8 @@ check times.
|
|
|
69
90
|
| --- | --- | --- |
|
|
70
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 |
|
|
71
92
|
| `kernel/prove.ts` | No | Grows the trees. If it's wrong, the kernel rejects the tree |
|
|
72
|
-
| `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`), axioms `propext`, `Classical.choice`, `Quot.sound` only. The exporter sends the kernel's real trees; tampered ones compute to false |
|
|
73
|
-
| `search/`, `synth/`, `hw/` | No | GPU work: counterexample search over every short history, and invariant/rank synthesis. Every hit is replayed by the interpreter or proven by the kernel before it counts |
|
|
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 |
|
|
74
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 |
|
|
75
96
|
|
|
76
97
|
How the untrusted parts are held to the trusted ones, automatically:
|
|
@@ -83,6 +104,9 @@ How the untrusted parts are held to the trusted ones, automatically:
|
|
|
83
104
|
evaluator, and the same cases through Lean's own `evalE` (`lean-kernel/conformance.ts`).
|
|
84
105
|
- **Examples** (`examples/`): every law proven by the kernel and re-checked by Lean, every rule run at random through
|
|
85
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.
|
|
86
110
|
- **Tampering and mutation** (`kernel/tamper.test.ts`, `kernel/refusals.test.ts`, `kernel/mutants.ts`): real proofs
|
|
87
111
|
broken structurally or offered for weakened goals that have a model must be rejected, every refusal the kernel makes is
|
|
88
112
|
exercised, and every one-operator mutation of `kernel.ts` and `check.ts` is run against the tests to find what they
|
|
@@ -138,11 +162,30 @@ const failsClosed = trial.decide('a missing fact means no trial', ({spent}) => s
|
|
|
138
162
|
machine reads.
|
|
139
163
|
- **Arithmetic** over the naturals: `plus`, `minus` (truncated), `times(k)`, `mul` (a product of two terms), `min`,
|
|
140
164
|
`max`, `when(cond, a, b)`, `count(cond)`; conditions `lt`, `atMost`, `eq`, `and`, `or`, `not`, `implies`.
|
|
165
|
+
- **Sets and quantifiers**: `.carries(...)` for sets a request brings, `everyKey(k => ...)` (and `has`, `subsetOf`,
|
|
166
|
+
`disjointFrom`, `sameAs`) anywhere in a step or inside a law, `M.each(k => ...)` to write every key, and
|
|
167
|
+
`someKey(k => ..., witness?)` for "some key where", proven through a witness the prover finds or the one given.
|
|
168
|
+
- **Functions** (`.fns({perMinute: divBy(60)})`, or `fn(arity, js, {facts, lean})` for your own): read like params that
|
|
169
|
+
take arguments, `perMinute(t)`; the kernel knows each only by its facts (`.facts(...)`, and a library function's
|
|
170
|
+
own), and the Lean export takes them as the hypothesis they are, proving them where the function has a Lean
|
|
171
|
+
definition. A function defined and proven in Lean is how a rule reaches past what the kernel decides: `examples/functions.ts`.
|
|
172
|
+
- **Liveness** (`rule.leadsTo(name, {from, to, rank, helpful})`, `proveLeadsTo`): whenever `from` holds, `to` holds later in
|
|
173
|
+
every run that takes each helpful request infinitely often; proven by a rank no step raises while waiting and each
|
|
174
|
+
helpful request lowers in its region. `rank` may return several terms (compared in order) and `helpful` may be a list
|
|
175
|
+
of `{when, request}`; a request names only the inputs it fixes (`examples/liveness.ts`: batches, a history with a
|
|
176
|
+
query, a machine with sets).
|
|
141
177
|
- **Loops** (`loop(...)`): state, `while`, `body`, a `rank` (or a tuple of ranks) that must drop each turn.
|
|
142
|
-
- **
|
|
178
|
+
- **Lean** (`gdp-wired/lean`): `toLean([{id, machine, laws, stepLaws}, ...])` writes the proofs as a Lean file, and
|
|
179
|
+
`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» ≤
|
|
181
|
+
(ρ «b» + ρ «m»)`, for a Lean development to cite.
|
|
182
|
+
- **Composition** (`gdp-wired/compose`): `product(a, b)`, `interleave(a, b)`, `refinement(impl, spec, {relation, witness, stutter})`,
|
|
183
|
+
`steps(spec, k)` (k spec steps as one, each skippable) and `ghost(m, {name: ever(...) | count(...) | total(m, map, name)})`
|
|
143
184
|
build plain machines from machines (each side's state named `a_x`, `b_x`). A refinement's laws are the relation, proven
|
|
144
185
|
by induction, and its step law says the spec allows the request `witness` sends it at each of the implementation's:
|
|
145
|
-
`examples/composition.ts` proves a meter that counts down refines one that counts up, and refuses two broken ones
|
|
186
|
+
`examples/composition.ts` proves a meter that counts down refines one that counts up, and refuses two broken ones;
|
|
187
|
+
a two-phase meter refines it with stuttering; an escrow never refunds after a release (at any distance); and the
|
|
188
|
+
connection pool's in-use count is the total of every client's holdings.
|
|
146
189
|
- **Running it**: `rule.interpreter()` is persistent (searches branch from any state); `rule.compiled()` is what an app
|
|
147
190
|
ships: maps and the log updated in place, so a step costs the same however long the history. Both refuse params and
|
|
148
191
|
inputs that aren't naturals below 2^53 and any value past 2^53 − 1, where the proofs over the naturals stop describing
|
|
@@ -194,7 +237,7 @@ its Lean twin in 1.35 s. What each proves, what each ships at runtime, and where
|
|
|
194
237
|
| `next/` | Guards synthesized from laws, relational laws (migrations, tenant isolation, through `core/compose.ts`), a ReBAC audit |
|
|
195
238
|
| `sat/` | Bit-vector laws: bit-blasted, solved, LRAT-checked in TypeScript, optionally by Lean |
|
|
196
239
|
| `enum/`, `perf/` | The enumeration, and the benchmarks |
|
|
197
|
-
| `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` |
|
|
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` |
|
|
198
241
|
| `compare/` | The same three rules in gdp-wired, Bend and plain Lean, checked and measured |
|
|
199
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 |
|
|
200
243
|
| `modal/` | Everything above as Modal jobs: `suite.json` is the regression suite |
|
|
@@ -204,7 +247,7 @@ its Lean twin in 1.35 s. What each proves, what each ships at runtime, and where
|
|
|
204
247
|
## Running
|
|
205
248
|
|
|
206
249
|
The npm package is the library: the builders (`gdp-wired`), the runtimes (`gdp-wired/machine`), composition
|
|
207
|
-
(`gdp-wired/compose`), the kernel
|
|
250
|
+
(`gdp-wired/compose`), the Lean export (`gdp-wired/lean`), the kernel
|
|
208
251
|
(`gdp-wired/kernel`), the prover (`gdp-wired/prove`), the derivation checker and solver (`gdp-wired/check`,
|
|
209
252
|
`gdp-wired/solver`). Everything else here (examples, comparisons, the Lean twin, the GPU searches) runs from a clone and
|
|
210
253
|
needs [Bun](https://bun.sh); Lean 4.34.0 for `lean/` and `lean-kernel/`; a WebGPU runtime (Chrome, or Dawn's node
|
|
@@ -218,7 +261,7 @@ bun test core/compile.test.ts core/runtime.test.ts kernel/history.test.ts
|
|
|
218
261
|
cd kernel && bun run-kernel.ts && bun test kernel.test.ts
|
|
219
262
|
```
|
|
220
263
|
|
|
221
|
-
The whole suite (
|
|
264
|
+
The whole suite (122 jobs, about 20 minutes of wall time) runs on [Modal](https://modal.com):
|
|
222
265
|
|
|
223
266
|
```sh
|
|
224
267
|
modal run modal/run.py --jobs modal/suite.json --out runs/modal/suite && sh modal/reap.sh
|
package/dist/core/compose.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type C, type E, type Law, type Machine } from './machine.js';
|
|
2
|
-
import type
|
|
2
|
+
import { type StepLaw } from '../kernel/kernel.js';
|
|
3
3
|
export declare const side: (m: Machine, prefix: "a_" | "b_", c: C) => C;
|
|
4
4
|
export declare function product(a: Machine, b: Machine, opts?: {
|
|
5
5
|
name?: string;
|
|
@@ -15,10 +15,24 @@ export declare function interleave(a: Machine, b: Machine, opts?: {
|
|
|
15
15
|
export declare function refinement(impl: Machine, spec: Machine, opts: {
|
|
16
16
|
relation: Law[];
|
|
17
17
|
witness: Record<string, E>;
|
|
18
|
+
stutter?: C;
|
|
18
19
|
name?: string;
|
|
19
20
|
}): {
|
|
20
21
|
machine: Machine;
|
|
21
22
|
laws: Law[];
|
|
22
23
|
stepLaws: StepLaw[];
|
|
23
24
|
};
|
|
25
|
+
export declare function refinementSides(impl: Machine, spec: Machine): {
|
|
26
|
+
impl: Machine;
|
|
27
|
+
spec: Machine;
|
|
28
|
+
};
|
|
24
29
|
export declare function agree(a: Machine): Law[];
|
|
30
|
+
export declare function steps(m: Machine, k: number): Machine;
|
|
31
|
+
export type Ghost = {
|
|
32
|
+
start: E;
|
|
33
|
+
step: E;
|
|
34
|
+
};
|
|
35
|
+
export declare function ghost(m: Machine, vars: Record<string, Ghost>): Machine;
|
|
36
|
+
export declare const ever: (name: string, cond: C) => Ghost;
|
|
37
|
+
export declare const count: (name: string, cond: C) => Ghost;
|
|
38
|
+
export declare function total(m: Machine, map: string, name: string): Ghost;
|
package/dist/core/compose.js
CHANGED
|
@@ -3,18 +3,24 @@
|
|
|
3
3
|
// is trusted: a proof is about the machine built, and the combinators are tested against runs of their parts.
|
|
4
4
|
// product(a, b) both step on every request (their params and inputs shared by name)
|
|
5
5
|
// interleave(a, b) one of them steps on each request, `turn` picking which
|
|
6
|
-
// refinement(impl, spec, {relation, witness})
|
|
6
|
+
// refinement(impl, spec, {relation, witness, stutter})
|
|
7
7
|
// impl refines spec: the spec, sent the request `witness` names for each of impl's, always allows it,
|
|
8
|
-
// and `relation` holds of the two states at every step (a forward simulation)
|
|
8
|
+
// and `relation` holds of the two states at every step (a forward simulation); where `stutter`
|
|
9
|
+
// holds, impl's step is one the spec doesn't see, and the spec stays
|
|
10
|
+
// ghost(m, vars) state that watches the machine without changing it: what happened before ("ever", "count") and
|
|
11
|
+
// totals across a map's keys ("total"), for laws about whole histories
|
|
12
|
+
// steps(m, k) k steps of m as one: the spec of a refinement that takes several steps for one of impl's
|
|
9
13
|
// Each side's state, maps and flags are renamed a_x and b_x; params and inputs keep their names.
|
|
10
14
|
import { all, any, bit, not, same, unreachable, x } from "./machine.js";
|
|
15
|
+
import { substC, substE } from "../kernel/kernel.js";
|
|
16
|
+
import { nameQuantifiers } from "./dsl.js";
|
|
11
17
|
function rename(e, prefix, m, inputs) {
|
|
12
18
|
switch (e.op) {
|
|
13
19
|
case 'var': return m.params.includes(e.name) ? e : m.inputs.includes(e.name) ? (inputs[e.name] ?? e) : x(`${prefix}${e.name}`);
|
|
14
20
|
case 'const': return e;
|
|
15
21
|
case 'mul': return { ...e, a: rename(e.a, prefix, m, inputs) };
|
|
16
22
|
case 'if': return { op: 'if', c: renameC(e.c, prefix, m, inputs), then: rename(e.then, prefix, m, inputs), else: rename(e.else, prefix, m, inputs) };
|
|
17
|
-
case 'get': return { op: 'get', map: `${prefix}${e.map}`, key: rename(e.key, prefix, m, inputs) };
|
|
23
|
+
case 'get': return { op: 'get', map: m.inputMaps?.includes(e.map) ? e.map : `${prefix}${e.map}`, key: rename(e.key, prefix, m, inputs) };
|
|
18
24
|
case 'add':
|
|
19
25
|
case 'sub':
|
|
20
26
|
case 'min':
|
|
@@ -30,6 +36,8 @@ function renameC(c, prefix, m, inputs) {
|
|
|
30
36
|
return { op: 'not', x: renameC(c.x, prefix, m, inputs) };
|
|
31
37
|
if (c.op === 'and' || c.op === 'or')
|
|
32
38
|
return { op: c.op, xs: c.xs.map(y => renameC(y, prefix, m, inputs)) };
|
|
39
|
+
if (c.op === 'all')
|
|
40
|
+
return { op: 'all', key: `${prefix}${c.key}`, c: renameC(c.c, prefix, m, inputs) }; // the key renamed with its side
|
|
33
41
|
return { ...c, a: rename(c.a, prefix, m, inputs), b: rename(c.b, prefix, m, inputs) };
|
|
34
42
|
}
|
|
35
43
|
// A condition (or law) of one side, read in the composed machine.
|
|
@@ -46,6 +54,8 @@ function renamed(m, p, inputs, stays) {
|
|
|
46
54
|
flags: Object.fromEntries(Object.entries(m.step.flags ?? {}).map(([n, c]) => [`${p}${n}`, renameC(c, p, m, inputs)])),
|
|
47
55
|
state: Object.fromEntries(Object.entries(m.step.state ?? {}).map(([n, e]) => [`${p}${n}`, keep(rename(e, p, m, inputs), x(`${p}${n}`))])),
|
|
48
56
|
maps: Object.fromEntries(Object.entries(m.step.maps ?? {}).map(([n, w]) => {
|
|
57
|
+
if ('each' in w)
|
|
58
|
+
return [`${p}${n}`, { each: `${p}${w.each}`, value: keep(rename(w.value, p, m, inputs), { op: 'get', map: `${p}${n}`, key: x(`${p}${w.each}`) }) }];
|
|
49
59
|
const key = rename(w.key, p, m, inputs);
|
|
50
60
|
return [`${p}${n}`, { key, value: keep(rename(w.value, p, m, inputs), { op: 'get', map: `${p}${n}`, key }) }];
|
|
51
61
|
})),
|
|
@@ -57,6 +67,8 @@ const join = (a, b) => ({
|
|
|
57
67
|
state: { ...a.state, ...b.state }, maps: { ...a.maps, ...b.maps },
|
|
58
68
|
step: { flags: { ...a.step.flags, ...b.step.flags }, state: { ...a.step.state, ...b.step.state }, maps: { ...a.step.maps, ...b.step.maps } },
|
|
59
69
|
});
|
|
70
|
+
// The maps requests carry: shared by name, as inputs are.
|
|
71
|
+
const carriedBy = (...ms) => { const names = [...new Set(ms.flatMap(m => m.inputMaps ?? []))]; return names.length ? { inputMaps: names } : {}; };
|
|
60
72
|
const conj = (cs) => { const xs = cs.filter((c) => c !== undefined); return xs.length ? all(...xs) : undefined; };
|
|
61
73
|
// a and b stepped by the same requests. `bInputs` rewrites what b sees (other tenants' requests as no-ops, say).
|
|
62
74
|
export function product(a, b, opts = {}) {
|
|
@@ -66,7 +78,7 @@ export function product(a, b, opts = {}) {
|
|
|
66
78
|
name: opts.name ?? `${a.name} | ${b.name}`,
|
|
67
79
|
params: [...new Set([...a.params, ...b.params, ...(opts.params ?? [])])],
|
|
68
80
|
inputs: [...new Set([...a.inputs, ...b.inputs, ...(opts.inputs ?? [])])],
|
|
69
|
-
...join(A, B),
|
|
81
|
+
...join(A, B), ...carriedBy(a, b),
|
|
70
82
|
...(assume ? { assume } : {}),
|
|
71
83
|
...(pre ? { pre } : {}),
|
|
72
84
|
};
|
|
@@ -84,7 +96,7 @@ export function interleave(a, b, opts = {}) {
|
|
|
84
96
|
name: opts.name ?? `${a.name} || ${b.name}`,
|
|
85
97
|
params: [...new Set([...a.params, ...b.params])],
|
|
86
98
|
inputs: [...new Set([...a.inputs, ...b.inputs, turn])],
|
|
87
|
-
...join(A, B),
|
|
99
|
+
...join(A, B), ...carriedBy(a, b),
|
|
88
100
|
assume: all(bit(turn), ...(A.assume ? [any(not(isA), A.assume)] : []), ...(B.assume ? [any(not(isB), B.assume)] : [])),
|
|
89
101
|
...(pre ? { pre } : {}),
|
|
90
102
|
};
|
|
@@ -98,13 +110,16 @@ export function interleave(a, b, opts = {}) {
|
|
|
98
110
|
export function refinement(impl, spec, opts) {
|
|
99
111
|
if (!spec.inputs.every(i => i in opts.witness))
|
|
100
112
|
throw new Error(`refinement: name every input of ${spec.name} (${spec.inputs.join(', ')})`);
|
|
101
|
-
|
|
102
|
-
|
|
113
|
+
if ((spec.inputMaps ?? []).some(mp => !(impl.inputMaps ?? []).includes(mp)))
|
|
114
|
+
throw new Error(`refinement: a map ${spec.name}'s requests carry is one ${impl.name}'s carry too (shared by name)`);
|
|
115
|
+
const A = renamed(impl, 'a_', {}), B = renamed(spec, 'b_', opts.witness, opts.stutter);
|
|
116
|
+
// A step the spec doesn't see needs no request it allows.
|
|
117
|
+
const allowedBy = B.assume && opts.stutter ? any(opts.stutter, B.assume) : B.assume;
|
|
103
118
|
const machine = {
|
|
104
119
|
name: opts.name ?? `${impl.name} refines ${spec.name}`,
|
|
105
120
|
params: [...new Set([...impl.params, ...spec.params])],
|
|
106
121
|
inputs: impl.inputs,
|
|
107
|
-
...join(A, B),
|
|
122
|
+
...join(A, B), ...carriedBy(impl, spec),
|
|
108
123
|
...(A.assume ? { assume: A.assume } : {}),
|
|
109
124
|
...(impl.pre ? { pre: impl.pre } : {}),
|
|
110
125
|
};
|
|
@@ -112,6 +127,16 @@ export function refinement(impl, spec, opts) {
|
|
|
112
127
|
const stepLaws = allowedBy ? [{ name: `${spec.name} allows the request it is sent`, then: allowedBy }] : [];
|
|
113
128
|
return { machine, laws, stepLaws };
|
|
114
129
|
}
|
|
130
|
+
// A refinement's two sides as machines in its composite's names (for lean-kernel/Refine.lean, which builds the composite
|
|
131
|
+
// from them): the implementation's parts named a_x, the spec's b_x, the spec's inputs b_<input> (which the composite
|
|
132
|
+
// never reads: the witness stands for them there).
|
|
133
|
+
export function refinementSides(impl, spec) {
|
|
134
|
+
const named = (m, p, inputs, inputNames) => {
|
|
135
|
+
const R = renamed(m, p, inputs);
|
|
136
|
+
return { name: m.name, params: m.params, inputs: inputNames, state: R.state, maps: R.maps, step: R.step, ...(m.inputMaps?.length ? { inputMaps: m.inputMaps } : {}), ...(R.assume ? { assume: R.assume } : {}), ...(m.pre ? { pre: m.pre } : {}) };
|
|
137
|
+
};
|
|
138
|
+
return { impl: named(impl, 'a_', {}, impl.inputs), spec: named(spec, 'b_', Object.fromEntries(spec.inputs.map(i => [i, x(`b_${i}`)])), spec.inputs.map(i => `b_${i}`)) };
|
|
139
|
+
}
|
|
115
140
|
// "a and b agree" in a product of a machine with another of its shape: every scalar equal, every map equal at every key.
|
|
116
141
|
export function agree(a) {
|
|
117
142
|
return [
|
|
@@ -119,3 +144,102 @@ export function agree(a) {
|
|
|
119
144
|
...Object.keys(a.maps ?? {}).map(n => ({ name: `a.${n}[k] = b.${n}[k]`, holds: same({ op: 'get', map: `a_${n}`, key: x('k') }, { op: 'get', map: `b_${n}`, key: x('k') }), forall: 'k' })),
|
|
120
145
|
];
|
|
121
146
|
}
|
|
147
|
+
// --- several steps as one --------------------------------------------------------------------------------------------
|
|
148
|
+
// k steps of m in a row as one step: the j-th request read as <input>@s<j> (j from 1), each step skipped where its input
|
|
149
|
+
// skip@s<j> is 1. Each step reads the state the one before it left (its flags decided there, every map read over the
|
|
150
|
+
// writes before), so several writes to one map become one write at every key, its key compared with theirs (which the
|
|
151
|
+
// runtimes decide on breakpoints). Its assumption: each step not skipped is one m allows. As the spec of a refinement,
|
|
152
|
+
// the witness names each step's request and when it is skipped.
|
|
153
|
+
export function steps(m, k) {
|
|
154
|
+
if (!Number.isInteger(k) || k < 1)
|
|
155
|
+
throw new Error('steps: k is a whole number at least 1');
|
|
156
|
+
if (m.history || m.inputMaps?.length)
|
|
157
|
+
throw new Error(`steps: ${m.name} has a history or maps a request carries (not taken here)`);
|
|
158
|
+
const at = (name, j) => `${name}@s${j}`;
|
|
159
|
+
let vars = Object.fromEntries(Object.keys(m.state).map(n => [n, x(n)]));
|
|
160
|
+
let maps = Object.fromEntries(Object.keys(m.maps ?? {}).map(map => [map, (key) => ({ op: 'get', map, key })]));
|
|
161
|
+
const allowed = [], written = new Set();
|
|
162
|
+
for (let j = 1; j <= k; j++) {
|
|
163
|
+
const skip = same(x(at('skip', j)), { op: 'const', value: 1 });
|
|
164
|
+
const sub = { vars: { ...vars, ...Object.fromEntries(m.inputs.map(i => [i, x(at(i, j))])) }, maps, flags: {} };
|
|
165
|
+
for (const [f, c] of Object.entries(m.step.flags ?? {}))
|
|
166
|
+
sub.flags[f] = substC(c, sub);
|
|
167
|
+
if (m.assume)
|
|
168
|
+
allowed.push(any(skip, substC(m.assume, sub)));
|
|
169
|
+
const kept = (now, old) => ({ op: 'if', c: skip, then: old, else: now });
|
|
170
|
+
const nextVars = { ...vars };
|
|
171
|
+
for (const [n, e] of Object.entries(m.step.state ?? {}))
|
|
172
|
+
nextVars[n] = kept(substE(e, sub), vars[n]);
|
|
173
|
+
const nextMaps = { ...maps };
|
|
174
|
+
for (const [map, w] of Object.entries(m.step.maps ?? {})) {
|
|
175
|
+
written.add(map);
|
|
176
|
+
const old = maps[map];
|
|
177
|
+
if ('each' in w)
|
|
178
|
+
nextMaps[map] = (key) => kept(substE(w.value, { ...sub, vars: { ...sub.vars, [w.each]: key } }), old(key));
|
|
179
|
+
else {
|
|
180
|
+
const wk = substE(w.key, sub), wv = substE(w.value, sub);
|
|
181
|
+
nextMaps[map] = (key) => kept({ op: 'if', c: same(key, wk), then: wv, else: old(key) }, old(key));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
vars = nextVars;
|
|
185
|
+
maps = nextMaps;
|
|
186
|
+
}
|
|
187
|
+
const each = 'k$steps';
|
|
188
|
+
return nameQuantifiers({
|
|
189
|
+
name: `${m.name}, ${k} steps at once`, params: m.params,
|
|
190
|
+
inputs: Array.from({ length: k }, (_, i) => [...m.inputs.map(n => at(n, i + 1)), at('skip', i + 1)]).flat(),
|
|
191
|
+
state: m.state, ...(m.maps ? { maps: m.maps } : {}),
|
|
192
|
+
step: {
|
|
193
|
+
state: Object.fromEntries(Object.keys(m.step.state ?? {}).map(n => [n, vars[n]])),
|
|
194
|
+
...(written.size ? { maps: Object.fromEntries([...written].map(map => [map, { each, value: maps[map](x(each)) }])) } : {}),
|
|
195
|
+
},
|
|
196
|
+
assume: all(...Array.from({ length: k }, (_, i) => bit(at('skip', i + 1))), ...allowed),
|
|
197
|
+
...(m.pre ? { pre: m.pre } : {}),
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
export function ghost(m, vars) {
|
|
201
|
+
const taken = [...m.params, ...m.inputs, ...Object.keys(m.state), ...Object.keys(m.maps ?? {}), ...Object.keys(m.step.flags ?? {})];
|
|
202
|
+
for (const name of Object.keys(vars))
|
|
203
|
+
if (taken.includes(name))
|
|
204
|
+
throw new Error(`ghost: ${name} is already a name of ${m.name}`);
|
|
205
|
+
if (m.history)
|
|
206
|
+
throw new Error(`${m.name}: ghosts go on a machine without a history (its sums are ghosts of that kind already)`);
|
|
207
|
+
return {
|
|
208
|
+
...m,
|
|
209
|
+
state: { ...m.state, ...Object.fromEntries(Object.entries(vars).map(([n, g]) => [n, g.start])) },
|
|
210
|
+
step: { ...m.step, state: { ...m.step.state, ...Object.fromEntries(Object.entries(vars).map(([n, g]) => [n, g.step])) } },
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
// Whether `cond` has held at some step so far (read where the step reads: the state before, the request, the flags).
|
|
214
|
+
export const ever = (name, cond) => ({ start: { op: 'const', value: 0 }, step: { op: 'if', c: cond, then: { op: 'const', value: 1 }, else: x(name) } });
|
|
215
|
+
// How many steps so far `cond` held at.
|
|
216
|
+
export const count = (name, cond) => ({ start: { op: 'const', value: 0 }, step: { op: 'add', a: x(name), b: { op: 'if', c: cond, then: { op: 'const', value: 1 }, else: { op: 'const', value: 0 } } } });
|
|
217
|
+
// The total of a map over every key: the map must start at 0 everywhere (so finitely many keys are nonzero), and each
|
|
218
|
+
// step writes one key, so the total moves by the value written less the value it replaces. That difference is pushed
|
|
219
|
+
// through the written term where its shape gives it (old + d adds d; old ∸ d takes min(d, old); an if moves each way
|
|
220
|
+
// by its branch's), so a proof needs no fact about the total beyond the law it is about; any other term falls back to
|
|
221
|
+
// total ∸ old + new (exact, since the total is at least any one cell).
|
|
222
|
+
export function total(m, map, name) {
|
|
223
|
+
if ((m.maps ?? {})[map] !== 0)
|
|
224
|
+
throw new Error(`total: ${map} must start at 0 at every key`);
|
|
225
|
+
const w = m.step.maps?.[map];
|
|
226
|
+
if (!w)
|
|
227
|
+
return { start: { op: 'const', value: 0 }, step: x(name) };
|
|
228
|
+
if ('each' in w)
|
|
229
|
+
throw new Error(`total: ${map} is written at every key at once, which no one step's difference describes`);
|
|
230
|
+
const old = { op: 'get', map, key: w.key }, t = x(name), same = (a) => JSON.stringify(a) === JSON.stringify(old);
|
|
231
|
+
const moved = (e) => {
|
|
232
|
+
if (same(e))
|
|
233
|
+
return t;
|
|
234
|
+
if (e.op === 'add' && same(e.a))
|
|
235
|
+
return { op: 'add', a: t, b: e.b };
|
|
236
|
+
if (e.op === 'add' && same(e.b))
|
|
237
|
+
return { op: 'add', a: t, b: e.a };
|
|
238
|
+
if (e.op === 'sub' && same(e.a))
|
|
239
|
+
return { op: 'sub', a: t, b: { op: 'min', a: e.b, b: old } };
|
|
240
|
+
if (e.op === 'if')
|
|
241
|
+
return { op: 'if', c: e.c, then: moved(e.then), else: moved(e.else) };
|
|
242
|
+
return { op: 'add', a: { op: 'sub', a: t, b: old }, b: e };
|
|
243
|
+
};
|
|
244
|
+
return { start: { op: 'const', value: 0 }, step: moved(w.value) };
|
|
245
|
+
}
|
package/dist/core/dsl.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type C, type E, type Law, type Machine, type State } from './machine.js';
|
|
2
|
-
import { type Goal, type Loop, type StepLaw, type Tree } from '../kernel/kernel.js';
|
|
1
|
+
import { type C, type E, type Law, type Machine, type Request, type State } from './machine.js';
|
|
2
|
+
import { type Goal, type Liveness, type Loop, type StepLaw, type Tree } from '../kernel/kernel.js';
|
|
3
3
|
export type TermLike = Term | number;
|
|
4
4
|
export declare const termOf: (t: TermLike) => E;
|
|
5
5
|
export declare class Term {
|
|
@@ -87,7 +87,13 @@ export type MapWrite = {
|
|
|
87
87
|
readonly map: string;
|
|
88
88
|
readonly key: E;
|
|
89
89
|
readonly value: E;
|
|
90
|
+
} | {
|
|
91
|
+
readonly map: string;
|
|
92
|
+
readonly each: string;
|
|
93
|
+
readonly value: E;
|
|
90
94
|
};
|
|
95
|
+
export declare const everyKey: (f: (key: Term) => Cond) => Cond;
|
|
96
|
+
export declare const someKey: (f: (key: Term) => Cond, witness?: TermLike) => Cond;
|
|
91
97
|
export declare class MapRef {
|
|
92
98
|
readonly name: string;
|
|
93
99
|
readonly init: number;
|
|
@@ -95,16 +101,57 @@ export declare class MapRef {
|
|
|
95
101
|
at(key: TermLike): Term;
|
|
96
102
|
set(key: TermLike, value: TermLike): MapWrite;
|
|
97
103
|
bump(key: TermLike, c?: Cond, by?: TermLike): MapWrite;
|
|
104
|
+
each(f: (key: Term) => TermLike): MapWrite;
|
|
105
|
+
has(key: TermLike): Cond;
|
|
106
|
+
subsetOf(other: MapRef): Cond;
|
|
107
|
+
disjointFrom(other: MapRef): Cond;
|
|
108
|
+
sameAs(other: MapRef): Cond;
|
|
109
|
+
within(n: number): Cond;
|
|
110
|
+
sizeBelow(n: number): Term;
|
|
111
|
+
}
|
|
112
|
+
export type ListOp = {
|
|
113
|
+
readonly when: Cond;
|
|
114
|
+
readonly at: (k: Term) => TermLike;
|
|
115
|
+
readonly len: TermLike;
|
|
116
|
+
};
|
|
117
|
+
export declare class ListRef {
|
|
118
|
+
readonly name: string;
|
|
119
|
+
readonly len: Term;
|
|
120
|
+
constructor(name: string, len: Term);
|
|
121
|
+
at(i: TermLike): Term;
|
|
122
|
+
has(x: TermLike): Cond;
|
|
123
|
+
sorted(): Cond;
|
|
124
|
+
push(x: TermLike, when?: Cond): ListOp;
|
|
125
|
+
pop(when?: Cond): ListOp;
|
|
126
|
+
set(i: TermLike, x: TermLike, when?: Cond): ListOp;
|
|
127
|
+
insertAt(i: TermLike, x: TermLike, when?: Cond): ListOp;
|
|
128
|
+
removeAt(i: TermLike, when?: Cond): ListOp;
|
|
129
|
+
apply(...ops: ListOp[]): Record<string, TermLike | MapWrite>;
|
|
130
|
+
within(): Forall;
|
|
131
|
+
increasing(): Forall;
|
|
98
132
|
}
|
|
99
133
|
export type Forall = {
|
|
100
|
-
readonly forall: (key: Term) => Cond;
|
|
134
|
+
readonly forall: (key: Term, ...more: Term[]) => Cond;
|
|
101
135
|
};
|
|
102
|
-
export declare
|
|
136
|
+
export declare function forall(f: (key: Term, key2: Term, key3: Term, key4: Term) => Cond): Forall;
|
|
103
137
|
export type Relation = {
|
|
104
138
|
readonly when: Cond;
|
|
105
139
|
readonly holds: Cond;
|
|
106
140
|
};
|
|
107
141
|
export declare const relation: (holds: Cond, whenever?: Cond) => Relation;
|
|
142
|
+
export declare function nameQuantifiers(m: Machine): Machine;
|
|
143
|
+
export type FnDef = {
|
|
144
|
+
arity: number;
|
|
145
|
+
js: string | ((...args: number[]) => number);
|
|
146
|
+
lean?: string;
|
|
147
|
+
facts?: (f: (...args: TermLike[]) => Term) => Record<string, Cond | Forall>;
|
|
148
|
+
};
|
|
149
|
+
export declare const fn: (arity: number, js: FnDef["js"], opts?: {
|
|
150
|
+
lean?: string;
|
|
151
|
+
facts?: FnDef["facts"];
|
|
152
|
+
}) => FnDef;
|
|
153
|
+
export declare const divBy: (c: number) => FnDef;
|
|
154
|
+
export declare const modBy: (c: number) => FnDef;
|
|
108
155
|
type Kinds = Record<string, Kind<unknown>>;
|
|
109
156
|
type ValuesOf<K extends Kinds> = {
|
|
110
157
|
[n in keyof K]: K[n] extends Kind<infer V> ? V : never;
|
|
@@ -121,6 +168,10 @@ type Spec = {
|
|
|
121
168
|
inputs: [string, Kind<unknown>][];
|
|
122
169
|
state: [string, number | ((p: never) => TermLike)][];
|
|
123
170
|
maps: [string, number][];
|
|
171
|
+
lists?: string[];
|
|
172
|
+
carried?: string[];
|
|
173
|
+
fns?: [string, FnDef][];
|
|
174
|
+
facts?: ((env: never) => Record<string, Cond | Forall>)[];
|
|
124
175
|
flags: ((env: never) => Record<string, Cond>)[];
|
|
125
176
|
step?: (env: never) => Record<string, TermLike | MapWrite | undefined>;
|
|
126
177
|
pre: ((p: never) => Cond)[];
|
|
@@ -148,6 +199,18 @@ export declare class Rule<P extends Obj = {}, I extends Obj = {}, S extends Obj
|
|
|
148
199
|
maps<T extends Record<string, number>>(init: T): Rule<P, I, S, M & {
|
|
149
200
|
[k in keyof T]: MapRef;
|
|
150
201
|
}, F, R, Su, Q>;
|
|
202
|
+
lists<const N extends string>(...names: N[]): Rule<P, I, S & {
|
|
203
|
+
[k in N as `${k}_len`]: Term;
|
|
204
|
+
}, M & {
|
|
205
|
+
[k in N]: ListRef;
|
|
206
|
+
}, F, R, Su, Q>;
|
|
207
|
+
fns<T extends Record<string, FnDef>>(defs: T): Rule<P & {
|
|
208
|
+
[k in keyof T]: (...args: TermLike[]) => Term;
|
|
209
|
+
}, I, S, M, F, R, Su, Q>;
|
|
210
|
+
facts(f: (p: P) => Record<string, Cond | Forall>): Rule<P, I, S, M, F, R, Su, Q>;
|
|
211
|
+
carries<const N extends string>(...names: N[]): Rule<P, I & {
|
|
212
|
+
[k in N]: MapRef;
|
|
213
|
+
}, S, M, F, R, Su, Q>;
|
|
151
214
|
flags<T extends Record<string, Cond>>(f: (env: P & I & S & M & F & Su & Q) => T): Rule<P, I, S, M, F & {
|
|
152
215
|
[k in keyof T]: Cond;
|
|
153
216
|
}, R, Su, Q>;
|
|
@@ -165,8 +228,18 @@ export declare class Rule<P extends Obj = {}, I extends Obj = {}, S extends Obj
|
|
|
165
228
|
relations(f: (env: P & I & Su & Q) => Relation[]): Rule<P, I, S, M, F, R, Su, Q>;
|
|
166
229
|
prune(order: keyof R & string, after: (p: P) => TermLike): Rule<P, I, S, M, F, R, Su, Q>;
|
|
167
230
|
law(name: string, f: (env: P & S & M & Su) => Cond | Forall): Law;
|
|
231
|
+
private lawOf;
|
|
168
232
|
stepLaw(name: string, then: (before: P & I & S & M & F & Su & Q, after: S & M & Su) => Cond | Forall, when?: (before: P & I & S & M & F & Su & Q) => Cond): StepLaw;
|
|
169
233
|
stepsLaw(name: string, k: number, then: (first: P & I & S & M & F & Su & Q, last: S & M & Su, requests: (I & Q)[]) => Cond | Forall, when?: (before: P & I & S & M & F & Su & Q, j: number) => Cond | undefined): StepLaw;
|
|
234
|
+
leadsTo(name: string, spec: {
|
|
235
|
+
from: (env: P & S & M & Su) => Cond;
|
|
236
|
+
to: (env: P & S & M & Su) => Cond;
|
|
237
|
+
rank: (env: P & S & M & Su) => TermLike | TermLike[];
|
|
238
|
+
helpful: ((env: P & S & M & Su) => Record<string, TermLike>) | {
|
|
239
|
+
when?: (env: P & S & M & Su) => Cond;
|
|
240
|
+
request: (env: P & S & M & Su) => Record<string, TermLike>;
|
|
241
|
+
}[];
|
|
242
|
+
}): Liveness;
|
|
170
243
|
decide(name: string, given: (env: P & I) => Cond | Cond[], then: (env: P & I) => Cond): Decision;
|
|
171
244
|
private kindAssumptions;
|
|
172
245
|
private env;
|
|
@@ -175,10 +248,10 @@ export declare class Rule<P extends Obj = {}, I extends Obj = {}, S extends Obj
|
|
|
175
248
|
[k in keyof P]: Raw;
|
|
176
249
|
}): Record<string, number>;
|
|
177
250
|
encodeInput(values: {
|
|
178
|
-
[k in keyof I]: Raw;
|
|
179
|
-
}):
|
|
251
|
+
[k in keyof I]: I[k] extends MapRef ? ReadonlySet<number> | readonly number[] | ReadonlyMap<number, number> : Raw;
|
|
252
|
+
}): Request;
|
|
180
253
|
randomParams(rand: Rand, bound: number): Record<string, number>;
|
|
181
|
-
randomInput(rand: Rand, bound: number):
|
|
254
|
+
randomInput(rand: Rand, bound: number): Request;
|
|
182
255
|
get orderedBy(): string | undefined;
|
|
183
256
|
interpreter(): {
|
|
184
257
|
init: (params: {
|
|
@@ -186,16 +259,12 @@ export declare class Rule<P extends Obj = {}, I extends Obj = {}, S extends Obj
|
|
|
186
259
|
}) => State;
|
|
187
260
|
queries: (params: {
|
|
188
261
|
[x: string]: number;
|
|
189
|
-
}, st: State, input: {
|
|
190
|
-
[x: string]: number;
|
|
191
|
-
}) => {
|
|
262
|
+
}, st: State, input: Request) => {
|
|
192
263
|
[x: string]: number;
|
|
193
264
|
};
|
|
194
265
|
step: (params: {
|
|
195
266
|
[x: string]: number;
|
|
196
|
-
},
|
|
197
|
-
[x: string]: number;
|
|
198
|
-
}) => State;
|
|
267
|
+
}, given: State, input: Request) => State;
|
|
199
268
|
};
|
|
200
269
|
compiled(): {
|
|
201
270
|
init: (params: object) => State & {
|
|
@@ -264,5 +333,7 @@ export type Verdict = {
|
|
|
264
333
|
export declare function proveRule(r: Rule<Obj, Obj, Obj, Obj, Obj, Obj, Obj, Obj>, laws: Law[]): Verdict;
|
|
265
334
|
export declare function proveSteps(r: Rule<Obj, Obj, Obj, Obj, Obj, Obj, Obj, Obj>, laws: Law[], stepLaws: StepLaw[]): Verdict;
|
|
266
335
|
export type { StepLaw };
|
|
336
|
+
export type { Liveness };
|
|
337
|
+
export declare function proveLeadsTo(r: Rule<Obj, Obj, Obj, Obj, Obj, Obj, Obj, Obj>, laws: Law[], l: Liveness): Verdict;
|
|
267
338
|
export declare const proveDecision: (d: Decision) => Verdict;
|
|
268
339
|
export declare const proveLoop: (l: LoopRule<Obj, Obj, Obj>, laws: Law[]) => Verdict;
|