Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
5050511
feat(n8n): handle untranslatable HTTP config as placeholder
senamakel Aug 30, 2026
400c521
chore: files changed crates/tinyflows-catalog/src/import/n8n/node_map…
senamakel Aug 30, 2026
a6d6707
refactor(n8n): simplify formatting of mark_untranslated_http_config c…
senamakel Aug 30, 2026
64c91b7
fix(bindings): allow hyphens in identifiers and reject incomplete jq …
senamakel Aug 30, 2026
2296ed8
refactor(closing): extract judge tests into a separate file
senamakel Aug 30, 2026
1d653f4
chore(contracts): move inline tests to a separate file
senamakel Aug 30, 2026
6271f1d
chore(driver): move inline tests to a separate file
senamakel Aug 30, 2026
4abcdde
chore(host): move inline tests to a separate file
senamakel Aug 30, 2026
17e278f
chore(intake): move inline tests to a separate test file
senamakel Aug 30, 2026
f561621
fix(ledger): move mongo conformance test to its own file
senamakel Aug 30, 2026
fe9cc84
chore(ledger): move sqlite tests to a separate file
senamakel Aug 30, 2026
5702840
refactor(ledger): extract signature tests into a separate file
senamakel Aug 30, 2026
ab54ee4
feat(examples): replace inline service example with a module include
senamakel Aug 30, 2026
c3dfed8
refactor(intake): extract recipe lowering into its own module
senamakel Aug 30, 2026
49882df
refactor(ledger): extract MongoLedger impl into a separate file
senamakel Aug 30, 2026
e5c0067
refactor(ledger): extract SqliteLedger impl into a separate file
senamakel Aug 30, 2026
e0a4fc4
refactor(engine): extract continuation module into dedicated file
senamakel Aug 30, 2026
e3140c8
refactor(testkit): extract helper functions into a separate module
senamakel Aug 30, 2026
98c5a6b
refactor(ledger): extract transcript conformance tests into a dedicat…
senamakel Aug 30, 2026
e84a813
chore: files changed crates/tinyflows-adaptive/src/ledger/conformance…
senamakel Aug 30, 2026
ffa9cf9
refactor(ledger): extract lineage conformance tests into a dedicated …
senamakel Aug 30, 2026
efaad9e
refactor(ledger): extract tenant conformance tests into a separate file
senamakel Aug 30, 2026
bb7ff99
refactor(tests): extract driver integration tests into a separate file
senamakel Aug 30, 2026
4d5adb4
refactor(tests): extract driver tests into part files
senamakel Aug 30, 2026
38a78bb
chore: files changed crates/tinyflows-adaptive/tests/intake.rs,crates…
senamakel Aug 30, 2026
62230b5
refactor(tests): extract intake tests into a shared module
senamakel Aug 30, 2026
e15e2b2
refactor(intake): extract recipe tests into a separate module
senamakel Aug 30, 2026
11db9d4
refactor(intake): extract recipe use-step tests into a dedicated module
senamakel Aug 30, 2026
2c73035
refactor(tests): extract closing tests into a separate module
senamakel Aug 30, 2026
98e699b
refactor(tests): extract interception e2e tests into a module
senamakel Aug 30, 2026
0d6c6b8
chore: files changed crates/tinyflows/src/engine/build/activation.rs,…
senamakel Aug 30, 2026
bec3e66
chore: files changed crates/tinyflows/src/engine/build/activation.rs,…
senamakel Aug 30, 2026
554e420
refactor(activation): extract input derivation into a separate module
senamakel Aug 30, 2026
49d5343
refactor: move doc comments from include sites to the included test m…
senamakel Aug 30, 2026
bbef585
chore: files changed crates/tinyflows-catalog/src/import/n8n/node_map…
senamakel Aug 30, 2026
ad6d554
fix(adaptive, catalog, engine): correct episode primary key and guard…
senamakel Aug 30, 2026
104c332
fix(ledger): support both `id` and `_id` fields for episode identity
senamakel Aug 30, 2026
30577e1
test(tenants): add conformance test for isolated episode storage
senamakel Aug 30, 2026
2629486
feat(import/n8n): preserve untranslated HTTP config parts in import m…
senamakel Aug 30, 2026
33f7cfc
test(import): add test for preserving unsupported n8n HTTP parts
senamakel Aug 30, 2026
91c820b
fix(test): add f64::MAX to non-positive interval test case
senamakel Aug 30, 2026
2336d6f
fix(import/n8n): replace naive text search with lightweight lexer for…
senamakel Aug 30, 2026
b9c92ba
refactor(import): simplify control flow in n8n node mapping
senamakel Aug 30, 2026
5d8e08d
refactor(n8n): extract code-node detection into its own module
senamakel Aug 30, 2026
cf7f39f
test(ledger): add test for legacy episode table migration to tenant-s…
senamakel Aug 30, 2026
a19899f
chore(ledger): reformat long line in migration test
senamakel Aug 30, 2026
ba80cbb
fix(import/n8n): correct detection of top-level return statements
senamakel Aug 30, 2026
46dc178
fix(ledger): match scope when updating an episode
senamakel Aug 30, 2026
2ae062d
fix(ledger): preserve scope and started_at when updating episodes
senamakel Aug 30, 2026
18b6187
fix(ledger): include global-scope rows when resolving lesson evidence
senamakel Aug 30, 2026
f88896a
feat(tenants): add conformance test for global lesson evidence visibi…
senamakel Aug 30, 2026
f0fdcda
fix(n8n): handle template literals and arrow functions in code global…
senamakel Aug 30, 2026
387448f
test(n8n): add tests for untranslated JSON body and template literal …
senamakel Aug 30, 2026
1938f15
fix(ledger): reformat filter closure for readability
senamakel Aug 30, 2026
48c562a
test(n8n): remove superseded HTTP mapping tests
senamakel Aug 30, 2026
421b77d
chore: files changed crates/tinyflows-catalog/src/import/n8n/node_map…
senamakel Aug 30, 2026
cd14b3c
chore: files changed crates/tinyflows-catalog/src/import/n8n/node_map…
senamakel Aug 30, 2026
8f65893
test(drafts): scope lock-pruning assertion to the test's own directory
senamakel Aug 30, 2026
bae2b55
fix(import/n8n): handle regex literals and arrow function edge cases …
senamakel Aug 30, 2026
bbc3ead
fix(bindings): allow hyphens in node binding identifiers
senamakel Aug 30, 2026
7eda53e
chore: files changed crates/tinyflows-adaptive/src/ledger/mongo.rs,cr…
senamakel Aug 30, 2026
52eacaa
chore: files changed crates/tinyflows-adaptive/src/ledger/mongo_tests…
senamakel Aug 30, 2026
f7ab2d7
fix(tests): use fully qualified paths in test assertions
senamakel Aug 30, 2026
99d69ae
fix(test): correct episode listing in legacy scope test
senamakel Aug 30, 2026
da3dbc6
fix(import): track paren depth for function declaration bodies
senamakel Aug 30, 2026
d848a5f
test(gates): consolidate indexed binding tests into a shared module
senamakel Aug 30, 2026
cf99b2b
fix(tests): correct include path for indexed binding tests
senamakel Aug 30, 2026
5fd93fb
chore: files changed crates/tinyflows-catalog/src/import/n8n/node_map…
senamakel Aug 30, 2026
6ef99aa
chore: files changed crates/tinyflows-catalog/src/import/n8n/node_map…
senamakel Aug 30, 2026
8e9032b
chore: files changed crates/tinyflows-catalog/src/import/n8n/node_map…
senamakel Aug 30, 2026
655aa89
chore(tests): rename test files to follow Rust convention
senamakel Aug 30, 2026
f9342da
feat(n8n import): detect n8n runtime usage in Python code nodes and m…
senamakel Aug 30, 2026
0ea1aff
test(n8n): add test cases for Python helper and function-local then g…
senamakel Aug 30, 2026
04dc7e9
fix(n8n): fix brace style in code node mapping
senamakel Aug 30, 2026
5dbd023
fix(n8n): simplify function body detection in JavaScript node mapping
senamakel Aug 30, 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
464 changes: 1 addition & 463 deletions crates/tinyflows-adaptive/examples/service.rs

Large diffs are not rendered by default.

463 changes: 463 additions & 0 deletions crates/tinyflows-adaptive/examples/service/runtime.rs

Large diffs are not rendered by default.

214 changes: 2 additions & 212 deletions crates/tinyflows-adaptive/src/closing/judge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,215 +289,5 @@ fn without_a_model(evidence: &Evidence<'_>) -> Option<Verdict> {
}

#[cfg(test)]
mod tests {
use super::*;

/// A provider that answers the judge with a fixed blocker.
struct Says(&'static str);

#[async_trait::async_trait]
impl tinyflows::caps::LlmProvider for Says {
async fn complete(
&self,
_request: serde_json::Value,
_conn: Option<&str>,
) -> tinyflows::error::Result<serde_json::Value> {
Ok(serde_json::json!({
"satisfied": false,
"blocker": self.0,
"gap": "nothing was fetched",
}))
}
}

async fn verdict_for(blocker: &'static str, failed: Option<String>) -> Verdict {
let outcome = tinyflows::engine::RunOutcome {
// Non-empty: the mechanical pre-judge must not settle this one,
// because the point is what the MODEL's answer becomes.
output: serde_json::json!({ "nodes": { "fetch": { "json": 1 } } }),
pending_approvals: Vec::new(),
cancelled: false,
};
let diagnosis = Diagnosis::default();
let evidence = Evidence {
outcome: &outcome,
diagnosis: &diagnosis,
changed: String::new(),
failed,
};
let caps = tinyflows::caps::Capabilities {
llm: std::sync::Arc::new(Says(blocker)),
..tinyflows::caps::mock::mock_capabilities()
};
judge(&Goal::new("do the thing"), &evidence, &caps, None)
.await
.expect("judged")
}

#[tokio::test]
async fn a_mechanically_broken_run_cannot_be_called_terminal() {
// Field observation: a shell step exited nonzero, the judge answered
// `missing_evidence`, and the episode ended with two of its three
// attempts unused — when rewriting the script was the whole fix.
// The prompt says mechanical failures are goal_not_met; a model that
// misreads it must not get to end the episode anyway.
let verdict = verdict_for("missing_evidence", Some("script exited 5".into())).await;
assert_eq!(verdict.blocker, Blocker::GoalNotMet);
assert!(verdict.blocker.continuable());
}

#[tokio::test]
async fn a_run_that_completed_keeps_the_judges_terminal_verdict() {
// No mechanical failure: the judge is the authority on whether
// another attempt could help, and this downgrade must not become a
// blanket refusal to ever stand down.
let verdict = verdict_for("missing_evidence", None).await;
assert_eq!(verdict.blocker, Blocker::MissingEvidence);
}

#[tokio::test]
async fn a_broken_run_still_waiting_on_a_person_stays_terminal() {
// NeedsInput and ExternalWait survive the downgrade: both mean
// something OUTSIDE the loop must move, which a broken run does not
// change.
let verdict = verdict_for("needs_input", Some("script exited 5".into())).await;
assert_eq!(verdict.blocker, Blocker::NeedsInput);
}

use serde_json::json;
use tinyflows::diagnostics::{HiddenError, NeverRan, NullBinding};

fn outcome(output: serde_json::Value) -> RunOutcome {
RunOutcome {
output,
pending_approvals: Vec::new(),
cancelled: false,
}
}

fn evidence<'a>(o: &'a RunOutcome, d: &'a Diagnosis) -> Evidence<'a> {
Evidence {
outcome: o,
diagnosis: d,
changed: String::new(),
failed: None,
}
}

#[test]
fn a_parked_approval_needs_no_model() {
let mut o = outcome(json!({}));
o.pending_approvals = vec!["gate".into()];
let d = Diagnosis::default();
let verdict = without_a_model(&evidence(&o, &d)).expect("settled without a model");
assert_eq!(verdict.blocker, Blocker::NeedsInput);
assert!(
verdict.advanced,
"reaching the gate is progress, not a stall"
);
}

#[test]
fn a_cancelled_run_did_not_fail_it_was_stopped() {
let mut o = outcome(json!({ "nodes": { "a": {} } }));
o.cancelled = true;
let d = Diagnosis::default();
let verdict = without_a_model(&evidence(&o, &d)).expect("settled");
assert_eq!(verdict.blocker, Blocker::ExternalWait);
assert!(
!verdict.blocker.continuable(),
"retrying now is not retrying later"
);
}

#[test]
fn a_run_where_nothing_ran_and_nothing_changed_is_terminal() {
let o = outcome(json!({}));
let d = Diagnosis {
never_ran: vec![NeverRan {
node_id: "work".into(),
routed_by: Some("gate".into()),
}],
..Diagnosis::default()
};
let verdict = without_a_model(&evidence(&o, &d)).expect("settled");
assert_eq!(verdict.blocker, Blocker::MissingEvidence);
assert!(!verdict.blocker.continuable());
}

#[test]
fn a_run_that_produced_something_goes_to_the_model() {
let o = outcome(json!({ "nodes": { "a": { "items": [1] } } }));
let d = Diagnosis::default();
assert!(
without_a_model(&evidence(&o, &d)).is_none(),
"a real outcome is a judgement, not a fact"
);
}

#[test]
fn an_unverifiable_null_binding_is_not_reported_as_a_finding() {
// The engine marks expressions it could not evaluate even in principle.
// Reporting those buries the ones that are real.
let o = outcome(json!({}));
let d = Diagnosis {
null_bindings: vec![NullBinding {
node_id: "a".into(),
location: "config.prompt".into(),
expression: "=nodes.x.item".into(),
unverifiable: true,
reads_from: None,
suggestion: "n/a".into(),
}],
..Diagnosis::default()
};
assert!(evidence(&o, &d).findings().is_empty());
}

#[test]
fn a_swallowed_error_reaches_the_judge() {
// The failure a naive reading misses entirely: the step is marked
// failed and its diagnostics are empty.
let o = outcome(json!({}));
let d = Diagnosis {
hidden_errors: vec![HiddenError {
node_id: "fetch".into(),
message: Some("404".into()),
}],
..Diagnosis::default()
};
let findings = evidence(&o, &d).findings();
assert_eq!(findings.len(), 1);
assert!(findings[0].contains("swallowed"), "{findings:?}");
assert!(findings[0].contains("404"));
}

#[test]
fn a_null_binding_names_the_node_it_should_have_read_from() {
let o = outcome(json!({}));
let d = Diagnosis {
null_bindings: vec![NullBinding {
node_id: "review".into(),
location: "config.prompt".into(),
expression: "=nodes.fetch.item.body".into(),
unverifiable: false,
reads_from: Some("fetch".into()),
suggestion: "did you mean .item.json.body".into(),
}],
..Diagnosis::default()
};
let findings = evidence(&o, &d).findings();
assert!(findings[0].contains("reading from `fetch`"), "{findings:?}");
assert!(
findings[0].contains("item.json.body"),
"the suggestion carries"
);
}

#[test]
fn a_clean_run_says_so_rather_than_showing_an_empty_list() {
let o = outcome(json!({ "nodes": {} }));
let d = Diagnosis::default();
assert!(evidence(&o, &d).render().contains("found nothing wrong"));
}
}
#[path = "judge_tests.rs"]
mod tests;
Loading