lean_ctx/core/execution_ledger/host/
context_protocol.rs1use lean_ctx_ocla::DecisionSignerAdmissionV1;
6use lean_ctx_protocol::{
7 AcceptanceState, DecisionKind, DecisionStageV1, EngineInvocationV1, EngineObservationV1,
8};
9
10use super::{HostReceiptAttempt, HostReceiptAuthority, PublishedCanonicalReceipt, digest, now};
11use crate::core::{
12 canonical::canonical_serialize,
13 execution_protocol::RecordedExecutionProtocolV1,
14 outcome::{contracts::OutcomeContractV1, evaluator::evaluate_for_task},
15};
16
17impl HostReceiptAuthority {
18 pub fn published_context_protocol(
20 &self,
21 attempt: &HostReceiptAttempt,
22 invocation: &EngineInvocationV1,
23 observation: &EngineObservationV1,
24 published: &PublishedCanonicalReceipt,
25 ) -> Result<Option<RecordedExecutionProtocolV1>, String> {
26 if !self.allow_context_decision_signing {
27 return Ok(None);
28 }
29 let context = attempt
30 .context
31 .as_ref()
32 .ok_or("host_context_decision_missing")?;
33 let decision = attempt
34 .context_decision
35 .as_ref()
36 .ok_or("host_context_decision_missing")?;
37 let (receipt, _) = published
38 .read_canonical()
39 .map_err(|error| error.to_string())?;
40 let identity = digest(&canonical_serialize(&(
41 "native-unobserved-outcome-v1",
42 &attempt.task,
43 &receipt.receipt_id,
44 &decision.decision_id,
45 )))?;
46 let mut outcome = evaluate_for_task(
47 &OutcomeContractV1::for_task_class(context.decision().task_class),
48 &[],
49 format!("outcome:{}", identity.hex()),
50 attempt.task.task_id.as_str(),
51 now()?.as_str(),
52 );
53 if outcome.accepted != AcceptanceState::Unknown || outcome.quality_score_milli.is_some() {
54 return Err("native publication has no accepted outcome evidence".into());
55 }
56 outcome.plan_id = Some(attempt.plan.plan_id.clone());
57 outcome.receipt_id = Some(
58 lean_ctx_protocol::ReceiptId::new(receipt.receipt_id.as_str())
59 .map_err(|error| error.to_string())?,
60 );
61 outcome.decision_refs = vec![decision.decision_id.as_str().into()];
62 let protocol = RecordedExecutionProtocolV1 {
63 context_plan: context.context_projection().clone(),
64 execution_plan: attempt.plan.clone(),
65 invocation: invocation.clone(),
66 observation: observation.clone(),
67 receipt,
68 published_receipt: published.clone(),
69 accepted_outcome: outcome,
70 decision: decision.clone(),
71 };
72 let grant = DecisionSignerAdmissionV1 {
73 key_admission: self.signer_admission.clone(),
74 task: attempt.task.clone(),
75 stage: DecisionStageV1::Planning,
76 kind: DecisionKind::ContextSelection,
77 };
78 let key = self.signing_key.verifying_key();
79 protocol
80 .validated_for(
81 &attempt.task,
82 (&self.signer_admission, &key),
83 (&grant, &key),
84 Some(context),
85 &now()?,
86 )
87 .map_err(|error| error.to_string())?;
88 Ok(Some(protocol))
89 }
90}