use pointlock_ir::{
ActChannel, ActionExecution, ActionName, ActionOutcome, ActionOutcomeKind,
AssertionOutcomeRecord, AttemptRecord, BindingState, CallFrame, CheckpointView, ErrorClass,
EvidenceRef, ExecutionMode, FlowId, Frontier, Hash, HumanPending, HumanPurpose, IterState,
ObservationRecord, PathFrame, PendingIntent, RunLogEvent, RunLogPayload, RunPath, StepId,
StepRecord, StepState, StepVerdict, Verdict, VerdictStatus,
};
use serde_json::Value;
use std::collections::BTreeMap;
use crate::error::FoldError;
#[derive(Debug, Clone, PartialEq)]
pub struct RunMeta {
pub run_id: String,
pub flow_id: FlowId,
pub ir_hash: Hash,
pub lockfile_digest: Hash,
pub params_snapshot: Value,
pub binding: BindingState,
pub created_at_ms: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RunStatus {
Running,
Suspended,
AwaitingHuman,
Finished,
}
impl RunStatus {
pub fn as_str(&self) -> &'static str {
match self {
RunStatus::Running => "running",
RunStatus::Suspended => "suspended",
RunStatus::AwaitingHuman => "awaitingHuman",
RunStatus::Finished => "finished",
}
}
pub fn parse(value: &str) -> Option<Self> {
match value {
"running" => Some(RunStatus::Running),
"suspended" => Some(RunStatus::Suspended),
"awaitingHuman" => Some(RunStatus::AwaitingHuman),
"finished" => Some(RunStatus::Finished),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct FoldedRun {
pub view: CheckpointView,
pub status: RunStatus,
}
pub fn fold_checkpoint(meta: &RunMeta, events: &[RunLogEvent]) -> Result<FoldedRun, FoldError> {
Ok(fold_state(meta, events)?.finish())
}
pub(crate) fn fold_state(meta: &RunMeta, events: &[RunLogEvent]) -> Result<FoldState, FoldError> {
let mut state = FoldState::seed(meta);
let mut prev_seq: Option<u64> = None;
for event in events {
if event.run_id != meta.run_id {
return Err(FoldError::RunIdMismatch {
seq: event.seq,
expected: meta.run_id.clone(),
actual: event.run_id.clone(),
});
}
if let Some(prev) = prev_seq
&& event.seq <= prev
{
return Err(FoldError::NonMonotonicSeq {
prev,
seq: event.seq,
});
}
prev_seq = Some(event.seq);
state.apply(event)?;
}
Ok(state)
}
#[derive(Debug, Clone)]
struct InFlightStep {
run_path: RunPath,
step_id: StepId,
effect_hash: Hash,
judge_hash: Hash,
resolved_inputs: Value,
attempts: Vec<AttemptRecord>,
call_outputs: Option<Value>,
observations: Vec<ObservationRecord>,
evidence: Vec<EvidenceRef>,
assertion_outcomes: Vec<AssertionOutcomeRecord>,
verdict: Option<StepVerdict>,
}
type DispatchIdentity = (Option<u32>, Option<ActChannel>, Option<ActionName>);
#[derive(Debug, Clone)]
pub(crate) struct FoldState {
view: CheckpointView,
status: RunStatus,
root_flow_id: FlowId,
started: bool,
in_flight: Vec<InFlightStep>,
intent_dispatch: BTreeMap<String, DispatchIdentity>,
frame_paths: Vec<RunPath>,
}
impl FoldState {
fn seed(meta: &RunMeta) -> Self {
let root_path: RunPath = vec![PathFrame::Flow {
flow_id: meta.flow_id.clone(),
ir_hash: meta.ir_hash.clone(),
}];
FoldState {
view: CheckpointView {
run_id: meta.run_id.clone(),
ir_hash: meta.ir_hash.clone(),
lockfile_digest: meta.lockfile_digest.clone(),
params_snapshot: meta.params_snapshot.clone(),
binding: meta.binding.clone(),
completed: Vec::new(),
frames: Vec::new(),
frontier: Frontier {
run_path: root_path,
state: StepState::Pending,
pending_intent: None,
},
human_pending: None,
},
status: RunStatus::Running,
root_flow_id: meta.flow_id.clone(),
started: false,
in_flight: Vec::new(),
intent_dispatch: BTreeMap::new(),
frame_paths: Vec::new(),
}
}
pub(crate) fn finish(mut self) -> FoldedRun {
for frame in &mut self.view.frames {
frame.iter_stack.clear();
}
for pair in self.in_flight.windows(2) {
let (parent, child) = (&pair[0], &pair[1]);
if child.run_path.len() <= parent.run_path.len() {
continue;
}
let extends = parent
.run_path
.iter()
.zip(child.run_path.iter())
.all(|(a, b)| same_site(a, b));
let Some(PathFrame::Iteration { index, key }) =
child.run_path.get(parent.run_path.len())
else {
continue;
};
let Some(var) = parent
.resolved_inputs
.get("as")
.and_then(Value::as_str)
.filter(|_| extends)
else {
continue;
};
let owner = self.frame_paths.iter().rposition(|prefix| {
parent.run_path.len() >= prefix.len()
&& prefix
.iter()
.zip(parent.run_path.iter())
.all(|(a, b)| same_site(a, b))
});
if let Some(owner) = owner
&& owner < self.view.frames.len()
{
self.view.frames[owner].iter_stack.push(IterState {
var: var.to_owned(),
index: *index,
key: key.clone(),
});
}
}
FoldedRun {
view: self.view,
status: self.status,
}
}
pub(crate) fn apply(&mut self, event: &RunLogEvent) -> Result<(), FoldError> {
let seq = event.seq;
if !self.started && !matches!(event.payload, RunLogPayload::RunStarted { .. }) {
return Err(FoldError::EventBeforeRunStarted {
seq,
event_type: event.payload.event_type(),
});
}
match &event.payload {
RunLogPayload::RunStarted {
ir_hash,
lockfile_digest,
params_snapshot,
supervise_policy: _,
} => {
if self.started {
return Err(FoldError::DuplicateRunStarted { seq });
}
self.started = true;
self.view.ir_hash = ir_hash.clone();
self.view.lockfile_digest = lockfile_digest.clone();
self.view.params_snapshot = params_snapshot.clone();
self.view.frames.push(CallFrame {
flow_id: self.root_flow_id.clone(),
ir_hash: ir_hash.clone(),
call_step_id: None,
inputs_snapshot: params_snapshot.clone(),
vars: Default::default(),
iter_stack: Vec::new(),
next_index: 0,
});
self.view.frontier = Frontier {
run_path: vec![PathFrame::Flow {
flow_id: self.root_flow_id.clone(),
ir_hash: ir_hash.clone(),
}],
state: StepState::Pending,
pending_intent: None,
};
self.frame_paths.push(vec![PathFrame::Flow {
flow_id: self.root_flow_id.clone(),
ir_hash: ir_hash.clone(),
}]);
self.status = RunStatus::Running;
}
RunLogPayload::StepEntered {
step_id,
effect_hash,
judge_hash,
resolved_inputs,
} => {
self.in_flight.push(InFlightStep {
run_path: event.run_path.clone(),
step_id: step_id.clone(),
effect_hash: effect_hash.clone(),
judge_hash: judge_hash.clone(),
resolved_inputs: resolved_inputs.clone(),
attempts: Vec::new(),
call_outputs: None,
observations: Vec::new(),
evidence: Vec::new(),
assertion_outcomes: Vec::new(),
verdict: None,
});
self.view.frontier = Frontier {
run_path: event.run_path.clone(),
state: StepState::Ready,
pending_intent: None,
};
}
RunLogPayload::PreflightProbed { outcomes } => {
if !outcomes.is_empty() {
let missed = outcomes
.iter()
.any(|outcome| outcome.result != VerdictStatus::Pass);
self.view.frontier.state = if missed {
StepState::Drifted
} else {
StepState::Probing
};
}
}
RunLogPayload::ActionIntent {
call_id,
args_snapshot,
chain_index,
channel,
action_name,
} => {
self.intent_dispatch.insert(
call_id.clone(),
(*chain_index, *channel, action_name.clone()),
);
self.view.frontier.state = StepState::Acting;
self.view.frontier.pending_intent = Some(PendingIntent {
call_id: call_id.clone(),
args_snapshot: args_snapshot.clone(),
});
}
RunLogPayload::ActionSettled { call_id, outcome } => {
let step =
self.in_flight
.last_mut()
.ok_or_else(|| FoldError::EventOutsideStep {
seq,
event_type: event.payload.event_type(),
})?;
let dispatch = self.intent_dispatch.remove(call_id).unwrap_or_default();
step.attempts
.push(attempt_record(call_id, outcome, dispatch));
if self
.view
.frontier
.pending_intent
.as_ref()
.is_some_and(|intent| intent.call_id == *call_id)
{
self.view.frontier.pending_intent = None;
}
self.view.frontier.state = StepState::Settling;
}
RunLogPayload::ObservationRecorded { observation } => {
let step =
self.in_flight
.last_mut()
.ok_or_else(|| FoldError::EventOutsideStep {
seq,
event_type: event.payload.event_type(),
})?;
if let Some(screenshot) = &observation.screenshot {
step.evidence.push(screenshot.clone());
}
if let Some(ui_snapshot) = &observation.ui_snapshot {
step.evidence.push(ui_snapshot.clone());
}
step.observations.push(observation.clone());
self.view.frontier.state = StepState::Observing;
}
RunLogPayload::AssertionEvaluated { outcome } => {
let step =
self.in_flight
.last_mut()
.ok_or_else(|| FoldError::EventOutsideStep {
seq,
event_type: event.payload.event_type(),
})?;
step.assertion_outcomes.push(outcome.clone());
self.view.frontier.state = StepState::Asserting;
}
RunLogPayload::VerdictRecorded {
verdict,
localized,
localization_gaps: _,
remote_archival_error: _,
} => {
let in_flight_at_path = self
.in_flight
.iter()
.rposition(|step| same_instance(&step.run_path, &event.run_path));
if let Some(index) = in_flight_at_path {
let step = &mut self.in_flight[index];
merge_evidence(&mut step.evidence, localized);
step.verdict = Some(project_verdict(verdict));
self.view.frontier.state = StepState::Judged;
} else if let Some(record) = self
.view
.completed
.iter_mut()
.rev()
.find(|record| same_instance(&record.run_path, &event.run_path))
{
merge_evidence(&mut record.evidence, localized);
record.verdict = Some(project_verdict(verdict));
} else {
return Err(FoldError::VerdictWithoutTarget { seq });
}
}
RunLogPayload::StepExited {
state,
output,
localized,
..
} => {
let mut step = self
.in_flight
.pop()
.ok_or(FoldError::StepExitedWithoutEntry { seq })?;
merge_evidence(&mut step.evidence, localized);
if self
.view
.human_pending
.as_ref()
.is_some_and(|pending| pending.run_path == event.run_path)
{
self.view.human_pending = None;
}
self.view.completed.push(StepRecord {
run_path: step.run_path,
step_id: step.step_id,
effect_hash: step.effect_hash,
judge_hash: step.judge_hash,
attempts: step.attempts,
resolved_inputs: step.resolved_inputs,
output: output.clone().or(step.call_outputs),
observations: step.observations,
evidence: step.evidence,
assertion_outcomes: step.assertion_outcomes,
verdict: step.verdict,
});
let frame = self
.view
.frames
.last_mut()
.ok_or(FoldError::NoActiveFrame { seq })?;
if self
.frame_paths
.last()
.is_some_and(|prefix| direct_body_child(prefix, &event.run_path))
{
frame.next_index += 1;
}
self.view.frontier = Frontier {
run_path: event.run_path.clone(),
state: *state,
pending_intent: None,
};
}
RunLogPayload::CallFramePushed {
frame,
rebase: false,
} => {
self.view.frames.push(frame.clone());
self.frame_paths.push(event.run_path.clone());
}
RunLogPayload::CallFramePushed {
frame,
rebase: true,
} => {
let level = event
.run_path
.iter()
.filter(|frame| matches!(frame, PathFrame::Call { .. }))
.count();
let depth = self.view.frames.len();
let Some(open) = self.view.frames.get_mut(level) else {
return Err(FoldError::RebaseWithoutFrame { seq, level, depth });
};
open.ir_hash = frame.ir_hash.clone();
}
RunLogPayload::CallFramePopped { outputs } => {
if self.view.frames.len() <= 1 {
return Err(FoldError::PoppedRootFrame { seq });
}
self.view.frames.pop();
self.frame_paths.pop();
if let Some(step) = self.in_flight.last_mut() {
step.call_outputs = outputs.clone();
}
}
RunLogPayload::HandlerTriggered {
hook: _,
trigger: _,
disposition: _,
} => {
}
RunLogPayload::HumanRequested {
request_id,
purpose,
mode,
prompt,
presents: _,
decisions: _,
output_schema: _,
deadline_at_ms,
} => {
self.view.human_pending = Some(HumanPending {
run_path: event.run_path.clone(),
request_id: request_id.clone(),
purpose: *purpose,
mode: *mode,
prompt: prompt.clone(),
deadline_at_ms: *deadline_at_ms,
});
self.view.frontier.state = StepState::AwaitingHuman;
self.status = RunStatus::AwaitingHuman;
}
RunLogPayload::HumanResponded {
request_id,
purpose,
response,
actor: _,
} => {
let paired = self
.view
.human_pending
.as_ref()
.is_some_and(|pending| pending.request_id == *request_id);
if !paired {
return Err(FoldError::UnpairedHumanResponse {
seq,
request_id: request_id.clone(),
});
}
let retains = *purpose == HumanPurpose::Supervision
&& response.get("decision").and_then(Value::as_str) == Some("suspend");
if retains {
self.status = RunStatus::AwaitingHuman;
} else {
self.view.human_pending = None;
self.status = RunStatus::Running;
}
}
RunLogPayload::RunSuspended { .. } => {
self.status = if self.view.human_pending.is_some() {
RunStatus::AwaitingHuman
} else {
RunStatus::Suspended
};
}
RunLogPayload::RunResumed {
alignment_report: _,
supervise_policy: _,
event_cursor,
} => {
self.status = RunStatus::Running;
if let Some(cursor) = event_cursor {
if self.view.binding.session_lineage.last() != Some(&cursor.session_id) {
self.view
.binding
.session_lineage
.push(cursor.session_id.clone());
}
self.view.binding.event_cursor = cursor.clone();
}
}
RunLogPayload::RunFinished {
verdict: _,
remote_archival_error: _,
} => {
self.status = RunStatus::Finished;
}
}
Ok(())
}
}
fn direct_body_child(prefix: &RunPath, path: &RunPath) -> bool {
path.len() == prefix.len() + 1
&& prefix.iter().zip(path.iter()).all(|(a, b)| same_site(a, b))
&& matches!(
path.last(),
Some(PathFrame::Step { .. }) | Some(PathFrame::Call { .. })
)
}
fn same_instance(a: &[PathFrame], b: &[PathFrame]) -> bool {
a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| same_site(x, y))
}
fn same_site(a: &PathFrame, b: &PathFrame) -> bool {
match (a, b) {
(PathFrame::Flow { flow_id: a, .. }, PathFrame::Flow { flow_id: b, .. }) => a == b,
(PathFrame::Step { step_id: a }, PathFrame::Step { step_id: b }) => a == b,
(
PathFrame::Call {
step_id: a,
callee_flow_id: af,
..
},
PathFrame::Call {
step_id: b,
callee_flow_id: bf,
..
},
) => a == b && af == bf,
(
PathFrame::Iteration { index: a, key: ak },
PathFrame::Iteration { index: b, key: bk },
) => a == b && ak == bk,
(a, b) => a == b,
}
}
fn merge_evidence(evidence: &mut Vec<EvidenceRef>, localized: &[EvidenceRef]) {
for entry in localized {
let duplicate = evidence
.iter()
.any(|existing| existing.sha256 == entry.sha256 && existing.asset.id == entry.asset.id);
if !duplicate {
evidence.push(entry.clone());
}
}
}
fn attempt_record(
call_id: &str,
outcome: &ActionOutcome,
dispatch: DispatchIdentity,
) -> AttemptRecord {
let kind = match outcome {
ActionOutcome::Succeeded { .. } => ActionOutcomeKind::Succeeded,
ActionOutcome::Failed { .. } => ActionOutcomeKind::Failed,
ActionOutcome::Cancelled { .. } => ActionOutcomeKind::Cancelled,
ActionOutcome::TimedOut { .. } => ActionOutcomeKind::TimedOut,
};
let error_class = match outcome {
ActionOutcome::Succeeded { .. } => None,
ActionOutcome::Failed { error }
| ActionOutcome::Cancelled { error }
| ActionOutcome::TimedOut { error } => parse_error_class(&error.code),
};
let (execution_mode, fallback_reason) = match outcome {
ActionOutcome::Succeeded { result } => match &result.execution {
Some(ActionExecution::NativeSemantic { .. }) => {
(Some(ExecutionMode::NativeSemantic), None)
}
Some(ActionExecution::WebSemantic { .. }) => (Some(ExecutionMode::WebSemantic), None),
Some(ActionExecution::CoordinateFallback {
fallback_reason, ..
}) => (
Some(ExecutionMode::CoordinateFallback),
Some(*fallback_reason),
),
None => (None, None),
},
_ => (None, None),
};
let (chain_index, channel, action_name) = dispatch;
AttemptRecord {
call_id: call_id.to_owned(),
outcome: kind,
error_class,
execution_mode,
fallback_reason,
chain_index,
channel,
action_name,
}
}
fn parse_error_class(code: &str) -> Option<ErrorClass> {
serde_json::from_value(Value::String(code.to_owned())).ok()
}
fn project_verdict(verdict: &Verdict) -> StepVerdict {
StepVerdict {
status: verdict.status,
degraded: verdict.degraded,
supersedes: verdict.supersedes.clone(),
}
}
#[cfg(test)]
mod tests {
use pointlock_ir::{BindingState, EventCursor, SupervisePolicy};
use serde_json::json;
use super::*;
fn hash(fill: char) -> Hash {
Hash::new(format!("sha256:{}", fill.to_string().repeat(64))).expect("valid hash")
}
fn meta() -> RunMeta {
RunMeta {
run_id: "run-1".to_owned(),
flow_id: FlowId::new("checkout").expect("valid flow id"),
ir_hash: hash('a'),
lockfile_digest: hash('b'),
params_snapshot: json!({"user": "alice"}),
binding: BindingState {
device_id: "dev-1".to_owned(),
session_lineage: vec!["s-1".to_owned()],
event_cursor: EventCursor {
session_id: "s-1".to_owned(),
last_sequence: 0,
},
},
created_at_ms: 1,
}
}
fn event(seq: u64, run_path: RunPath, payload: RunLogPayload) -> RunLogEvent {
RunLogEvent {
run_id: "run-1".to_owned(),
seq,
at_ms: 1_000 + seq,
run_path,
payload,
}
}
fn run_started() -> RunLogPayload {
RunLogPayload::RunStarted {
ir_hash: hash('a'),
lockfile_digest: hash('b'),
params_snapshot: json!({"user": "alice"}),
supervise_policy: Some(SupervisePolicy::Mutating),
}
}
#[test]
fn zero_events_fold_to_the_seeded_pre_start_view() {
let folded = fold_checkpoint(&meta(), &[]).expect("fold");
assert!(folded.view.frames.is_empty());
assert_eq!(folded.view.frontier.state, StepState::Pending);
assert_eq!(folded.status, RunStatus::Running);
}
#[test]
fn run_started_initializes_the_root_frame_from_meta_flow_id() {
let folded = fold_checkpoint(&meta(), &[event(1, vec![], run_started())]).expect("fold");
assert_eq!(folded.view.frames.len(), 1);
assert_eq!(folded.view.frames[0].flow_id.as_str(), "checkout");
assert_eq!(
folded.view.frames[0].inputs_snapshot,
json!({"user": "alice"})
);
assert_eq!(folded.view.frames[0].next_index, 0);
}
#[test]
fn events_before_run_started_are_rejected() {
let err = fold_checkpoint(
&meta(),
&[event(
1,
vec![],
RunLogPayload::StepEntered {
step_id: StepId::new("login").expect("valid step id"),
effect_hash: hash('c'),
judge_hash: hash('d'),
resolved_inputs: json!({}),
},
)],
)
.expect_err("must reject");
assert_eq!(
err,
FoldError::EventBeforeRunStarted {
seq: 1,
event_type: "stepEntered"
}
);
}
#[test]
fn duplicate_run_started_is_rejected() {
let err = fold_checkpoint(
&meta(),
&[
event(1, vec![], run_started()),
event(2, vec![], run_started()),
],
)
.expect_err("must reject");
assert_eq!(err, FoldError::DuplicateRunStarted { seq: 2 });
}
#[test]
fn non_monotonic_seq_is_rejected() {
let err = fold_checkpoint(
&meta(),
&[
event(1, vec![], run_started()),
event(
1,
vec![],
RunLogPayload::RunSuspended {
provider_state_summary: None,
reason: None,
},
),
],
)
.expect_err("must reject");
assert_eq!(err, FoldError::NonMonotonicSeq { prev: 1, seq: 1 });
}
#[test]
fn step_exited_without_entry_is_rejected() {
let err = fold_checkpoint(
&meta(),
&[
event(1, vec![], run_started()),
event(
2,
vec![],
RunLogPayload::StepExited {
provider_state_summary: None,
state: StepState::Judged,
output: None,
localized: Vec::new(),
localization_gaps: Vec::new(),
},
),
],
)
.expect_err("must reject");
assert_eq!(err, FoldError::StepExitedWithoutEntry { seq: 2 });
}
#[test]
fn step_record_fields_are_harvested_from_the_carrier_events() {
let step_path: RunPath = vec![PathFrame::Step {
step_id: StepId::new("login").expect("valid step id"),
}];
let folded = fold_checkpoint(
&meta(),
&[
event(1, vec![], run_started()),
event(
2,
step_path.clone(),
RunLogPayload::StepEntered {
step_id: StepId::new("login").expect("valid step id"),
effect_hash: hash('c'),
judge_hash: hash('d'),
resolved_inputs: json!({"element": {"identifier": "loginButton"}}),
},
),
event(
3,
step_path.clone(),
RunLogPayload::StepExited {
provider_state_summary: None,
state: StepState::Judged,
output: Some(json!({"ok": true})),
localized: Vec::new(),
localization_gaps: Vec::new(),
},
),
],
)
.expect("fold");
let record = &folded.view.completed[0];
assert_eq!(record.effect_hash, hash('c'));
assert_eq!(record.judge_hash, hash('d'));
assert_eq!(
record.resolved_inputs,
json!({"element": {"identifier": "loginButton"}})
);
assert_eq!(record.output, Some(json!({"ok": true})));
}
#[test]
fn popping_the_root_frame_is_rejected() {
let err = fold_checkpoint(
&meta(),
&[
event(1, vec![], run_started()),
event(2, vec![], RunLogPayload::CallFramePopped { outputs: None }),
],
)
.expect_err("must reject");
assert_eq!(err, FoldError::PoppedRootFrame { seq: 2 });
}
#[test]
fn unpaired_human_response_is_rejected() {
let err = fold_checkpoint(
&meta(),
&[
event(1, vec![], run_started()),
event(
2,
vec![],
RunLogPayload::HumanResponded {
request_id: "req-ghost".to_owned(),
purpose: pointlock_ir::HumanPurpose::Step,
response: json!({}),
actor: "cli:tester".to_owned(),
},
),
],
)
.expect_err("must reject");
assert_eq!(
err,
FoldError::UnpairedHumanResponse {
seq: 2,
request_id: "req-ghost".to_owned()
}
);
}
fn human_requested(request_id: &str, purpose: HumanPurpose) -> RunLogPayload {
RunLogPayload::HumanRequested {
request_id: request_id.to_owned(),
purpose,
mode: match purpose {
HumanPurpose::Step => Some(pointlock_ir::HumanMode::Confirm),
HumanPurpose::Supervision => None,
},
prompt: "Decide".to_owned(),
presents: json!([]),
decisions: None,
output_schema: None,
deadline_at_ms: match purpose {
HumanPurpose::Step => Some(9_000),
HumanPurpose::Supervision => None,
},
}
}
#[test]
fn supervision_suspend_answer_keeps_the_request_pending() {
let step_path: RunPath = vec![PathFrame::Step {
step_id: StepId::new("pay").expect("valid step id"),
}];
let folded = fold_checkpoint(
&meta(),
&[
event(1, vec![], run_started()),
event(
2,
step_path.clone(),
human_requested("req-1", HumanPurpose::Supervision),
),
event(
3,
step_path.clone(),
RunLogPayload::HumanResponded {
request_id: "req-1".to_owned(),
purpose: HumanPurpose::Supervision,
response: json!({"decision": "suspend"}),
actor: "cli:tester".to_owned(),
},
),
event(
4,
vec![],
RunLogPayload::RunSuspended {
provider_state_summary: None,
reason: None,
},
),
],
)
.expect("fold");
let pending = folded.view.human_pending.expect("request stays pending");
assert_eq!(pending.request_id, "req-1");
assert_eq!(folded.status, RunStatus::AwaitingHuman);
let folded = fold_checkpoint(
&meta(),
&[
event(1, vec![], run_started()),
event(
2,
step_path.clone(),
human_requested("req-1", HumanPurpose::Supervision),
),
event(
3,
step_path.clone(),
RunLogPayload::HumanResponded {
request_id: "req-1".to_owned(),
purpose: HumanPurpose::Supervision,
response: json!({"decision": "suspend"}),
actor: "cli:tester".to_owned(),
},
),
event(
4,
step_path,
RunLogPayload::HumanResponded {
request_id: "req-1".to_owned(),
purpose: HumanPurpose::Supervision,
response: json!({"decision": "proceed"}),
actor: "cli:tester".to_owned(),
},
),
],
)
.expect("fold");
assert!(folded.view.human_pending.is_none());
assert_eq!(folded.status, RunStatus::Running);
}
#[test]
fn run_suspended_while_a_request_is_pending_stays_awaiting_human() {
let step_path: RunPath = vec![PathFrame::Step {
step_id: StepId::new("ask").expect("valid step id"),
}];
let folded = fold_checkpoint(
&meta(),
&[
event(1, vec![], run_started()),
event(2, step_path, human_requested("req-2", HumanPurpose::Step)),
event(
3,
vec![],
RunLogPayload::RunSuspended {
provider_state_summary: None,
reason: None,
},
),
],
)
.expect("fold");
assert_eq!(folded.status, RunStatus::AwaitingHuman);
let pending = folded.view.human_pending.expect("pending");
assert_eq!(pending.deadline_at_ms, Some(9_000));
assert_eq!(pending.mode, Some(pointlock_ir::HumanMode::Confirm));
}
#[test]
fn step_exit_settles_the_pending_request_without_a_response() {
let step_path: RunPath = vec![PathFrame::Step {
step_id: StepId::new("ask").expect("valid step id"),
}];
let folded = fold_checkpoint(
&meta(),
&[
event(1, vec![], run_started()),
event(
2,
step_path.clone(),
RunLogPayload::StepEntered {
step_id: StepId::new("ask").expect("valid step id"),
effect_hash: hash('c'),
judge_hash: hash('d'),
resolved_inputs: json!({"presents": []}),
},
),
event(
3,
step_path.clone(),
human_requested("req-3", HumanPurpose::Step),
),
event(
4,
vec![],
RunLogPayload::RunSuspended {
provider_state_summary: None,
reason: None,
},
),
event(
5,
vec![],
RunLogPayload::RunResumed {
alignment_report: pointlock_ir::AlignmentReport {
entries: vec![],
resume_point: None,
requires_confirmation: vec![],
},
supervise_policy: None,
event_cursor: None,
},
),
event(
6,
step_path,
RunLogPayload::StepExited {
provider_state_summary: None,
state: StepState::Judged,
output: None,
localized: Vec::new(),
localization_gaps: Vec::new(),
},
),
],
)
.expect("fold");
assert!(folded.view.human_pending.is_none());
assert_eq!(folded.status, RunStatus::Running);
}
#[test]
fn error_class_is_adopted_only_when_the_code_spells_a_class() {
assert_eq!(
parse_error_class("action_failed_final"),
Some(ErrorClass::ActionFailedFinal)
);
assert_eq!(parse_error_class("SOME_DAEMON_CODE"), None);
}
}