feat(executor): thread the defining node into ExecRequest (Phase 4b C2)
Carry the resolved script's owner (its defining node) from dispatch into the executor so `import` resolution can be lexical (§5.5). The executing app (`app_id`) stays the SDK isolation boundary; `script_owner` is a separate axis — the lexical origin for imports. - `ExecRequest.script_owner: Option<ScriptOwner>` (serde default; `None` falls back to `App(app_id)` in the engine for old payloads / cluster wire). - `ScriptOwner` gains `Serialize`/`Deserialize` for the wire. - The engine computes `default_origin` and hands it to the resolver (used fully in C3; the resolve() lookup already uses it). - Every dispatch site sets it from the resolved `Script.owner()`: the 4 dispatcher arms (queue/trigger/http/invoke_async, via a new `ResolvedTrigger.script_owner`), the orchestrator id-bypass, and the `invoke()` SDK path (new `ResolvedScript.owner`, so a group script invoked by an app resolves imports from the group). Behaviour-preserving: app scripts still resolve `App(app_id)`; group scripts can't yet carry imports (lifted in C4). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -31,7 +31,7 @@ use picloud_orchestrator_core::{ExecutionGate, ExecutorClient};
|
||||
use picloud_shared::{
|
||||
AppId, DeadLetterId, ExecResponseSummary, ExecutionId, ExecutionLogSink, ExecutionSource,
|
||||
HttpDispatchPayload, InboxDeliveryOutcome, InboxFailureKind, InboxResolver, InboxResult,
|
||||
RequestId, Script, ScriptId, ScriptSandbox, TriggerEvent,
|
||||
RequestId, Script, ScriptId, ScriptOwner, ScriptSandbox, TriggerEvent,
|
||||
};
|
||||
use rand::Rng;
|
||||
use uuid::Uuid;
|
||||
@@ -471,6 +471,7 @@ impl Dispatcher {
|
||||
rest: String::new(),
|
||||
sandbox_overrides: script.sandbox,
|
||||
app_id: claimed.app_id,
|
||||
script_owner: script.owner(),
|
||||
principal: Some(principal),
|
||||
trigger_depth: 0,
|
||||
root_execution_id: execution_id,
|
||||
@@ -830,6 +831,7 @@ impl Dispatcher {
|
||||
is_dead_letter_handler: matches!(trigger.kind, TriggerKind::DeadLetter),
|
||||
active: trigger.enabled && script.enabled,
|
||||
script_id: script.id,
|
||||
script_owner: script.owner(),
|
||||
script_source: script.source,
|
||||
script_name: script.name,
|
||||
script_updated_at: script.updated_at,
|
||||
@@ -872,6 +874,7 @@ impl Dispatcher {
|
||||
rest: String::new(),
|
||||
sandbox_overrides: resolved.sandbox_overrides,
|
||||
app_id: row.app_id,
|
||||
script_owner: resolved.script_owner,
|
||||
principal: Some(principal),
|
||||
trigger_depth: row.trigger_depth,
|
||||
root_execution_id: row.root_execution_id.unwrap_or(execution_id),
|
||||
@@ -935,6 +938,7 @@ impl Dispatcher {
|
||||
rest: payload.rest,
|
||||
sandbox_overrides: script.sandbox,
|
||||
app_id: row.app_id,
|
||||
script_owner: script.owner(),
|
||||
// HTTP outbox rows don't run as the trigger registrant —
|
||||
// they run with no principal (public ingress) or the
|
||||
// attached one (origin_principal forensic field is not
|
||||
@@ -953,6 +957,7 @@ impl Dispatcher {
|
||||
// enqueue is dropped at fire time by the post-match active check.
|
||||
active: script.enabled,
|
||||
script_id,
|
||||
script_owner: script.owner(),
|
||||
script_source: script.source,
|
||||
script_name: payload.script_name,
|
||||
script_updated_at: script.updated_at,
|
||||
@@ -1035,6 +1040,7 @@ impl Dispatcher {
|
||||
rest: String::new(),
|
||||
sandbox_overrides: script.sandbox,
|
||||
app_id: row.app_id,
|
||||
script_owner: script.owner(),
|
||||
// invoke_async runs with no principal — same as HTTP outbox.
|
||||
principal: None,
|
||||
trigger_depth,
|
||||
@@ -1054,6 +1060,7 @@ impl Dispatcher {
|
||||
// enqueue is dropped at fire time by the post-match active check.
|
||||
active: script.enabled,
|
||||
script_id: script.id,
|
||||
script_owner: script.owner(),
|
||||
script_source: script.source,
|
||||
script_name: script.name,
|
||||
script_updated_at: script.updated_at,
|
||||
@@ -1356,6 +1363,10 @@ pub struct ResolvedTrigger {
|
||||
pub script_id: ScriptId,
|
||||
pub script_source: String,
|
||||
pub script_name: String,
|
||||
/// Phase 4b: the resolved script's defining node — the lexical origin
|
||||
/// for its `import`s (§5.5). `None` only for a corrupt owner row; the
|
||||
/// executor then falls back to `App(app_id)`.
|
||||
pub script_owner: Option<ScriptOwner>,
|
||||
/// v1.1.3: freshness comparator for the orchestrator's top-level
|
||||
/// script cache. The dispatcher hands `(script_id, updated_at)`
|
||||
/// in alongside the source so cached ASTs can be reused across
|
||||
|
||||
@@ -95,6 +95,7 @@ impl InvokeServiceImpl {
|
||||
Ok(ResolvedScript {
|
||||
script_id: script.id,
|
||||
app_id: cx.app_id,
|
||||
owner: script.owner(),
|
||||
source: script.source,
|
||||
updated_at: script.updated_at,
|
||||
name: script.name,
|
||||
@@ -124,6 +125,9 @@ impl InvokeServiceImpl {
|
||||
// The execution-context app is always the caller's app — a group
|
||||
// script runs under the inheriting app, never its owner.
|
||||
app_id: cx.app_id,
|
||||
// …but its `import`s resolve from its *defining node* (the group
|
||||
// for an inherited script) — the lexical origin (§5.5).
|
||||
owner: script.owner(),
|
||||
source: script.source,
|
||||
updated_at: script.updated_at,
|
||||
name: script.name,
|
||||
|
||||
Reference in New Issue
Block a user