open-standards/jouleclaw/jouleclaw-rs/crates/jouleclaw-mission/examples/three_role_demo.rs
Transaction Science 4def92aac1 jouleclaw: wave-24 — contract + handoff + mission + value report
Four precision primitives for multi-day, multi-agent runs.

- jouleclaw-contract: ValidationContract / Assertion / Feature /
  Evidence / verify() -> ContractVerdict. Authored before code,
  lives independently of implementation. Typed coverage matrix
  with per-assertion rollup; must-have gaps block ship.
- jouleclaw-graph: RunEvent::HandoffRecorded variant +
  WorkerHandoffEvent + HandoffCommand (BLAKE3 over NUL-joined
  argv). Forces every agent to write down what happened; hash
  chain protects against post-hoc edits.
- jouleclaw-mission: three-role Mission { contract, run,
  evidence } with async Worker trait. Orchestrator authors,
  Worker does, Validator judges. Serial-by-default dispatch;
  validate() is a pure function of (contract, evidence).
- jouleclaw-hydra-client: ValueReport companion to CallReport.
  Tracks closure rate (delivered / attempted) per call;
  mean_closure_rate aggregates across a run. The activity-vs-
  value gap closed at the dispatch layer.

Spec gains normative sections 16 (contract) / 17 (handoff) /
18 (mission) / 19 (value report). Vocab v1.jsonld + v1.ttl +
v1.shapes.ttl extended with the new classes, enumerations,
properties, and SHACL shapes.

Drive-by: jouleclaw-stack had jouleclaw-prov as a dev-dep while
using it in library code; promoted to [dependencies].

Tests: contract 17/17, graph 40/40 (+2 new), mission 6/6,
hydra-client 20/20 (+4 new). Two runnable examples
(missions_pass, three_role_demo).

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-03 02:35:34 -04:00

185 lines
6.3 KiB
Rust

//! End-to-end: Orchestrator authors a contract + a graph, a
//! Worker runs each feature, the Validator returns the verdict.
//!
//! Run with: `cargo run -p jouleclaw-mission --example three_role_demo`
use async_trait::async_trait;
use jouleclaw_contract::{
Acceptance, Assertion, AssertionCheck, EvidenceItem, Feature, Priority,
ValidationContract,
};
use jouleclaw_graph::{
Edge, MergeStrategy, Node, NodeId, NodeKind, RunGraph, WorkerHandoffEvent,
};
use jouleclaw_mission::{
Mission, MissionOptions, Worker, WorkerAssignment, WorkerOutcome,
};
// One Worker stand-in. Real implementations route to an L1 tool,
// an L3 model, or a human-in-the-loop.
struct DemoWorker;
#[async_trait]
impl Worker for DemoWorker {
async fn run(
&self,
a: WorkerAssignment,
) -> Result<WorkerOutcome, Box<dyn std::error::Error + Send + Sync>> {
// Pretend each feature emits the evidence its claimed
// assertion requires.
let mut ev = Vec::new();
for c in &a.feature.claims {
if let Some(assertion) = a.contract.assertion(c) {
match &assertion.check {
AssertionCheck::Command {
command_hash,
expected_exit,
} => ev.push(EvidenceItem::CommandRun {
command_hash: command_hash.clone(),
exit_code: *expected_exit,
}),
AssertionCheck::Artifact { path, expected_hash } => {
ev.push(EvidenceItem::ArtifactPresent {
path: path.clone(),
hash: expected_hash
.clone()
.unwrap_or_else(|| "ff".repeat(32)),
})
}
AssertionCheck::Tool { tool_id, args_hash } => {
ev.push(EvidenceItem::ToolResult {
tool_id: tool_id.clone(),
args_hash: args_hash.clone(),
passed: true,
})
}
AssertionCheck::Checker { checker_id } => {
ev.push(EvidenceItem::CheckerResult {
checker_id: checker_id.clone(),
passed: true,
})
}
}
}
}
Ok(WorkerOutcome {
handoff: WorkerHandoffEvent {
worker: a.worker_id.clone(),
node_id: None,
feature_id: Some(a.feature.id.clone()),
closed_at: 1_700_000_000,
completed: vec![format!("did {}", a.feature.title)],
undone: vec![],
commands: vec![],
issues_found: vec![],
procedures_followed: vec!["AGENTS.md".into()],
},
evidence: ev,
})
}
}
#[tokio::main]
async fn main() {
// ── Orchestrator: author the contract + the graph ────────────
let contract = ValidationContract::seal(
"Three-role mission demo",
"did:key:zOrchestrator",
vec![
Assertion {
id: "A-1".into(),
title: "Build passes".into(),
rationale: "cargo test -p target".into(),
priority: Priority::MustHave,
check: AssertionCheck::Command {
command_hash: "build-hash".into(),
expected_exit: 0,
},
},
Assertion {
id: "A-2".into(),
title: "Receipt journal written".into(),
rationale: String::new(),
priority: Priority::MustHave,
check: AssertionCheck::Artifact {
path: "out/receipts.jsonl".into(),
expected_hash: None,
},
},
],
vec![
Feature {
id: "F-build".into(),
title: "Build".into(),
description: String::new(),
claims: vec!["A-1".into()],
},
Feature {
id: "F-receipts".into(),
title: "Wire receipts".into(),
description: String::new(),
claims: vec!["A-2".into()],
},
],
Acceptance::default(),
)
.expect("contract seals");
let graph = RunGraph {
entry: NodeId(1),
terminals: vec![NodeId(2)],
nodes: vec![
Node {
id: NodeId(1),
kind: NodeKind::Stage("F-build".into()),
label: "F-build".into(),
merge: MergeStrategy::AllRequired,
},
Node {
id: NodeId(2),
kind: NodeKind::Stage("F-receipts".into()),
label: "F-receipts".into(),
merge: MergeStrategy::AllRequired,
},
],
edges: vec![Edge {
from: NodeId(1),
to: NodeId(2),
label: None,
}],
};
let mut mission = Mission::start(
contract,
graph,
"demo",
MissionOptions {
strict_workers: true,
},
)
.expect("mission starts");
// ── Workers: dispatched serially by the convenience helper ───
let worker = DemoWorker;
let verdict = mission
.run_serial(&worker, "did:key:zWorker")
.await
.expect("workers run");
// ── Validator: the verdict ───────────────────────────────────
println!("contract: {}", verdict.contract_hash);
println!("passed: {}", verdict.passed);
println!("summary: {}", verdict.summary);
println!("handoffs: {}", mission.run.journal.entries().len() - 1);
println!(
"coverage: {} verified / {} should-have-gaps / {} blocking",
verdict
.coverage
.assertion_rollup
.values()
.filter(|r| matches!(r, jouleclaw_contract::AssertionRollup::Verified))
.count(),
verdict.should_have_gaps.len(),
verdict.blocking_gaps.len(),
);
}