jcp: a ModelIn caveat says which model a grant authorizes, and the frame names the model it runs #285
19 changed files with 167 additions and 6 deletions
24
jcp/SPEC.md
24
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.
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -204,6 +204,7 @@ mod tests {
|
|||
action: "read".into(),
|
||||
resource: r,
|
||||
payload_hash: ContentId::of_bytes(b"q"),
|
||||
model: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -220,6 +220,7 @@ impl Citation {
|
|||
action: "retrieve".into(),
|
||||
resource,
|
||||
payload_hash: self.id()?,
|
||||
model: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ pub fn checks() -> Vec<Check> {
|
|||
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())?;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<ContentId>),
|
||||
/// 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<ContentId>,
|
||||
}
|
||||
|
||||
/// 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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue