openlatch-client 0.6.19

OpenLatch runtime enforcement node — the capture-and-enforce adapter that evaluates every covered action against a coding agent's Autonomy Zone before it runs
//! Regression: Cursor 3.24 sends the agent's `tool_use_id` on
//! `beforeShellExecution` too — the same id its `preToolUse` twin carried a
//! moment earlier. The twin is record-only (D-16): it must never claim the
//! call, or the deciding event folds into it and the block is answered to
//! Cursor but stored nowhere. Observed on a Windows host, 2026-10-12: Cursor
//! refused `Get-Content ~/.aws/credentials` with the policy's own message,
//! while the only stored event said `allow` with no rule.

use super::tests::{cursor_envelope, cursor_headers, cursor_state};
use super::*;
use crate::core::policy::test_support::{resident, rule};
use crate::generated::types::PolicyRuleMode;

const PRE_TOOL_SHELL_3_24: &str =
    include_str!("../../../tests/fixtures/cursor/pre-tool-shell-ide-3.24.json");
const BEFORE_SHELL_3_24: &str =
    include_str!("../../../tests/fixtures/cursor/before-shell-ide-3.24.json");

/// Both reports name the same call. The record-only twin is stored as an
/// allow with no rule; the deciding `beforeShellExecution` is stored with the
/// rule that refused it — one stored decision for the action, never zero.
#[tokio::test]
async fn cursor_before_shell_execution_is_stored_when_its_twin_shares_the_call_id() {
    let pre: serde_json::Value = serde_json::from_str(PRE_TOOL_SHELL_3_24).expect("fixture");
    let before: serde_json::Value = serde_json::from_str(BEFORE_SHELL_3_24).expect("fixture");
    assert_eq!(
        pre["tool_use_id"], before["tool_use_id"],
        "the fixtures describe one call, as Cursor 3.24 reports it"
    );
    assert_eq!(pre["session_id"], before["session_id"]);

    let (state, mut logged) = cursor_state(resident(
        vec![rule(
            "OL-SEC-001",
            "*.aws/credentials*",
            PolicyRuleMode::Enforce,
        )],
        true,
    ));

    let (_, _, Json(answer)) = ingest_cloudevent(
        State(state.clone()),
        cursor_headers(),
        cursor_envelope("evt-pre-3.24", "pre_tool_use", PRE_TOOL_SHELL_3_24),
    )
    .await;
    assert_eq!(
        answer.verdict.to_string(),
        "allow",
        "the twin is record-only"
    );
    let stored: serde_json::Value =
        serde_json::from_str(&logged.try_recv().expect("the twin is recorded")).expect("json");
    assert_eq!(stored["olverdict"], "allow");
    assert!(
        stored.get("olpolicyruleid").is_none(),
        "no rule on the twin"
    );

    let (_, _, Json(answer)) = ingest_cloudevent(
        State(state.clone()),
        cursor_headers(),
        cursor_envelope("evt-before-3.24", "pre_tool_use", BEFORE_SHELL_3_24),
    )
    .await;
    assert_eq!(answer.verdict.to_string(), "block", "the deciding event");

    let stored = logged
        .try_recv()
        .expect("the deciding beforeShellExecution is stored, not folded into its twin");
    let stored: serde_json::Value = serde_json::from_str(&stored).expect("json");
    assert_eq!(stored["olverdict"], "block");
    assert_eq!(stored["olpolicyruleid"], "OL-SEC-001");
    assert_eq!(stored["data"]["hook_event_name"], "beforeShellExecution");
}