diff --git a/jcp/SPEC.md b/jcp/SPEC.md index c6f6030a4..90b6245bc 100644 --- a/jcp/SPEC.md +++ b/jcp/SPEC.md @@ -369,6 +369,7 @@ CallerIs(did:key) ; the caller must be this principal NotBefore(uint) ; unix seconds NotAfter(uint) ; unix seconds — a window narrower than `expiry` MaxCostUj(uint) ; a ceiling on ONE invocation, distinct from the cumulative budget +ModelIn([content-id]) ; the frame must name its model (§6), and it must be one of these ``` Every predicate ranges only over what an enforcement point holds when it decides: @@ -378,11 +379,21 @@ so a condition on content is expressible only as `PayloadIs`, binding an exact payload rather than inspecting it. An implementation MUST NOT invent a predicate that requires data the decision point does not have. -Two of these express authority the fixed axes cannot. `PayloadIs` makes a grant +Three of these express authority the fixed axes cannot. `PayloadIs` makes a grant **one-shot**: it authorizes one specific call, so a leaked grant cannot be replayed against different arguments. `MaxCostUj` bounds a **single invocation**, where `budget_uj` bounds the cumulative total — so a delegate cannot spend an -entire remaining budget in one call. +entire remaining budget in one call. `ModelIn` says **which model** the grant +authorizes. The capability's `resource` names the endpoint, and one endpoint can +run many models; `ModelIn` names the model content addresses this grant may +run there. It ranges over the frame's `model` field (§6), which the runtime that +dispatches the call fills with the content address of the model it will load, +and it **fails closed**: a frame that names no model does not satisfy it, so a +dispatcher that does not say what it will run cannot exercise the grant. This is +authorization by content address, not a registry (§12): the grant names the +bytes it permits, and nothing has to be looked up. A delegate narrows the set +by adding a smaller `ModelIn` beside the inherited one; both must hold, so the +intersection governs. **The metered path (REQUIRED).** A caveat whose predicate ranges over the invocation's cost — `MaxCostUj` — cannot be evaluated in the two-phase metered @@ -1007,9 +1018,16 @@ Frame { action: string resource: content-id payload_hash: content-id + model: content-id? ; OPTIONAL: the model the action runs (§4.9 ModelIn) } ``` +`model` is filled by the runtime that dispatches the call, which knows the +content address of the model it will load. It is absent for an action that runs +no model, and an absent `model` is omitted from the canonical encoding, so such +a frame, and every receipt and vector over it, is byte-identical to one built +before the field existed. + `trust = data` marks untrusted output. A conforming runtime MUST refuse to exercise any capability from a `data` frame (§7), regardless of its content. Tool and agent outputs MUST be tagged `data`. @@ -1220,6 +1238,6 @@ gate. - **C20 — standing grants:** an event-triggered firing mints a strictly-weaker child that traces to the standing grant; the wrong event class, an exhausted firing count, and a firing past the cumulative budget are each refused without advancing the ledger; a policy that would amplify its grant is refused at author time. - **C21 — checkpoints + amendments:** an agent checkpoints its own state; only the grant's issuer — a principal distinct from the agent — may amend, only to tighten, and only up to what the grant has **left** at that checkpoint; an agent steering itself, a non-issuer amending, a widening amendment, an amendment above the remainder of a checkpoint that stated its spend, and an amendment bound to a different checkpoint are each refused. - **C23 — receipt binds its payload + profile migration:** a `jcp/2` receipt commits the `payload_hash` it decided over; substituting only the payload (same grant, action, and cost) fails verification — the forgery a payload-blind receipt could not detect; and receipts published under `jcp/1` still verify, because the verifier recomputes the preimage under the profile the receipt names rather than assuming one. -- **C24 — caveats:** a caveat embedded in the grant is enforced offline by the decision point; `PayloadIs` makes a grant one-shot (a replay with different arguments is denied `caveat-unsatisfied`); `MaxCostUj` bounds a single invocation independently of the cumulative budget; a caveat may narrow below the capability; attenuation inherits every parent caveat and refuses to drop one; and a grant with no caveats encodes byte-identically to one minted before they existed. +- **C24 — caveats:** a caveat embedded in the grant is enforced offline by the decision point; `PayloadIs` makes a grant one-shot (a replay with different arguments is denied `caveat-unsatisfied`); `MaxCostUj` bounds a single invocation independently of the cumulative budget; `ModelIn` admits only a frame naming one of its models, and denies a frame naming another or none; a caveat may narrow below the capability; attenuation inherits every parent caveat and refuses to drop one; and a grant with no caveats encodes byte-identically to one minted before they existed. - **C25 — flow:** a value's propagated label must sit at or below the grant's ceiling and clear the resource's taint floor; an argument whose provenance was **not computed** fails closed against a ceilinged grant rather than passing as clean; several ceilings in a chain compose to the strictest and attenuation never widens one; a flow denial debits nothing, so a tainted call costs zero joules; and a grant with no flow caveat encodes byte-identically to one minted before flow existed. - **C22 — dual ceilings:** a money ceiling beside the joule ceiling is enforced independently — the first to bind denies (`funds-exceeded` / `budget-exceeded`), a foreign-currency cost is refused `currency-mismatch` rather than converted, an allow debits both dimensions and a deny debits neither, and an absent money ceiling encodes byte-identically to a joules-only grant (so its content id and signature are unchanged). Attenuation narrows money independently: dropping, raising, or re-denominating a bounded parent's ceiling is refused; a plain attenuation inherits it. diff --git a/jcp/jcp-rs/crates/jcp-action-receipt/examples/end_to_end.rs b/jcp/jcp-rs/crates/jcp-action-receipt/examples/end_to_end.rs index 42a06b98a..a5b977fa0 100644 --- a/jcp/jcp-rs/crates/jcp-action-receipt/examples/end_to_end.rs +++ b/jcp/jcp-rs/crates/jcp-action-receipt/examples/end_to_end.rs @@ -97,6 +97,7 @@ fn main() -> Result<(), Box> { action: "read".into(), resource, payload_hash: ContentId::of_bytes(b"input/quarterly-report"), + model: None, }; let decision = rt.decide(&frame, &delegated, sub_did, authorised_cost(), 0); let verdict = match decision.verdict { diff --git a/jcp/jcp-rs/crates/jcp-bridge/src/lib.rs b/jcp/jcp-rs/crates/jcp-bridge/src/lib.rs index 65368cd89..48c05e0fe 100644 --- a/jcp/jcp-rs/crates/jcp-bridge/src/lib.rs +++ b/jcp/jcp-rs/crates/jcp-bridge/src/lib.rs @@ -45,6 +45,7 @@ pub fn mcp_call_frame(grant_id: ContentId, server: &str, tool: &str, args: &[u8] action: tool.to_string(), resource: mcp_resource(server, tool), payload_hash: ContentId::of_bytes(args), + model: None, } } @@ -59,6 +60,7 @@ pub fn mcp_result_frame(grant_id: ContentId, server: &str, tool: &str, output: & action: tool.to_string(), resource: mcp_resource(server, tool), payload_hash: ContentId::of_bytes(output), + model: None, } } @@ -71,6 +73,7 @@ pub fn a2a_task_frame(grant_id: ContentId, agent: &str, method: &str, payload: & action: method.to_string(), resource: a2a_resource(agent, method), payload_hash: ContentId::of_bytes(payload), + model: None, } } @@ -92,6 +95,7 @@ pub fn x402_pay_frame(grant_id: ContentId, network: &str, pay_to: &str, payload: action: "pay".to_string(), resource: x402_resource(network, pay_to), payload_hash: ContentId::of_bytes(payload), + model: None, } } diff --git a/jcp/jcp-rs/crates/jcp-channel/src/lib.rs b/jcp/jcp-rs/crates/jcp-channel/src/lib.rs index f78936958..d39d4e896 100644 --- a/jcp/jcp-rs/crates/jcp-channel/src/lib.rs +++ b/jcp/jcp-rs/crates/jcp-channel/src/lib.rs @@ -204,6 +204,7 @@ mod tests { action: "read".into(), resource: r, payload_hash: ContentId::of_bytes(b"q"), + model: None, } } diff --git a/jcp/jcp-rs/crates/jcp-cite/src/lib.rs b/jcp/jcp-rs/crates/jcp-cite/src/lib.rs index 2b62bd4a6..7d396d545 100644 --- a/jcp/jcp-rs/crates/jcp-cite/src/lib.rs +++ b/jcp/jcp-rs/crates/jcp-cite/src/lib.rs @@ -220,6 +220,7 @@ impl Citation { action: "retrieve".into(), resource, payload_hash: self.id()?, + model: None, }) } } diff --git a/jcp/jcp-rs/crates/jcp-cli/src/main.rs b/jcp/jcp-rs/crates/jcp-cli/src/main.rs index 238d41ff8..7cb2724f6 100644 --- a/jcp/jcp-rs/crates/jcp-cli/src/main.rs +++ b/jcp/jcp-rs/crates/jcp-cli/src/main.rs @@ -160,10 +160,10 @@ fn script(sg: &SignedGrant, db: ContentId, prod_secrets: ContentId, agent: Did) } fn call(sg: &SignedGrant, resource: ContentId, action: &str, trust: Trust) -> Frame { - Frame { kind: FrameKind::ToolCall, trust, grant_id: sg.grant.id(), action: action.into(), resource, payload_hash: ContentId::of_bytes(b"q") } + Frame { kind: FrameKind::ToolCall, trust, grant_id: sg.grant.id(), action: action.into(), resource, payload_hash: ContentId::of_bytes(b"q"), model: None } } fn result(sg: &SignedGrant, resource: ContentId, action: &str, trust: Trust) -> Frame { - Frame { kind: FrameKind::ToolResult, trust, grant_id: sg.grant.id(), action: action.into(), resource, payload_hash: ContentId::of_bytes(b"out") } + Frame { kind: FrameKind::ToolResult, trust, grant_id: sg.grant.id(), action: action.into(), resource, payload_hash: ContentId::of_bytes(b"out"), model: None } } fn truncate(s: &str, n: usize) -> String { diff --git a/jcp/jcp-rs/crates/jcp-conformance/src/bin/gen_vectors.rs b/jcp/jcp-rs/crates/jcp-conformance/src/bin/gen_vectors.rs index 92e329f76..791a88f90 100644 --- a/jcp/jcp-rs/crates/jcp-conformance/src/bin/gen_vectors.rs +++ b/jcp/jcp-rs/crates/jcp-conformance/src/bin/gen_vectors.rs @@ -42,6 +42,7 @@ fn main() { action: action.into(), resource, payload_hash: ContentId::of_bytes(b"q"), + model: None, }; let mut rt = Runtime::new(); @@ -165,6 +166,7 @@ fn main() { action: "read".into(), resource, payload_hash: ContentId::of_bytes(b"q"), + model: None, }; let mut drt = Runtime::new(); let d_allow = drt.decide_priced(&dual_f, &dual_sg, holder_did, Energy::measured(10), &Price::new("USD", 300), 10); diff --git a/jcp/jcp-rs/crates/jcp-conformance/src/lib.rs b/jcp/jcp-rs/crates/jcp-conformance/src/lib.rs index dee97256d..09e0e2f08 100644 --- a/jcp/jcp-rs/crates/jcp-conformance/src/lib.rs +++ b/jcp/jcp-rs/crates/jcp-conformance/src/lib.rs @@ -65,7 +65,7 @@ pub fn checks() -> Vec { Check { id: "C21 checkpoint + amendment under separation of duties", run: c21_checkpoint }, Check { id: "C22 dual ceilings: money beside joules, no rate invented", run: c22_dual_ceilings }, Check { id: "C23 receipt binds its payload + jcp/1 still verifies", run: c23_payload_binding }, - Check { id: "C24 caveats: one-shot, per-call cost, monotone attenuation", run: c24_caveats }, + Check { id: "C24 caveats: one-shot, per-call cost, model, monotone attenuation", run: c24_caveats }, Check { id: "C-neg expired / forged / wrong-caller", run: cneg_denials }, Check { id: "C-neg canonical rejects floats", run: cneg_canonical }, ] @@ -102,6 +102,7 @@ fn frame(sg: &SignedGrant, resource: ContentId, action: &str, trust: Trust) -> F action: action.into(), resource, payload_hash: ContentId::of_bytes(b"p"), + model: None, } } @@ -356,6 +357,34 @@ fn c24_caveats() -> Result<(), String> { return Err("clearing caveats counted as narrowing".into()); } + // ModelIn: the frame must name one of the grant's models; a frame naming + // another, or none, is denied — the dispatcher must say what it will run. + let approved = ContentId::of_bytes(b"approved weights"); + let mut gm = grant(&sk(1), holder, r, &["read"], 1_000_000, 0).grant; + gm.caveats = vec![Caveat::ModelIn(vec![approved])]; + let sgm = issue(&sk(1), gm).map_err(|e| e.to_string())?; + let mut rtm = Runtime::new(); + let mut named = frame(&sgm, r, "read", Trust::Control); + named.model = Some(approved); + if !rtm.decide(&named, &sgm, holder, Energy::measured(1), 10).is_allow() { + return Err("a frame naming the granted model was denied".into()); + } + let mut other = frame(&sgm, r, "read", Trust::Control); + other.model = Some(ContentId::of_bytes(b"other weights")); + if rtm.decide(&other, &sgm, holder, Energy::measured(1), 10).verdict != Verdict::Deny(DenyReason::CaveatUnsatisfied) { + return Err("a frame naming another model was not denied".into()); + } + if rtm.decide(&frame(&sgm, r, "read", Trust::Control), &sgm, holder, Energy::measured(1), 10).verdict + != Verdict::Deny(DenyReason::CaveatUnsatisfied) + { + return Err("a frame naming no model passed a ModelIn caveat".into()); + } + // A frame naming no model encodes without the field. + let bytes = canonical_cbor(&frame(&sgm, r, "read", Trust::Control)).map_err(|e| e.to_string())?; + if String::from_utf8_lossy(&bytes).contains("model") { + return Err("an absent model must not appear in the frame's encoding".into()); + } + // No caveats ⇒ the field is absent from the encoding entirely. let plain = grant(&sk(1), holder, r, &["read"], 100, 0); let bytes = canonical_cbor(&plain.grant).map_err(|e| e.to_string())?; diff --git a/jcp/jcp-rs/crates/jcp-orchestrate/src/lib.rs b/jcp/jcp-rs/crates/jcp-orchestrate/src/lib.rs index 416f3c59d..ec301d3c0 100644 --- a/jcp/jcp-rs/crates/jcp-orchestrate/src/lib.rs +++ b/jcp/jcp-rs/crates/jcp-orchestrate/src/lib.rs @@ -478,6 +478,7 @@ impl Orchestrator { action: task.kind.clone(), resource: harness_resource(&id), payload_hash: ContentId::of_bytes(&task.payload), + model: None, }; let decision = self.runtime.decide(&frame, &grant, self.caller, Energy::estimator(cost), self.now); let receipt = seal(&frame, &grant.grant, &decision, &self.steward_sk, &self.steward) @@ -602,6 +603,7 @@ impl Orchestrator { action: task.kind.clone(), resource: harness_resource(harness), payload_hash: ContentId::of_bytes(&task.payload), + model: None, }; self.runtime.decide( &frame, diff --git a/jcp/jcp-rs/crates/jcp-receipt/src/lib.rs b/jcp/jcp-rs/crates/jcp-receipt/src/lib.rs index 8446364cc..9a9131969 100644 --- a/jcp/jcp-rs/crates/jcp-receipt/src/lib.rs +++ b/jcp/jcp-rs/crates/jcp-receipt/src/lib.rs @@ -836,6 +836,7 @@ mod tests { action: "read".into(), resource, payload_hash: ContentId::of_bytes(b"q"), + model: None, }; let mut rt = Runtime::new(); let d = rt.decide(&frame, &sg, grant.subject, jcp_schema::Energy::measured(30), 10); @@ -869,6 +870,7 @@ mod tests { action: "read".into(), resource, payload_hash: ContentId::of_bytes(b"q"), + model: None, }; let mut rt = Runtime::new(); let allow_f = mk(Trust::Control); @@ -1039,6 +1041,7 @@ mod tests { action: "read".into(), resource, payload_hash: ContentId::of_bytes(b"q"), + model: None, }; let steward_sk = sk(7); let pubkey = steward_sk.verifying_key().to_bytes(); diff --git a/jcp/jcp-rs/crates/jcp-runtime/src/lib.rs b/jcp/jcp-rs/crates/jcp-runtime/src/lib.rs index b4d3439fe..b6e01258b 100644 --- a/jcp/jcp-rs/crates/jcp-runtime/src/lib.rs +++ b/jcp/jcp-rs/crates/jcp-runtime/src/lib.rs @@ -862,6 +862,7 @@ mod tests { action: action.into(), resource, payload_hash: ContentId::of_bytes(b"payload"), + model: None, } } @@ -1054,6 +1055,50 @@ mod tests { assert_eq!(d.verdict, Verdict::Deny(DenyReason::CaveatUnsatisfied)); } + /// Which model a grant authorizes. The capability's resource names the + /// endpoint; `ModelIn` names the model content addresses that endpoint may + /// run for this grant. A frame naming one of them is allowed, a frame + /// naming another is denied, and a frame naming none is denied too, so a + /// dispatcher that does not say what it will run cannot exercise the grant. + /// A denial debits nothing. + #[test] + fn model_in_admits_only_a_named_model_and_fails_closed() { + let approved = ContentId::of_bytes(b"nli weights, revision 7"); + let other = ContentId::of_bytes(b"some other weights"); + let (sg, holder, r) = caveated(vec![Caveat::ModelIn(vec![approved])]); + let mut rt = Runtime::new(); + + let mut f = frame(&sg, r, "read", Trust::Control); + f.model = Some(approved); + assert!(rt.decide(&f, &sg, holder, Energy::measured(10), 10).is_allow()); + + let mut wrong = frame(&sg, r, "read", Trust::Control); + wrong.model = Some(other); + let d = rt.decide(&wrong, &sg, holder, Energy::measured(10), 10); + assert_eq!(d.verdict, Verdict::Deny(DenyReason::CaveatUnsatisfied)); + + let unnamed = frame(&sg, r, "read", Trust::Control); + assert_eq!(unnamed.model, None); + let d = rt.decide(&unnamed, &sg, holder, Energy::measured(10), 10); + assert_eq!(d.verdict, Verdict::Deny(DenyReason::CaveatUnsatisfied)); + assert_eq!(rt.spent(&sg.grant.id()), 10, "only the allowed call was debited"); + } + + /// A delegate narrows the models by adding a smaller `ModelIn` beside the + /// inherited one; both must hold, so only their intersection is allowed. + #[test] + fn model_in_narrows_by_intersection() { + let (a, b) = (ContentId::of_bytes(b"model a"), ContentId::of_bytes(b"model b")); + let (sg, holder, r) = caveated(vec![Caveat::ModelIn(vec![a, b]), Caveat::ModelIn(vec![b])]); + let mut rt = Runtime::new(); + let mut fa = frame(&sg, r, "read", Trust::Control); + fa.model = Some(a); + assert_eq!(rt.decide(&fa, &sg, holder, Energy::measured(1), 10).verdict, Verdict::Deny(DenyReason::CaveatUnsatisfied)); + let mut fb = frame(&sg, r, "read", Trust::Control); + fb.model = Some(b); + assert!(rt.decide(&fb, &sg, holder, Energy::measured(1), 10).is_allow()); + } + /// A per-invocation ceiling, distinct from the cumulative budget: a delegate /// cannot burn the whole remaining budget in one call. #[test] @@ -1260,6 +1305,7 @@ mod tests { action: "read".into(), resource: r, payload_hash: ContentId::of_bytes(b"p"), + model: None, } } diff --git a/jcp/jcp-rs/crates/jcp-schema/src/lib.rs b/jcp/jcp-rs/crates/jcp-schema/src/lib.rs index 813826069..35b6ccfd9 100644 --- a/jcp/jcp-rs/crates/jcp-schema/src/lib.rs +++ b/jcp/jcp-rs/crates/jcp-schema/src/lib.rs @@ -334,6 +334,14 @@ pub enum Caveat { /// `budget_uj`, which is cumulative: this bounds any single call, so a /// delegate cannot spend the whole remaining budget in one shot. MaxCostUj(u64), + /// The frame must name the model its action runs ([`Frame::model`]), and + /// that model must be one of these content addresses: **which** model a + /// grant authorizes, where the capability's `resource` names only the + /// endpoint that runs it. It fails closed: a frame naming no model does + /// not satisfy it, so a dispatcher that does not say what it will run + /// cannot exercise the grant. This is authorization by content address, + /// not a registry (SPEC §12): the grant names the bytes it permits. + ModelIn(Vec), /// A **flow ceiling** (SPEC §4.10): the greatest-tainted provenance label /// whose values may exercise this grant. Unlike the other caveats, this one /// cannot be decided from the frame alone — it needs the argument's propagated @@ -370,6 +378,7 @@ impl Caveat { Caveat::NotBefore(t) => now >= *t, Caveat::NotAfter(t) => now <= *t, Caveat::MaxCostUj(max) => cost_uj <= *max, + Caveat::ModelIn(set) => frame.model.as_ref().is_some_and(|m| set.contains(m)), // Flow is decided by the runtime gate against the argument's label, // not from the frame — so as a caveat it never blocks. Caveat::FlowBelow(_) => true, @@ -537,6 +546,14 @@ pub struct Frame { pub action: String, pub resource: ContentId, pub payload_hash: ContentId, + /// The content address of the model this frame's action runs, named by + /// the runtime that dispatches it, which knows what it will load. Checked + /// by [`Caveat::ModelIn`]. Absent for an action that runs no model, and + /// then omitted from the encoding, so a frame without one is + /// byte-identical to one built before the field existed, as is every + /// receipt and vector over it. + #[serde(skip_serializing_if = "Option::is_none", default)] + pub model: Option, } /// Why the runtime denied an action (SPEC §7). A deterministic, enumerable set — @@ -638,6 +655,35 @@ mod tests { Did::from_verifying_key(&SigningKey::from_bytes(&[seed; 32]).verifying_key()) } + /// A frame that names no model encodes exactly as one built before the + /// field existed, so its hash and every receipt over it are unchanged; a + /// frame that names one encodes differently. `ModelIn` holds only for a + /// frame naming a model in its set. + #[test] + fn a_frame_without_a_model_keeps_its_bytes() { + #[derive(Serialize)] + struct Before { + kind: FrameKind, + trust: Trust, + grant_id: ContentId, + action: String, + resource: ContentId, + payload_hash: ContentId, + } + let (g, r, p) = (ContentId::of_bytes(b"g"), ContentId::of_bytes(b"r"), ContentId::of_bytes(b"p")); + let f = Frame { kind: FrameKind::ToolCall, trust: Trust::Control, grant_id: g, action: "read".into(), resource: r, payload_hash: p, model: None }; + let before = Before { kind: FrameKind::ToolCall, trust: Trust::Control, grant_id: g, action: "read".into(), resource: r, payload_hash: p }; + assert_eq!(crate::cbor::canonical_cbor(&f).unwrap(), crate::cbor::canonical_cbor(&before).unwrap()); + + let m = ContentId::of_bytes(b"model weights"); + let named = Frame { model: Some(m), ..f.clone() }; + assert_ne!(crate::cbor::canonical_cbor(&named).unwrap(), crate::cbor::canonical_cbor(&f).unwrap()); + let c = Caveat::ModelIn(vec![m]); + assert!(c.holds(&named, did(1), 0, 0)); + assert!(!c.holds(&f, did(1), 0, 0), "a frame naming no model fails closed"); + assert!(!c.holds(&Frame { model: Some(ContentId::of_bytes(b"other")), ..f }, did(1), 0, 0)); + } + #[test] fn did_key_roundtrips() { let d = did(7); diff --git a/jcp/jcp-rs/crates/jcp-threshold/src/lib.rs b/jcp/jcp-rs/crates/jcp-threshold/src/lib.rs index 2b7e4b181..b7cf439f3 100644 --- a/jcp/jcp-rs/crates/jcp-threshold/src/lib.rs +++ b/jcp/jcp-rs/crates/jcp-threshold/src/lib.rs @@ -279,6 +279,7 @@ mod tests { action: "delete".into(), resource: r, payload_hash: ContentId::of_bytes(b"x"), + model: None, }; let chain = vec![sg]; let cost = Energy::estimator(100); diff --git a/jcp/jcp-rs/crates/jcp-wire/src/lib.rs b/jcp/jcp-rs/crates/jcp-wire/src/lib.rs index de4effdc3..ef4c90c68 100644 --- a/jcp/jcp-rs/crates/jcp-wire/src/lib.rs +++ b/jcp/jcp-rs/crates/jcp-wire/src/lib.rs @@ -327,6 +327,7 @@ mod tests { action: "read".into(), resource: r, payload_hash: ContentId::of_bytes(b"q"), + model: None, }; WireRequest::sign(frame, chain.to_vec(), &sk(3), Energy::estimator(cost), 10).unwrap() } diff --git a/joule-code/joule-code-rs/crates/joule-code-jcp/src/lib.rs b/joule-code/joule-code-rs/crates/joule-code-jcp/src/lib.rs index fa4c7b4a0..bc79cca27 100644 --- a/joule-code/joule-code-rs/crates/joule-code-jcp/src/lib.rs +++ b/joule-code/joule-code-rs/crates/joule-code-jcp/src/lib.rs @@ -164,6 +164,7 @@ impl JcpGate { action: tier_action(tier).to_string(), resource: leaf.capability.resource, payload_hash: ContentId::of_bytes(format!("{}\n{}", req.prior, req.intent).as_bytes()), + model: None, }; let cost = Energy::estimator((inner.costs)(tier)); let (caller, now) = (inner.caller, inner.now); diff --git a/joule-ui/joule-ui-rs/crates/joule-ui-interaction/examples/jcp_metered.rs b/joule-ui/joule-ui-rs/crates/joule-ui-interaction/examples/jcp_metered.rs index 0fc77d90b..5350560c9 100644 --- a/joule-ui/joule-ui-rs/crates/joule-ui-interaction/examples/jcp_metered.rs +++ b/joule-ui/joule-ui-rs/crates/joule-ui-interaction/examples/jcp_metered.rs @@ -54,6 +54,7 @@ impl JcpAuthorizer { action: action.to_string(), resource: jcp_schema::ContentId::of_bytes(action.as_bytes()), payload_hash: jcp_schema::ContentId::of_bytes(b""), + model: None, } } } diff --git a/jouleclaw/jouleclaw-rs/crates/jouleclaw-jcp/src/lib.rs b/jouleclaw/jouleclaw-rs/crates/jouleclaw-jcp/src/lib.rs index 762e7fb03..6595939a2 100644 --- a/jouleclaw/jouleclaw-rs/crates/jouleclaw-jcp/src/lib.rs +++ b/jouleclaw/jouleclaw-rs/crates/jouleclaw-jcp/src/lib.rs @@ -243,6 +243,7 @@ impl JcpGate { action: action.to_string(), resource, payload_hash: payload, + model: None, }; let cost = Energy::estimator(cost_uj); let (caller, now) = (inner.caller, inner.now); @@ -277,6 +278,7 @@ impl JcpGate { action: tool_id.to_string(), resource: tool_resource(tool_id), payload_hash: ContentId::of_bytes(request), + model: None, }; let cost = Energy::estimator(cost_uj); let (caller, now) = (inner.caller, inner.now); diff --git a/jouleclaw/jouleclaw-rs/crates/jouleclaw-jcp/src/stack.rs b/jouleclaw/jouleclaw-rs/crates/jouleclaw-jcp/src/stack.rs index e5b67d6d8..36265ceb4 100644 --- a/jouleclaw/jouleclaw-rs/crates/jouleclaw-jcp/src/stack.rs +++ b/jouleclaw/jouleclaw-rs/crates/jouleclaw-jcp/src/stack.rs @@ -159,6 +159,7 @@ impl JcpGate { action: action.to_string(), resource: leaf.capability.resource, payload_hash: payload, + model: None, }; let cost = Energy::estimator(cost_uj); let (caller, now) = (inner.caller, inner.now); diff --git a/sandbox/sandbox-rs/crates/sandbox-jcp/src/lib.rs b/sandbox/sandbox-rs/crates/sandbox-jcp/src/lib.rs index e1d15efc4..4e6e976cc 100644 --- a/sandbox/sandbox-rs/crates/sandbox-jcp/src/lib.rs +++ b/sandbox/sandbox-rs/crates/sandbox-jcp/src/lib.rs @@ -113,6 +113,7 @@ impl JcpGate { action: EXECUTE_ACTION.to_string(), resource, payload_hash: resource, + model: None, }; let cost = Energy::estimator(ceiling_uj); let decision = self.runtime.decide_chained(&frame, &self.chain, self.caller, cost, self.now);