gdp-wired 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +237 -2
- package/dist/core/check.d.ts +3 -0
- package/dist/core/check.js +31 -0
- package/dist/core/compose.d.ts +24 -0
- package/dist/core/compose.js +121 -0
- package/dist/core/dsl.d.ts +268 -0
- package/dist/core/dsl.js +526 -0
- package/dist/core/machine.d.ts +145 -0
- package/dist/core/machine.js +516 -0
- package/dist/core/solver.d.ts +41 -0
- package/dist/core/solver.js +182 -0
- package/dist/kernel/kernel.d.ts +88 -0
- package/dist/kernel/kernel.js +593 -0
- package/dist/kernel/prove.d.ts +43 -0
- package/dist/kernel/prove.js +347 -0
- package/package.json +79 -4
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { add, k, scale, solve, sub } from "../core/solver.js";
|
|
2
|
+
import { canon, checkLaws, checkStepLaws, historyMachine, leafConstraints, linear, obligations, stepLawObligations, truth, unreachable, withProducts } from "./kernel.js";
|
|
3
|
+
// The atoms the path leaves open, in evaluation order: deciding any of them makes progress.
|
|
4
|
+
function openE(e, path, out) {
|
|
5
|
+
switch (e.op) {
|
|
6
|
+
case 'var':
|
|
7
|
+
case 'const': return;
|
|
8
|
+
case 'mul': return openE(e.a, path, out);
|
|
9
|
+
case 'prod': return (openE(e.a, path, out), openE(e.b, path, out));
|
|
10
|
+
case 'get': return openE(e.key, path, out);
|
|
11
|
+
case 'if': {
|
|
12
|
+
const t = truth(e.c, path);
|
|
13
|
+
if (t === undefined)
|
|
14
|
+
openC(e.c, path, out);
|
|
15
|
+
else
|
|
16
|
+
openE(t ? e.then : e.else, path, out);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
case 'add':
|
|
20
|
+
case 'sub':
|
|
21
|
+
case 'min':
|
|
22
|
+
case 'max': {
|
|
23
|
+
openE(e.a, path, out), openE(e.b, path, out);
|
|
24
|
+
const a = linear(e.a, path), b = linear(e.b, path);
|
|
25
|
+
if (a && b && linear(e, path) === undefined)
|
|
26
|
+
out.push(e.op === 'sub' ? sub(b, a) : sub(a, b));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
default: return unreachable(e);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function openC(c, path, out) {
|
|
33
|
+
if (truth(c, path) !== undefined)
|
|
34
|
+
return;
|
|
35
|
+
if (c.op === 'flag')
|
|
36
|
+
return;
|
|
37
|
+
if (c.op === 'not')
|
|
38
|
+
return openC(c.x, path, out);
|
|
39
|
+
if (c.op === 'and' || c.op === 'or')
|
|
40
|
+
return c.xs.forEach(y => openC(y, path, out));
|
|
41
|
+
openE(c.a, path, out), openE(c.b, path, out);
|
|
42
|
+
const a = linear(c.a, path), b = linear(c.b, path);
|
|
43
|
+
if (a && b) {
|
|
44
|
+
const d = sub(a, b);
|
|
45
|
+
if (c.op === '<=')
|
|
46
|
+
out.push(d);
|
|
47
|
+
else if (c.op === '<')
|
|
48
|
+
out.push(add(d, k(1)));
|
|
49
|
+
else
|
|
50
|
+
out.push(d, scale(d, -1));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// A hypothesis is settled when it gives facts that are all its content: here, when its truth is decided, or it is a
|
|
54
|
+
// conjunction of settled parts / a linear comparison. A disjunction is settled only once one disjunct is left open and
|
|
55
|
+
// that one is settled: facts from its last disjunct are not all its content while a case inside it is still open.
|
|
56
|
+
function openH(c, path, out, positive = true) {
|
|
57
|
+
if (c.op === 'not')
|
|
58
|
+
return openH(c.x, path, out, !positive);
|
|
59
|
+
if ((c.op === 'and' && positive) || (c.op === 'or' && !positive))
|
|
60
|
+
return c.xs.forEach(y => openH(y, path, out, positive));
|
|
61
|
+
if (c.op === 'and' || c.op === 'or') {
|
|
62
|
+
if (truth(c, path) !== undefined)
|
|
63
|
+
return;
|
|
64
|
+
const open = c.xs.filter(y => truth(y, path) !== !positive);
|
|
65
|
+
if (open.length === 1)
|
|
66
|
+
openH(open[0], path, out, positive);
|
|
67
|
+
else
|
|
68
|
+
openC(c, path, out);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (c.op === 'flag')
|
|
72
|
+
return;
|
|
73
|
+
const before = out.length;
|
|
74
|
+
openE(c.a, path, out), openE(c.b, path, out);
|
|
75
|
+
if (out.length === before && !positive && c.op === '=' && truth(c, path) === undefined)
|
|
76
|
+
openC(c, path, out);
|
|
77
|
+
}
|
|
78
|
+
// Pairs of reads of one map whose keys the path doesn't yet compare.
|
|
79
|
+
function openCells(rows, path) {
|
|
80
|
+
const names = [...new Set(rows.flatMap(r => Object.keys(r.lin)))].filter(n => n.includes('['));
|
|
81
|
+
const out = [];
|
|
82
|
+
const keyText = (n) => n.slice(n.indexOf('[') + 1, -1);
|
|
83
|
+
for (const [i, p] of names.entries())
|
|
84
|
+
for (const q of names.slice(i + 1)) {
|
|
85
|
+
if (p.slice(0, p.indexOf('[')) !== q.slice(0, q.indexOf('[')))
|
|
86
|
+
continue;
|
|
87
|
+
const d = sub(parse(keyText(p)), parse(keyText(q)));
|
|
88
|
+
const dec = (lin) => path.get(canon(lin))?.holds ?? (path.has(canon(add(k(1), scale(lin, -1)))) ? !path.get(canon(add(k(1), scale(lin, -1)))).holds : undefined);
|
|
89
|
+
const a = dec(d), b = dec(scale(d, -1));
|
|
90
|
+
if (a === undefined)
|
|
91
|
+
out.push(d);
|
|
92
|
+
else if (a === true && b === undefined)
|
|
93
|
+
out.push(scale(d, -1));
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
function parse(text) {
|
|
98
|
+
const out = {};
|
|
99
|
+
let depth = 0, start = 0;
|
|
100
|
+
for (let i = 0; i <= text.length; i++) {
|
|
101
|
+
const ch = text[i];
|
|
102
|
+
if (ch === '[')
|
|
103
|
+
depth++;
|
|
104
|
+
else if (ch === ']')
|
|
105
|
+
depth--;
|
|
106
|
+
else if ((ch === ',' && depth === 0) || i === text.length) {
|
|
107
|
+
if (i > start) {
|
|
108
|
+
const part = text.slice(start, i), at = part.lastIndexOf(':');
|
|
109
|
+
out[part.slice(0, at)] = Number(part.slice(at + 1));
|
|
110
|
+
}
|
|
111
|
+
start = i + 1;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
// The budget: 200,000 nodes and, by default, 20 seconds per goal; past either the goal is left `unknown`.
|
|
117
|
+
export function grow(goal, path = new Map(), budget = { nodes: 0, until: performance.now() + 20_000 }) {
|
|
118
|
+
if (++budget.nodes > 200_000 || (budget.until !== undefined && performance.now() > budget.until))
|
|
119
|
+
return { stuck: 'unknown', path };
|
|
120
|
+
const rows = leafConstraints(goal, path);
|
|
121
|
+
const verdict = solve(rows);
|
|
122
|
+
if (verdict.kind === 'refuted')
|
|
123
|
+
return { tree: { leaf: verdict.steps }, nodes: budget.nodes };
|
|
124
|
+
const open = [];
|
|
125
|
+
for (const h of goal.hyps)
|
|
126
|
+
openH(h, path, open);
|
|
127
|
+
open.push(...openCells(rows, path));
|
|
128
|
+
let split = open.find(lin => Object.keys(lin).some(key => key !== '1') && !path.has(canon(lin)) && !path.has(canon(add(k(1), scale(lin, -1)))))
|
|
129
|
+
?? (verdict.kind === 'unknown' ? bisect(rows, path) ?? fractional(verdict.point, path) : undefined);
|
|
130
|
+
// Products: with monomials about, try every pair of rows multiplied; failing that, split a factor at zero.
|
|
131
|
+
const monomials = rows.some(r => Object.keys(r.lin).some(key => key.includes('*')));
|
|
132
|
+
// A witness that multiplies out (each monomial's value is its factors' product) is a real model: nothing to refute.
|
|
133
|
+
if (!split && monomials && verdict.kind === 'sat') {
|
|
134
|
+
const w = verdict.witness, value = (key) => w[key] ?? 0;
|
|
135
|
+
const real = rows.every(r => Object.keys(r.lin).every(key => !key.includes('*') || value(key) === key.split('*').reduce((acc, f) => acc * value(f), 1)));
|
|
136
|
+
if (real)
|
|
137
|
+
return { stuck: 'sat', witness: w, path };
|
|
138
|
+
}
|
|
139
|
+
if (!split && monomials && rows.length <= 40) {
|
|
140
|
+
// Only rows about the factors (no map cells), each pair once: elimination over hundreds of products blows up.
|
|
141
|
+
const factorSet = new Set(rows.flatMap(r => Object.keys(r.lin).filter(key => key.includes('*')).flatMap(key => key.split('*'))));
|
|
142
|
+
const relevant = rows.flatMap((r, i) => (Object.keys(r.lin).every(key => key === '1' || (!key.includes('[') && key.split('*').every(f => factorSet.has(f)))) ? [i] : []));
|
|
143
|
+
const pairs = [];
|
|
144
|
+
for (const [a, i] of relevant.entries())
|
|
145
|
+
for (const j of relevant.slice(a))
|
|
146
|
+
pairs.push([i, j]);
|
|
147
|
+
pairs.splice(300);
|
|
148
|
+
const product = solve(withProducts(rows, pairs));
|
|
149
|
+
if (product.kind === 'refuted')
|
|
150
|
+
return { tree: { leaf: product.steps, products: pairs }, nodes: budget.nodes };
|
|
151
|
+
// Degree three: each row times each product of two variables (x·y >= 0 is the pair of -x <= 0 and -y <= 0).
|
|
152
|
+
const degree = Math.max(...rows.flatMap(r => Object.keys(r.lin).map(key => (key === '1' ? 0 : key.split('*').length))));
|
|
153
|
+
if (degree >= 3) {
|
|
154
|
+
const signs = rows.flatMap((r, i) => (Object.keys(r.lin).length === 1 && Object.values(r.lin)[0] < 0 && !Object.keys(r.lin)[0].includes('*') && !Object.keys(r.lin)[0].includes('[') ? [i] : []));
|
|
155
|
+
const sq = signs.flatMap((i, a) => signs.slice(a).map(j => [i, j])), cubes = [];
|
|
156
|
+
for (let s = 0; s < sq.length; s++)
|
|
157
|
+
for (let i = 0; i < rows.length; i++)
|
|
158
|
+
cubes.push([rows.length + s, i]);
|
|
159
|
+
const third = solve(withProducts(rows, [...sq, ...cubes]));
|
|
160
|
+
if (third.kind === 'refuted')
|
|
161
|
+
return { tree: { leaf: third.steps, products: [...sq, ...cubes] }, nodes: budget.nodes };
|
|
162
|
+
}
|
|
163
|
+
const factors = [...new Set(rows.flatMap(r => Object.keys(r.lin).filter(key => key.includes('*')).flatMap(key => key.split('*'))))];
|
|
164
|
+
const fresh = (lin) => !path.has(canon(lin)) && !path.has(canon(add(k(1), scale(lin, -1))));
|
|
165
|
+
// Split a factor at zero, else two factors against each other (so (x - y)² >= 0 becomes a product of rows).
|
|
166
|
+
const zero = factors.find(f => fresh({ [f]: 1 }));
|
|
167
|
+
const apart = factors.flatMap((f, i) => factors.slice(i + 1).map(g => ({ [f]: 1, [g]: -1 }))).find(fresh);
|
|
168
|
+
if (zero)
|
|
169
|
+
split = { [zero]: 1 };
|
|
170
|
+
else if (apart)
|
|
171
|
+
split = apart;
|
|
172
|
+
}
|
|
173
|
+
if (!split)
|
|
174
|
+
return { stuck: verdict.kind === 'sat' ? 'sat' : 'unknown', ...(verdict.kind === 'sat' ? { witness: verdict.witness } : {}), path };
|
|
175
|
+
const id = canon(split);
|
|
176
|
+
const yes = grow(goal, new Map(path).set(id, { lin: split, holds: true }), budget);
|
|
177
|
+
if (!('tree' in yes))
|
|
178
|
+
return yes;
|
|
179
|
+
const no = grow(goal, new Map(path).set(id, { lin: split, holds: false }), budget);
|
|
180
|
+
if (!('tree' in no))
|
|
181
|
+
return no;
|
|
182
|
+
return { tree: { split, yes: yes.tree, no: no.tree }, nodes: budget.nodes };
|
|
183
|
+
}
|
|
184
|
+
// Integer completeness for bounded systems: when elimination is inexact and finds no witness, halve a variable's range
|
|
185
|
+
// (x <= mid or x >= mid + 1). Each half is a split like any other, so the kernel checks it the same way; a variable
|
|
186
|
+
// pinned to one value becomes an equality, which the solver substitutes exactly.
|
|
187
|
+
function bisect(rows, path) {
|
|
188
|
+
const refutes = (extra) => solve([...rows, { lin: extra, label: '' }]).kind === 'refuted';
|
|
189
|
+
const names = [...new Set(rows.flatMap(r => Object.keys(r.lin).filter(key => key !== '1')))];
|
|
190
|
+
for (const x of names) {
|
|
191
|
+
let hi = 1;
|
|
192
|
+
while (hi < 1 << 20 && !refutes({ 1: hi + 1, [x]: -1 }))
|
|
193
|
+
hi *= 2; // x >= hi + 1 is impossible
|
|
194
|
+
if (hi >= 1 << 20)
|
|
195
|
+
continue;
|
|
196
|
+
// The least lo with x <= lo possible, by halving (x <= c is impossible exactly for the c below it).
|
|
197
|
+
let lo = 0, top = hi;
|
|
198
|
+
while (lo < top) {
|
|
199
|
+
const c = Math.floor((lo + top) / 2);
|
|
200
|
+
if (refutes({ [x]: 1, 1: -c }))
|
|
201
|
+
lo = c + 1;
|
|
202
|
+
else
|
|
203
|
+
top = c;
|
|
204
|
+
}
|
|
205
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
206
|
+
for (const c of lo === hi ? [lo, lo - 1] : [mid]) {
|
|
207
|
+
const lin = c === 0 ? { [x]: 1 } : { [x]: 1, 1: -c };
|
|
208
|
+
if (!path.has(canon(lin)) && !path.has(canon(add(k(1), scale(lin, -1)))))
|
|
209
|
+
return lin;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return undefined;
|
|
213
|
+
}
|
|
214
|
+
// Branch and bound for unbounded systems: split on a variable the rational solution puts between two integers.
|
|
215
|
+
function fractional(point, path) {
|
|
216
|
+
for (const [x, value] of Object.entries(point ?? {})) {
|
|
217
|
+
if (Math.abs(value - Math.round(value)) < 1e-9 || x.includes('*'))
|
|
218
|
+
continue;
|
|
219
|
+
const c = Math.floor(value), lin = c === 0 ? { [x]: 1 } : { [x]: 1, 1: -c };
|
|
220
|
+
if (!path.has(canon(lin)) && !path.has(canon(add(k(1), scale(lin, -1)))))
|
|
221
|
+
return lin;
|
|
222
|
+
}
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
// The keys a proof assumes the every-key laws at: every key the step and the laws read, and the fresh key of the goal.
|
|
226
|
+
function readKeys(m, laws) {
|
|
227
|
+
const out = new Map();
|
|
228
|
+
const inE = (e) => {
|
|
229
|
+
if (e.op === 'get')
|
|
230
|
+
(out.set(JSON.stringify(e.key), e.key), inE(e.key));
|
|
231
|
+
else if (e.op === 'if')
|
|
232
|
+
(inC(e.c), inE(e.then), inE(e.else));
|
|
233
|
+
else if (e.op === 'mul')
|
|
234
|
+
inE(e.a);
|
|
235
|
+
else if ('a' in e)
|
|
236
|
+
(inE(e.a), inE(e.b));
|
|
237
|
+
};
|
|
238
|
+
const inC = (c) => (c.op === 'flag' ? undefined : c.op === 'not' ? inC(c.x) : c.op === 'and' || c.op === 'or' ? c.xs.forEach(inC) : (inE(c.a), inE(c.b)));
|
|
239
|
+
Object.values(m.step.state ?? {}).forEach(inE);
|
|
240
|
+
Object.values(m.step.flags ?? {}).forEach(inC);
|
|
241
|
+
for (const w of Object.values(m.step.maps ?? {}))
|
|
242
|
+
(out.set(JSON.stringify(w.key), w.key), inE(w.key), inE(w.value));
|
|
243
|
+
for (const l of laws.filter(l => !l.forall))
|
|
244
|
+
inC(l.holds);
|
|
245
|
+
return [...out.values()];
|
|
246
|
+
}
|
|
247
|
+
export function proveLaws(m, laws) {
|
|
248
|
+
const keys = readKeys(m, laws);
|
|
249
|
+
const instances = {};
|
|
250
|
+
for (const law of laws)
|
|
251
|
+
instances[law.name] = Object.fromEntries(laws.filter(l => l.forall).map(l => [l.name, [...keys, ...(law.forall ? [{ op: 'var', name: `${law.forall}#` }] : [])]]));
|
|
252
|
+
const trees = {};
|
|
253
|
+
let nodes = 0, leaves = 0;
|
|
254
|
+
for (const goal of obligations(m, laws, instances)) {
|
|
255
|
+
const out = grow(goal);
|
|
256
|
+
if (!('tree' in out))
|
|
257
|
+
return { proven: false, nodes, leaves, failed: { goal: goal.name, stuck: out.stuck, ...(out.witness ? { witness: out.witness } : {}) }, checked: false };
|
|
258
|
+
trees[goal.name] = out.tree;
|
|
259
|
+
nodes += out.nodes;
|
|
260
|
+
leaves += countLeaves(out.tree);
|
|
261
|
+
}
|
|
262
|
+
const proof = { instances, trees };
|
|
263
|
+
return { proven: true, nodes, leaves, checked: checkLaws(m, laws, proof), proof };
|
|
264
|
+
}
|
|
265
|
+
// Step laws (kernel.ts's stepLawObligations): the laws proven first (they are assumed), then one goal per step law, the
|
|
266
|
+
// every-key laws assumed at every key the step, the laws and the step laws read.
|
|
267
|
+
export function proveStepLaws(m, laws, stepLaws) {
|
|
268
|
+
const base = proveLaws(m, laws);
|
|
269
|
+
if (!base.proven || !base.proof)
|
|
270
|
+
return { proven: false, nodes: base.nodes, leaves: base.leaves, ...(base.failed ? { failed: base.failed } : {}), checked: false };
|
|
271
|
+
const plain = m.history ? historyMachine(m).machine : m;
|
|
272
|
+
const keys = [...readKeys(plain, laws), ...stepLaws.flatMap(sl => [sl.then, ...(sl.when === undefined ? [] : Array.isArray(sl.when) ? sl.when : [sl.when])].flatMap(readKeysC))];
|
|
273
|
+
const instances = Object.fromEntries(laws.filter(l => l.forall).map(l => [l.name, keys]));
|
|
274
|
+
const trees = {};
|
|
275
|
+
let nodes = base.nodes, leaves = base.leaves;
|
|
276
|
+
for (const sl of stepLaws)
|
|
277
|
+
for (const goal of stepLawObligations(m, laws, instances, sl)) {
|
|
278
|
+
const out = grow(goal);
|
|
279
|
+
if (!('tree' in out))
|
|
280
|
+
return { proven: false, nodes, leaves, failed: { goal: goal.name, stuck: out.stuck, ...(out.witness ? { witness: out.witness } : {}) }, checked: false };
|
|
281
|
+
trees[goal.name] = out.tree;
|
|
282
|
+
nodes += out.nodes;
|
|
283
|
+
leaves += countLeaves(out.tree);
|
|
284
|
+
}
|
|
285
|
+
const proof = { laws: base.proof, instances, trees };
|
|
286
|
+
return { proven: true, nodes, leaves, checked: checkStepLaws(m, laws, stepLaws, proof), proof };
|
|
287
|
+
}
|
|
288
|
+
// The key terms a condition reads maps at; a primed map's key is a key of the map before the step too.
|
|
289
|
+
function readKeysC(c) {
|
|
290
|
+
const out = [];
|
|
291
|
+
const inE = (e) => { if (e.op === 'get')
|
|
292
|
+
(out.push(e.key), inE(e.key));
|
|
293
|
+
else if (e.op === 'if')
|
|
294
|
+
(inC(e.c), inE(e.then), inE(e.else));
|
|
295
|
+
else if (e.op === 'mul')
|
|
296
|
+
inE(e.a);
|
|
297
|
+
else if ('a' in e)
|
|
298
|
+
(inE(e.a), inE(e.b)); };
|
|
299
|
+
const inC = (x) => (x.op === 'flag' ? undefined : x.op === 'not' ? inC(x.x) : x.op === 'and' || x.op === 'or' ? x.xs.forEach(inC) : (inE(x.a), inE(x.b)));
|
|
300
|
+
inC(c);
|
|
301
|
+
return out;
|
|
302
|
+
}
|
|
303
|
+
export const countLeaves = (t) => ('leaf' in t ? 1 : countLeaves(t.yes) + countLeaves(t.no));
|
|
304
|
+
// A real counterexample for a goal the prover couldn't close (with products, its witness may be abstract): the free
|
|
305
|
+
// variables over 0..bound, every hypothesis evaluated with true arithmetic. Goals that read maps are left to the
|
|
306
|
+
// machine searches (search/), which run whole histories.
|
|
307
|
+
export function concretize(goal, bound = 12) {
|
|
308
|
+
const names = new Set();
|
|
309
|
+
let reads = false;
|
|
310
|
+
const walkE = (e) => { if (e.op === 'var')
|
|
311
|
+
names.add(e.name);
|
|
312
|
+
else if (e.op === 'get')
|
|
313
|
+
reads = true;
|
|
314
|
+
else if (e.op === 'if')
|
|
315
|
+
(walkC(e.c), walkE(e.then), walkE(e.else));
|
|
316
|
+
else if ('a' in e)
|
|
317
|
+
(walkE(e.a), 'b' in e && walkE(e.b)); };
|
|
318
|
+
const walkC = (c) => (c.op === 'flag' ? undefined : c.op === 'not' ? walkC(c.x) : c.op === 'and' || c.op === 'or' ? c.xs.forEach(walkC) : (walkE(c.a), walkE(c.b)));
|
|
319
|
+
goal.hyps.forEach(walkC);
|
|
320
|
+
if (reads || names.size > 4)
|
|
321
|
+
return undefined;
|
|
322
|
+
const ev = (e, env) => {
|
|
323
|
+
switch (e.op) {
|
|
324
|
+
case 'var': return env[e.name];
|
|
325
|
+
case 'const': return e.value;
|
|
326
|
+
case 'add': return ev(e.a, env) + ev(e.b, env);
|
|
327
|
+
case 'sub': return Math.max(0, ev(e.a, env) - ev(e.b, env));
|
|
328
|
+
case 'min': return Math.min(ev(e.a, env), ev(e.b, env));
|
|
329
|
+
case 'max': return Math.max(ev(e.a, env), ev(e.b, env));
|
|
330
|
+
case 'mul': return e.k * ev(e.a, env);
|
|
331
|
+
case 'prod': return ev(e.a, env) * ev(e.b, env);
|
|
332
|
+
case 'if': return holds(e.c, env) ? ev(e.then, env) : ev(e.else, env);
|
|
333
|
+
case 'get': return NaN;
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
const holds = (c, env) => c.op === 'not' ? !holds(c.x, env) : c.op === 'and' ? c.xs.every(y => holds(y, env)) : c.op === 'or' ? c.xs.some(y => holds(y, env))
|
|
337
|
+
: c.op === 'flag' ? false : c.op === '<' ? ev(c.a, env) < ev(c.b, env) : c.op === '<=' ? ev(c.a, env) <= ev(c.b, env) : ev(c.a, env) === ev(c.b, env);
|
|
338
|
+
const vars = [...names];
|
|
339
|
+
const total = (bound + 1) ** vars.length;
|
|
340
|
+
for (let i = 0; i < total; i++) {
|
|
341
|
+
let rest = i;
|
|
342
|
+
const env = Object.fromEntries(vars.map(v => { const d = rest % (bound + 1); rest = Math.floor(rest / (bound + 1)); return [v, d]; }));
|
|
343
|
+
if (goal.hyps.every(h => holds(h, env)))
|
|
344
|
+
return env;
|
|
345
|
+
}
|
|
346
|
+
return undefined;
|
|
347
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,81 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdp-wired",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Laws about stateful rules (budgets, caps, windows, workflows, policy decisions), proven automatically by a small checked kernel, with the rule shipped as code",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"verification",
|
|
7
|
+
"theorem-proving",
|
|
8
|
+
"invariants",
|
|
9
|
+
"state-machine",
|
|
10
|
+
"rate-limiting",
|
|
11
|
+
"policy",
|
|
12
|
+
"lean"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"author": "Tom X Nguyen",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/monotykamary/gdp-wired.git"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://github.com/monotykamary/gdp-wired#readme",
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/monotykamary/gdp-wired/issues"
|
|
23
|
+
},
|
|
24
|
+
"type": "module",
|
|
25
|
+
"sideEffects": false,
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/core/dsl.d.ts",
|
|
29
|
+
"default": "./dist/core/dsl.js"
|
|
30
|
+
},
|
|
31
|
+
"./machine": {
|
|
32
|
+
"types": "./dist/core/machine.d.ts",
|
|
33
|
+
"default": "./dist/core/machine.js"
|
|
34
|
+
},
|
|
35
|
+
"./compose": {
|
|
36
|
+
"types": "./dist/core/compose.d.ts",
|
|
37
|
+
"default": "./dist/core/compose.js"
|
|
38
|
+
},
|
|
39
|
+
"./kernel": {
|
|
40
|
+
"types": "./dist/kernel/kernel.d.ts",
|
|
41
|
+
"default": "./dist/kernel/kernel.js"
|
|
42
|
+
},
|
|
43
|
+
"./prove": {
|
|
44
|
+
"types": "./dist/kernel/prove.d.ts",
|
|
45
|
+
"default": "./dist/kernel/prove.js"
|
|
46
|
+
},
|
|
47
|
+
"./check": {
|
|
48
|
+
"types": "./dist/core/check.d.ts",
|
|
49
|
+
"default": "./dist/core/check.js"
|
|
50
|
+
},
|
|
51
|
+
"./solver": {
|
|
52
|
+
"types": "./dist/core/solver.d.ts",
|
|
53
|
+
"default": "./dist/core/solver.js"
|
|
54
|
+
},
|
|
55
|
+
"./package.json": "./package.json"
|
|
56
|
+
},
|
|
57
|
+
"files": [
|
|
58
|
+
"dist",
|
|
59
|
+
"README.md",
|
|
60
|
+
"LICENSE"
|
|
61
|
+
],
|
|
62
|
+
"engines": {
|
|
63
|
+
"node": ">=20"
|
|
64
|
+
},
|
|
65
|
+
"scripts": {
|
|
66
|
+
"typecheck": "tsc -p .",
|
|
67
|
+
"lint": "oxlint --type-aware --deny-warnings --disable-nested-config",
|
|
68
|
+
"check": "tsc -p . && oxlint --type-aware --deny-warnings --disable-nested-config",
|
|
69
|
+
"build": "bun scripts/build.ts",
|
|
70
|
+
"prepublishOnly": "bun scripts/build.ts",
|
|
71
|
+
"smoke": "sh scripts/smoke.sh",
|
|
72
|
+
"test": "cd core && bun test solver.test.ts dsl.test.ts conformance.test.ts compose.test.ts && cd ../kernel && bun test tamper.test.ts refusals.test.ts steplaw.test.ts && cd ../examples && bun test examples.test.ts && cd .. && bun examples/readme.ts && bun examples/composition.ts && bun examples/budget.ts"
|
|
73
|
+
},
|
|
74
|
+
"devDependencies": {
|
|
75
|
+
"@types/bun": "1.3.1",
|
|
76
|
+
"typescript": "5.9.3",
|
|
77
|
+
"@webgpu/types": "0.1.66",
|
|
78
|
+
"oxlint": "1.88.0",
|
|
79
|
+
"oxlint-tsgolint": "7.0.2004"
|
|
80
|
+
}
|
|
81
|
+
}
|