gdp-wired 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,7 @@
1
- <img src="https://raw.githubusercontent.com/monotykamary/gdp-wired/main/static/banner-ink.svg" alt="gdp-wired" width="100%" />
1
+ <picture>
2
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/monotykamary/gdp-wired/main/static/banner-dark.svg">
3
+ <img src="https://raw.githubusercontent.com/monotykamary/gdp-wired/main/static/banner-light.svg" alt="gdp-wired: an automatic prover for stateful rules" width="100%">
4
+ </picture>
2
5
 
3
6
  # gdp-wired
4
7
 
@@ -27,8 +30,10 @@ proveRule(budget, [bounded]) // {proven: true, checked: true}
27
30
  Names are typed: `({s, bb}) => …` or a law that reads the request `c` is a TypeScript error, not a runtime surprise.
28
31
  Forget the cap (`s.plus(c)`) and the kernel refuses the proof; a search from the start turns it into a real run:
29
32
  `{params: {b: 1, m: 0}, trace: [{c: 2}]}`. `bun examples/budget.ts` runs all of it, including the generated JavaScript,
30
- and `examples/domains/` holds 92 rules from nine domains (finance, access control, public services, operations,
31
- protocols, commerce, supply chains, games, education), each checked three ways.
33
+ and `examples/domains/` holds 187 rules from 21 domains (finance, access control, public services, operations,
34
+ protocols, commerce, supply chains, games, education, healthcare, energy, networking, AI agents, cloud, ad tech,
35
+ governance, insurance, bookings, safety and IoT, mobility, media), each checked three ways: the kernel proves every law
36
+ and refutes every broken variant, random runs of both runtimes agree, and Lean checks the trees.
32
37
 
33
38
  ## Where it fits
34
39
 
@@ -101,6 +106,39 @@ The untrusted parts are held to the trusted ones by the test suite:
101
106
  tests, and the few that survive are equivalent (`kernel/MUTANTS.md`).
102
107
  - **Fuzz**: the kernel never accepts a refutation of something that has a model.
103
108
 
109
+ ## With gdp-ts
110
+
111
+ [gdp-ts](https://github.com/rauchg/gdp-ts) makes every call site present a proof that a check ran on exactly these
112
+ values, and trusts the module that performs the check. `gdp-wired/gdp` proves that check: the trusted module hands its
113
+ private prover to a `guard` (a function's condition) or a `gate` (a rule's flag), which proves the laws when the module
114
+ loads, refuses to load if one fails, and mints a proof only where the proven condition holds. gdp-ts then holds every
115
+ call site to it: the policy is proven, every caller is held to it, and Lean checks the proof again.
116
+
117
+ ```ts
118
+ // proofs/can-set-protection.ts (trusted: the only module that can mint this proof)
119
+ import {defineProof, type Named, type Proof} from '@gdp-ts/core'
120
+ import {fun, no, oneOf} from 'gdp-wired'
121
+ import {guard} from 'gdp-wired/gdp'
122
+
123
+ const canSet = fun('can set password protection')
124
+ .inputs({role: oneOf('owner', 'member', 'viewer', 'none'), plan: oneOf('free', 'pro', 'enterprise')})
125
+ .returns(({role, plan}) => ({allow: role.is('owner').or(role.is('member')).and(plan.is('free').not())}))
126
+ const laws = [canSet.law('the free plan never has protection', ({plan}, out) => [plan.is('free'), out.allow], () => no)]
127
+
128
+ const CanSetProtection = defineProof('CanSetProtection') // not exported
129
+ export interface CanSetProtection<U, P> extends Proof<'CanSetProtection', [U, P]> {}
130
+ const mint = guard(CanSetProtection, canSet, 'allow', {laws}) // proven here, or the module doesn't load
131
+
132
+ export async function canSetProtection<U, P>(user: Named<U, UserId>, project: Named<P, ProjectId>): Promise<CanSetProtection<U, P> | null> {
133
+ return mint({role: await db.role(user.value, project.value), plan: await db.plan(project.value)}, user, project)
134
+ }
135
+ ```
136
+
137
+ A `gate` does the same for a rule with state: each request takes the rule's step, and a proof is minted when the step
138
+ takes the flag (a one-time token redeemed, a sign-in while the account is unlocked, an email within the hourly cap).
139
+ Requests outside what was proven (a failed `assume`, a history read out of order) mint nothing and leave the state as it
140
+ was. `examples/gdp.ts` runs gdp-ts's own example this way; `@gdp-ts/core` is an optional peer dependency.
141
+
104
142
  ## Writing laws
105
143
 
106
144
  The builders (`core/dsl.ts`) hand out typed values for every name you declare; everything compiles to the plain machine
@@ -230,7 +268,7 @@ Lean, compiled natively, wins where the state stays small. Details in [`docs/com
230
268
  | `core/` | Machines, the interpreter and JS compiler, the builders, composition (`compose.ts`), the linear solver and its checker, Houdini invariants, models, the first Lean export |
231
269
  | `kernel/` | The trusted kernel and the untrusted prover, with soundness fuzz (`kernel.test.ts`, `history.test.ts`) |
232
270
  | `lean-kernel/` | The kernel's Lean twin, its soundness proofs, and the exported trees |
233
- | `examples/` | The builders at work: `domains/` (92 rules across nine domains, each with laws and broken variants), `harness.ts` (the triple check), `readme.ts`, `budget.ts`, `composition.ts`, `sets.ts`, `quantifiers.ts`, `functions.ts`, `lists.ts`, `sizes.ts`, `liveness.ts`, `steps.ts` (laws over up to 16 steps), and `jev-fabric/` (a Bend project's 59 proven laws, ported) |
271
+ | `examples/` | The builders at work: `domains/` (187 rules across 21 domains, each with laws and broken variants), `harness.ts` (the triple check), `readme.ts`, `budget.ts`, `gdp.ts` (gdp-ts's example, its checks proven), `composition.ts`, `sets.ts`, `quantifiers.ts`, `functions.ts`, `lists.ts`, `sizes.ts`, `liveness.ts`, `steps.ts` (laws over up to 16 steps), and `jev-fabric/` (a Bend project's 59 proven laws, ported) |
234
272
  | `docs/` | The comparison with Bend and Lean, and the full scope |
235
273
  | `compare/` | The same three rules in gdp-wired, Bend and plain Lean, checked and measured |
236
274
  | `research/` | Everything around the library ([`research/README.md`](research/README.md)): the GPU searches (`search/`, `hw/`, `modal-gpu/`), invariant and rank synthesis (`synth/`), relational laws and guard synthesis (`next/`), bit-vector laws (`sat/`), the enumeration and benchmarks (`enum/`, `gpu/`, `perf/`), the first Lean checker (`lean/`) and the first prototype, the audit path (`audit/`) |
@@ -266,10 +304,24 @@ modal run modal/run.py --jobs modal/suite.json --out runs/modal/suite && sh moda
266
304
 
267
305
  Each directory's README has its own runs and results.
268
306
 
307
+ ## The skill
308
+
309
+ `npx skills add monotykamary/gdp-wired` installs a skill that guides coding agents (and people) through writing a rule
310
+ the way the code does it, stating its laws, reading a refused proof and handing a claim to Lean:
311
+
312
+ - [**Workflow**](skills/gdp-wired/SKILL.md#workflow): the checklist for adding or changing a rule whose invariants matter.
313
+ - [**Writing rules**](skills/gdp-wired/references/writing-rules.md): kinds, maps, step and k-step laws, histories,
314
+ liveness, decisions, two-run laws, loops.
315
+ - [**Reading verdicts**](skills/gdp-wired/references/reading-verdicts.md): a bug or a missing invariant, and what to
316
+ change.
317
+ - [**Shipping**](skills/gdp-wired/references/shipping.md): running the proven rule, and holding existing code to it.
318
+ - [**The Lean handoff**](skills/gdp-wired/references/lean-handoff.md): exporting the proofs, and functions proven in Lean.
319
+ - [**Where it fits**](skills/gdp-wired/references/where-it-fits.md): gdp-wired, Lean or Bend.
320
+
269
321
  ## Credits
270
322
 
271
323
  [gdp-ts](https://github.com/rauchg/gdp-ts) by Guillermo Rauch (Ghosts of Departed Proofs for TypeScript) is where the
272
- audit path starts; [Handwired](https://github.com/chenglou/handwired) by Cheng Lou is the circuit and GPU runtime this
324
+ audit path starts, and what `gdp-wired/gdp` mints proofs for; [Handwired](https://github.com/chenglou/handwired) by Cheng Lou is the circuit and GPU runtime this
273
325
  builds on, through a fork; [Bend](https://github.com/bendlang/bend) is the language the laws were first written in,
274
326
  and the yardstick for the benchmarks.
275
327
 
@@ -469,6 +469,7 @@ export declare class Fun<P extends Obj = {}, I extends Obj = {}, O extends Obj =
469
469
  }): Value<O>;
470
470
  }
471
471
  export declare const fun: (name: string) => Fun;
472
+ export declare const funOf: (d: Decision) => Fun<Obj, Obj, Obj> | undefined;
472
473
  export type Agreement = {
473
474
  checked: number;
474
475
  failure?: string;
package/dist/core/dsl.js CHANGED
@@ -1457,11 +1457,11 @@ export class Fun {
1457
1457
  }
1458
1458
  // Whenever the arguments (and the result) meet `given`, the result meets `then`.
1459
1459
  law(name, given, then) {
1460
- return this.r.decide(name, env => given(env, this.result(env)), env => then(this.result(env), env));
1460
+ return lawOfFun(this, this.r.decide(name, env => given(env, this.result(env)), env => then(this.result(env), env)));
1461
1461
  }
1462
1462
  // A law about two calls under the same params: `then(outA, outB, a, b)` given `given(a, b)` (noninterference, say).
1463
1463
  relate(name, given, then) {
1464
- return this.r.relate(name, given, (a, b) => then(this.result(a), this.result(b), a, b));
1464
+ return lawOfFun(this, this.r.relate(name, given, (a, b) => then(this.result(a), this.result(b), a, b)));
1465
1465
  }
1466
1466
  // Against the code it describes: `reference` called with the same arguments (drawn by their kinds, or by `draw`, the
1467
1467
  // `edges` first) must return the same value for every field it returns (a field it leaves out is not compared). The
@@ -1499,6 +1499,11 @@ export class Fun {
1499
1499
  }
1500
1500
  }
1501
1501
  export const fun = (name) => new Fun(rule(name));
1502
+ // The function a law was stated about. Variants of one function share its arguments, so a law's terms alone don't say
1503
+ // which result it is about; a guard (gdp.ts) takes only laws stated about the function it runs.
1504
+ const lawsOfFuns = new WeakMap();
1505
+ const lawOfFun = (f, d) => { lawsOfFuns.set(d, f); return d; };
1506
+ export const funOf = (d) => lawsOfFuns.get(d);
1502
1507
  // A loop runs `body` while `while` holds, each turn with fresh inputs; it halts if a rank over the state and params
1503
1508
  // (or a tuple of them, compared in order) drops on every turn.
1504
1509
  export class LoopRule {
@@ -0,0 +1,35 @@
1
+ import type { Named, NamesOf, Proof, Prover } from '@gdp-ts/core';
2
+ import { type Decision, type Fun, type Rule, type StepLaw, type Verdict } 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 declare class Unproven extends Error {
7
+ readonly verdict: Verdict;
8
+ constructor(what: string, verdict: Verdict);
9
+ }
10
+ type AnyFun = Fun<any, any, any>;
11
+ type Conditions<O> = {
12
+ [k in keyof O]: O[k] extends boolean ? k : never;
13
+ }[keyof O] & string;
14
+ type ArgsOf<F extends AnyFun> = Parameters<F['run']>[0];
15
+ type ParamsOf<F extends AnyFun> = NonNullable<Parameters<F['run']>[1]>;
16
+ export declare function guard<K extends string, F extends AnyFun>(prover: Prover<K>, fn: F, field: Conditions<ReturnType<F['run']>>, opts: {
17
+ laws: Decision[];
18
+ params?: ParamsOf<F>;
19
+ }): Mint<K, ArgsOf<F>>;
20
+ type AnyRule = Rule<any, any, any, any, any, any, any, any>;
21
+ type FlagOf<R> = R extends Rule<any, any, any, any, infer F, any, any, any> ? keyof F & string : never;
22
+ type InputOf<R extends AnyRule> = Parameters<R['encodeInput']>[0];
23
+ type RuleParams<R extends AnyRule> = Parameters<R['encodeParams']>[0];
24
+ export type Gate<K extends string, I> = {
25
+ request: Mint<K, I>;
26
+ readonly state: State;
27
+ values(): Record<string, unknown>;
28
+ };
29
+ export declare function gate<K extends string, R extends AnyRule>(prover: Prover<K>, r: R, flag: FlagOf<R>, opts: {
30
+ laws: Law[];
31
+ stepLaws?: StepLaw[];
32
+ params?: RuleParams<R>;
33
+ state?: State;
34
+ }): Gate<K, InputOf<R>>;
35
+ export {};
@@ -0,0 +1,66 @@
1
+ import { funOf, proveDecision, proveSteps } from "./dsl.js";
2
+ import { evalC, interpret } from "./machine.js";
3
+ import { allowed, requestEnv } from "./watch.js";
4
+ // A verdict that isn't a checked proof, as the error a guard or gate is refused with (the verdict on `.verdict`).
5
+ export class Unproven extends Error {
6
+ verdict;
7
+ constructor(what, verdict) {
8
+ const found = [verdict.goal && `goal ${verdict.goal}`, verdict.stuck, verdict.witness && `a counterexample: ${JSON.stringify(verdict.witness)}`, verdict.why].filter(Boolean).join('; ');
9
+ super(`${what}: ${verdict.proven ? 'proven, but the kernel did not check the proof' : `not proven${found ? ` (${found})` : ''}`}`);
10
+ this.verdict = verdict;
11
+ }
12
+ }
13
+ const proven = (what, v) => { if (!(v.proven && v.checked))
14
+ throw new Unproven(what, v); };
15
+ // A proof wherever `fn`'s result `field` holds. Every law in `laws` must have been stated about `fn` (fn.law or
16
+ // fn.relate) and is proven now; the arguments it is later called with must meet the function's kinds and `assume`.
17
+ // const access = fun('document access').inputs({...}).returns(env => ({permit: permit(env)}))
18
+ // const mint = guard(CanRead, access, 'permit', {laws: [access.law('...', ...)]})
19
+ export function guard(prover, fn, field, opts) {
20
+ if (opts.laws.length === 0)
21
+ throw new Error(`${fn.name}: a guard needs a law to prove`);
22
+ for (const d of opts.laws) {
23
+ if (funOf(d) !== fn)
24
+ throw new Error(`${fn.name}: "${d.name}" was not stated about this function (state it with ${fn.name}.law)`);
25
+ proven(`${fn.name}: "${d.name}"`, proveDecision(d));
26
+ }
27
+ const r = fn.rule, m = r.machine, params = (opts.params ?? {}), ps = r.encodeParams(params);
28
+ if (m.pre && !evalC(m.pre, ps, { scalars: {}, maps: {} }, m))
29
+ throw new Error(`${fn.name}: the params ${JSON.stringify(params)} don't meet its precondition`);
30
+ return (args, ...about) => {
31
+ const input = r.encodeInput(args);
32
+ if (m.assume && !evalC(m.assume, { ...ps, ...input }, { scalars: {}, maps: {} }, m))
33
+ return null;
34
+ return fn.run(args, params)[field] === true ? prover.prove(...about) : null;
35
+ };
36
+ }
37
+ // A proof for each request whose step takes `flag`, the rule's laws (and step laws) proven now. One gate holds one
38
+ // run's state: keep one per state it guards (per process, or loaded and saved around each request in a transaction).
39
+ // const tokens = rule('one-time tokens').inputs({...}).maps({...}).flags(env => ({redeem: ...})).step(...)
40
+ // const redeem = gate(Redeemed, tokens, 'redeem', {laws: tokenLaws})
41
+ // redeem.request({op: 'redeem', token: 7}, user, token) // a Redeemed proof the first time, null after
42
+ export function gate(prover, r, flag, opts) {
43
+ const m = r.machine;
44
+ if (!(flag in (m.step.flags ?? {})))
45
+ throw new Error(`${m.name}: no flag ${flag}`);
46
+ if (opts.laws.length + (opts.stepLaws?.length ?? 0) === 0)
47
+ throw new Error(`${m.name}: a gate needs a law to prove`);
48
+ proven(m.name, proveSteps(r, opts.laws, opts.stepLaws ?? []));
49
+ const params = (opts.params ?? {}), ps = r.encodeParams(params);
50
+ if (m.pre && !evalC(m.pre, ps, { scalars: {}, maps: {} }, m))
51
+ throw new Error(`${m.name}: the params ${JSON.stringify(params)} don't meet its precondition`);
52
+ const run = interpret(m), compiled = r.compiled();
53
+ let st = opts.state ?? compiled.init(ps);
54
+ return {
55
+ request: (args, ...about) => {
56
+ const input = r.encodeInput(args);
57
+ if (!allowed(m, run, ps, st, input))
58
+ return null;
59
+ const granted = requestEnv(m, run, ps, st, input)[`?${flag}`] === 1;
60
+ st = compiled.step(ps, st, input);
61
+ return granted ? prover.prove(...about) : null;
62
+ },
63
+ get state() { return st; },
64
+ values: () => r.decode(st),
65
+ };
66
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdp-wired",
3
- "version": "0.6.0",
3
+ "version": "0.7.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 && 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/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
  }