Joule Code is the coding cargo class of the substrate: a coding operation — an edit, refactor, or generated function — is a content-addressed, signed transition over a source tree, resolved through the energy-tiered cascade (L0 Cache → L1 Lawful → L2 Embed → L3 Model → L4 Wire, stop-at-first-close) with a signed JWP receipt (measured + estimated µJ, honest Provenance) per operation. The agent schema is JouleClaw; L4 frontier agents are reached over ACP with the credential held by the vendor's own client — no protocol token, no required contact with any single company. Stands up the standard to the family schema: - README, SPEC.md (RFC-2119 normative), dual licence (Apache-2.0 code / CC-BY-4.0 spec), ROADMAP, CONTRIBUTING, CHANGELOG. - spec/: cargo (the coding-operation type), cascade (the tier binding), receipt (the JWP energy record), collab (federated, interface-level/Research). - vocab/LEXICON.md. - conformance/v1/: C1 content-addressing, C2 signing, C3 receipt, C4 cascade vectors + VERSIONS + regeneration; real Ed25519 RFC-8032 KATs, derived fields regenerated against the reference library. - joule-code-rs/: lean reference library (schema, cargo, receipt, cascade, conformance). cargo test --workspace passes; the conformance harness runs 11 pass / 0 fail / 7 skip (derived fields pending regeneration). Single-endpoint operation is the v0.1 surface; federated collaboration is specified at interface level and classified Research. Registers the standard in the family README. Co-Authored-By: Claude Opus 4.8 <[email protected]>
240 lines
8.5 KiB
Rust
240 lines
8.5 KiB
Rust
//! The Joule Code coding-operation cargo (SPEC §2, `spec/cargo.md`).
|
|
//!
|
|
//! A [`CodingOp`] is a signed transition that moves a source tree from a prior
|
|
//! state to a successor via an ordered edit set. Identity is the content
|
|
//! address of its canonical form excluding the signature; the signature binds
|
|
//! `op_id ‖ prior` so it cannot be replayed onto a different base.
|
|
|
|
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
|
|
use joule_code_schema::{ContentId, SchemaError, Tier, ALG_ED25519};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
/// Errors from building, signing, or verifying a coding operation.
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum CargoError {
|
|
#[error(transparent)]
|
|
Schema(#[from] SchemaError),
|
|
/// The edit list is empty (SPEC §2 requires non-empty).
|
|
#[error("edits must be non-empty")]
|
|
NoEdits,
|
|
/// Edits are not in ascending `(path, start)` order (non-canonical).
|
|
#[error("edits are not in canonical (path, start) order")]
|
|
EditsMisordered,
|
|
/// Two edits to the same file overlap.
|
|
#[error("edits overlap")]
|
|
EditsOverlap,
|
|
/// The algorithm identifier is not known to this implementation.
|
|
#[error("unknown algorithm id: {0}")]
|
|
UnknownAlgorithm(u8),
|
|
/// A malformed key or signature.
|
|
#[error("bad key or signature: {0}")]
|
|
Crypto(String),
|
|
/// The recomputed `op_id` did not match the signed one.
|
|
#[error("op_id mismatch (tampered op)")]
|
|
OpIdMismatch,
|
|
/// The signature did not verify against `op_id ‖ prior`.
|
|
#[error("signature does not bind op_id ‖ prior")]
|
|
BadSignature,
|
|
}
|
|
|
|
/// Where an edit applies within a file.
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub enum Anchor {
|
|
/// A half-open byte range against the prior blob.
|
|
ByteRange { start: u64, end: u64 },
|
|
/// A content-addressed hunk (rename-stable).
|
|
Hunk { hunk_id: ContentId },
|
|
}
|
|
|
|
/// One edit: a replacement of an anchored region of one file.
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct Edit {
|
|
/// Tree-relative, forward-slash path.
|
|
pub path: String,
|
|
/// Where the replacement applies.
|
|
pub anchor: Anchor,
|
|
/// The new bytes for the anchored region.
|
|
pub replacement: Vec<u8>,
|
|
}
|
|
|
|
impl Edit {
|
|
/// Sort key for canonical ordering: `(path, start)`. Hunk-anchored edits
|
|
/// sort after byte-range edits at the same path (start = u64::MAX).
|
|
fn sort_key(&self) -> (&str, u64) {
|
|
let start = match &self.anchor {
|
|
Anchor::ByteRange { start, .. } => *start,
|
|
Anchor::Hunk { .. } => u64::MAX,
|
|
};
|
|
(self.path.as_str(), start)
|
|
}
|
|
|
|
fn byte_span(&self) -> Option<(u64, u64)> {
|
|
match &self.anchor {
|
|
Anchor::ByteRange { start, end } => Some((*start, *end)),
|
|
Anchor::Hunk { .. } => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A coding operation: base + edits + author + intent + closing tier (SPEC §2).
|
|
/// The signature is carried separately in [`SignedOp`].
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct CodingOp {
|
|
/// Signature algorithm id (`0x01` = Ed25519).
|
|
pub alg: u8,
|
|
/// The tree state this op builds on.
|
|
pub prior: ContentId,
|
|
/// Ordered, non-empty, disjoint edits.
|
|
pub edits: Vec<Edit>,
|
|
/// The author's Ed25519 verifying key.
|
|
pub author: [u8; 32],
|
|
/// The request that produced the op (advisory).
|
|
pub intent: String,
|
|
/// The cascade tier that closed the request.
|
|
pub tier: Tier,
|
|
}
|
|
|
|
impl CodingOp {
|
|
/// Validate canonical structure (SPEC §2, `spec/cargo.md` §4): known alg,
|
|
/// non-empty edits, ascending `(path, start)` order, no overlap.
|
|
pub fn validate(&self) -> Result<(), CargoError> {
|
|
if self.alg != ALG_ED25519 {
|
|
return Err(CargoError::UnknownAlgorithm(self.alg));
|
|
}
|
|
if self.edits.is_empty() {
|
|
return Err(CargoError::NoEdits);
|
|
}
|
|
for pair in self.edits.windows(2) {
|
|
if pair[0].sort_key() > pair[1].sort_key() {
|
|
return Err(CargoError::EditsMisordered);
|
|
}
|
|
}
|
|
// Overlap check for byte-range edits within the same path.
|
|
for i in 0..self.edits.len() {
|
|
for j in (i + 1)..self.edits.len() {
|
|
if self.edits[i].path != self.edits[j].path {
|
|
continue;
|
|
}
|
|
if let (Some((a0, a1)), Some((b0, b1))) =
|
|
(self.edits[i].byte_span(), self.edits[j].byte_span())
|
|
{
|
|
if a0 < b1 && b0 < a1 {
|
|
return Err(CargoError::EditsOverlap);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// The operation id: content address of the canonical form (SPEC §3).
|
|
/// Validates structure first so a non-canonical op has no id.
|
|
pub fn op_id(&self) -> Result<ContentId, CargoError> {
|
|
self.validate()?;
|
|
Ok(ContentId::of_canonical(self)?)
|
|
}
|
|
|
|
/// The signature preimage: `op_id ‖ prior` (SPEC §2).
|
|
fn preimage(&self) -> Result<Vec<u8>, CargoError> {
|
|
let op_id = self.op_id()?;
|
|
let mut pre = op_id.as_str().as_bytes().to_vec();
|
|
pre.extend_from_slice(self.prior.as_str().as_bytes());
|
|
Ok(pre)
|
|
}
|
|
|
|
/// Sign this op with the author's signing key, producing a [`SignedOp`].
|
|
pub fn sign(self, key: &SigningKey) -> Result<SignedOp, CargoError> {
|
|
let sig = key.sign(&self.preimage()?);
|
|
Ok(SignedOp {
|
|
op: self,
|
|
sig: sig.to_bytes().to_vec(),
|
|
})
|
|
}
|
|
}
|
|
|
|
/// A coding operation plus its signature (SPEC §2). The signature is an
|
|
/// Ed25519 signature (64 bytes); it is held as `Vec<u8>` because `serde` does
|
|
/// not derive for arrays larger than 32, and verification checks the length.
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SignedOp {
|
|
pub op: CodingOp,
|
|
pub sig: Vec<u8>,
|
|
}
|
|
|
|
impl SignedOp {
|
|
/// Verify against a stated `prior` (the base the verifier believes is
|
|
/// current). Recomputes `op_id`, rebuilds `op_id ‖ prior`, and checks the
|
|
/// signature against `op.author`. Rejects replay onto a different base and
|
|
/// tampered edits (SPEC §2, `spec/cargo.md` §4).
|
|
pub fn verify(&self, expected_prior: &ContentId) -> Result<(), CargoError> {
|
|
if &self.op.prior != expected_prior {
|
|
return Err(CargoError::BadSignature);
|
|
}
|
|
let vk = VerifyingKey::from_bytes(&self.op.author)
|
|
.map_err(|e| CargoError::Crypto(e.to_string()))?;
|
|
let sig_arr: [u8; 64] = self
|
|
.sig
|
|
.as_slice()
|
|
.try_into()
|
|
.map_err(|_| CargoError::Crypto("signature must be 64 bytes".into()))?;
|
|
let sig = Signature::from_bytes(&sig_arr);
|
|
vk.verify(&self.op.preimage()?, &sig)
|
|
.map_err(|_| CargoError::BadSignature)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use ed25519_dalek::SigningKey;
|
|
|
|
fn sample(prior: &str) -> CodingOp {
|
|
let sk = SigningKey::from_bytes(&[7u8; 32]);
|
|
CodingOp {
|
|
alg: ALG_ED25519,
|
|
prior: ContentId(format!("b3:{prior}")),
|
|
edits: vec![Edit {
|
|
path: "src/lib.rs".into(),
|
|
anchor: Anchor::ByteRange { start: 0, end: 0 },
|
|
replacement: b"pub fn add() {}".to_vec(),
|
|
}],
|
|
author: sk.verifying_key().to_bytes(),
|
|
intent: "add add()".into(),
|
|
tier: Tier::L3,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn sign_then_verify_roundtrips() {
|
|
let sk = SigningKey::from_bytes(&[7u8; 32]);
|
|
let op = sample("AAAA");
|
|
let prior = op.prior.clone();
|
|
let signed = op.sign(&sk).unwrap();
|
|
assert!(signed.verify(&prior).is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn replay_onto_different_base_rejected() {
|
|
let sk = SigningKey::from_bytes(&[7u8; 32]);
|
|
let signed = sample("AAAA").sign(&sk).unwrap();
|
|
let other = ContentId("b3:CCCC".into());
|
|
assert!(signed.verify(&other).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn overlapping_edits_have_no_op_id() {
|
|
let mut op = sample("AAAA");
|
|
op.edits = vec![
|
|
Edit { path: "f".into(), anchor: Anchor::ByteRange { start: 0, end: 10 }, replacement: vec![1] },
|
|
Edit { path: "f".into(), anchor: Anchor::ByteRange { start: 5, end: 15 }, replacement: vec![2] },
|
|
];
|
|
assert!(matches!(op.op_id(), Err(CargoError::EditsOverlap)));
|
|
}
|
|
|
|
#[test]
|
|
fn unknown_alg_fails_closed() {
|
|
let mut op = sample("AAAA");
|
|
op.alg = 0xFF;
|
|
assert!(matches!(op.op_id(), Err(CargoError::UnknownAlgorithm(0xFF))));
|
|
}
|
|
}
|