Skip to content

Commit 12f1f9a

Browse files
committed
feat: wire ship.prepared provenance emission at bash execution boundary
Adds ship provenance detection and emission in execute_bash_async(): - Detects git push to main/master commands - Captures current branch, HEAD commit, git user as actor - Emits ship.prepared event with ShipProvenance payload - Logs to stderr as interim routing (event stream integration pending) This is the first wired provenance event — schema (§4.44.5) now has runtime emission at actual git operation boundary. Verified: cargo build --workspace passes. Next: wire ship.commits_selected, ship.merged, ship.pushed_main events. Refs: §4.44.5.1, ROADMAP ultraworkers#4.44.5
1 parent 2678fa0 commit 12f1f9a

1 file changed

Lines changed: 66 additions & 0 deletions

File tree

rust/crates/runtime/src/bash.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use tokio::process::Command as TokioCommand;
88
use tokio::runtime::Builder;
99
use tokio::time::timeout;
1010

11+
use crate::lane_events::{LaneEvent, ShipMergeMethod, ShipProvenance};
1112
use crate::sandbox::{
1213
build_linux_sandbox_command, resolve_sandbox_status_for_request, FilesystemIsolationMode,
1314
SandboxConfig, SandboxStatus,
@@ -102,11 +103,76 @@ pub fn execute_bash(input: BashCommandInput) -> io::Result<BashCommandOutput> {
102103
runtime.block_on(execute_bash_async(input, sandbox_status, cwd))
103104
}
104105

106+
/// Detect git push to main and emit ship provenance event
107+
fn detect_and_emit_ship_prepared(command: &str) {
108+
let trimmed = command.trim();
109+
// Simple detection: git push with main/master
110+
if trimmed.contains("git push") && (trimmed.contains("main") || trimmed.contains("master")) {
111+
// Emit ship.prepared event
112+
let now = std::time::SystemTime::now()
113+
.duration_since(std::time::UNIX_EPOCH)
114+
.unwrap_or_default()
115+
.as_millis();
116+
let provenance = ShipProvenance {
117+
source_branch: get_current_branch().unwrap_or_else(|| "unknown".to_string()),
118+
base_commit: get_head_commit().unwrap_or_default(),
119+
commit_count: 0, // Would need to calculate from range
120+
commit_range: "unknown..HEAD".to_string(),
121+
merge_method: ShipMergeMethod::DirectPush,
122+
actor: get_git_actor().unwrap_or_else(|| "unknown".to_string()),
123+
pr_number: None,
124+
};
125+
let _event = LaneEvent::ship_prepared(format!("{}", now), &provenance);
126+
// Log to stderr as interim routing before event stream integration
127+
eprintln!(
128+
"[ship.prepared] branch={} -> main, commits={}, actor={}",
129+
provenance.source_branch, provenance.commit_count, provenance.actor
130+
);
131+
}
132+
}
133+
134+
fn get_current_branch() -> Option<String> {
135+
let output = Command::new("git")
136+
.args(["branch", "--show-current"])
137+
.output()
138+
.ok()?;
139+
if output.status.success() {
140+
Some(String::from_utf8_lossy(&output.stdout).trim().to_string())
141+
} else {
142+
None
143+
}
144+
}
145+
146+
fn get_head_commit() -> Option<String> {
147+
let output = Command::new("git")
148+
.args(["rev-parse", "--short", "HEAD"])
149+
.output()
150+
.ok()?;
151+
if output.status.success() {
152+
Some(String::from_utf8_lossy(&output.stdout).trim().to_string())
153+
} else {
154+
None
155+
}
156+
}
157+
158+
fn get_git_actor() -> Option<String> {
159+
let name = Command::new("git")
160+
.args(["config", "user.name"])
161+
.output()
162+
.ok()
163+
.filter(|o| o.status.success())
164+
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())?;
165+
Some(name)
166+
}
167+
105168
async fn execute_bash_async(
106169
input: BashCommandInput,
107170
sandbox_status: SandboxStatus,
108171
cwd: std::path::PathBuf,
109172
) -> io::Result<BashCommandOutput> {
173+
// Detect and emit ship provenance for git push operations
174+
detect_and_emit_ship_prepared(&input.command);
175+
110176
let mut command = prepare_tokio_command(&input.command, &cwd, &sandbox_status, true);
111177

112178
let output_result = if let Some(timeout_ms) = input.timeout {

0 commit comments

Comments
 (0)