diff --git a/nash-observe/src/lib.rs b/nash-observe/src/lib.rs index 6377d76..3b7838e 100644 --- a/nash-observe/src/lib.rs +++ b/nash-observe/src/lib.rs @@ -105,6 +105,12 @@ struct Batch<'a> { batch_type: &'static str, source: &'static str, session_id: Option<&'a str>, + #[serde(skip_serializing_if = "Option::is_none")] + environment_kind: Option<&'a str>, + #[serde(skip_serializing_if = "Option::is_none")] + environment_id: Option<&'a str>, + #[serde(skip_serializing_if = "Option::is_none")] + environment_label: Option<&'a str>, shell_id: &'a str, parent_shell: Option<&'a str>, events: &'a [Event], @@ -119,6 +125,9 @@ struct Config { hook_url: Option, token: Option, session_id: Option, + environment_kind: Option, + environment_id: Option, + environment_label: Option, spool: Option, } @@ -159,6 +168,9 @@ impl Config { hook_url, token: std::env::var("NUCLEIC_HOOK_TOKEN").ok(), session_id: std::env::var("NUCLEIC_SESSION_ID").ok(), + environment_kind: std::env::var("NUCLEIC_SHELL_ENVIRONMENT_KIND").ok(), + environment_id: std::env::var("NUCLEIC_SHELL_ENVIRONMENT_ID").ok(), + environment_label: std::env::var("NUCLEIC_SHELL_ENVIRONMENT_LABEL").ok(), spool, }) } @@ -609,6 +621,9 @@ fn flusher(cfg: Config, shell_id: String, parent_shell: Option, rx: mpsc batch_type: "shell-batch", source: "nash", session_id: cfg.session_id.as_deref(), + environment_kind: cfg.environment_kind.as_deref(), + environment_id: cfg.environment_id.as_deref(), + environment_label: cfg.environment_label.as_deref(), shell_id: &shell_id, parent_shell: parent_shell.as_deref(), events: buf, diff --git a/nash/tests/observe.rs b/nash/tests/observe.rs index 03289de..4cc5198 100644 --- a/nash/tests/observe.rs +++ b/nash/tests/observe.rs @@ -129,6 +129,15 @@ impl Sink { } events } + + fn next_batch(&self) -> serde_json::Value { + let (auth, batch) = self + .rx + .recv_timeout(Duration::from_secs(5)) + .expect("shell batch"); + assert_eq!(auth.as_deref(), Some("Bearer test-token")); + batch + } } impl Drop for Sink { @@ -162,6 +171,9 @@ fn run_nash(sink: &Sink, script: &str) -> std::process::ExitStatus { .env("NUCLEIC_SHELL_SOCKET", sink.socket()) .env("NUCLEIC_HOOK_TOKEN", "test-token") .env("NUCLEIC_SESSION_ID", "sess-1") + .env("NUCLEIC_SHELL_ENVIRONMENT_KIND", "agent") + .env("NUCLEIC_SHELL_ENVIRONMENT_ID", "agent") + .env("NUCLEIC_SHELL_ENVIRONMENT_LABEL", "Agent") .env_remove("NUCLEIC_SHELL_PARENT") .status() .unwrap() @@ -198,6 +210,18 @@ fn exec_events_flow() { assert_eq!(false_ev["exitCode"], 1); } +#[test] +fn emitter_environment_flows_on_batch_envelope() { + let sink = Sink::start(); + let status = run_nash(&sink, "true"); + assert_eq!(status.code(), Some(0)); + + let batch = sink.next_batch(); + assert_eq!(batch["environmentKind"], "agent"); + assert_eq!(batch["environmentId"], "agent"); + assert_eq!(batch["environmentLabel"], "Agent"); +} + #[test] fn external_command_exit_codes() { let sink = Sink::start(); @@ -509,6 +533,7 @@ fn spool_fallback_when_socket_absent() { .env("NUCLEIC_SHELL_SOCKET", "/nonexistent/control.sock") .env("NUCLEIC_SHELL_SPOOL", &dir) .env("NUCLEIC_SESSION_ID", "sess-1") + .env_remove("NUCLEIC_SHELL_HOOK_URL") .env_remove("NUCLEIC_SHELL_PARENT") .status() .unwrap();