From 95d6b95272696b849cb317230e241bfabdb658b2 Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Wed, 15 Jul 2026 19:38:31 +0200 Subject: [PATCH] feat(executor): expose ctx.trigger_depth and pin the invoke depth bump MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `invoke_callee_sees_incremented_depth` asserted NOTHING — it ended `let _ = resp` with a comment that `ctx.trigger_depth` wasn't exposed, so it would have passed even if `invoke` forwarded the depth unchanged, i.e. never incrementing the chain-depth counter that bounds runaway trigger loops. `build_ctx_map` now surfaces `ctx.trigger_depth` (read-only: 0 for direct ingress, +1 per synchronous `invoke` re-entry or dispatched handler) — the value the author intended to read and a legitimate diagnostic for a script. The test now pins the increment: the caller is depth 0, the invoke callee must see 1. Mutation-verified: forwarding the depth unchanged makes the callee see 0 and the test fails. Co-Authored-By: Claude Opus 4.8 (1M context) --- crates/executor-core/src/engine.rs | 8 ++++++++ crates/executor-core/tests/sdk_invoke.rs | 17 ++++++++++++----- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/crates/executor-core/src/engine.rs b/crates/executor-core/src/engine.rs index b26e287..01e1609 100644 --- a/crates/executor-core/src/engine.rs +++ b/crates/executor-core/src/engine.rs @@ -498,6 +498,14 @@ fn build_ctx_map(req: &ExecRequest) -> Map { "invocation_type".into(), invocation_type_str(req.invocation_type).into(), ); + // Read-only depth of this call in a trigger/invoke chain: 0 for direct + // ingress, +1 per synchronous re-entry (`invoke`) or dispatched handler. + // Exposed so a script can observe where it sits in the chain (the same value + // the platform bounds via `trigger_depth_max`). + ctx.insert( + "trigger_depth".into(), + (i64::from(req.trigger_depth)).into(), + ); let mut request = Map::new(); request.insert("path".into(), req.path.clone().into()); diff --git a/crates/executor-core/tests/sdk_invoke.rs b/crates/executor-core/tests/sdk_invoke.rs index 3e2f927..a8f3465 100644 --- a/crates/executor-core/tests/sdk_invoke.rs +++ b/crates/executor-core/tests/sdk_invoke.rs @@ -194,16 +194,23 @@ async fn invoke_callee_sees_incremented_depth() { #{ statusCode: 200, body: d } "# .to_string(); + // The caller is direct ingress (depth 0); the callee, re-entered via + // `invoke`, must see depth 1. Previously this test asserted NOTHING (`let _ = + // resp`) because `ctx.trigger_depth` wasn't exposed — so it would have passed + // even if `invoke` forwarded the depth unchanged (never incrementing the + // chain-depth counter that bounds runaway trigger loops). `ctx.trigger_depth` + // is now surfaced in build_ctx_map, so we can pin the increment. let req = baseline_request(app); + assert_eq!(req.trigger_depth, 0, "the caller is direct ingress"); let resp = tokio::task::spawn_blocking(move || engine.execute(&src, req)) .await .unwrap() .unwrap(); - // ctx exposes trigger_depth? — not yet (it's not in build_ctx_map). - // Skip the strict assertion — the test still ensures the invoke - // chain didn't throw. (See HANDBACK §11 for cx.trigger_depth surface - // exposure as a v1.2 follow-up.) - let _ = resp; + assert_eq!( + resp.body, + json!(1), + "the invoke callee must see trigger_depth = caller + 1" + ); } #[tokio::test(flavor = "multi_thread", worker_threads = 2)]