//! `SdkCallCx` — per-call context every stateful SDK service receives.
//!
//! Service trait methods (added by subsequent v1.1.x PRs starting with
//! KV) all take `&SdkCallCx` so they can:
//!   * scope by `app_id` for cross-app isolation,
//!   * audit `principal` when authenticated,
//!   * carry `execution_id` / `request_id` into emitted events,
//!   * bound trigger chains via `trigger_depth` / `root_execution_id`.
//!
//! The struct lives in `picloud-shared` (not `executor-core`) because
//! future service impls live in `manager-core` and the trait that hands
//! the cx in is shared by both sides. Pure value type — no handles, no
//! DB pool references, no allocations beyond what's in `Principal`.

use crate::{AppId, ExecutionId, Principal, RequestId, ScriptId, TriggerEvent};

/// Per-invocation context for every stateful SDK service call.
///
/// Constructed once at the start of an invocation by `executor-core`
/// from the incoming `ExecRequest`, then handed (by reference) to every
/// service trait method the script triggers during execution. Services
/// MUST derive `app_id` from this struct — never from script-passed
/// arguments — to preserve cross-app isolation.
#[derive(Debug, Clone)]
pub struct SdkCallCx {
    /// Owning application for this invocation. Source of truth for
    /// every `(app_id, …)` storage lookup the script makes.
    pub app_id: AppId,

    /// The script being executed. Used for audit-log attribution and
    /// the default outbound-HTTP `User-Agent` (`picloud/<v>
    /// (script:<id>)`). Added in v1.1.4 for the `http::*` SDK.
    pub script_id: ScriptId,

    /// Caller identity, when authenticated. `None` for unauthenticated
    /// data-plane HTTP requests (the common case for public endpoints);
    /// `Some` when the call came in via the dashboard, an API key, or a
    /// future authed surface.
    pub principal: Option<Principal>,

    /// Unique id for THIS execution. Matches `ExecRequest.execution_id`.
    pub execution_id: ExecutionId,

    /// Unique id for the ingress request that started the chain. The
    /// same `request_id` is shared across every execution triggered by
    /// the same request (direct + trigger fan-out).
    pub request_id: RequestId,

    /// `0` for direct invocations (HTTP request, manual run). Each
    /// indirect invocation through the triggers framework (v1.1.1)
    /// increments this; the dispatcher rejects beyond a configured
    /// ceiling to prevent runaway feedback loops.
    pub trigger_depth: u32,

    /// `== execution_id` when `trigger_depth == 0`; otherwise the
    /// `execution_id` of the original ingress execution. Lets the audit
    /// log group every fan-out execution under the originating event.
    pub root_execution_id: ExecutionId,

    /// `true` only when this invocation is a `dead_letter` trigger
    /// handler. Set by the dispatcher when it picks an outbox row
    /// whose trigger has `kind = 'dead_letter'`. The retry / dead-
    /// letter machinery short-circuits when this is set: handlers
    /// execute once, with no retry, and a failed run can NEVER be
    /// dead-lettered itself (design notes §4 recursion-stop rule).
    /// `false` for every other invocation, including the script
    /// being used as a non-DL trigger handler.
    pub is_dead_letter_handler: bool,

    /// The event that fired this script, when it's a triggered
    /// invocation. `None` for direct ingress (HTTP request, manual
    /// run). Surfaced to scripts as `ctx.event`.
    pub event: Option<TriggerEvent>,
}
