gdp-wired 0.6.0 → 0.8.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 +102 -10
- package/dist/core/dsl.d.ts +159 -12
- package/dist/core/dsl.js +483 -26
- package/dist/core/gdp.d.ts +31 -0
- package/dist/core/gdp.js +58 -0
- package/dist/core/machine.d.ts +18 -0
- package/dist/kernel/prove.js +39 -2
- package/dist/lean-kernel/emit.d.ts +8 -1
- package/dist/lean-kernel/emit.js +206 -8
- package/dist/lean-kernel/lean-terms.d.ts +4 -0
- package/dist/lean-kernel/lean-terms.js +38 -0
- package/package.json +18 -4
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Named, NamesOf, Proof, Prover } from '@gdp-ts/core';
|
|
2
|
+
import { type Decision, type Fun, type Rule, type StepLaw, Unproven } from './dsl.js';
|
|
3
|
+
import { type Law, type State } from './machine.js';
|
|
4
|
+
type AnyNamed = Named<any, any>;
|
|
5
|
+
export type Mint<K extends string, A> = <S extends AnyNamed[]>(args: A, ...about: S) => Proof<K, NamesOf<S>> | null;
|
|
6
|
+
export { Unproven };
|
|
7
|
+
type AnyFun = Fun<any, any, any>;
|
|
8
|
+
type Conditions<O> = {
|
|
9
|
+
[k in keyof O]: O[k] extends boolean ? k : never;
|
|
10
|
+
}[keyof O] & string;
|
|
11
|
+
type ArgsOf<F extends AnyFun> = Parameters<F['run']>[0];
|
|
12
|
+
type ParamsOf<F extends AnyFun> = NonNullable<Parameters<F['run']>[1]>;
|
|
13
|
+
export declare function guard<K extends string, F extends AnyFun>(prover: Prover<K>, fn: F, field: Conditions<ReturnType<F['run']>>, opts: {
|
|
14
|
+
laws: Decision[];
|
|
15
|
+
params?: ParamsOf<F>;
|
|
16
|
+
}): Mint<K, ArgsOf<F>>;
|
|
17
|
+
type AnyRule = Rule<any, any, any, any, any, any, any, any>;
|
|
18
|
+
type FlagOf<R> = R extends Rule<any, any, any, any, infer F, any, any, any> ? keyof F & string : never;
|
|
19
|
+
type InputOf<R extends AnyRule> = Parameters<R['encodeInput']>[0];
|
|
20
|
+
type RuleParams<R extends AnyRule> = Parameters<R['encodeParams']>[0];
|
|
21
|
+
export type Gate<K extends string, I> = {
|
|
22
|
+
request: Mint<K, I>;
|
|
23
|
+
readonly state: State;
|
|
24
|
+
values(): Record<string, unknown>;
|
|
25
|
+
};
|
|
26
|
+
export declare function gate<K extends string, R extends AnyRule>(prover: Prover<K>, r: R, flag: FlagOf<R>, opts: {
|
|
27
|
+
laws: Law[];
|
|
28
|
+
stepLaws?: StepLaw[];
|
|
29
|
+
params?: RuleParams<R>;
|
|
30
|
+
state?: State;
|
|
31
|
+
}): Gate<K, InputOf<R>>;
|
package/dist/core/gdp.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Unproven, funOf, proveDecision, proveSteps } from "./dsl.js";
|
|
2
|
+
import { evalC, interpret } from "./machine.js";
|
|
3
|
+
import { allowed, requestEnv } from "./watch.js";
|
|
4
|
+
export { Unproven };
|
|
5
|
+
const proven = (what, v) => { if (!(v.proven && v.checked))
|
|
6
|
+
throw new Unproven(what, v); };
|
|
7
|
+
// A proof wherever `fn`'s result `field` holds. Every law in `laws` must have been stated about `fn` (fn.law or
|
|
8
|
+
// fn.relate) and is proven now; the arguments it is later called with must meet the function's kinds and `assume`.
|
|
9
|
+
// const access = fun('document access').inputs({...}).returns(env => ({permit: permit(env)}))
|
|
10
|
+
// const mint = guard(CanRead, access, 'permit', {laws: [access.law('...', ...)]})
|
|
11
|
+
export function guard(prover, fn, field, opts) {
|
|
12
|
+
if (opts.laws.length === 0)
|
|
13
|
+
throw new Error(`${fn.name}: a guard needs a law to prove`);
|
|
14
|
+
for (const d of opts.laws) {
|
|
15
|
+
if (funOf(d) !== fn)
|
|
16
|
+
throw new Error(`${fn.name}: "${d.name}" was not stated about this function (state it with ${fn.name}.law)`);
|
|
17
|
+
proven(`${fn.name}: "${d.name}"`, proveDecision(d));
|
|
18
|
+
}
|
|
19
|
+
const r = fn.rule, m = r.machine, params = (opts.params ?? {}), ps = r.encodeParams(params);
|
|
20
|
+
if (m.pre && !evalC(m.pre, ps, { scalars: {}, maps: {} }, m))
|
|
21
|
+
throw new Error(`${fn.name}: the params ${JSON.stringify(params)} don't meet its precondition`);
|
|
22
|
+
return (args, ...about) => {
|
|
23
|
+
const input = r.encodeInput(args);
|
|
24
|
+
if (m.assume && !evalC(m.assume, { ...ps, ...input }, { scalars: {}, maps: {} }, m))
|
|
25
|
+
return null;
|
|
26
|
+
return fn.run(args, params)[field] === true ? prover.prove(...about) : null;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
// A proof for each request whose step takes `flag`, the rule's laws (and step laws) proven now. One gate holds one
|
|
30
|
+
// run's state: keep one per state it guards (per process, or loaded and saved around each request in a transaction).
|
|
31
|
+
// const tokens = rule('one-time tokens').inputs({...}).maps({...}).flags(env => ({redeem: ...})).step(...)
|
|
32
|
+
// const redeem = gate(Redeemed, tokens, 'redeem', {laws: tokenLaws})
|
|
33
|
+
// redeem.request({op: 'redeem', token: 7}, user, token) // a Redeemed proof the first time, null after
|
|
34
|
+
export function gate(prover, r, flag, opts) {
|
|
35
|
+
const m = r.machine;
|
|
36
|
+
if (!(flag in (m.step.flags ?? {})))
|
|
37
|
+
throw new Error(`${m.name}: no flag ${flag}`);
|
|
38
|
+
if (opts.laws.length + (opts.stepLaws?.length ?? 0) === 0)
|
|
39
|
+
throw new Error(`${m.name}: a gate needs a law to prove`);
|
|
40
|
+
proven(m.name, proveSteps(r, opts.laws, opts.stepLaws ?? []));
|
|
41
|
+
const params = (opts.params ?? {}), ps = r.encodeParams(params);
|
|
42
|
+
if (m.pre && !evalC(m.pre, ps, { scalars: {}, maps: {} }, m))
|
|
43
|
+
throw new Error(`${m.name}: the params ${JSON.stringify(params)} don't meet its precondition`);
|
|
44
|
+
const run = interpret(m), compiled = r.compiled();
|
|
45
|
+
let st = opts.state ?? compiled.init(ps);
|
|
46
|
+
return {
|
|
47
|
+
request: (args, ...about) => {
|
|
48
|
+
const input = r.encodeInput(args);
|
|
49
|
+
if (!allowed(m, run, ps, st, input))
|
|
50
|
+
return null;
|
|
51
|
+
const granted = requestEnv(m, run, ps, st, input)[`?${flag}`] === 1;
|
|
52
|
+
st = compiled.step(ps, st, input);
|
|
53
|
+
return granted ? prover.prove(...about) : null;
|
|
54
|
+
},
|
|
55
|
+
get state() { return st; },
|
|
56
|
+
values: () => r.decode(st),
|
|
57
|
+
};
|
|
58
|
+
}
|
package/dist/core/machine.d.ts
CHANGED
|
@@ -70,6 +70,24 @@ export type Machine = {
|
|
|
70
70
|
export type Fn = {
|
|
71
71
|
js: string;
|
|
72
72
|
lean?: string;
|
|
73
|
+
recur?: Recurrence;
|
|
74
|
+
};
|
|
75
|
+
export type Recurrence = {
|
|
76
|
+
args: string[];
|
|
77
|
+
zero: E;
|
|
78
|
+
next: E;
|
|
79
|
+
lemmas: {
|
|
80
|
+
name: string;
|
|
81
|
+
holds: C;
|
|
82
|
+
}[];
|
|
83
|
+
between: {
|
|
84
|
+
name: string;
|
|
85
|
+
holds: C;
|
|
86
|
+
}[];
|
|
87
|
+
uses: Record<string, {
|
|
88
|
+
arity: number;
|
|
89
|
+
lean?: string;
|
|
90
|
+
}>;
|
|
73
91
|
};
|
|
74
92
|
export type History = {
|
|
75
93
|
keep: C;
|
package/dist/kernel/prove.js
CHANGED
|
@@ -448,13 +448,50 @@ function fnKeys(m, goals) {
|
|
|
448
448
|
// value that makes one of its reads one of these (by matching: a function of several arguments reads a paired key), and
|
|
449
449
|
// at the keys themselves.
|
|
450
450
|
const withFacts = (m, inst, keys) => ({ ...inst, ...Object.fromEntries((m.facts ?? []).filter(f => f.forall).map(f => {
|
|
451
|
-
if (f.keys?.length)
|
|
452
|
-
|
|
451
|
+
if (f.keys?.length) {
|
|
452
|
+
// A fact about several keys reads a function of several arguments (at a paired key): each read matched against the
|
|
453
|
+
// keys the goals read binds some of the fact's keys; one match, or two that agree, binding every key is an instance.
|
|
454
|
+
const vars = [f.forall, ...f.keys], reads = fnKeys(m, [{ name: '', hyps: [f.holds] }]);
|
|
455
|
+
if (!reads.length)
|
|
456
|
+
return [f.name, tuples(keys, vars.length)];
|
|
457
|
+
const partial = reads.flatMap(pattern => keys.flatMap(key => { const b = matchMany(pattern, key, vars); return b ? [b] : []; }));
|
|
458
|
+
const whole = (b) => vars.every(x => x in b);
|
|
459
|
+
const agree = (a, b) => Object.entries(b).every(([x, e]) => !(x in a) || JSON.stringify(a[x]) === JSON.stringify(e));
|
|
460
|
+
const found = [...partial.filter(whole), ...partial.flatMap((a, i) => partial.slice(i + 1).filter(b => agree(a, b)).map(b => ({ ...a, ...b })).filter(whole))];
|
|
461
|
+
const had = inst[f.name] ?? [], out = [...had], seen = new Set();
|
|
462
|
+
for (let t = 0; t < had.length / vars.length; t++)
|
|
463
|
+
seen.add(JSON.stringify(had.slice(t * vars.length, (t + 1) * vars.length)));
|
|
464
|
+
for (const b of found) {
|
|
465
|
+
const tuple = vars.map(x => b[x]), id = JSON.stringify(tuple);
|
|
466
|
+
if (!seen.has(id)) {
|
|
467
|
+
seen.add(id);
|
|
468
|
+
out.push(...tuple);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return [f.name, out];
|
|
472
|
+
}
|
|
453
473
|
const had = inst[f.name] ?? [], seen = new Set(had.map(e => JSON.stringify(e)));
|
|
454
474
|
const reads = fnKeys(m, [{ name: '', hyps: [f.holds] }]);
|
|
455
475
|
const at = [...keys, ...reads.flatMap(pattern => keys.flatMap(key => { const b = match(pattern, key, f.forall); return b ? [b] : []; }))];
|
|
456
476
|
return [f.name, [...had, ...at.filter(e => { const id = JSON.stringify(e); return seen.has(id) ? false : (seen.add(id), true); })]];
|
|
457
477
|
})) });
|
|
478
|
+
// The values of the names in xs that make pattern the term, for those it reads, if some do (structurally).
|
|
479
|
+
function matchMany(pattern, term, xs) {
|
|
480
|
+
const bound = {};
|
|
481
|
+
const go = (p, t) => {
|
|
482
|
+
if (p.op === 'var' && xs.includes(p.name)) {
|
|
483
|
+
if (p.name in bound)
|
|
484
|
+
return JSON.stringify(bound[p.name]) === JSON.stringify(t);
|
|
485
|
+
bound[p.name] = t;
|
|
486
|
+
return true;
|
|
487
|
+
}
|
|
488
|
+
if (p.op !== t.op)
|
|
489
|
+
return false;
|
|
490
|
+
const pe = Object.entries(p), te = t;
|
|
491
|
+
return pe.every(([k, val]) => k === 'op' || (typeof val === 'object' && val !== null ? (Array.isArray(val) ? Array.isArray(te[k]) && te[k].length === val.length && val.every((y, i) => go(y, te[k][i])) : go(val, te[k])) : val === te[k]));
|
|
492
|
+
};
|
|
493
|
+
return go(pattern, term) ? bound : undefined;
|
|
494
|
+
}
|
|
458
495
|
// The value of x that makes pattern the term, if one does (structurally).
|
|
459
496
|
function match(pattern, term, x) {
|
|
460
497
|
let bound;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { C, E, Law, Machine } from '../core/machine.js';
|
|
1
|
+
import type { C, E, Law, Machine, Recurrence } from '../core/machine.js';
|
|
2
2
|
import { type Step } from '../core/solver.js';
|
|
3
3
|
import { type Fold, type Goal, type Instances, type Liveness, type Loop, type Path, type StepLaw, type Tree } from '../kernel/kernel.js';
|
|
4
4
|
import { leanLibrary } from './library.js';
|
|
@@ -93,6 +93,13 @@ export declare function smachineToLean(id: string, given: Machine, laws: Law[],
|
|
|
93
93
|
};
|
|
94
94
|
};
|
|
95
95
|
}): string;
|
|
96
|
+
type Uses = Record<string, {
|
|
97
|
+
lean?: string;
|
|
98
|
+
recur?: Recurrence;
|
|
99
|
+
arity: number;
|
|
100
|
+
}>;
|
|
101
|
+
export declare function recurToLean(lean: string, r: Recurrence, uses?: Uses): string;
|
|
102
|
+
export declare const recurPairing = "theorem Recur.pair_inj {a b c d : Nat} (h : (a + b) * (a + b) + b = (c + d) * (c + d) + d) : a = c \u2227 b = d := by\n have sq : \u2200 s t : Nat, s < t \u2192 s * s + s < t * t := by\n intro s t hst\n have h1 : (s + 1) * (s + 1) \u2264 t * t := Nat.mul_le_mul hst hst\n have h2 : (s + 1) * (s + 1) = s * s + s + s + 1 := by simp only [Nat.add_mul, Nat.mul_add, Nat.mul_one, Nat.one_mul]; omega\n omega\n have hb : b \u2264 a + b := Nat.le_add_left b a\n have hd : d \u2264 c + d := Nat.le_add_left d c\n rcases Nat.lt_trichotomy (a + b) (c + d) with hlt | heq | hgt\n \u00B7 have := sq _ _ hlt; omega\n \u00B7 rw [heq] at h; omega\n \u00B7 have := sq _ _ hgt; omega\nnoncomputable def Recur.unpair (k : Nat) : Nat \u00D7 Nat :=\n Classical.epsilon fun p : Nat \u00D7 Nat => (p.1 + p.2) * (p.1 + p.2) + p.2 = k\ntheorem Recur.unpair_pair (a b : Nat) : Recur.unpair ((a + b) * (a + b) + b) = (a, b) := by\n have h := Classical.epsilon_spec (p := fun p : Nat \u00D7 Nat => (p.1 + p.2) * (p.1 + p.2) + p.2 = (a + b) * (a + b) + b) \u27E8(a, b), rfl\u27E9\n obtain \u27E8h1, h2\u27E9 := Recur.pair_inj h\n exact Prod.ext h1 h2";
|
|
96
103
|
export declare function hmachineToLean(id: string, m: Machine, laws: Law[], tampers?: {
|
|
97
104
|
what: string;
|
|
98
105
|
relations: {
|
package/dist/lean-kernel/emit.js
CHANGED
|
@@ -3,7 +3,7 @@ import { add, k, scale, sub } from "../core/solver.js";
|
|
|
3
3
|
import { canon, columnTerm, stateName, termColumn, historyMachine, foldObligations, leafConstraints, loopLiveness, loopMachine, linear, livenessHelpful, livenessParts, livenessRanks, loopObligations, trueC, obligations, progressObligations, stepLawObligations, substC, withProducts } from "../kernel/kernel.js";
|
|
4
4
|
import { grow, proveBuilt, proveLaws, proveStepLaws } from "../kernel/prove.js";
|
|
5
5
|
import { refinement, refinementSides } from "../core/compose.js";
|
|
6
|
-
import { leanTerms } from "./lean-terms.js";
|
|
6
|
+
import { leanSurface, leanTerms } from "./lean-terms.js";
|
|
7
7
|
import { leanLibrary } from "./library.js";
|
|
8
8
|
export { leanLibrary };
|
|
9
9
|
// --- to Lean ------------------------------------------------------------------------------------------------------------
|
|
@@ -474,8 +474,8 @@ const boundKeys = (m) => [...Object.values(m.step.flags ?? {}).flatMap(c => (c.o
|
|
|
474
474
|
// `historyTampers`: the history with other relations, each refused.
|
|
475
475
|
export function smachineToLean(id, given, laws, opts = {}) {
|
|
476
476
|
const hm = given.history ? { ...given, history: { ...given.history, prune: undefined } } : undefined;
|
|
477
|
-
const hplain = hm ? historyMachine(hm) : undefined,
|
|
478
|
-
const
|
|
477
|
+
const hplain = hm ? historyMachine(hm) : undefined, stepLaws = opts.stepLaws ?? [], rf = opts.refined, gv = opts.ghosts ?? [];
|
|
478
|
+
const { machine: m, origins } = specializeFacts(hplain ? hplain.machine : given, laws, stepLaws);
|
|
479
479
|
const claims = [];
|
|
480
480
|
const claimSteps = () => claims.flatMap(cl => cl.steps);
|
|
481
481
|
// The machine's own step laws, one step or k (Sets.lean's skstep_sound).
|
|
@@ -741,24 +741,203 @@ def ${id}_proof : List SEntry := ${list(laws.map((_, j) => `${id}_e${j}`))}
|
|
|
741
741
|
theorem ${id}_check : checkSMachine ${id}_machine ${id}_proof = true := by decide +kernel
|
|
742
742
|
theorem ${id}_laws : ∀ ρ M, SReach ${id}_machine ρ M → ∀ e ∈ ${id}_proof, e.Holds ρ M :=
|
|
743
743
|
smachine_sound ${id}_machine ${id}_proof ${id}_check
|
|
744
|
-
${hm ? histBlock() : readable(id, laws, vars, mapNames, { binders: '(ρ : Nat → Nat) (μ : Nat → Nat → Nat)', hyp: `SReach ${id}_machine ρ μ`, call: `${id}_laws ρ μ h` })}${factsProof(id, m, mapNames, opts.simp)}${opts.tamper ? `
|
|
744
|
+
${hm ? histBlock() : readable(id, laws, vars, mapNames, { binders: '(ρ : Nat → Nat) (μ : Nat → Nat → Nat)', hyp: `SReach ${id}_machine ρ μ`, call: `${id}_laws ρ μ h` })}${factsProof(id, m, mapNames, vars, opts.simp, origins)}${opts.tamper ? `
|
|
745
745
|
-- TAMPERED: ${opts.tamper.what}. Lean computes the check to false.
|
|
746
746
|
def ${id}_tampered : SMachine := ${lit(opts.tamper.machine)}
|
|
747
747
|
example : checkSMachine ${id}_tampered ${id}_proof = false := by decide +kernel` : ''}${records.length ? `\n\n${records.join('\n\n')}` : ''}${opts.after ? `\n\n${opts.after(ix)}` : ''}`;
|
|
748
748
|
}
|
|
749
|
+
function specializeFacts(m, laws, stepLaws) {
|
|
750
|
+
const origins = new Map(), facts = m.facts ?? [];
|
|
751
|
+
if (!facts.some(f => f.keys?.length))
|
|
752
|
+
return { machine: m, origins };
|
|
753
|
+
const r = proveStepLaws(m, laws, stepLaws);
|
|
754
|
+
if (!r.proven || !r.proof)
|
|
755
|
+
throw new Error(`${m.name}: not proven (${r.failed?.stuck ?? ''})`);
|
|
756
|
+
const all = [...Object.values(r.proof.laws.instances), r.proof.instances, ...Object.values(r.proof.perStep ?? {})];
|
|
757
|
+
const overParams = (t) => varsOf({ op: '=', a: t, b: t }).every(x => m.params.includes(x)) && !JSON.stringify(t).includes('"op":"get"');
|
|
758
|
+
const out = [];
|
|
759
|
+
for (const f of facts) {
|
|
760
|
+
if (!f.keys?.length) {
|
|
761
|
+
out.push(f);
|
|
762
|
+
continue;
|
|
763
|
+
}
|
|
764
|
+
const vars = [f.forall, ...f.keys], seen = new Set();
|
|
765
|
+
for (const inst of all) {
|
|
766
|
+
const keys = inst[f.name] ?? [];
|
|
767
|
+
for (let t = 0; t + vars.length <= keys.length; t += vars.length) {
|
|
768
|
+
const tuple = keys.slice(t, t + vars.length), open = tuple.map((e2, i) => [e2, i]).filter(([e2]) => !overParams(e2)).map(([, i]) => i);
|
|
769
|
+
// A tuple with two keys over the state is left out (the prover offers more instances than a proof uses): if the
|
|
770
|
+
// proof needs it, proving the machine again without it fails, and says so.
|
|
771
|
+
if (open.length > 1)
|
|
772
|
+
continue;
|
|
773
|
+
const left = open[0] ?? 0, at = Object.fromEntries(vars.flatMap((x, i) => (i === left ? [] : [[x, tuple[i]]])));
|
|
774
|
+
const id = JSON.stringify([left, at]);
|
|
775
|
+
if (seen.has(id))
|
|
776
|
+
continue;
|
|
777
|
+
seen.add(id);
|
|
778
|
+
const name = `${f.name} #${seen.size}`;
|
|
779
|
+
out.push({ name, forall: vars[left], holds: substC(f.holds, { vars: at, maps: {}, flags: {} }) });
|
|
780
|
+
origins.set(name, { from: f, at });
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
return { machine: { ...m, facts: out }, origins };
|
|
785
|
+
}
|
|
786
|
+
// A function defined by a recurrence (core/dsl.ts recur): its Lean definition by the same recurrence, and its lemmas
|
|
787
|
+
// proven together by induction (each case by omega, its min, max and ifs split first), and its laws between two points
|
|
788
|
+
// by induction on the distance. A function of several arguments is `<name>_rec`, its further arguments first, and the
|
|
789
|
+
// function the machine reads is its key's (`<name>`, through Recur.unpair: the arguments paired as the builders pair
|
|
790
|
+
// them, (a + b)² + b, which Recur.pair_inj proves injective). The kernel proved the lemmas by the same inductions; Lean
|
|
791
|
+
// proves them again of the definition.
|
|
792
|
+
const recurArg = (a) => `«${a}»`;
|
|
793
|
+
// The arguments a key was paired from (the builders' argsKey: left-nested (k + a)² + a), if it was.
|
|
794
|
+
const unpairKey = (key, n) => {
|
|
795
|
+
if (n === 1)
|
|
796
|
+
return [key];
|
|
797
|
+
if (key.op !== 'add' || key.a.op !== 'prod' || key.a.a.op !== 'add')
|
|
798
|
+
return undefined;
|
|
799
|
+
const s = key.a.a, b = key.b;
|
|
800
|
+
if (JSON.stringify(key.a.b) !== JSON.stringify(s) || JSON.stringify(s.b) !== JSON.stringify(b))
|
|
801
|
+
return undefined;
|
|
802
|
+
const rest = unpairKey(s.a, n - 1);
|
|
803
|
+
return rest ? [...rest, b] : undefined;
|
|
804
|
+
};
|
|
805
|
+
// f at x (and the further arguments), as Lean reads it.
|
|
806
|
+
const recurCall = (lean, r, x, args = r.args.map(recurArg)) => (r.args.length ? `(${lean}_rec ${[...args, x].join(' ')})` : `(${lean} ${x})`);
|
|
807
|
+
// A read of a function the recurrence uses: a recurrence by its definition at the unpaired arguments, any other by its
|
|
808
|
+
// Lean term at the key.
|
|
809
|
+
const useRead = (uses, where) => (map, key, e) => {
|
|
810
|
+
const u = uses[map];
|
|
811
|
+
if (!u?.lean)
|
|
812
|
+
throw new Error(`${where}: ${map} has no Lean definition`);
|
|
813
|
+
if (u.recur) {
|
|
814
|
+
const args = unpairKey(key, u.arity);
|
|
815
|
+
if (!args)
|
|
816
|
+
throw new Error(`${where}: ${map} read at a key not paired by its arguments`);
|
|
817
|
+
return recurCall(u.lean, u.recur, e(args[0]), args.slice(1).map(e));
|
|
818
|
+
}
|
|
819
|
+
return `((${u.lean}) ${e(key)})`;
|
|
820
|
+
};
|
|
821
|
+
// The lemmas of the recurrences a term reads, at the keys it reads them, with the names as given.
|
|
822
|
+
const useLemmas = (uses, t, names, where) => {
|
|
823
|
+
const out = [];
|
|
824
|
+
const walk = (x) => {
|
|
825
|
+
if (!x || typeof x !== 'object')
|
|
826
|
+
return;
|
|
827
|
+
const o = x;
|
|
828
|
+
if (o.op === 'get' && o.map) {
|
|
829
|
+
const u = uses[o.map];
|
|
830
|
+
if (u?.recur?.lemmas.length) {
|
|
831
|
+
const args = unpairKey(o.key, u.arity), s = leanSurface(names, where, useRead(uses, where)).e;
|
|
832
|
+
out.push(`have := ${u.lean}_lemmas ${[...args.slice(1).map(s), s(args[0])].join(' ')}`);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
for (const v of Object.values(o))
|
|
836
|
+
walk(v);
|
|
837
|
+
};
|
|
838
|
+
walk(t);
|
|
839
|
+
return [...new Set(out)];
|
|
840
|
+
};
|
|
841
|
+
export function recurToLean(lean, r, uses = {}) {
|
|
842
|
+
const binders = r.args.map(a => `(${recurArg(a)} : Nat)`).join(' '), argNames = Object.fromEntries(r.args.map(a => [a, recurArg(a)]));
|
|
843
|
+
const self = r.args.length ? `${lean}_rec` : lean;
|
|
844
|
+
const read = useRead(uses, lean);
|
|
845
|
+
const surface = (names) => leanSurface({ ...argNames, ...names }, lean, read);
|
|
846
|
+
const text = JSON.stringify(r), unfold = [self, ...(text.includes('"op":"min"') ? ['Nat.min_def'] : []), ...(text.includes('"op":"max"') ? ['Nat.max_def'] : [])].join(', ');
|
|
847
|
+
const lemmaAt = (x) => r.lemmas.map(l => `(${surface({ x, fx: recurCall(lean, r, x) }).c(l.holds)})`).join(' ∧ ');
|
|
848
|
+
const close = (haves) => `${haves.map(h => `${h}; `).join('')}(repeat' split) <;> omega`;
|
|
849
|
+
const step = (n) => useLemmas(uses, r.next, { ...argNames, x: n, prev: recurCall(lean, r, n) }, lean);
|
|
850
|
+
const lemmas = r.lemmas.length ? `
|
|
851
|
+
-- ${r.lemmas.map(l => l.name).join('; ')}
|
|
852
|
+
theorem ${lean}_lemmas ${binders} (x : Nat) : ${lemmaAt('x')} := by
|
|
853
|
+
induction x with
|
|
854
|
+
| zero => (try simp only [${unfold}]); ${close(useLemmas(uses, { op: 'and', xs: r.lemmas.map(l => l.holds) }, { ...argNames, x: '0', fx: recurCall(lean, r, '0') }, lean))}
|
|
855
|
+
| succ n ih => (try simp only [${unfold}] at ih ⊢); ${close([...step('n'), ...useLemmas(uses, { op: 'and', xs: r.lemmas.map(l => l.holds) }, { ...argNames, x: '(n + 1)', fx: recurCall(lean, r, '(n + 1)') }, lean)])}` : '';
|
|
856
|
+
const args = r.args.map(recurArg).join(' ');
|
|
857
|
+
const pairAt = (x, y) => r.between.map(l => `(${surface({ x, fx: recurCall(lean, r, x), y, fy: recurCall(lean, r, y) }).c(l.holds)})`).join(' ∧ ');
|
|
858
|
+
const between = r.between.length ? `
|
|
859
|
+
-- ${r.between.map(l => l.name).join('; ')}, for every x <= y
|
|
860
|
+
theorem ${lean}_between_by ${binders} (x d : Nat) : ${pairAt('x', '(x + d)')} := by
|
|
861
|
+
induction d with
|
|
862
|
+
| zero => simp only [Nat.add_zero]; (repeat' split) <;> omega
|
|
863
|
+
| succ n ih =>
|
|
864
|
+
rw [show x + (n + 1) = (x + n) + 1 from by omega]
|
|
865
|
+
${r.lemmas.length ? `have := ${lean}_lemmas ${args} (x + n); ` : ''}(try simp only [${unfold}] at ih this ⊢); ${close(step('(x + n)'))}
|
|
866
|
+
theorem ${lean}_between ${binders} (x y : Nat) (h : x ≤ y) : ${pairAt('x', 'y')} := by
|
|
867
|
+
obtain ⟨d, rfl⟩ := Nat.exists_eq_add_of_le h
|
|
868
|
+
exact ${lean}_between_by ${args} x d` : '';
|
|
869
|
+
const zero = surface({}).e(r.zero), next = surface({ x: 'x', prev: recurCall(lean, r, 'x') }).e(r.next);
|
|
870
|
+
if (!r.args.length)
|
|
871
|
+
return `def ${lean} : Nat → Nat\n | 0 => ${zero}\n | x + 1 => ${next}${lemmas}${between}`;
|
|
872
|
+
// The key's function: the arguments unpaired, innermost first.
|
|
873
|
+
const u = (i) => (i === r.args.length ? '(Recur.unpair k)' : `(Recur.unpair ${u(i + 1)}.1)`);
|
|
874
|
+
const keyOf = (x) => r.args.map(recurArg).reduce((k, a) => `((${k} + ${a}) * (${k} + ${a}) + ${a})`, x);
|
|
875
|
+
return `def ${self} ${binders} : Nat → Nat
|
|
876
|
+
| 0 => ${zero}
|
|
877
|
+
| x + 1 => ${next}
|
|
878
|
+
noncomputable def ${lean} (k : Nat) : Nat := ${self} ${r.args.map((_, i) => `${u(i + 1)}.2`).join(' ')} ${u(1)}.1
|
|
879
|
+
theorem ${lean}_key ${binders} (x : Nat) : ${lean} ${keyOf('x')} = ${self} ${args} x := by
|
|
880
|
+
simp only [${lean}, Recur.unpair_pair]${lemmas}${between}`;
|
|
881
|
+
}
|
|
882
|
+
// The pairing the builders read a function of several arguments by, and Lean's inverse of it (choice over the pairs:
|
|
883
|
+
// Recur.pair_inj makes it the pair).
|
|
884
|
+
export const recurPairing = `theorem Recur.pair_inj {a b c d : Nat} (h : (a + b) * (a + b) + b = (c + d) * (c + d) + d) : a = c ∧ b = d := by
|
|
885
|
+
have sq : ∀ s t : Nat, s < t → s * s + s < t * t := by
|
|
886
|
+
intro s t hst
|
|
887
|
+
have h1 : (s + 1) * (s + 1) ≤ t * t := Nat.mul_le_mul hst hst
|
|
888
|
+
have h2 : (s + 1) * (s + 1) = s * s + s + s + 1 := by simp only [Nat.add_mul, Nat.mul_add, Nat.mul_one, Nat.one_mul]; omega
|
|
889
|
+
omega
|
|
890
|
+
have hb : b ≤ a + b := Nat.le_add_left b a
|
|
891
|
+
have hd : d ≤ c + d := Nat.le_add_left d c
|
|
892
|
+
rcases Nat.lt_trichotomy (a + b) (c + d) with hlt | heq | hgt
|
|
893
|
+
· have := sq _ _ hlt; omega
|
|
894
|
+
· rw [heq] at h; omega
|
|
895
|
+
· have := sq _ _ hgt; omega
|
|
896
|
+
noncomputable def Recur.unpair (k : Nat) : Nat × Nat :=
|
|
897
|
+
Classical.epsilon fun p : Nat × Nat => (p.1 + p.2) * (p.1 + p.2) + p.2 = k
|
|
898
|
+
theorem Recur.unpair_pair (a b : Nat) : Recur.unpair ((a + b) * (a + b) + b) = (a, b) := by
|
|
899
|
+
have h := Classical.epsilon_spec (p := fun p : Nat × Nat => (p.1 + p.2) * (p.1 + p.2) + p.2 = (a + b) * (a + b) + b) ⟨(a, b), rfl⟩
|
|
900
|
+
obtain ⟨h1, h2⟩ := Recur.pair_inj h
|
|
901
|
+
exact Prod.ext h1 h2`;
|
|
749
902
|
// The facts, proven in Lean of the functions' Lean definitions, when every function has one: a run that starts with
|
|
750
|
-
// those functions starts where SReach asks the facts to hold.
|
|
751
|
-
|
|
903
|
+
// those functions starts where SReach asks the facts to hold. A recurrence's equations unfold its definition (a function
|
|
904
|
+
// of several arguments read at its key first); its lemmas and laws between two points are its theorems at the keys.
|
|
905
|
+
function factsProof(id, m, maps, vars, simp = [], origins = new Map()) {
|
|
752
906
|
const fns = Object.entries(m.fns ?? {}), facts = m.facts ?? [];
|
|
753
907
|
if (!facts.length || !fns.length || fns.some(([, f]) => !f.lean))
|
|
754
908
|
return '';
|
|
909
|
+
const recurs = fns.filter(([, f]) => f.recur);
|
|
910
|
+
simp = [...simp, ...recurs.flatMap(([, f]) => (f.recur.args.length ? [`${f.lean}_rec`, `${f.lean}_key`] : [f.lean]))];
|
|
911
|
+
const at = leanSurface(Object.fromEntries(vars.map((x, j) => [x, `(ρ ${j})`])), `${id}'s facts`);
|
|
912
|
+
// Each recurrence's key lemma rewrites the function at its key before anything else simplifies the key.
|
|
913
|
+
const keyed = recurs.filter(([, f]) => f.recur.args.length).map(([, f]) => `${f.lean}_key`);
|
|
914
|
+
const tactic = (fact) => {
|
|
915
|
+
// The theorem a fact is an instance of, cited first so that both it and the goal are simplified alike.
|
|
916
|
+
const cite = () => {
|
|
917
|
+
const o = origins.get(fact.name), general = o?.from ?? fact;
|
|
918
|
+
const owner = recurs.find(([n]) => general.name.startsWith(`${n}: `)), what = owner ? general.name.slice(owner[0].length + 2) : '';
|
|
919
|
+
if (!owner || !general.forall)
|
|
920
|
+
return undefined;
|
|
921
|
+
const [, f] = owner, r = f.recur, keys = [general.forall, ...(general.keys ?? [])];
|
|
922
|
+
// Each key: the one left (v), or its term, read at the columns.
|
|
923
|
+
const arg = keys.map(x => (x === fact.forall ? 'v' : `(${at.e(o?.at[x] ?? { op: 'var', name: x })})`));
|
|
924
|
+
if (r.lemmas.some(l => l.name === what))
|
|
925
|
+
return `${f.lean}_lemmas ${[...arg.slice(1), arg[0]].join(' ')}`;
|
|
926
|
+
if (r.between.some(l => l.name === what))
|
|
927
|
+
return `${f.lean}_between ${[...arg.slice(2), arg[0], arg[1]].join(' ')}`;
|
|
928
|
+
return undefined;
|
|
929
|
+
};
|
|
930
|
+
const c = cite(), hs = [...fns.map((_, j) => `h${j}`)];
|
|
931
|
+
return `simp only [MLaw.Holds]; (try intro v); ${c ? `have hc := ${c}; ` : ''}simp only [evalC, evalE, upd, ${hs.join(', ')}]${keyed.length ? `; (try simp only [${keyed.join(', ')}])` : ''}; (try simp [${[...hs, ...simp].join(', ')}]${c ? ' at hc ⊢' : ''}); all_goals omega`;
|
|
932
|
+
};
|
|
755
933
|
return `
|
|
756
934
|
-- The facts hold of the functions' Lean definitions: ${fns.map(([n, f]) => `${n} := ${f.lean}`).join('; ')}.
|
|
757
935
|
theorem ${id}_facts_hold (ρ : Nat → Nat) (μ : Nat → Nat → Nat) ${fns.map(([n, f], j) => `(h${j} : μ ${maps.indexOf(n)} = ${f.lean})`).join(' ')} :
|
|
758
936
|
∀ l ∈ ${id}_machine.facts, l.Holds ρ μ := by
|
|
759
937
|
intro l hl
|
|
760
938
|
simp only [${id}_machine, List.mem_cons, List.not_mem_nil, or_false] at hl
|
|
761
|
-
rcases hl with ${facts.map(() => 'rfl').join(' | ')}
|
|
939
|
+
rcases hl with ${facts.map(() => 'rfl').join(' | ')}
|
|
940
|
+
${facts.map(f => ` · ${tactic(f)}`).join('\n')}`;
|
|
762
941
|
}
|
|
763
942
|
// Machines with a history through Kern.hmachine_sound (History.lean): Lean builds the plain machine (sums as state,
|
|
764
943
|
// relations in the assumption) and each relation's record goal; the trees and the instance keys are the kernel's.
|
|
@@ -777,6 +956,25 @@ export function toLean(items, opts = {}) {
|
|
|
777
956
|
const ident = (id) => { const s = id.replace(/[^A-Za-z0-9_]/g, '_'); return /^[A-Za-z]/.test(s) ? s : `x_${s}`; };
|
|
778
957
|
const parts = [], theorems = [];
|
|
779
958
|
let goals = 0;
|
|
959
|
+
// Each recurrence defined once, before the first item that reads it.
|
|
960
|
+
const recurs = new Map();
|
|
961
|
+
const recurDefs = (m) => Object.entries(m.fns ?? {}).filter(([, f]) => f.recur).map(([n, f]) => {
|
|
962
|
+
// The functions it reads are the machine's <n>.<use>, defined before it (flatFns puts them first).
|
|
963
|
+
const uses = Object.fromEntries(Object.entries(f.recur.uses).map(([u, d]) => { const g = m.fns?.[`${n}.${u}`]; return [u, { ...d, ...(g?.recur ? { recur: g.recur } : {}) }]; }));
|
|
964
|
+
const text = recurToLean(f.lean, f.recur, uses), had = recurs.get(f.lean);
|
|
965
|
+
if (had !== undefined) {
|
|
966
|
+
if (had !== text)
|
|
967
|
+
throw new Error(`${f.lean}: two recurrences of one name`);
|
|
968
|
+
return '';
|
|
969
|
+
}
|
|
970
|
+
const pairing = f.recur.args.length && !recurs.has('') ? (recurs.set('', recurPairing), `${recurPairing}\n`) : '';
|
|
971
|
+
recurs.set(f.lean, text);
|
|
972
|
+
if (f.recur.lemmas.length)
|
|
973
|
+
theorems.push(`${f.lean}_lemmas`);
|
|
974
|
+
if (f.recur.between.length)
|
|
975
|
+
theorems.push(`${f.lean}_between`);
|
|
976
|
+
return `${pairing}${text}\n`;
|
|
977
|
+
}).join('');
|
|
780
978
|
for (let item of items) {
|
|
781
979
|
const id = ident(item.id);
|
|
782
980
|
if ('machine' in item) {
|
|
@@ -791,7 +989,7 @@ export function toLean(items, opts = {}) {
|
|
|
791
989
|
}
|
|
792
990
|
// One twin: every machine goes to Lean as a machine with sets (one with none, if it has none): its laws, step laws,
|
|
793
991
|
// liveness and ghosts in one export; a machine with a history as its plain machine, and its laws over histories.
|
|
794
|
-
parts.push(`-- ${item.id}\n${item.lean?.defs ? `${item.lean.defs}\n` : ''}${smachineToLean(id, m, item.laws, { stepLaws: item.stepLaws, ...(item.liveness?.length ? { liveness: item.liveness } : {}),
|
|
992
|
+
parts.push(`-- ${item.id}\n${recurDefs(m)}${item.lean?.defs ? `${item.lean.defs}\n` : ''}${smachineToLean(id, m, item.laws, { stepLaws: item.stepLaws, ...(item.liveness?.length ? { liveness: item.liveness } : {}),
|
|
795
993
|
...(item.ghosts?.length ? { ghosts: item.ghosts } : {}), ...(item.lean?.simp ? { simp: item.lean.simp } : {}), ...(item.lean?.after ? { after: item.lean.after } : {}) })}`);
|
|
796
994
|
theorems.push(`${id}_laws`, ...(m.history ? [`${id}_hlaws`] : []), ...(item.stepLaws ?? []).map((_, j) => `${id}_step${j}_holds`), ...(item.liveness ?? []).map((_, j) => `${id}_live${j}_holds`));
|
|
797
995
|
if (item.lean?.after)
|
|
@@ -3,3 +3,7 @@ export declare function leanTerms(vars: readonly string[], maps: readonly string
|
|
|
3
3
|
e: (t: E) => string;
|
|
4
4
|
c: (t: C) => string;
|
|
5
5
|
};
|
|
6
|
+
export declare function leanSurface(names: Record<string, string>, where: string, get?: (map: string, key: E, e: (t: E) => string) => string): {
|
|
7
|
+
e: (t: E) => string;
|
|
8
|
+
c: (t: C) => string;
|
|
9
|
+
};
|
|
@@ -42,3 +42,41 @@ export function leanTerms(vars, maps, where) {
|
|
|
42
42
|
};
|
|
43
43
|
return { e, c };
|
|
44
44
|
}
|
|
45
|
+
// Terms and conditions as plain Lean over Nat (a recurrence's definition and its lemmas): each variable as the Lean
|
|
46
|
+
// expression `names` gives it.
|
|
47
|
+
export function leanSurface(names, where, get) {
|
|
48
|
+
const name = (n) => { const s = names[n]; if (s === undefined)
|
|
49
|
+
throw new Error(`${where}: no Lean expression for ${n}`); return s; };
|
|
50
|
+
const e = (t) => {
|
|
51
|
+
switch (t.op) {
|
|
52
|
+
case 'var': return name(t.name);
|
|
53
|
+
case 'const': return String(t.value);
|
|
54
|
+
case 'mul': return `(${t.k} * ${e(t.a)})`;
|
|
55
|
+
case 'add': return `(${e(t.a)} + ${e(t.b)})`;
|
|
56
|
+
case 'sub': return `(${e(t.a)} - ${e(t.b)})`;
|
|
57
|
+
case 'prod': return `(${e(t.a)} * ${e(t.b)})`;
|
|
58
|
+
case 'min':
|
|
59
|
+
case 'max': return `(${t.op} ${e(t.a)} ${e(t.b)})`;
|
|
60
|
+
case 'if': return `(if ${c(t.c)} then ${e(t.then)} else ${e(t.else)})`;
|
|
61
|
+
case 'get':
|
|
62
|
+
if (!get)
|
|
63
|
+
throw new Error(`${where}: reads ${t.map}, a function or a map`);
|
|
64
|
+
return get(t.map, t.key, e);
|
|
65
|
+
default: return unreachable(t);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
const c = (t) => {
|
|
69
|
+
switch (t.op) {
|
|
70
|
+
case '<=': return `${e(t.a)} ≤ ${e(t.b)}`;
|
|
71
|
+
case '<': return `${e(t.a)} < ${e(t.b)}`;
|
|
72
|
+
case '=': return `${e(t.a)} = ${e(t.b)}`;
|
|
73
|
+
case 'not': return `¬(${c(t.x)})`;
|
|
74
|
+
case 'and':
|
|
75
|
+
case 'or': return t.xs.length === 0 ? (t.op === 'and' ? 'True' : 'False') : `(${t.xs.map(c).join(t.op === 'and' ? ' ∧ ' : ' ∨ ')})`;
|
|
76
|
+
case 'flag':
|
|
77
|
+
case 'all': throw new Error(`${where}: takes no flag or quantifier`);
|
|
78
|
+
default: return unreachable(t);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
return { e, c };
|
|
82
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdp-wired",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "An automatic prover for stateful rules (budgets, caps, windows, workflows, policies): laws proven with no proof to write, checked again in Lean, counterexamples found on the GPU, the rule shipped as code",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"verification",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"state-machine",
|
|
10
10
|
"rate-limiting",
|
|
11
11
|
"policy",
|
|
12
|
+
"gdp-ts",
|
|
12
13
|
"lean",
|
|
13
14
|
"automatic-prover",
|
|
14
15
|
"theorem-prover",
|
|
@@ -59,6 +60,10 @@
|
|
|
59
60
|
"types": "./dist/core/solver.d.ts",
|
|
60
61
|
"default": "./dist/core/solver.js"
|
|
61
62
|
},
|
|
63
|
+
"./gdp": {
|
|
64
|
+
"types": "./dist/core/gdp.d.ts",
|
|
65
|
+
"default": "./dist/core/gdp.js"
|
|
66
|
+
},
|
|
62
67
|
"./package.json": "./package.json"
|
|
63
68
|
},
|
|
64
69
|
"files": [
|
|
@@ -69,6 +74,14 @@
|
|
|
69
74
|
"engines": {
|
|
70
75
|
"node": ">=20"
|
|
71
76
|
},
|
|
77
|
+
"peerDependencies": {
|
|
78
|
+
"@gdp-ts/core": ">=0.1.0"
|
|
79
|
+
},
|
|
80
|
+
"peerDependenciesMeta": {
|
|
81
|
+
"@gdp-ts/core": {
|
|
82
|
+
"optional": true
|
|
83
|
+
}
|
|
84
|
+
},
|
|
72
85
|
"scripts": {
|
|
73
86
|
"typecheck": "tsc -p .",
|
|
74
87
|
"lint": "oxlint --type-aware --deny-warnings --disable-nested-config",
|
|
@@ -76,13 +89,14 @@
|
|
|
76
89
|
"build": "bun scripts/build.ts",
|
|
77
90
|
"prepublishOnly": "bun scripts/build.ts",
|
|
78
91
|
"smoke": "sh scripts/smoke.sh",
|
|
79
|
-
"test": "bun scripts/gen-lean-library.ts --check && cd core && bun test solver.test.ts dsl.test.ts conformance.test.ts compose.test.ts quantifiers.test.ts functions.test.ts keyterms.test.ts lists.test.ts liveness.test.ts sizes.test.ts values.test.ts builders.test.ts && cd ../kernel && bun test tamper.test.ts refusals.test.ts steplaw.test.ts sets.test.ts && cd ../examples && bun test examples.test.ts && cd .. && bun examples/readme.ts && bun examples/composition.ts && bun examples/sets.ts && bun examples/quantifiers.ts && bun examples/functions.ts && bun examples/lists.ts && bun examples/sizes.ts && bun examples/liveness.ts && bun examples/budget.ts && bun examples/steps.ts && bun examples/jev-fabric/run.ts"
|
|
92
|
+
"test": "bun scripts/gen-lean-library.ts --check && cd core && bun test solver.test.ts dsl.test.ts conformance.test.ts compose.test.ts quantifiers.test.ts functions.test.ts keyterms.test.ts lists.test.ts liveness.test.ts sizes.test.ts values.test.ts builders.test.ts gdp.test.ts datatypes.test.ts lemmas.test.ts && cd ../kernel && bun test tamper.test.ts refusals.test.ts steplaw.test.ts sets.test.ts && cd ../examples && bun test examples.test.ts && cd .. && bun examples/readme.ts && bun examples/composition.ts && bun examples/sets.ts && bun examples/quantifiers.ts && bun examples/functions.ts && bun examples/datatypes.ts && bun examples/lemmas.ts && bun examples/lists.ts && bun examples/sizes.ts && bun examples/liveness.ts && bun examples/budget.ts && bun examples/gdp.ts && bun examples/steps.ts && bun examples/jev-fabric/run.ts"
|
|
80
93
|
},
|
|
81
94
|
"devDependencies": {
|
|
95
|
+
"@gdp-ts/core": "0.1.0",
|
|
82
96
|
"@types/bun": "1.3.1",
|
|
83
|
-
"typescript": "5.9.3",
|
|
84
97
|
"@webgpu/types": "0.1.66",
|
|
85
98
|
"oxlint": "1.88.0",
|
|
86
|
-
"oxlint-tsgolint": "7.0.2004"
|
|
99
|
+
"oxlint-tsgolint": "7.0.2004",
|
|
100
|
+
"typescript": "5.9.3"
|
|
87
101
|
}
|
|
88
102
|
}
|