connector-spend: one function decides the charge; a bound result is billed its true cost whatever else is malformed (s4.bound-malformed-overrun-undercharged, s4.binding-identity-unpinned)

Ruling spend-bills-on-identity-not-validity: ProviderCall::charge is the only charge decision; complete no longer routes billing through validate_against. Tests: malformed bound overruns reach the ceiling; each identity field alone sends a result to the reservation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-28 18:13:14 +01:00
commit d6f9792f39

View file

@ -393,6 +393,34 @@ impl ProviderCall {
})
}
/// What a completed `result` is billed: the one owner of the charge
/// decision. A transported result bound to `invocation` by request id and
/// caller (on the result and on its receipt) costs what its own outcome's
/// usage derives, with the overrun past the reservation recorded
/// truthfully, whether or not it is otherwise malformed. Any other result
/// (refused, or bound to another request or caller) costs the whole
/// reservation. Validity decides what the caller is told, never this.
fn charge(&self, invocation: &TransportInvocation, result: &TransportResult) -> Charge {
match &result.disposition {
TransportDisposition::Transported { receipt, .. }
if result.request_id == invocation.request_id()
&& result.caller_runtime_id == invocation.caller_runtime_id
&& receipt.request_id == invocation.request_id()
&& receipt.caller_runtime_id == invocation.caller_runtime_id =>
{
let cost_micro_usd = self.cost_of(&receipt.outcome);
Charge {
cost_micro_usd,
overrun_micro_usd: self.overrun_of(cost_micro_usd),
}
}
_ => Charge {
cost_micro_usd: self.reserved_micro_usd,
overrun_micro_usd: 0,
},
}
}
/// How far `cost_micro_usd` exceeds what was reserved. The reservation is
/// the worst case the kit can compute, so a nonzero overrun means the
/// backend broke its contract (usage above its own request's bounds).
@ -839,28 +867,7 @@ impl TransportService {
// is recorded and the slot released, so the caller gets an answer and
// a retry replays it.
let admissible = self.admits_result(&result, &claim);
// The service prices what the backend reported, but only for a result
// bound to this invocation: a transported result charges the cost its
// own outcome's usage derives, and its overrun past the reservation,
// whatever its receipt says (a receipt that misstates either is
// replaced by the stand-in but still charged the true cost). Any
// other result, including one bound to another request, charges the
// whole reservation.
let charge = match &result.disposition {
TransportDisposition::Transported { receipt, .. }
if result.validate_against(invocation).is_ok() =>
{
let cost_micro_usd = claim.call.cost_of(&receipt.outcome);
Charge {
cost_micro_usd,
overrun_micro_usd: claim.call.overrun_of(cost_micro_usd),
}
}
_ => Charge {
cost_micro_usd: reserved_micro_usd,
overrun_micro_usd: 0,
},
};
let charge = claim.call.charge(invocation, &result);
let charged_micro_usd = charge.cost_micro_usd;
let mut response = if admissible {
encrypt_result(&result, &security).inspect_err(|_| {
@ -3471,6 +3478,76 @@ mod tests {
drop(two);
}
#[test]
fn a_bound_malformed_overrun_is_charged_its_true_cost_and_reaches_the_ceiling() {
let reserved = reservation();
for case in 0..3 {
let store = TestStore::new();
let mut service = open_limited(&store, 2 * reserved, 3_600_000);
let claim = claim_at(&mut service, "request-1", 1_000);
let mut result = overrun_result(claim.call(), "done");
let TransportDisposition::Transported { events, receipt } = &mut result.disposition
else {
unreachable!();
};
match case {
0 => events[0].sequence = 1,
1 => {
let TransportOutcome::Completed { usage, .. } = &receipt.outcome else {
unreachable!();
};
receipt.outcome = TransportOutcome::Failed {
kind: FailureKind::Provider,
message: String::new(),
usage: Some(usage.clone()),
};
}
_ => receipt.provider = String::new(),
}
let invocation = invocation_at("request-1", 1_000, MAX_COST);
assert!(result.validate_against(&invocation).is_err(), "{case}");
let response = service.complete(*claim, result, FRAME).unwrap();
// The caller is told the result was refused; the bill is its true cost.
assert_eq!(
receipt_of(&response, &invocation).provider,
STAND_IN_PROVIDER,
"{case}"
);
assert_eq!(charged_of(&service, "request-1"), OVERRUN_COST, "{case}");
assert_eq!(service.window_spend(CALLER, 0), OVERRUN_COST, "{case}");
assert_eq!(
refusal_at(&mut service, "request-2", 1_000, MAX_COST),
Refusal::SpendCeiling,
"{case}"
);
}
}
#[test]
fn each_binding_check_alone_sends_a_result_to_the_reservation() {
let reserved = reservation();
// Every other field is valid and the usage overruns: a guard that
// ignored the one changed identity field would charge 30,000,000.
for case in 0..4 {
let store = TestStore::new();
let mut service = open_limited(&store, 2 * reserved, 3_600_000);
let claim = claim_at(&mut service, "request-1", 1_000);
let mut result = overrun_result(claim.call(), "done");
let TransportDisposition::Transported { receipt, .. } = &mut result.disposition else {
unreachable!();
};
match case {
0 => result.request_id = "request-2".to_string(),
1 => result.caller_runtime_id = "another-caller".to_string(),
2 => receipt.request_id = "request-2".to_string(),
_ => receipt.caller_runtime_id = "another-caller".to_string(),
}
service.complete(*claim, result, FRAME).unwrap();
assert_eq!(charged_of(&service, "request-1"), reserved, "{case}");
assert_eq!(service.window_spend(CALLER, 0), reserved, "{case}");
}
}
#[test]
fn a_result_that_is_not_transported_is_charged_the_reservation() {
let store = TestStore::new();
@ -3518,10 +3595,7 @@ mod tests {
let mut service = open_limited(&store, ceiling, window);
assert_eq!(service.admission_window_start_unix_ms(CALLER), None);
assert_eq!(run_window(&mut service, "fill", 50_000), 3);
assert_eq!(
service.admission_window_start_unix_ms(CALLER),
Some(50_000)
);
assert_eq!(service.admission_window_start_unix_ms(CALLER), Some(50_000));
// Behind the full window: the cause is named, no slot is taken and
// nothing is reserved.
assert_eq!(