Skip to content

Commit 9174471

Browse files
hyperpolymathclaude
andcommitted
feat: idrisiser Phase 1 — interface parser, Idris2 codegen, Zig bridge, tests
Complete implementation of idrisiser's core pipeline: Manifest (redesigned): - [project] with name and module-prefix - [[interfaces]] array: name, source, format (openapi/c-header/protobuf/type-sig), verify list, pre/postconditions, invariants - [proofs] config: totality, round-trip, QTT, search depth - [idris2] config: flags, codegen backend, packages Parser (codegen/parser.rs — 500 LOC): - OpenAPI: extract paths as function contracts from YAML - C header: parse function declarations, extract params/return types - Protocol Buffers: extract RPC method signatures - Type signatures: parse Idris2-style type annotations - Graceful fallback when source files not found - Auto-generate preconditions for pointer params (NULL checks) Idris2 codegen (codegen/idris2.rs — 460 LOC): - Generate complete .idr modules with %default total - Contract types: Result, Params records, Pre/Post predicates - Proof obligations: Totality, PreSatisfied, PostSatisfied, RoundTrip - FFI declarations matching Zig bridge exports - Safe wrappers enforcing contracts at the type level - AllProofsComplete witness composing all proof obligations Zig bridge (codegen/zig_bridge.rs — 220 LOC): - Generate C-ABI exports for each function contract - Delegate to user's original implementation - Lifecycle functions (init/shutdown) - Result codes matching Idris2 ABI Also generates: .ipkg for Idris2 build system, build.sh script Rust ABI (abi/mod.rs — 170 LOC): - FfiResult, InterfaceFormat, ContractClause, ProofObligation - ProofKind enum (Totality, Termination, Invariant, TypeSafety, Resource, RoundTrip) - VerificationSummary with from_obligations constructor Example: examples/user-api/ with OpenAPI + C header interfaces Tests: 20 passing (6 unit + 4 parser + 10 integration) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e47d128 commit 9174471

12 files changed

Lines changed: 2964 additions & 58 deletions

File tree

Cargo.lock

Lines changed: 906 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/user-api/idrisiser.toml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# SPDX-License-Identifier: PMPL-1.0-or-later
2+
# Example idrisiser manifest — verify a REST API and a C library.
3+
4+
[project]
5+
name = "example-project"
6+
module-prefix = "Example.Verified"
7+
8+
[[interfaces]]
9+
name = "user-api"
10+
source = "openapi.yaml"
11+
format = "openapi"
12+
preconditions = ["valid_auth_token"]
13+
postconditions = ["status < 500"]
14+
15+
[[interfaces]]
16+
name = "core-lib"
17+
source = "include/core.h"
18+
format = "c-header"
19+
verify = ["process_item", "reduce"]
20+
invariants = ["heap_intact"]
21+
22+
[proofs]
23+
require-totality = true
24+
round-trip-proofs = true
25+
qtt-tracking = false
26+
search-depth = 100

examples/user-api/include/core.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/* Minimal C header for testing idrisiser parsing */
2+
3+
#ifndef CORE_H
4+
#define CORE_H
5+
6+
#include <stddef.h>
7+
#include <stdint.h>
8+
9+
int process_item(void* input, size_t len);
10+
int reduce(void* a, void* b, void* out);
11+
void* allocate_buffer(size_t size);
12+
void free_buffer(void* buf);
13+
14+
#endif

examples/user-api/openapi.yaml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Minimal OpenAPI spec for testing idrisiser parsing
2+
openapi: "3.0.0"
3+
info:
4+
title: User API
5+
version: "1.0.0"
6+
paths:
7+
/users:
8+
get:
9+
summary: List all users
10+
responses:
11+
"200":
12+
description: OK
13+
/users/{id}:
14+
get:
15+
summary: Get user by ID
16+
parameters:
17+
- name: id
18+
in: path
19+
required: true
20+
responses:
21+
"200":
22+
description: OK
23+
"404":
24+
description: Not found
25+
/users/{id}/posts:
26+
get:
27+
summary: List posts for a user
28+
responses:
29+
"200":
30+
description: OK

src/abi/mod.rs

Lines changed: 163 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,166 @@
11
// SPDX-License-Identifier: PMPL-1.0-or-later
2-
// Copyright (c) 2026 Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
2+
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
33
//
4-
// ABI module for idrisiser.
5-
// Rust-side types mirroring the Idris2 ABI formal definitions.
6-
// The Idris2 proofs guarantee correctness; this module provides runtime types.
4+
// ABI module — Rust-side types mirroring the Idris2 ABI formal definitions.
5+
//
6+
// The Idris2 proofs in src/interface/abi/*.idr guarantee correctness at compile time.
7+
// This Rust module provides runtime representations for:
8+
// - Interface formats and contracts
9+
// - Proof obligations and their discharge status
10+
// - Verification results
11+
12+
use serde::{Deserialize, Serialize};
13+
14+
/// FFI result codes. Must match Idrisiser.ABI.Types.Result.
15+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
16+
#[repr(i32)]
17+
pub enum FfiResult {
18+
Ok = 0,
19+
Error = 1,
20+
InvalidParam = 2,
21+
OutOfMemory = 3,
22+
NullPointer = 4,
23+
ProofFailure = 5,
24+
}
25+
26+
/// Supported interface formats.
27+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
28+
pub enum InterfaceFormat {
29+
OpenApi,
30+
CHeader,
31+
Protobuf,
32+
TypeSig,
33+
}
34+
35+
/// A contract clause (precondition, postcondition, or invariant).
36+
#[derive(Debug, Clone, Serialize, Deserialize)]
37+
pub struct ContractClause {
38+
/// The kind of clause.
39+
pub kind: ClauseKind,
40+
/// Human-readable description of the clause.
41+
pub description: String,
42+
/// Whether this clause has been proven (discharged).
43+
pub discharged: bool,
44+
}
45+
46+
/// Kind of contract clause.
47+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
48+
pub enum ClauseKind {
49+
Precondition,
50+
Postcondition,
51+
Invariant,
52+
}
53+
54+
/// A proof obligation generated from a contract.
55+
#[derive(Debug, Clone, Serialize, Deserialize)]
56+
pub struct ProofObligation {
57+
/// Which function this obligation belongs to.
58+
pub function_name: String,
59+
/// The kind of proof required.
60+
pub kind: ProofKind,
61+
/// The Idris2 type signature of the proof.
62+
pub type_signature: String,
63+
/// Whether the proof has been discharged (verified).
64+
pub discharged: bool,
65+
}
66+
67+
/// Kinds of proofs that idrisiser generates.
68+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
69+
pub enum ProofKind {
70+
/// Function covers all inputs and terminates.
71+
Totality,
72+
/// Recursive operations have a decreasing measure.
73+
Termination,
74+
/// A state invariant holds before and after.
75+
Invariant,
76+
/// Input/output types match the declared schema.
77+
TypeSafety,
78+
/// Linear/affine resources are properly tracked (QTT).
79+
Resource,
80+
/// Serialise then deserialise yields the original value.
81+
RoundTrip,
82+
}
83+
84+
/// Summary of a verification run.
85+
#[derive(Debug, Clone, Serialize, Deserialize)]
86+
pub struct VerificationSummary {
87+
/// Number of interfaces verified.
88+
pub interfaces: usize,
89+
/// Number of functions across all interfaces.
90+
pub functions: usize,
91+
/// Total proof obligations generated.
92+
pub total_obligations: usize,
93+
/// Obligations successfully discharged.
94+
pub discharged: usize,
95+
/// Obligations remaining (not yet proven).
96+
pub remaining: usize,
97+
/// Whether all obligations are discharged.
98+
pub all_proven: bool,
99+
}
100+
101+
impl VerificationSummary {
102+
/// Create a summary from a list of proof obligations.
103+
pub fn from_obligations(
104+
interfaces: usize,
105+
functions: usize,
106+
obligations: &[ProofObligation],
107+
) -> Self {
108+
let total = obligations.len();
109+
let discharged = obligations.iter().filter(|o| o.discharged).count();
110+
Self {
111+
interfaces,
112+
functions,
113+
total_obligations: total,
114+
discharged,
115+
remaining: total - discharged,
116+
all_proven: discharged == total,
117+
}
118+
}
119+
}
120+
121+
#[cfg(test)]
122+
mod tests {
123+
use super::*;
124+
125+
#[test]
126+
fn verification_summary_all_proven() {
127+
let obligations = vec![
128+
ProofObligation {
129+
function_name: "foo".into(),
130+
kind: ProofKind::Totality,
131+
type_signature: "foo_total : FooTotal".into(),
132+
discharged: true,
133+
},
134+
ProofObligation {
135+
function_name: "foo".into(),
136+
kind: ProofKind::RoundTrip,
137+
type_signature: "foo_rt : FooRoundTrip".into(),
138+
discharged: true,
139+
},
140+
];
141+
let summary = VerificationSummary::from_obligations(1, 1, &obligations);
142+
assert!(summary.all_proven);
143+
assert_eq!(summary.remaining, 0);
144+
}
7145

8-
// TODO: Define Idris2-specific ABI types and verification functions.
146+
#[test]
147+
fn verification_summary_partial() {
148+
let obligations = vec![
149+
ProofObligation {
150+
function_name: "bar".into(),
151+
kind: ProofKind::Totality,
152+
type_signature: "".into(),
153+
discharged: true,
154+
},
155+
ProofObligation {
156+
function_name: "bar".into(),
157+
kind: ProofKind::Invariant,
158+
type_signature: "".into(),
159+
discharged: false,
160+
},
161+
];
162+
let summary = VerificationSummary::from_obligations(1, 1, &obligations);
163+
assert!(!summary.all_proven);
164+
assert_eq!(summary.remaining, 1);
165+
}
166+
}

0 commit comments

Comments
 (0)