Skip to content

Commit db8c1f8

Browse files
author
rohan
committed
refactor: promote gds-proof to Layer 1 with gds-framework integration
gds-proof was a standalone package with no GDS dependencies, using protocol-only types (ProofableBlock/ProofableModel). This refactor makes it a proper Layer 1 package that depends on gds-framework and integrates with the existing verification infrastructure. Changes: - Add gds-framework>=0.3.0 dependency - Rename ProofableBlock -> SymbolicBlock, ProofableModel -> SymbolicModel - Add adapter.py: GDSSymbolicBlock/GDSSymbolicModel bridge GDS types to proof engine, auto-deriving state symbols from Mechanism.updates - Add findings.py: convert proof results to Finding/VerificationReport with PROOF-001 check ID and exportable_predicate population - Rename reachability -> inductive_safety to avoid naming collision with gds_analysis.reachability (simulation-based) - Add docs/proof/ with overview and getting-started guide - Add changelog entries for v0.1.0 and v0.2.0 - 130 tests pass (18 new), 92% coverage, zero lint errors Deprecated aliases maintained for backward compatibility until v1.0.0.
1 parent 1c62cfc commit db8c1f8

18 files changed

Lines changed: 1787 additions & 662 deletions

File tree

CHANGELOG.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,63 @@
11
# Changelog
22

3+
## 2026-04-07 — gds-proof Layer 1 Integration
4+
5+
Promotes gds-proof from a standalone package (Layer 0, protocol-only) to a
6+
Layer 1 package that depends on `gds-framework` and integrates with the
7+
existing verification infrastructure.
8+
9+
### gds-proof v0.2.0
10+
11+
**Breaking:** `ProofableBlock` renamed to `SymbolicBlock`, `ProofableModel`
12+
renamed to `SymbolicModel`. Deprecated aliases remain until v1.0.0.
13+
14+
**Breaking:** `analyze_reachability()` renamed to `analyze_inductive_safety()`,
15+
`ReachabilityAnalysisResult` renamed to `InductiveSafetyResult`. Deprecated
16+
aliases remain until v1.0.0. This resolves a naming collision with
17+
`gds_analysis.reachability` (simulation-based forward/backward state sets).
18+
19+
New capabilities:
20+
21+
- **gds-framework dependency** -- gds-proof is now a Layer 1 package alongside
22+
gds-analysis, gds-domains, and gds-viz
23+
- **`GDSSymbolicBlock`** -- adapter wrapping `AtomicBlock` + `GDSSpec` with
24+
user-supplied SymPy expressions. Auto-derives `prev_state_symbols` from
25+
`Mechanism.updates` + `Entity.variables[var].symbol`
26+
- **`GDSSymbolicModel`** -- adapter wrapping `GDSSpec` with symbolic enrichments
27+
and invariants. Auto-derives assumption context from `ParameterDef.bounds`
28+
- **`findings.py`** -- converts proof results to `Finding`/`VerificationReport`
29+
objects. Populates `Finding.exportable_predicate` with canonical SymPy form.
30+
Check ID: `PROOF-001` (invariant preservation)
31+
- **`derive_state_symbols()`** -- utility to extract SymPy symbols from mechanism
32+
updates + entity variable symbols
33+
- **`derive_assumption_context()`** -- utility to build SymPy assumption dicts
34+
from parameter schema bounds
35+
36+
Documentation:
37+
38+
- Added `docs/proof/index.md` and `docs/proof/getting-started.md`
39+
- Updated `CLAUDE.md` for Layer 1 architecture
40+
- Added gds-proof to mkdocs.yml navigation and llmstxt sections
41+
42+
---
43+
44+
## 2026-04-07 — gds-proof v0.1.0
45+
46+
New package added via PR #193. Extracts the domain-agnostic proof engine from
47+
`crypto-econ-dynamics-skill` into gds-core as the ninth workspace package.
48+
49+
Capabilities:
50+
51+
- Deterministic SHA-256 model identity (`hash_model`, `hash_proof`)
52+
- 5-strategy symbolic implication prover (`analyze_invariants`)
53+
- 3-layer predicate-guarded reachability (`analyze_reachability`)
54+
- User-authored multi-lemma ProofScript with independent verification
55+
- Canonical srepr serialization for third-party evidence exchange
56+
57+
112 tests, 95% coverage.
58+
59+
---
60+
361
## 2026-04-05 — Package Consolidation (14 → 8 packages)
462

563
Implements the consolidation plan from issue #143. Reduces the monorepo from

docs/changelog.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,29 @@
33
All notable changes to the GDS ecosystem are documented here. Each release
44
lists affected packages, breaking changes, and new capabilities.
55

6+
## 2026-04-07 — gds-proof Layer 1 Integration
7+
8+
Promotes gds-proof from standalone (Layer 0, protocol-only) to Layer 1
9+
(depends on `gds-framework`, integrates with verification pipeline).
10+
11+
### gds-proof v0.2.0
12+
13+
**Breaking:** `ProofableBlock` -> `SymbolicBlock`, `ProofableModel` -> `SymbolicModel`.
14+
`analyze_reachability()` -> `analyze_inductive_safety()`. Deprecated aliases until v1.0.0.
15+
16+
New:
17+
18+
- **`GDSSymbolicBlock` / `GDSSymbolicModel`** -- adapters bridging GDS types to proof engine
19+
- **`findings.py`** -- converts proof results to `Finding`/`VerificationReport` (check ID: `PROOF-001`)
20+
- **`derive_state_symbols()`** / **`derive_assumption_context()`** -- GDS type utilities
21+
- Documentation: `docs/proof/` with overview and getting-started guide
22+
23+
### gds-proof v0.1.0
24+
25+
New package (PR #193). Deterministic model identity and SymPy-based invariant
26+
proof verification. 5-strategy implication prover, 3-layer inductive safety,
27+
multi-lemma ProofScript system. 112 tests, 95% coverage.
28+
629
---
730

831
## 2026-04-03 — Tier 0 + Tier 1 Complete

docs/proof/getting-started.md

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
# Getting Started
2+
3+
## Installation
4+
5+
```bash
6+
uv add gds-proof
7+
```
8+
9+
For development (monorepo):
10+
11+
```bash
12+
git clone https://github.com/BlockScience/gds-core.git
13+
cd gds-core
14+
uv sync --all-packages
15+
```
16+
17+
## Your First Invariant Proof
18+
19+
The typical workflow: define a GDS spec, enrich blocks with symbolic expressions, declare invariants, and run the proof engine.
20+
21+
### 1. Define the structural spec
22+
23+
```python
24+
import sympy
25+
from gds import GDSSpec, Mechanism, Entity, interface, state_var, typedef
26+
27+
# Types and state
28+
Balance = typedef("Balance", float)
29+
account = Entity(
30+
name="Account",
31+
variables={"balance": state_var(Balance, symbol="x_prev")},
32+
)
33+
34+
# Mechanism block
35+
withdrawal = Mechanism(
36+
name="withdrawal",
37+
interface=interface(forward_in=["Balance Command"]),
38+
updates=[("Account", "balance")],
39+
)
40+
41+
# Register in a spec
42+
spec = GDSSpec(name="bank_account")
43+
spec.collect(Balance, account, withdrawal)
44+
```
45+
46+
### 2. Enrich with symbolic expressions
47+
48+
GDS blocks are structural -- they don't carry SymPy math. Use `GDSSymbolicBlock` to pair a block with its symbolic state transition:
49+
50+
```python
51+
from gds_proof import (
52+
GDSSymbolicBlock, GDSSymbolicModel,
53+
Invariant, predicate_from_post_check,
54+
)
55+
56+
X = sympy.Symbol("x_prev") # pre-state (plain, no assumptions!)
57+
U = sympy.Symbol("u") # input
58+
59+
# Build a predicate: balance must remain positive after withdrawal
60+
pred = predicate_from_post_check(
61+
name="no_overdraft",
62+
post_state_check=sympy.Symbol("x") > 0, # desired post-state property
63+
state_transition={"x": X - U}, # x = x_prev - u
64+
)
65+
66+
# Enrich the mechanism with symbolic expressions
67+
symbolic_block = GDSSymbolicBlock(
68+
block=withdrawal,
69+
spec=spec,
70+
state_transition={"x_prev": X - U}, # f(x_prev, u) = x_prev - u
71+
output_expressions={"balance": X - U}, # observable output
72+
predicates_list=[pred.expr], # admissibility guard
73+
inputs=frozenset({U}),
74+
)
75+
```
76+
77+
### 3. Declare invariants and run analysis
78+
79+
```python
80+
from gds_proof import analyze_invariants, analyze_inductive_safety
81+
82+
# Build the proof-ready model
83+
model = GDSSymbolicModel(
84+
spec=spec,
85+
enrichments={"withdrawal": symbolic_block},
86+
invariants_dict={
87+
"balance_nonneg": Invariant(
88+
name="balance_nonneg",
89+
expr=sympy.Ge(X, 0), # x_prev >= 0
90+
),
91+
},
92+
assumptions={
93+
X: {"nonnegative": True, "real": True},
94+
U: {"nonnegative": True, "real": True},
95+
},
96+
)
97+
98+
# Run symbolic analysis
99+
result = analyze_invariants(model)
100+
for r in result.results:
101+
print(f"{r.invariant_name} x {r.mechanism_name}: "
102+
f"{r.status} ({r.proof_method})")
103+
104+
# Run inductive safety analysis
105+
safety = analyze_inductive_safety(model)
106+
print(f"Multi-step verdict: {safety.multi_step.verdict}")
107+
```
108+
109+
### 4. Convert to VerificationReport
110+
111+
Integrate proof results with the existing verification pipeline:
112+
113+
```python
114+
from gds_proof.findings import (
115+
symbolic_analysis_to_findings,
116+
proof_findings_to_report,
117+
)
118+
119+
findings = symbolic_analysis_to_findings(result)
120+
report = proof_findings_to_report("bank_account", findings)
121+
print(f"Checks: {report.checks_total}, Errors: {report.errors}")
122+
```
123+
124+
## Auxiliary Proofs for INCONCLUSIVE Results
125+
126+
When the automatic prover can't resolve a pair, build a manual proof script:
127+
128+
```python
129+
from gds_proof import (
130+
hash_model, ProofBuilder, LemmaKind,
131+
verify_proof, attach_proof,
132+
)
133+
134+
model_hash = hash_model(model)
135+
136+
# Build a proof script with a geometric series lemma
137+
k = sympy.Symbol("k", integer=True, nonneg=True)
138+
script = (
139+
ProofBuilder(
140+
model_hash, "balance_nonneg",
141+
"convergence_proof", "Balance converges to steady state",
142+
)
143+
.lemma(
144+
"series_limit",
145+
LemmaKind.EQUALITY,
146+
expr=sympy.Sum(sympy.Rational(1, 2) ** k, (k, 0, sympy.oo)),
147+
expected=sympy.Integer(2),
148+
)
149+
.build()
150+
)
151+
152+
# Verify independently (no trust in original analyst)
153+
proof_result = verify_proof(script, model_hash)
154+
print(f"Proof status: {proof_result.status}")
155+
156+
# Attach to invariant if VERIFIED
157+
if proof_result.status == "VERIFIED":
158+
inv = model.invariants()["balance_nonneg"]
159+
inv = attach_proof(inv, script, model_hash)
160+
print(f"Proof hash: {inv.proof_hash}")
161+
```
162+
163+
## Next Steps
164+
165+
- [Proof Overview](index.md) -- architecture, key concepts, and verification integration
166+
- [Framework](../framework/index.md) -- GDS specification and structural types
167+
- [Analysis](../analysis/index.md) -- simulation-based reachability and PSUU

docs/proof/index.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# gds-proof
2+
3+
[![PyPI](https://img.shields.io/pypi/v/gds-proof)](https://pypi.org/project/gds-proof/)
4+
[![Python](https://img.shields.io/pypi/pyversions/gds-proof)](https://pypi.org/project/gds-proof/)
5+
[![License](https://img.shields.io/github/license/BlockScience/gds-core)](https://github.com/BlockScience/gds-core/blob/main/LICENSE)
6+
7+
**Deterministic model identity and SymPy-based invariant proof verification for GDS models.**
8+
9+
## What is this?
10+
11+
`gds-proof` is a formal verification layer for the GDS ecosystem. It proves that system invariants are preserved across state transitions using symbolic implication analysis powered by SymPy.
12+
13+
Where `gds-analysis` answers "what happens when I simulate this?", `gds-proof` answers "can I *prove* this property always holds?"
14+
15+
- **`analyze_invariants()`** -- 5-strategy symbolic implication prover for every (invariant, block) pair
16+
- **`analyze_inductive_safety()`** -- 3-layer predicate-guarded inductive safety (single-step, predicate sufficiency, multi-step induction)
17+
- **`ProofBuilder` / `verify_proof()`** -- user-authored multi-lemma proof scripts for INCONCLUSIVE results
18+
- **`hash_model()` / `hash_proof()`** -- deterministic SHA-256 identity for models and proof scripts
19+
- **`GDSSymbolicBlock` / `GDSSymbolicModel`** -- adapters bridging GDS structural types to the proof engine
20+
21+
## Architecture
22+
23+
```
24+
gds-framework gds-proof
25+
| |
26+
| GDSSpec, AtomicBlock, | SymbolicBlock/SymbolicModel protocols
27+
| Mechanism, Entity, | GDSSymbolicBlock/GDSSymbolicModel adapters
28+
| Finding, VerificationReport | analyze_invariants(), analyze_inductive_safety()
29+
| | ProofBuilder, verify_proof(), attach_proof()
30+
+-------+ | hash_model(), hash_proof()
31+
| | Finding integration (findings.py)
32+
+--- Your application ---+
33+
|
34+
| Structural spec + symbolic expressions
35+
| -> invariant proofs + VerificationReport
36+
```
37+
38+
`gds-proof` sits at Layer 1 of the GDS ecosystem, alongside `gds-analysis`, `gds-domains`, and `gds-viz`. It depends on `gds-framework` for structural types and integrates with the existing verification pipeline via `Finding` and `VerificationReport`.
39+
40+
## Key Concepts
41+
42+
| Concept | What it is |
43+
|---------|-----------|
44+
| **Invariant** | A named SymPy BooleanExpr (e.g., `x >= 0`) that must hold across all transitions |
45+
| **SymbolicBlock** | A block enriched with symbolic state transition `f(x, u)`, output map `c(x, u)`, and predicates |
46+
| **Predicate** | Admissibility guard on inputs -- `U_{x} = {u : check(f(x, u)) = True}` |
47+
| **Proof obligation** | `I(x) AND P(x,u) -> I(f(x,u))` for each (invariant, block) pair |
48+
| **ProofScript** | Multi-lemma chain targeting one invariant, verified independently |
49+
| **Model hash** | SHA-256 of canonical_dict -- changes when the model changes |
50+
51+
## The R1/R3 Gap
52+
53+
GDS blocks are structural (R1) -- they declare *what* state variables a mechanism updates, but not *how*. The proof engine needs symbolic expressions (R3). The adapter layer bridges this:
54+
55+
- **Structural (from GDSSpec)**: block names, entity variables, mechanism updates, parameter schema
56+
- **Behavioral (from user)**: SymPy state transitions, output expressions, predicates
57+
- **Bridge**: `GDSSymbolicBlock` wraps an `AtomicBlock` with user-supplied symbolic expressions
58+
59+
## Verification Integration
60+
61+
Proof results convert to `gds-framework` `Finding` objects via `findings.py`:
62+
63+
| Check ID | What it checks |
64+
|----------|---------------|
65+
| `PROOF-001` | Invariant preservation (symbolic analysis) |
66+
67+
`Finding.exportable_predicate` is populated with the invariant's canonical SymPy form, making it available to the OWL/RDF exporter in `gds-interchange`.
68+
69+
## Quick Start
70+
71+
```bash
72+
uv add gds-proof
73+
```
74+
75+
See [Getting Started](getting-started.md) for a full walkthrough.
76+
77+
## Credits
78+
79+
Built on [gds-framework](../framework/index.md) and [SymPy](https://www.sympy.org/) by [BlockScience](https://block.science).

mkdocs.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,9 @@ plugins:
126126
Symbolic Math (gds-domains):
127127
- {symbolic/index.md: "SymPy bridge for gds-domains (control) — symbolic equations and linearization"}
128128
- {symbolic/getting-started.md: "Compile symbolic equations to ODE functions"}
129+
Proof (gds-proof):
130+
- {proof/index.md: "Deterministic model identity and SymPy invariant proof verification"}
131+
- {proof/getting-started.md: "Build your first invariant proof with GDS types"}
129132
Analysis (gds-analysis):
130133
- {analysis/index.md: "Bridge GDSSpec structural annotations to gds-sim runtime"}
131134
- {analysis/getting-started.md: "Convert a GDSSpec to a runnable simulation model"}
@@ -391,6 +394,9 @@ nav:
391394
- Symbolic Math:
392395
- Overview: symbolic/index.md
393396
- Getting Started: symbolic/getting-started.md
397+
- Proof:
398+
- Overview: proof/index.md
399+
- Getting Started: proof/getting-started.md
394400
- Analysis:
395401
- Overview: analysis/index.md
396402
- Getting Started: analysis/getting-started.md

0 commit comments

Comments
 (0)