Skip to content

Commit 24aad25

Browse files
Claude Sonnet (coordinator)claude
andcommitted
feat(prd-6-future): ledger-attest proc-macro crate — #[attested] lint skeleton
Closes #59. Establishes the type attestation infrastructure so formal verification coverage is machine-checkable at compile time. - crates/ledger-attest/: new proc-macro crate exporting #[attested("invariant")] - syn v2 / quote / proc-macro2 only, no runtime deps - emits const fn trait-bound check; missing Attested impl = compile error - crates/ledger-core/src/attest.rs: Attested trait + AttestationSpec struct - doc-test compile_fail covers the missing-impl error path - unit tests assert all three attestees return correct invariant strings - ConstraintEvaluation: #[attested("constraint_evaluation_bounded")] kani_module = "kani_proofs::vendor_constraints" - InvoiceVerification: #[attested("invoice_arithmetic_valid")] kani_module = "kani_proofs::invoice_arithmetic" - CommitGate: #[attested("commit_gate_total")] kani_module = "kani_proofs::commit_gate" Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 0539143 commit 24aad25

9 files changed

Lines changed: 170 additions & 0 deletions

File tree

Cargo.lock

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

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
[workspace]
22
members = [
33
"crates/ledger-core",
4+
"crates/ledger-attest",
45
"crates/ledgerr-focus",
56
"crates/ledgerr-host",
67
"crates/ledgerr-mcp",

crates/ledger-attest/Cargo.toml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[package]
2+
name = "ledger-attest"
3+
version.workspace = true
4+
edition.workspace = true
5+
license.workspace = true
6+
publish = false
7+
8+
[lib]
9+
proc-macro = true
10+
11+
[dependencies]
12+
syn = { version = "2", features = ["full"] }
13+
quote = "1"
14+
proc-macro2 = "1"

crates/ledger-attest/src/lib.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use proc_macro::TokenStream;
2+
use quote::quote;
3+
use syn::{parse_macro_input, Item, LitStr};
4+
5+
/// Emit a compile-time trait-bound assertion that the annotated type implements
6+
/// `crate::attest::Attested`.
7+
///
8+
/// Usage (within `ledger-core`):
9+
/// ```ignore
10+
/// #[attested("my_invariant")]
11+
/// pub struct MyType { ... }
12+
/// ```
13+
///
14+
/// If `MyType` does not implement `Attested`, the compiler emits a
15+
/// trait-not-satisfied error at the call site.
16+
///
17+
/// # Note on crate paths
18+
/// The generated assertion references `crate::attest::Attested`. This macro is
19+
/// intended for use within `ledger-core`. External crates using this attribute
20+
/// must have `pub mod attest` with the `Attested` trait re-exported as
21+
/// `crate::attest::Attested` at their crate root.
22+
#[proc_macro_attribute]
23+
pub fn attested(attr: TokenStream, item: TokenStream) -> TokenStream {
24+
let _invariant_lit = parse_macro_input!(attr as LitStr);
25+
let item = parse_macro_input!(item as Item);
26+
27+
let type_name = match &item {
28+
Item::Struct(s) => s.ident.clone(),
29+
Item::Enum(e) => e.ident.clone(),
30+
_ => {
31+
return syn::Error::new(
32+
proc_macro2::Span::call_site(),
33+
"#[attested] can only be applied to structs and enums",
34+
)
35+
.to_compile_error()
36+
.into();
37+
}
38+
};
39+
40+
let expanded = quote! {
41+
#item
42+
43+
const _: fn() = || {
44+
fn _assert_attested<T: crate::attest::Attested>() {}
45+
_assert_attested::<#type_name>();
46+
};
47+
};
48+
49+
expanded.into()
50+
}

crates/ledger-core/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ arc-kit-au = { path = "../arc-kit-au", optional = true }
3030
tokio = { workspace = true }
3131
tracing = "0.1"
3232
notify = "8.2"
33+
ledger-attest = { path = "../ledger-attest" }
3334

3435
# filesystem metadata — xattr on Linux; sidecar fallback is pure std
3536
[target.'cfg(target_os = "linux")'.dependencies]

crates/ledger-core/src/attest.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//! Attestation trait — types with formal verification backing.
2+
3+
/// Metadata describing a type's formal verification coverage.
4+
pub struct AttestationSpec {
5+
pub invariant: &'static str,
6+
pub z3_predicate: Option<&'static str>,
7+
pub kasuari_description: Option<&'static str>,
8+
pub kani_module: Option<&'static str>,
9+
}
10+
11+
/// Implemented by types that carry formal property attestations.
12+
///
13+
/// # Compile-fail: missing impl
14+
/// ```compile_fail
15+
/// use ledger_attest::attested;
16+
/// use ledger_core::attest::Attested;
17+
///
18+
/// #[attested("missing")]
19+
/// pub struct MissingImpl { pub x: u32 }
20+
/// ```
21+
pub trait Attested {
22+
fn attestation_spec() -> AttestationSpec;
23+
}
24+
25+
#[cfg(test)]
26+
mod tests {
27+
use super::*;
28+
use crate::constraints::{ConstraintEvaluation, InvoiceVerification};
29+
use crate::validation::CommitGate;
30+
31+
#[test]
32+
fn attestation_spec_invariant_strings_are_correct() {
33+
assert_eq!(
34+
ConstraintEvaluation::attestation_spec().invariant,
35+
"constraint_evaluation_bounded"
36+
);
37+
assert_eq!(
38+
InvoiceVerification::attestation_spec().invariant,
39+
"invoice_arithmetic_valid"
40+
);
41+
assert_eq!(CommitGate::attestation_spec().invariant, "commit_gate_total");
42+
}
43+
44+
#[test]
45+
fn attestation_spec_kani_modules_are_set() {
46+
assert!(ConstraintEvaluation::attestation_spec().kani_module.is_some());
47+
assert!(InvoiceVerification::attestation_spec().kani_module.is_some());
48+
assert!(CommitGate::attestation_spec().kani_module.is_some());
49+
}
50+
}

crates/ledger-core/src/constraints.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
//! Uses the Cassowary algorithm to evaluate constraints against transaction populations.
33
44
use serde::{Deserialize, Serialize};
5+
use ledger_attest::attested;
6+
use crate::attest::{Attested, AttestationSpec};
57

68
/// Constraint strength levels (matching Kasuari).
79
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
@@ -17,6 +19,7 @@ pub enum ConstraintStrength {
1719
}
1820

1921
/// Result of constraint evaluation.
22+
#[attested("constraint_evaluation_bounded")]
2023
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2124
pub struct ConstraintEvaluation {
2225
/// Whether REQUIRED constraints passed.
@@ -98,6 +101,7 @@ impl ConstraintEvaluation {
98101
}
99102

100103
/// Structured result from invoice verification.
104+
#[attested("invoice_arithmetic_valid")]
101105
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
102106
pub struct InvoiceVerification {
103107
pub evaluation: ConstraintEvaluation,
@@ -106,6 +110,18 @@ pub struct InvoiceVerification {
106110
pub audit_note: String,
107111
}
108112

113+
114+
impl Attested for ConstraintEvaluation {
115+
fn attestation_spec() -> AttestationSpec {
116+
AttestationSpec {
117+
invariant: "constraint_evaluation_bounded",
118+
z3_predicate: None,
119+
kasuari_description: Some("strong_ratio, medium_ratio, weak_ratio in [0.0, 1.0]"),
120+
kani_module: Some("kani_proofs::vendor_constraints"),
121+
}
122+
}
123+
}
124+
109125
/// A historical constraint set for a vendor or category.
110126
#[derive(Debug, Clone, Serialize, Deserialize)]
111127
pub struct VendorConstraintSet {
@@ -213,6 +229,18 @@ impl LayoutSolver {
213229
}
214230
}
215231

232+
233+
impl Attested for InvoiceVerification {
234+
fn attestation_spec() -> AttestationSpec {
235+
AttestationSpec {
236+
invariant: "invoice_arithmetic_valid",
237+
z3_predicate: Some("total = subtotal + gst"),
238+
kasuari_description: None,
239+
kani_module: Some("kani_proofs::invoice_arithmetic"),
240+
}
241+
}
242+
}
243+
216244
#[cfg(test)]
217245
mod tests {
218246
use super::*;

crates/ledger-core/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
pub mod attest;
12
pub mod calendar;
23
pub mod classify;
34
pub mod constraints;

crates/ledger-core/src/validation.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
55
use std::fmt;
66
use serde::{Deserialize, Serialize};
7+
use ledger_attest::attested;
8+
use crate::attest::{Attested, AttestationSpec};
79

810
/// Disposition classifies how an issue should be handled by the pipeline.
911
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
@@ -232,6 +234,7 @@ pub enum ApprovalGate {
232234

233235
/// Gate decision for committing a reconciled transaction.
234236
/// Replaces unconditional tray-approval with confidence-aware routing.
237+
#[attested("commit_gate_total")]
235238
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
236239
pub enum CommitGate {
237240
/// All checks passed above threshold -- may commit automatically.
@@ -329,6 +332,18 @@ pub mod verbs {
329332
}
330333
}
331334

335+
336+
impl Attested for CommitGate {
337+
fn attestation_spec() -> AttestationSpec {
338+
AttestationSpec {
339+
invariant: "commit_gate_total",
340+
z3_predicate: Some("Approved | PendingOperator | Blocked covers all Reconciled states"),
341+
kasuari_description: None,
342+
kani_module: Some("kani_proofs::commit_gate"),
343+
}
344+
}
345+
}
346+
332347
#[cfg(test)]
333348
mod tests {
334349
use super::*;

0 commit comments

Comments
 (0)