Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
139 commits
Select commit Hold shift + click to select a range
ed9aa54
chore: files changed src/caps/approval.rs
senamakel Aug 15, 2026
a895d94
chore: files changed src/caps/mod.rs
senamakel Aug 15, 2026
5e15218
chore: files changed src/caps/mod.rs
senamakel Aug 15, 2026
5f08086
chore: files changed src/caps/mock.rs
senamakel Aug 15, 2026
37ef0df
chore: files changed src/caps/mock.rs
senamakel Aug 15, 2026
fc97a0d
chore: files changed src/model/node_kind.rs,src/nodes/execution.rs,sr…
senamakel Aug 15, 2026
72a8a18
chore: files changed src/nodes/integration/approval.rs
senamakel Aug 15, 2026
2d4d4e9
chore: files changed src/nodes/integration/mod.rs
senamakel Aug 15, 2026
f84a6a0
chore: files changed src/caps/mod.rs
senamakel Aug 15, 2026
f00b31d
chore: files changed src/caps/mod.rs,src/main.rs
senamakel Aug 15, 2026
6205547
chore: files changed src/nodes/integration/approval_tests.rs
senamakel Aug 15, 2026
2af344e
chore: files changed src/catalog.rs,src/catalog/contracts/group_03.rs…
senamakel Aug 15, 2026
a9f3a71
chore: files changed src/validate.rs
senamakel Aug 15, 2026
e794410
chore: files changed src/validate_tests/validate_tests_part_03_tests.rs
senamakel Aug 15, 2026
523a930
chore: files changed src/visualization.rs,tests/smoke_all_nodes.rs
senamakel Aug 15, 2026
fdfbc4c
chore: files changed src/caps/mock.rs,src/nodes/integration/approval.…
senamakel Aug 15, 2026
3fd5c3d
chore: files changed CHANGELOG.md,wiki/Capability-Traits.md,wiki/Node…
senamakel Aug 15, 2026
1f4a0ae
chore: files changed examples/hitl_review.rs
senamakel Aug 15, 2026
28af480
chore: files changed examples/hitl_review.rs
senamakel Aug 15, 2026
27d1470
chore: files changed CHANGELOG.md,examples/hitl_review.rs
senamakel Aug 15, 2026
3b7b2f5
feat(nodes): add an approval node kind and the ApprovalProvider capab…
senamakel Aug 15, 2026
cfec2ea
Merge remote-tracking branch 'upstream/main' into hitl-approval
senamakel Aug 15, 2026
d9e820b
chore: files changed tests/fuzz_interception.proptest-regressions
senamakel Aug 15, 2026
776f19c
chore: files changed src/validate.rs
senamakel Aug 15, 2026
bc345c5
chore: files changed src/nodes/integration/approval.rs
senamakel Aug 15, 2026
2af7dbd
chore: files changed src/nodes/integration/approval.rs
senamakel Aug 15, 2026
0f1f733
chore: files changed src/nodes/integration/approval.rs
senamakel Aug 15, 2026
0be092d
chore: files changed src/nodes/integration/approval.rs
senamakel Aug 15, 2026
7e0587d
chore: files changed examples/hitl_review.rs
senamakel Aug 15, 2026
dbba788
chore: files changed README.md
senamakel Aug 15, 2026
35546de
chore: files changed wiki/Capability-Traits.md
senamakel Aug 15, 2026
1530b69
chore: files changed wiki/Node-Catalog.md
senamakel Aug 15, 2026
0c00033
chore: files changed src/validate_tests/validate_tests_part_03_tests.rs
senamakel Aug 15, 2026
ddb5381
chore: files changed src/caps/mock_approvals.rs
senamakel Aug 15, 2026
de1948b
chore: files changed src/caps/mock.rs
senamakel Aug 15, 2026
df2ef83
chore: files changed src/caps/mock.rs
senamakel Aug 15, 2026
74af005
chore: files changed src/caps/mock.rs
senamakel Aug 15, 2026
0ca98c2
chore: files changed src/caps/mock.rs
senamakel Aug 15, 2026
7541236
chore: files changed src/nodes/integration/approval.rs
senamakel Aug 15, 2026
282ff5c
chore: files changed src/nodes/integration/approval_tests.rs
senamakel Aug 15, 2026
b059f0d
chore: files changed src/nodes/integration/approval_tests.rs
senamakel Aug 15, 2026
29f935c
chore: files changed src/nodes/integration/approval_tests.rs
senamakel Aug 15, 2026
6165a8f
chore: files changed src/nodes/integration/approval_tests.rs
senamakel Aug 15, 2026
a4f5bc2
chore: files changed tests/smoke_all_nodes.rs
senamakel Aug 15, 2026
c69f66f
chore: files changed examples/hitl_review.rs
senamakel Aug 15, 2026
ffb86af
chore: files changed src/catalog/contracts/group_03.rs
senamakel Aug 15, 2026
cc7ec6e
chore: files changed src/catalog/contracts/group_03.rs
senamakel Aug 15, 2026
01f262a
chore: files changed src/caps/mock.rs,src/nodes/integration/approval_…
senamakel Aug 15, 2026
351640b
chore: files changed src/testkit/mocks_double.rs
senamakel Aug 15, 2026
2075f30
chore: files changed src/testkit/mocks_double.rs
senamakel Aug 15, 2026
50a0755
chore: files changed src/testkit/mocks.rs
senamakel Aug 15, 2026
1b0563d
chore: files changed src/testkit/mocks.rs
senamakel Aug 15, 2026
2ef13a6
chore: files changed src/testkit/mocks.rs
senamakel Aug 15, 2026
1846cd6
chore: files changed src/testkit/mocks.rs
senamakel Aug 15, 2026
d6c0c47
chore: files changed src/testkit/mocks.rs
senamakel Aug 15, 2026
19557f6
chore: files changed src/testkit/mocks.rs
senamakel Aug 15, 2026
bf9eea7
chore: files changed src/testkit/mocks_log.rs
senamakel Aug 15, 2026
bcb5e9a
chore: files changed src/testkit/mocks_double.rs
senamakel Aug 15, 2026
1429a17
chore: files changed src/testkit/mocks.rs
senamakel Aug 15, 2026
153bb66
chore: files changed src/testkit/mocks_log.rs
senamakel Aug 15, 2026
b55800a
chore: files changed src/testkit/mocks_log.rs
senamakel Aug 15, 2026
debba44
chore: files changed src/testkit/mocks.rs
senamakel Aug 15, 2026
bc8376d
chore: files changed src/testkit/mocks_tests.rs
senamakel Aug 15, 2026
2fec804
chore: files changed src/testkit/mocks_tests.rs
senamakel Aug 15, 2026
62a5ef2
chore: files changed src/caps/mock_builders.rs
senamakel Aug 15, 2026
1f93e21
chore: files changed src/caps/mock.rs
senamakel Aug 15, 2026
fbbdef4
chore: files changed src/caps/mock.rs
senamakel Aug 15, 2026
5e669d1
chore: files changed src/caps/mock_builders.rs
senamakel Aug 15, 2026
311555e
chore: files changed tests/zz_repro.rs
senamakel Aug 15, 2026
33f30f4
chore: files changed tests/zz_repro.rs
senamakel Aug 15, 2026
a9e53a0
chore: files changed tests/zz_repro.rs
senamakel Aug 15, 2026
79ec050
chore: files changed src/nodes/integration/gate.rs
senamakel Aug 15, 2026
545140f
chore: files changed tests/zz_repro.rs
senamakel Aug 15, 2026
23064a3
chore: files changed src/nodes/integration/gate.rs
senamakel Aug 15, 2026
ea01a35
chore: files changed src/caps/tasks.rs
senamakel Aug 15, 2026
476b20c
chore: files changed src/engine/build/outcome.rs
senamakel Aug 15, 2026
4251ad6
chore: files changed src/engine/build/outcome.rs
senamakel Aug 15, 2026
1783d30
chore: files changed src/caps/tasks.rs,src/engine/build/outcome.rs,sr…
senamakel Aug 15, 2026
c10de5f
chore: files changed src/caps/tasks.rs,src/engine/build/outcome.rs,sr…
senamakel Aug 15, 2026
779d977
chore: files changed src/engine/build/outcome.rs
senamakel Aug 15, 2026
0a5627f
chore: files changed src/engine/build/backoff.rs
senamakel Aug 15, 2026
997469e
chore: files changed src/engine/build/outcome.rs
senamakel Aug 15, 2026
32d5897
chore: files changed src/engine/build/activation.rs
senamakel Aug 15, 2026
5bb65cf
chore: files changed src/engine/build.rs
senamakel Aug 15, 2026
3445891
chore: files changed src/engine/build/backoff_tests.rs
senamakel Aug 15, 2026
4bb2ad0
chore: files changed src/nodes/integration/gate.rs
senamakel Aug 15, 2026
a266bb6
chore: files changed src/caps/tasks.rs
senamakel Aug 15, 2026
0e169f1
chore: files changed src/lib.rs
senamakel Aug 15, 2026
792dd62
chore: files changed tests/fuzz_resume.rs
senamakel Aug 15, 2026
6929e81
chore: files changed src/engine/build/backoff.rs
senamakel Aug 15, 2026
38c428f
chore: files changed src/engine/build/backoff.rs
senamakel Aug 15, 2026
d11ea3d
chore: files changed src/engine/build/backoff_tests.rs
senamakel Aug 15, 2026
e3dbdf2
chore: files changed src/engine/build/backoff_tests.rs
senamakel Aug 15, 2026
5e73697
chore: files changed tests/zz_repro.rs
senamakel Aug 15, 2026
f2c2fd7
chore: files changed src/nodes/integration/approval_request.rs
senamakel Aug 15, 2026
138c757
chore: files changed src/nodes/integration/approval.rs
senamakel Aug 15, 2026
9c8bb31
chore: files changed src/nodes/integration/approval.rs
senamakel Aug 15, 2026
5d0d218
chore: files changed src/nodes/integration/approval.rs
senamakel Aug 15, 2026
7b16473
chore: files changed src/nodes/integration/approval.rs
senamakel Aug 15, 2026
b789cfb
chore: files changed src/nodes/integration/approval_tests.rs
senamakel Aug 15, 2026
6bcbf7b
chore: files changed src/nodes/integration/approval_tests.rs
senamakel Aug 15, 2026
b5b8eb8
chore: files changed src/nodes/integration/approval_tests.rs,src/node…
senamakel Aug 15, 2026
8d5dec2
chore: files changed src/nodes/integration/approval_request.rs,src/no…
senamakel Aug 15, 2026
b61e8dd
chore: files changed src/validate.rs
senamakel Aug 15, 2026
784d046
chore: files changed src/validate_tests/validate_tests_part_03_tests.rs
senamakel Aug 15, 2026
af2b10b
chore: files changed src/nodes/integration/approval_tests/approval_te…
senamakel Aug 15, 2026
772d3df
chore: files changed src/validate.rs
senamakel Aug 15, 2026
f67a794
chore: files changed src/nodes/integration/approval_request.rs
senamakel Aug 15, 2026
2498212
chore: files changed src/nodes/integration/approval_request.rs
senamakel Aug 15, 2026
7c1acb7
chore: files changed src/testkit/mocks.rs
senamakel Aug 15, 2026
c7af224
chore: files changed src/testkit/mocks.rs
senamakel Aug 15, 2026
620c6f8
chore: files changed src/testkit/mocks_double.rs
senamakel Aug 15, 2026
354ef7d
chore: files changed src/testkit/mocks_double.rs
senamakel Aug 15, 2026
5bb9f98
chore: files changed src/testkit/mocks_double.rs
senamakel Aug 15, 2026
4765a70
chore: files changed src/testkit/mocks_double.rs
senamakel Aug 15, 2026
a516b5e
chore: files changed src/nodes/integration/approval_request.rs
senamakel Aug 15, 2026
c45d27f
chore: files changed src/testkit/mocks_double.rs
senamakel Aug 15, 2026
731a462
chore: files changed src/testkit/mocks_tests.rs
senamakel Aug 15, 2026
780b687
chore: files changed src/nodes/integration/approval_tests/approval_te…
senamakel Aug 15, 2026
709e99d
chore: files changed src/nodes/integration/approval_tests/approval_te…
senamakel Aug 15, 2026
0111b88
chore: files changed src/testkit/mocks_tests.rs
senamakel Aug 15, 2026
9ce8174
chore: files changed src/nodes/integration/approval.rs
senamakel Aug 15, 2026
86186ef
chore: files changed src/nodes/integration/approval_request.rs
senamakel Aug 15, 2026
207d897
chore: files changed src/nodes/integration/approval_request.rs
senamakel Aug 15, 2026
f77ec77
chore: files changed src/catalog/contracts/group_03.rs
senamakel Aug 15, 2026
f83bed9
chore: files changed src/nodes/integration/approval_tests/approval_te…
senamakel Aug 15, 2026
fca976c
chore: files changed src/nodes/integration/approval_request.rs
senamakel Aug 15, 2026
051d17c
chore: files changed src/catalog/contracts/group_03.rs,src/nodes/inte…
senamakel Aug 15, 2026
fda43c0
chore: files changed src/nodes/integration/approval_tests/approval_te…
senamakel Aug 15, 2026
5790907
chore: files changed src/nodes/integration/approval_request.rs
senamakel Aug 16, 2026
ca8c656
chore: files changed src/nodes/integration/approval_request.rs
senamakel Aug 16, 2026
a5f79e3
chore: files changed src/nodes/integration/approval_tests/approval_te…
senamakel Aug 16, 2026
231a454
chore: files changed src/nodes/integration/approval_tests/approval_te…
senamakel Aug 16, 2026
e73f268
chore: files changed src/nodes/integration/approval_tests/approval_te…
senamakel Aug 16, 2026
e505dfa
chore: files changed src/nodes/integration/approval_request.rs
senamakel Aug 16, 2026
2717ef8
chore: files changed src/engine/run_state.rs
senamakel Aug 16, 2026
480958c
chore: files changed src/engine/run_state.rs
senamakel Aug 16, 2026
f13ff32
chore: files changed src/nodes/integration/approval_tests/approval_te…
senamakel Aug 16, 2026
4173473
docs(changelog): record the approval-provenance behaviour change
senamakel Aug 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,44 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- **`approval` node kind + the `ApprovalProvider` capability** — a
human-in-the-loop review step that carries what is being reviewed (a URL, a
draft, any payload) and routes on the answer: `approved` / `rejected` ports,
the verdict (reviewer, comment, any edit they made) emitted as an item and
readable anywhere as `=nodes.<id>.decision.approved`. Reaches the human
through the new optional `caps::ApprovalProvider`, whose `decide` is
create-or-fetch on a stable `request_id` so a resume or a poll never notifies
the reviewer twice. Hosts that wire no provider still get a working node: it
pauses the run and is settled with `engine::resume`, exactly like a
`requires_approval` gate. Waits by suspending by default, with an optional
bounded `wait_mode: "poll"`; `on_reject` (`route` / `error` / `drop`) and
`on_timeout` (`error` / `reject` / `route`) decide what a "no" does.
Note for hosts that build `caps::Capabilities` with a struct literal: it gains
an `approvals: Option<Arc<dyn ApprovalProvider>>` field, so add
`approvals: None` (or a provider) to keep compiling.

### Changed

- **Approval provenance is preserved across a resume.** `RunInput::approvals`
(surfaced as `run.approvals`) now carries only the ids a host explicitly
authorised — those passed to `engine::resume` plus any already on that
channel. It is no longer seeded from `run.trigger.approvals`.

The trigger is the payload a caller submits, so folding it into the
authorised list meant a caller-written id was promoted to trusted by the
first resume. `run.trigger.approvals` still receives the union and the
`requires_approval` gate still reads it, so that documented channel is
unchanged; what changed is that trigger-origin ids no longer cross into the
explicit one. A host that relied on `RunInput::approvals` echoing ids it had
written into the trigger payload must now pass them to `engine::resume` (or
`RunInput::with_approvals`) instead.

The `approval` node reads only the explicit channel, and a verdict object in
a resume value must name its review (`node_id` / `request_id`) — an
unaddressed `{"approved": true}` is ignored rather than settling whichever
review reads it, since one resume value is delivered to every interrupted
node.

- **`tinyflows::testkit` — testing, mocking, and live debugging for workflows.**
Behind the default-off `testkit` feature; adds no dependencies.

Expand Down
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ Rust 2024 · MSRV 1.85 · `#![forbid(unsafe_code)]` · GPL-3.0-or-later.
approves and continues. A host can also drive durable, cross-process resume by
injecting a `Checkpointer` via `engine::run_with_checkpointer` /
`resume_with_checkpointer`.
- Human review as a graph step: an `approval` node carries what is being
reviewed (a URL, a draft, any payload), reaches the human through the
host-implemented `caps::ApprovalProvider`, and routes the verdict — reviewer,
comment, any edit they made — on its `approved` / `rejected` ports. With no
provider injected it degrades to the pause-and-resume gate above.
- Observability via `tracing` plus a `RunObserver` hook and `Run` /
`ExecutionStep` records.

Expand Down Expand Up @@ -177,7 +182,7 @@ cargo run --example hello_workflow --features mock

## Examples

The crate ships seven runnable examples under [`examples/`](examples/). Each is
The crate ships eight runnable examples under [`examples/`](examples/). Each is
gated on the `mock` cargo feature, so run them with:

```sh
Expand All @@ -192,6 +197,7 @@ cargo run --example <name> --features mock
| `capability_pipeline` | A linear `http_request → code → agent → tool_call` pipeline through the host capability traits (mocked). |
| `error_handling` | Per-node `retry` plus `on_error: "route"` recovering a failing node via its `error` port. |
| `hitl_approval` | A `requires_approval` gate pauses the run (`pending_approvals`), then `run_resumable(...).resume(...)` continues from the checkpoint. |
| `hitl_review` | An `approval` node against a host-implemented `ApprovalProvider`: the run suspends, a "human" approves with an edit, and the resume takes the `approved` branch. |
Comment thread
coderabbitai[bot] marked this conversation as resolved.
| `jq_expressions` | The jaq-backed jq engine in a `transform` node (e.g. `=.item.prices | add`). |

Omitting `--features mock` is harmless: the demo body is
Expand Down Expand Up @@ -316,7 +322,8 @@ Run all of them in one go:

```sh
for ex in hello_workflow conditional_branch parallel_and_merge \
capability_pipeline error_handling hitl_approval jq_expressions; do
capability_pipeline error_handling hitl_approval hitl_review \
jq_expressions; do
cargo run --example "$ex" --features mock
done
```
Expand Down
189 changes: 189 additions & 0 deletions examples/hitl_review.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
#![forbid(unsafe_code)]

//! Human review as a **step in the graph**: an `approval` node hands a URL to a
//! host-implemented review surface, the run pauses while nobody has answered,
//! and the branch it takes afterwards depends on what the human said.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
//!
//! `DeskReview` below stands in for whatever real surface a host has — a Slack
//! card, an inbox row, a web queue. It shows the two things the
//! [`ApprovalProvider`](tinyflows::caps::ApprovalProvider) contract asks for:
//! **create-or-fetch** on `request_id`, so re-asking never notifies the reviewer
//! twice, and a decision that can carry the human's own edit.
//!
//! Run: cargo run --example hitl_review --features mock
#[cfg(feature = "mock")]
#[tokio::main(flavor = "current_thread")]
async fn main() {
use std::collections::HashMap;
use std::sync::{Arc, Mutex};

use async_trait::async_trait;
use serde_json::{Value, json};
use tinyflows::caps::mock::mock_capabilities;
use tinyflows::caps::{
ApprovalDecision, ApprovalOutcome, ApprovalProvider, ApprovalRequest, Capabilities,
};
use tinyflows::compiler::compile;
use tinyflows::engine::{resume, run};
use tinyflows::model::{Edge, Node, NodeKind, WorkflowGraph};

/// A host's review desk: one row per `request_id`, holding the verdict once
/// a human has left one.
#[derive(Default)]
struct DeskReview {
rows: Mutex<HashMap<String, Option<ApprovalDecision>>>,
}

impl DeskReview {
/// What a human does later, from the host's own UI.
fn answer(&self, request_id: &str, decision: ApprovalDecision) {
self.rows
.lock()
.expect("lock")
.insert(request_id.to_string(), Some(decision));
}

/// Every review this desk has been asked to run, decided or not.
fn queue(&self) -> Vec<String> {
let mut ids: Vec<String> = self.rows.lock().expect("lock").keys().cloned().collect();
ids.sort();
ids
}
}

#[async_trait]
impl ApprovalProvider for DeskReview {
async fn decide(
&self,
request: &ApprovalRequest,
) -> tinyflows::error::Result<ApprovalOutcome> {
let mut rows = self.rows.lock().expect("lock");
// Create-or-fetch: the row is keyed on `request_id`, so the run
// asking again after a resume finds THIS review rather than opening
// a second one and pinging the reviewer twice.
let row = rows.entry(request.request_id.clone()).or_insert_with(|| {
println!(
"[desk] new review {:?}: {} -> {}",
request.request_id,
request.title.as_deref().unwrap_or("(untitled)"),
request.subject.value
);
None
});
Ok(match row.clone() {
Some(decision) => ApprovalOutcome::Decided(decision),
None => ApprovalOutcome::Pending,
})
}
}

fn node(id: &str, kind: NodeKind, config: Value) -> Node {
Node {
id: id.into(),
kind,
type_version: 1,
name: id.into(),
config,
ports: vec![],
position: None,
}
}
fn edge(from: &str, port: &str, to: &str) -> Edge {
Edge {
from_node: from.into(),
from_port: port.into(),
to_node: to.into(),
to_port: "main".into(),
}
}

// trigger -> review -> publish (on `approved`) / revise (on `rejected`).
let graph = WorkflowGraph {
nodes: vec![
node("trigger", NodeKind::Trigger, Value::Null),
node(
"review",
NodeKind::Approval,
json!({
// A real host would key this on the run id (e.g.
// `"=run.id"`) rather than a literal, so two runs of this
// graph never collide on the same review.
"request_id": "hitl-review-example",
"title": "Publish this post?",
"prompt": "Approving publishes it to the public feed.",
"subject_kind": "url",
"subject": "=item.url",
"assignees": ["editor@example.com"],
}),
),
node(
"publish",
NodeKind::Transform,
json!({ "set": { "published": "=item.subject" } }),
),
node(
"revise",
NodeKind::Transform,
json!({ "set": { "revise_because": "=item.comment" } }),
),
],
edges: vec![
edge("trigger", "main", "review"),
edge("review", "approved", "publish"),
edge("review", "rejected", "revise"),
],
..Default::default()
};

let compiled = compile(&graph).expect("compile");
let desk = Arc::new(DeskReview::default());
let caps = Capabilities {
approvals: Some(desk.clone()),
..mock_capabilities()
};
let trigger = json!({ "url": "https://example.com/drafts/42" });

// 1) Nobody has answered, so the run suspends at the review. Nothing is
// burned while the card sits in someone's queue.
let paused = run(&compiled, trigger.clone(), &caps).await.expect("run");
println!("--- before the human answers ---");
println!("pending_approvals: {:?}", paused.pending_approvals);
println!("desk queue: {:?}", desk.queue());

// 2) The human approves — and edits the URL on the way through, which the
// host reports as the decision's payload.
let request_id = desk.queue().first().cloned().expect("one open review");
desk.answer(
&request_id,
ApprovalDecision {
approved: true,
decided_by: Some("editor@example.com".into()),
comment: Some("fixed the slug".into()),
payload: Some(json!("https://example.com/drafts/42?utm=newsletter")),
},
);

// 3) Resuming re-asks the desk, which now has the verdict. Note the review
// id is unchanged, so the reviewer is never asked a second time.
let done = resume(&compiled, trigger, vec![], &caps)
.await
.expect("resume");
println!("--- after the human answers ---");
println!("pending_approvals: {:?}", done.pending_approvals);
println!(
"review port: {}",
done.output["nodes"]["review"]["port"]
);
println!(
"published: {}",
done.output["nodes"]["publish"]["items"][0]["json"]["published"]
);
println!("desk queue: {:?}", desk.queue());
}

#[cfg(not(feature = "mock"))]
fn main() {
eprintln!(
"this example needs the mock capabilities: cargo run --example hitl_review --features mock"
);
}
Loading