Some checks failed
joule-code conformance gate / Build + round-trip C1-C4 vectors (pull_request) Successful in 2m34s
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 2m21s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 19s
standards workspaces build + test / macOS energy backends (pull_request) Failing after 3s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 1m33s
`receipt_for_op` computed one obligation and sealed the rest as
discharged:
result: if is_frame { frame_result.clone() } else { DischargeResult::Closed },
`Closed` is JouleContract's positive "this obligation was discharged".
Every Pre, Post and Inv obligation `contract_for_op` declares was
sealed with it — attributed to a named `TierClass::Deterministic` tier,
carrying the binder's verifier keys, qualification and engagement, and
signed — although no code evaluated any of them. The crate doc presents
this binding as "not an analogy; the objects line up field by field".
The sharpest case is the Post obligation, because it states the *same
property* as the Frame obligation. The crate's own test
`out_of_frame_edit_is_caught_at_the_boundary` produced a receipt
carrying, in one `discharge` vector:
Frame: Undischarged { reason: "out-of-frame edit: deploy/secrets.env" }
Post ("every edit applied within the declared frame"): Closed
One signature asserting both that an edit escaped the frame and that
every edit stayed inside it. `validate` reported only the frame
demotion, so a relying party reading the Post entry saw a passed check
that was never run.
Every obligation `contract_for_op` writes is now decided, and all four
are decidable from primitives that already existed:
- **Post** "every edit applied within the declared frame" carries the
frame verdict — same property, same evidence.
- **Inv** "op signature verifies over op_id ‖ prior" is exactly what
`SignedOp::verify` decides: it recomputes `op_id`, rebuilds
`op_id ‖ prior`, checks the signature against `op.author`, and rejects
replay onto a different base.
- **Inv** "edits are ordered and pairwise disjoint" is exactly what
`CodingOp::validate` decides (SPEC §2).
- **Pre** "op builds on prior tree X" checks the contract was written
for the prior this receipt states, so the contract and the receipt
cannot disagree silently.
- Anything else a caller declared is `Undischarged { "not evaluated by
this binder: …" }`. **There is no default `Closed`** — that
fall-through was the defect.
Two signature changes make the undecidable decidable, and
`receipt_for_op` has no callers outside its own tests:
- It takes `&SignedOp` and the `prior` the verifier believes current,
because two obligations are statements about exactly those. With an
unsigned `CodingOp` neither could be decided — and both were sealed
as discharged anyway.
- The op id is **derived** from the op instead of supplied. A
caller-supplied `op_id` was trusted and never re-derived, so
`transition: bridge_id(op_id)` could name a different operation than
the one whose frame was checked.
The fixture told the same story: it set `author: [7u8; 32]`, which is
not a key, and returned `ContentId::of_bytes(b"the op canonical bytes")`
as the op id — a constant unrelated to the op. It now signs with a real
key and sorts the edits into canonical order, because `op_id` runs
`validate` and `sign` runs `op_id`.
`in_frame_op_discharges_and_validates_to_jr2` still passes unchanged:
an honest check of a valid in-frame signed op closes all four
obligations, so nothing was traded away to stop fabricating them.
Co-Authored-By: Claude Opus 5 <[email protected]>
650 lines
26 KiB
Rust
650 lines
26 KiB
Rust
//! A Joule Code coding operation under a JouleContract.
|
|
//!
|
|
//! The mapping is not an analogy; the objects line up field by field:
|
|
//!
|
|
//! - **The edit set is the frame.** A `CodingOp`'s `edits[].path` list is the
|
|
//! declared mutable footprint (JouleContract §3.4). An edit outside the
|
|
//! contracted paths is an out-of-frame effect — the breach primitive, made
|
|
//! concrete for code, caught at the boundary by [`frame_check`].
|
|
//! - **The tier cost is the ceiling.** The cascade's per-tier joule class
|
|
//! prices the contract's EnPI ceiling.
|
|
//! - **JCR provenance maps onto the energy classes.** `HwShunt` → `Metered`
|
|
//! (with a meter binding), `ModelBased` → `ModelBased` (with an anchor),
|
|
//! `Estimator` → `Unaccounted`. Honest in, honest out — an `Estimator`
|
|
//! figure can never serialize as `Metered` through this binding.
|
|
//!
|
|
//! The binding produces receipts that pass JouleContract's **receipt-local**
|
|
//! validation (at most JR-2 — JouleContract SPEC §10's two-stage rule);
|
|
//! composing them to JR-3+ with sealed evidence, a key log, and anchored
|
|
//! intervals is the deployment's verifier-of-record work, not this crate's.
|
|
|
|
use joule_code_cargo::{CodingOp, SignedOp};
|
|
use joule_code_receipt::{Energy, Provenance};
|
|
use joule_code_schema::ContentId;
|
|
use joulecontract_receipt::{
|
|
DischargeEntry, DischargeResult, Engagement, FrameMode, Generator, KeyId, MeterBinding,
|
|
Qualification, Receipt, TierClass,
|
|
};
|
|
use joulecontract_schema::{
|
|
Ceiling, ContentAddress, Contract, DerivationBinding, EnergyClass, GovernanceClass, Profile,
|
|
Rigor,
|
|
};
|
|
use std::collections::BTreeSet;
|
|
|
|
/// Microjoules per joule: JCR energy records are integer µJ; JouleContract
|
|
/// ceilings are EOC joules.
|
|
const UJ_PER_J: f64 = 1_000_000.0;
|
|
|
|
/// Bridge a Joule Code content identity (`b3:…`, BLAKE3/base64) into a
|
|
/// JouleContract content address (`sha2-256:…`). The foreign identity string
|
|
/// is the addressed content — a cross-standard *reference*, not a re-hash
|
|
/// claim: resolving it back to the Joule Code object goes through the
|
|
/// original `ContentId`.
|
|
pub fn bridge_id(id: &ContentId) -> ContentAddress {
|
|
ContentAddress::of_bytes(id.0.as_bytes())
|
|
}
|
|
|
|
/// JCR provenance → JouleContract energy class. The mapping only ever moves
|
|
/// trust *down*: a coarse estimator is `Unaccounted`, never promoted.
|
|
pub fn energy_class(p: Provenance) -> EnergyClass {
|
|
match p {
|
|
Provenance::HwShunt => EnergyClass::Metered,
|
|
Provenance::ModelBased => EnergyClass::ModelBased,
|
|
Provenance::Estimator => EnergyClass::Unaccounted,
|
|
}
|
|
}
|
|
|
|
/// Build the JouleContract for a coding request: what must hold, what may be
|
|
/// touched, what may be spent.
|
|
pub fn contract_for_op(
|
|
intent: &str,
|
|
prior: &ContentId,
|
|
frame_paths: &[String],
|
|
ceiling_joules: f64,
|
|
enb_ref: ContentAddress,
|
|
derivation: Option<DerivationBinding>,
|
|
rigor_target: Rigor,
|
|
) -> Contract {
|
|
Contract {
|
|
joulecontract: "0.8.0".into(),
|
|
subject: intent.to_string(),
|
|
rigor_target,
|
|
pre: vec![format!("op builds on prior tree {}", prior.0)],
|
|
post: vec!["every edit applied within the declared frame".into()],
|
|
inv: vec![
|
|
"op signature verifies over op_id ‖ prior".into(),
|
|
"edits are ordered and pairwise disjoint".into(),
|
|
],
|
|
frame: frame_paths.to_vec(),
|
|
ceiling: Ceiling {
|
|
enpi_joules: ceiling_joules,
|
|
unit: "EOC-J".into(),
|
|
enb_ref,
|
|
derivation,
|
|
},
|
|
sector_equivalence: None,
|
|
amends: None,
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
|
|
pub enum BindingError {
|
|
/// An edit outside the contracted footprint: the out-of-frame effect,
|
|
/// caught at the boundary instead of discovered in review.
|
|
#[error("out-of-frame edit: `{path}` is not within the contracted footprint")]
|
|
OutOfFrame { path: String },
|
|
#[error("contract addressing failed: {0}")]
|
|
Address(String),
|
|
/// The op's own id could not be derived, so no receipt can name the
|
|
/// operation it is about.
|
|
#[error("op id could not be derived: {0}")]
|
|
OpId(String),
|
|
}
|
|
|
|
/// Decide one declared obligation against what this binder actually holds.
|
|
///
|
|
/// Every arm either evaluates the obligation or reports it `Undischarged`
|
|
/// with the reason it was not evaluated. **There is no default `Closed`.**
|
|
/// `Closed` is JouleContract's positive "this obligation was discharged", and
|
|
/// it used to be the fall-through for every Pre, Post and Inv obligation —
|
|
/// signed, attributed to a named `Deterministic` tier, with nothing having
|
|
/// evaluated them. The worst case was the Post obligation below: it states
|
|
/// the same property as the frame obligation, so a single receipt could carry
|
|
/// `Frame = Undischarged { "out-of-frame edit: deploy/secrets.env" }` and
|
|
/// `Post = Closed` for "every edit applied within the declared frame" — one
|
|
/// signature asserting both that an edit escaped the frame and that every
|
|
/// edit stayed inside it.
|
|
fn discharge_for(
|
|
signed: &SignedOp,
|
|
prior: &ContentId,
|
|
frame_result: &DischargeResult,
|
|
ob: &joulecontract_schema::Obligation,
|
|
) -> DischargeResult {
|
|
use joulecontract_schema::ObligationKind as Kind;
|
|
match (&ob.kind, ob.text.as_str()) {
|
|
// Decided by `frame_check`.
|
|
(Kind::Frame, _) => frame_result.clone(),
|
|
|
|
// The same property as the frame obligation, so it carries the same
|
|
// evidence rather than a second, contradictory verdict.
|
|
(Kind::Post, "every edit applied within the declared frame") => frame_result.clone(),
|
|
|
|
// The contract names the tree the op must build on. `SignedOp::verify`
|
|
// below binds the op to `prior`; this checks the *contract* was
|
|
// written for that same prior, so the two cannot disagree silently.
|
|
(Kind::Pre, text) if text.starts_with("op builds on prior tree ") => {
|
|
let expected = format!("op builds on prior tree {}", prior.0);
|
|
if text == expected {
|
|
DischargeResult::Closed
|
|
} else {
|
|
DischargeResult::Undischarged {
|
|
reason: format!("contract declares `{text}`, this receipt states prior {}", prior.0),
|
|
}
|
|
}
|
|
}
|
|
|
|
// Exactly what `SignedOp::verify` decides: it recomputes `op_id`,
|
|
// rebuilds `op_id ‖ prior`, checks the signature against `op.author`,
|
|
// and rejects replay onto a different base.
|
|
(Kind::Inv, "op signature verifies over op_id ‖ prior") => match signed.verify(prior) {
|
|
Ok(()) => DischargeResult::Closed,
|
|
Err(e) => DischargeResult::Undischarged {
|
|
reason: format!("signature does not verify over op_id ‖ prior: {e}"),
|
|
},
|
|
},
|
|
|
|
// Exactly what `CodingOp::validate` decides (SPEC §2: known alg,
|
|
// non-empty edits, ascending `(path, start)`, no overlap).
|
|
(Kind::Inv, "edits are ordered and pairwise disjoint") => match signed.op.validate() {
|
|
Ok(()) => DischargeResult::Closed,
|
|
Err(e) => DischargeResult::Undischarged { reason: e.to_string() },
|
|
},
|
|
|
|
// An obligation this binder does not recognise. A caller may declare
|
|
// any prose it likes; none of it is discharged by being written down.
|
|
(_, text) => DischargeResult::Undischarged {
|
|
reason: format!("not evaluated by this binder: {text}"),
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Check every edit path against the contract's frame. A path is in-frame
|
|
/// when it equals a frame entry or sits under a frame entry treated as a
|
|
/// directory prefix (`src/` covers `src/lib.rs`).
|
|
pub fn frame_check(contract: &Contract, op: &CodingOp) -> Result<(), BindingError> {
|
|
for edit in &op.edits {
|
|
let in_frame = contract.frame.iter().any(|entry| {
|
|
let dir = if entry.ends_with('/') {
|
|
entry.clone()
|
|
} else {
|
|
format!("{entry}/")
|
|
};
|
|
edit.path == *entry || edit.path.starts_with(&dir)
|
|
});
|
|
if !in_frame {
|
|
return Err(BindingError::OutOfFrame {
|
|
path: edit.path.clone(),
|
|
});
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// The static identities a deployment binds once: who verifies, under what
|
|
/// qualification, paid by whom; who generates; where the meter attestation
|
|
/// comes from when the platform has one.
|
|
pub struct Binder {
|
|
pub verifier_keys: BTreeSet<KeyId>,
|
|
pub verifier_entity: Option<String>,
|
|
pub engagement: Engagement,
|
|
pub recourse: Option<String>,
|
|
pub qualification: Qualification,
|
|
pub tier_operator_keys: BTreeSet<KeyId>,
|
|
pub generator: Generator,
|
|
pub receipt_signer: KeyId,
|
|
/// Meter attestation for `HwShunt` readings (`Metered` binds or demotes).
|
|
pub meter: Option<MeterBinding>,
|
|
/// Calibration anchor for `ModelBased` readings.
|
|
pub energy_anchor: Option<ContentAddress>,
|
|
pub register_pin: ContentAddress,
|
|
/// The sealed evidence bundle address, where the deployment seals one.
|
|
pub evidence: Option<ContentAddress>,
|
|
}
|
|
|
|
impl Binder {
|
|
/// Discharge a coding operation against its contract into a JouleContract
|
|
/// receipt. The frame is checked **here** — its discharge mode is
|
|
/// `Observed` because the footprint was compared against the actual edit
|
|
/// set, not analyzed from the artifact. An out-of-frame op still gets a
|
|
/// receipt: the frame serializes `undischarged` with the offending path,
|
|
/// because a breach recorded honestly beats a breach driven underground.
|
|
/// Seal a receipt for one signed op against a contract.
|
|
///
|
|
/// Takes the **signed** op and the `prior` the verifier believes current,
|
|
/// because two of the contract's obligations are statements about exactly
|
|
/// those: the signature over `op_id ‖ prior`, and the tree the op builds
|
|
/// on. With an unsigned `CodingOp` and a caller-supplied `op_id` neither
|
|
/// could be decided — and both were nonetheless sealed as discharged.
|
|
///
|
|
/// The op id is **derived** from the op rather than supplied, so
|
|
/// `transition` always names the operation whose obligations were checked;
|
|
/// a caller-supplied id was trusted and never re-derived, so a receipt
|
|
/// could carry one op's frame verdict under another op's id.
|
|
///
|
|
/// # Errors
|
|
/// [`BindingError::OpId`] if the op's id cannot be derived (it is
|
|
/// structurally invalid), or [`BindingError::Address`] if the contract
|
|
/// cannot be addressed. A failing *obligation* is not an error: it is
|
|
/// recorded as `Undischarged` on the receipt, which is the point.
|
|
pub fn receipt_for_op(
|
|
&self,
|
|
contract: &Contract,
|
|
signed: &SignedOp,
|
|
prior: &ContentId,
|
|
energy: &Energy,
|
|
) -> Result<Receipt, BindingError> {
|
|
let op: &CodingOp = &signed.op;
|
|
let op_id = op.op_id().map_err(|e| BindingError::OpId(e.to_string()))?;
|
|
let frame_result = match frame_check(contract, op) {
|
|
Ok(()) => DischargeResult::Closed,
|
|
Err(BindingError::OutOfFrame { path }) => DischargeResult::Undischarged {
|
|
reason: format!("out-of-frame edit: {path}"),
|
|
},
|
|
Err(e) => return Err(e),
|
|
};
|
|
|
|
let provenance = energy_class(energy.provenance);
|
|
let spent_uj = energy.measured_uj.unwrap_or(energy.estimated_uj);
|
|
let joules_spent = spent_uj as f64 / UJ_PER_J;
|
|
|
|
let binding_tier = ContentAddress::of_bytes(b"joule-code-joulecontract deterministic binding checks v1");
|
|
let discharge = contract
|
|
.obligations()
|
|
.into_iter()
|
|
.map(|ob| {
|
|
let is_frame = ob.kind == joulecontract_schema::ObligationKind::Frame;
|
|
let result = discharge_for(signed, prior, &frame_result, &ob);
|
|
DischargeEntry {
|
|
obligation: ob,
|
|
tier: binding_tier.clone(),
|
|
tier_class: TierClass::Deterministic,
|
|
tier_operator_keys: self.tier_operator_keys.clone(),
|
|
result,
|
|
mode: is_frame.then_some(FrameMode::Observed),
|
|
qualification: Some(self.qualification.clone()),
|
|
verifier_keys: self.verifier_keys.clone(),
|
|
verifier_entity: self.verifier_entity.clone(),
|
|
engagement: Some(self.engagement.clone()),
|
|
recourse: self.recourse.clone(),
|
|
re_discharged_by: None,
|
|
}
|
|
})
|
|
.collect();
|
|
|
|
Ok(Receipt {
|
|
contract: contract
|
|
.content_address()
|
|
.map_err(|e| BindingError::Address(e.to_string()))?,
|
|
transition: bridge_id(&op_id),
|
|
generator: self.generator.clone(),
|
|
discharge,
|
|
execution: vec![ContentAddress::of_bytes(
|
|
format!("joule-code tier {:?}", op.tier).as_bytes(),
|
|
)],
|
|
joules_spent,
|
|
meter: match provenance {
|
|
EnergyClass::Metered => self.meter.clone(),
|
|
_ => None,
|
|
},
|
|
energy_anchor: match provenance {
|
|
EnergyClass::ModelBased => self.energy_anchor.clone(),
|
|
_ => None,
|
|
},
|
|
joules_allowed: contract.ceiling.enpi_joules,
|
|
provenance,
|
|
enb_ref: contract.ceiling.enb_ref.clone(),
|
|
grid_mix: None,
|
|
register_pin: self.register_pin.clone(),
|
|
evidence: self.evidence.clone(),
|
|
claimed_profile: Profile {
|
|
rigor: Rigor::Jr2,
|
|
energy: provenance,
|
|
governance: GovernanceClass::Managed,
|
|
},
|
|
receipt_signer: self.receipt_signer.clone(),
|
|
})
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use joule_code_cargo::{Anchor, CodingOp, Edit};
|
|
use joule_code_schema::Tier;
|
|
use joulecontract_receipt::{validate, LoopGovernance, TrustRoot};
|
|
use joulecontract_schema::Demotion;
|
|
|
|
fn key(label: &str) -> KeyId {
|
|
ContentAddress::of_bytes(label.as_bytes())
|
|
}
|
|
|
|
fn author_key() -> ed25519_dalek::SigningKey {
|
|
ed25519_dalek::SigningKey::from_bytes(&[42u8; 32])
|
|
}
|
|
|
|
/// The tree `contract()` declares its op must build on.
|
|
fn prior() -> ContentId {
|
|
ContentId::of_bytes(b"prior tree")
|
|
}
|
|
|
|
/// A real signed op. `author` is the signing key's own verifying key, and
|
|
/// the edits are sorted into canonical `(path, start)` order, because
|
|
/// `op_id` runs `validate` and `sign` runs `op_id` — an op that is not
|
|
/// canonical has no id and therefore no signature.
|
|
///
|
|
/// This replaces a fixture that set `author: [7u8; 32]` (not a key) and
|
|
/// returned `ContentId::of_bytes(b"the op canonical bytes")` as the id —
|
|
/// a constant unrelated to the op, which is what let the untrusted
|
|
/// `op_id` parameter go unnoticed.
|
|
fn op(paths: &[&str]) -> SignedOp {
|
|
let key = author_key();
|
|
let mut sorted: Vec<&&str> = paths.iter().collect();
|
|
sorted.sort();
|
|
let op = CodingOp {
|
|
alg: 0x01,
|
|
prior: prior(),
|
|
edits: sorted
|
|
.into_iter()
|
|
.map(|p| Edit {
|
|
path: (*p).to_string(),
|
|
anchor: Anchor::ByteRange { start: 0, end: 4 },
|
|
replacement: b"new!".to_vec(),
|
|
})
|
|
.collect(),
|
|
author: key.verifying_key().to_bytes(),
|
|
intent: "rename the settlement index field".into(),
|
|
tier: Tier::L1,
|
|
};
|
|
op.sign(&key).expect("a canonical op signs")
|
|
}
|
|
|
|
fn binder() -> Binder {
|
|
Binder {
|
|
verifier_keys: [key("audit-a")].into(),
|
|
verifier_entity: Some("vLEI:AUDIT-A".into()),
|
|
engagement: Engagement {
|
|
selected_by: "did:web:relying.example".into(),
|
|
compensated_by: "did:web:relying.example".into(),
|
|
},
|
|
recourse: Some("ISAE3000".into()),
|
|
qualification: Qualification {
|
|
attestation: key("binding qualification"),
|
|
signer_key: key("assessor"),
|
|
valid_until: 20_000,
|
|
validated_at: 10_000,
|
|
root: Some(TrustRoot::ReferenceImpl),
|
|
},
|
|
tier_operator_keys: [key("binding-operator")].into(),
|
|
generator: Generator {
|
|
system: "joule-code cascade".into(),
|
|
version_digest: key("cascade build"),
|
|
attestation: Some(key("cascade attestation")),
|
|
attestation_signer: Some(key("registry")),
|
|
key_set: [key("producer")].into(),
|
|
entity: Some("vLEI:PRODUCER".into()),
|
|
human_principals: vec!["did:web:eng.example".into()],
|
|
loop_governance: Some(LoopGovernance {
|
|
contract: key("loop contract"),
|
|
discharged_by: [key("audit-a")].into(),
|
|
root: TrustRoot::HumanAttested,
|
|
}),
|
|
},
|
|
receipt_signer: key("producer"),
|
|
meter: Some(MeterBinding {
|
|
attestation: key("rapl attestation"),
|
|
signer_key: key("substrate-telemetry"),
|
|
valid_until: 20_000,
|
|
validated_at: 10_000,
|
|
}),
|
|
energy_anchor: Some(key("calibration anchor")),
|
|
register_pin: key("register snapshot"),
|
|
evidence: None,
|
|
}
|
|
}
|
|
|
|
fn contract(frame: &[&str]) -> Contract {
|
|
contract_for_op(
|
|
"rename the settlement index field",
|
|
&ContentId::of_bytes(b"prior tree"),
|
|
&frame.iter().map(|s| s.to_string()).collect::<Vec<_>>(),
|
|
0.01,
|
|
key("enb"),
|
|
Some(DerivationBinding {
|
|
attestation: key("derivation"),
|
|
basis: "metered median of comparable L1 ops".into(),
|
|
signer: "did:web:energy-office.example".into(),
|
|
}),
|
|
Rigor::Jr2,
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn in_frame_op_discharges_and_validates_to_jr2() {
|
|
let c = contract(&["src/", "Cargo.toml"]);
|
|
let signed = op(&["src/lib.rs", "Cargo.toml"]);
|
|
let energy = Energy {
|
|
measured_uj: Some(1_200),
|
|
estimated_uj: 1_000,
|
|
provenance: Provenance::HwShunt,
|
|
};
|
|
let r = binder().receipt_for_op(&c, &signed, &prior(), &energy).unwrap();
|
|
let v = validate(&c, &r).unwrap();
|
|
assert_eq!(v.max_profile.rigor, Rigor::Jr2);
|
|
assert_eq!(v.max_profile.energy, EnergyClass::Metered);
|
|
assert!(v.permits(&r.claimed_profile), "demotions: {:?}", v.demotions);
|
|
assert!((r.joules_spent - 0.0012).abs() < 1e-9);
|
|
assert!(r.residual() > 0.0);
|
|
}
|
|
|
|
#[test]
|
|
fn out_of_frame_edit_is_caught_at_the_boundary() {
|
|
let c = contract(&["src/"]);
|
|
let signed = op(&["src/lib.rs", "deploy/secrets.env"]);
|
|
assert_eq!(
|
|
frame_check(&c, &signed.op),
|
|
Err(BindingError::OutOfFrame {
|
|
path: "deploy/secrets.env".into()
|
|
})
|
|
);
|
|
// The receipt still issues — with the frame undischarged and the
|
|
// offending path on the record. Honest breach beats hidden breach.
|
|
let energy = Energy {
|
|
measured_uj: None,
|
|
estimated_uj: 900,
|
|
provenance: Provenance::ModelBased,
|
|
};
|
|
let r = binder().receipt_for_op(&c, &signed, &prior(), &energy).unwrap();
|
|
let v = validate(&c, &r).unwrap();
|
|
assert!(v.demotions.iter().any(|d| matches!(
|
|
d,
|
|
Demotion::Undischarged { reason } if reason.contains("deploy/secrets.env")
|
|
)));
|
|
assert_eq!(v.max_profile.rigor, Rigor::Jr1, "undischarged frame caps rigor");
|
|
}
|
|
|
|
/// The contradiction this fix removes. The Post obligation states the same
|
|
/// property as the Frame obligation, so one receipt cannot carry
|
|
/// "an edit escaped the frame" and "every edit stayed inside it" at once.
|
|
#[test]
|
|
fn the_post_obligation_carries_the_frame_verdict() {
|
|
let c = contract(&["src/"]);
|
|
let signed = op(&["src/lib.rs", "deploy/secrets.env"]);
|
|
let energy = Energy {
|
|
measured_uj: Some(900),
|
|
estimated_uj: 900,
|
|
provenance: Provenance::HwShunt,
|
|
};
|
|
let r = binder().receipt_for_op(&c, &signed, &prior(), &energy).unwrap();
|
|
|
|
let post = r
|
|
.discharge
|
|
.iter()
|
|
.find(|d| d.obligation.text == "every edit applied within the declared frame")
|
|
.expect("the contract declares it");
|
|
let frame = r
|
|
.discharge
|
|
.iter()
|
|
.find(|d| d.obligation.kind == joulecontract_schema::ObligationKind::Frame)
|
|
.expect("the contract declares it");
|
|
|
|
assert_eq!(post.result, frame.result, "same property, same verdict");
|
|
assert!(
|
|
matches!(&post.result, DischargeResult::Undischarged { reason } if reason.contains("deploy/secrets.env")),
|
|
"got {:?}",
|
|
post.result
|
|
);
|
|
}
|
|
|
|
/// The signature obligation is decided, not assumed. A tampered signature
|
|
/// leaves it undischarged instead of sealing "verifies" over bytes that
|
|
/// do not.
|
|
#[test]
|
|
fn the_signature_obligation_is_decided_not_assumed() {
|
|
let c = contract(&["src/"]);
|
|
let mut signed = op(&["src/lib.rs"]);
|
|
let energy = Energy {
|
|
measured_uj: Some(900),
|
|
estimated_uj: 900,
|
|
provenance: Provenance::HwShunt,
|
|
};
|
|
|
|
// Intact: the obligation closes on a real check.
|
|
let good = binder().receipt_for_op(&c, &signed, &prior(), &energy).unwrap();
|
|
let sig_of = |r: &Receipt| {
|
|
r.discharge
|
|
.iter()
|
|
.find(|d| d.obligation.text == "op signature verifies over op_id \u{2016} prior")
|
|
.map(|d| d.result.clone())
|
|
.expect("the contract declares it")
|
|
};
|
|
assert_eq!(sig_of(&good), DischargeResult::Closed);
|
|
|
|
// Tampered: undischarged, with the reason on the record.
|
|
*signed.sig.last_mut().expect("signature bytes") ^= 1;
|
|
let bad = binder().receipt_for_op(&c, &signed, &prior(), &energy).unwrap();
|
|
assert!(
|
|
matches!(sig_of(&bad), DischargeResult::Undischarged { .. }),
|
|
"got {:?}",
|
|
sig_of(&bad)
|
|
);
|
|
}
|
|
|
|
/// A caller may declare any prose it likes; none of it is discharged by
|
|
/// being written down. The fall-through used to be `Closed`.
|
|
#[test]
|
|
fn an_unrecognised_obligation_is_never_closed() {
|
|
let mut c = contract(&["src/"]);
|
|
c.post.push("the author was well rested".into());
|
|
let signed = op(&["src/lib.rs"]);
|
|
let energy = Energy {
|
|
measured_uj: Some(900),
|
|
estimated_uj: 900,
|
|
provenance: Provenance::HwShunt,
|
|
};
|
|
let r = binder().receipt_for_op(&c, &signed, &prior(), &energy).unwrap();
|
|
|
|
let entry = r
|
|
.discharge
|
|
.iter()
|
|
.find(|d| d.obligation.text == "the author was well rested")
|
|
.expect("declared");
|
|
assert!(
|
|
matches!(&entry.result, DischargeResult::Undischarged { reason } if reason.contains("not evaluated")),
|
|
"got {:?}",
|
|
entry.result
|
|
);
|
|
}
|
|
|
|
/// The receipt names the operation whose obligations it checked: the id is
|
|
/// derived from the op, not supplied beside it.
|
|
#[test]
|
|
fn the_transition_names_the_op_it_checked() {
|
|
let c = contract(&["src/"]);
|
|
let signed = op(&["src/lib.rs"]);
|
|
let energy = Energy {
|
|
measured_uj: Some(900),
|
|
estimated_uj: 900,
|
|
provenance: Provenance::HwShunt,
|
|
};
|
|
let r = binder().receipt_for_op(&c, &signed, &prior(), &energy).unwrap();
|
|
assert_eq!(r.transition, bridge_id(&signed.op.op_id().unwrap()));
|
|
}
|
|
|
|
/// A contract written for a different tree does not have its precondition
|
|
/// discharged by a receipt that states another prior.
|
|
#[test]
|
|
fn a_contract_for_a_different_prior_leaves_the_precondition_open() {
|
|
let c = contract_for_op(
|
|
"rename the settlement index field",
|
|
&ContentId::of_bytes(b"some other tree"),
|
|
&["src/".to_string()],
|
|
0.01,
|
|
key("enb"),
|
|
None,
|
|
Rigor::Jr2,
|
|
);
|
|
let signed = op(&["src/lib.rs"]);
|
|
let energy = Energy {
|
|
measured_uj: Some(900),
|
|
estimated_uj: 900,
|
|
provenance: Provenance::HwShunt,
|
|
};
|
|
let r = binder().receipt_for_op(&c, &signed, &prior(), &energy).unwrap();
|
|
|
|
let pre = r
|
|
.discharge
|
|
.iter()
|
|
.find(|d| d.obligation.kind == joulecontract_schema::ObligationKind::Pre)
|
|
.expect("declared");
|
|
assert!(
|
|
matches!(&pre.result, DischargeResult::Undischarged { .. }),
|
|
"got {:?}",
|
|
pre.result
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn estimator_energy_is_unaccounted_never_promoted() {
|
|
let c = contract(&["src/"]);
|
|
let signed = op(&["src/lib.rs"]);
|
|
let energy = Energy::from_tokens(100, 10);
|
|
let r = binder().receipt_for_op(&c, &signed, &prior(), &energy).unwrap();
|
|
assert_eq!(r.provenance, EnergyClass::Unaccounted);
|
|
assert!(r.meter.is_none(), "an estimator figure never carries a meter binding");
|
|
let v = validate(&c, &r).unwrap();
|
|
assert_eq!(v.max_profile.energy, EnergyClass::Unaccounted);
|
|
}
|
|
|
|
#[test]
|
|
fn overrun_is_recorded_not_blocked() {
|
|
// Receipt-time enforcement (JouleContract §5.3): the deviation is on
|
|
// the record as a negative residual; amend-or-fix is the resolution.
|
|
let c = contract(&["src/"]);
|
|
let signed = op(&["src/lib.rs"]);
|
|
let energy = Energy {
|
|
measured_uj: Some(50_000), // 0.05 J against a 0.01 J ceiling
|
|
estimated_uj: 50_000,
|
|
provenance: Provenance::HwShunt,
|
|
};
|
|
let r = binder().receipt_for_op(&c, &signed, &prior(), &energy).unwrap();
|
|
assert!(r.residual() < 0.0);
|
|
assert!(validate(&c, &r).is_ok(), "the system keeps running; the record decides");
|
|
}
|
|
|
|
#[test]
|
|
fn provenance_mapping_is_total_and_downward_honest() {
|
|
assert_eq!(energy_class(Provenance::HwShunt), EnergyClass::Metered);
|
|
assert_eq!(energy_class(Provenance::ModelBased), EnergyClass::ModelBased);
|
|
assert_eq!(energy_class(Provenance::Estimator), EnergyClass::Unaccounted);
|
|
}
|
|
}
|