diff --git a/org-ops-core/src/git_commands.rs b/org-ops-core/src/git_commands.rs index 9c3faf1..f38415e 100644 --- a/org-ops-core/src/git_commands.rs +++ b/org-ops-core/src/git_commands.rs @@ -3,6 +3,7 @@ //! Wraps git operations with accord validation, corpus score checks, //! and Chronicle attribution. +use crate::chronicle_client::ChronicleClient; use crate::session::SessionContext; use crate::traits::OrgCommands; use std::process::Command; @@ -84,21 +85,8 @@ impl GovernedGitCommands { .map(|s| s.to_string()) } - fn emit_chronicle(&self, kind: &str, actor_did: &str, message: &str) -> bool { - let body = serde_json::json!({ - "pusher": {"login": actor_did}, - "ref": format!("refs/governed/{}", kind), - "repository": {"full_name": "platform/git-governance"}, - "commits": [{"message": format!("{}: {}", kind, message)}], - }); - reqwest::blocking::Client::new() - .post(&self.config.chronicle_webhook) - .header("X-Forgejo-Event", "push") - .json(&body) - .timeout(std::time::Duration::from_secs(5)) - .send() - .map(|r| r.status().is_success()) - .unwrap_or(false) + fn chronicle(&self) -> ChronicleClient { + ChronicleClient::from_legacy_webhook(&self.config.chronicle_webhook) } fn cmd_status(&self, _ctx: &SessionContext) -> anyhow::Result<()> { @@ -149,12 +137,18 @@ impl GovernedGitCommands { if !out.is_empty() { println!("{}", out.trim()); } - self.emit_chronicle( - "REPO_CLONED", - &format!("did:web:{}/user/operator", ctx.trust_domain), - &format!("repo={}", repo), + let actor_did = format!("did:web:{}/user/operator", ctx.trust_domain); + self.chronicle().emit( + "GOV_REPO_CLONED", + &actor_did, + &ChronicleClient::generate_id(), + serde_json::json!({ + "kind": "GOV_REPO_CLONED", + "description": format!("repo={}", repo), + "repo": repo, + }), ); - println!("Cloned. Chronicle: REPO_CLONED"); + println!("Cloned. Chronicle: GOV_REPO_CLONED"); Ok(()) } @@ -226,27 +220,45 @@ impl GovernedGitCommands { println!(" Actor: did:web:{}/user/operator", ctx.trust_domain); println!("--"); - // Emit COMMIT_CREATED for each commit in the push range (0x1704) + // Emit GOV_COMMIT_CREATED for each commit in the push range let actor_did = format!("did:web:{}/user/operator", ctx.trust_domain); + let chronicle = self.chronicle(); let (commit_log, _, _) = Self::git(&["log", &format!("{}/{}..HEAD", remote, branch), "--format=%H|%s", "--no-merges"]); for line in commit_log.lines().filter(|l| !l.is_empty()) { let parts: Vec<&str> = line.splitn(2, '|').collect(); if parts.len() >= 2 { - self.emit_chronicle( - "COMMIT_CREATED", + let commit_sha = parts[0].trim(); + chronicle.emit( + "GOV_COMMIT_CREATED", &actor_did, - &format!("sha={} msg={}", parts[0], parts[1]), + commit_sha, + serde_json::json!({ + "kind": "GOV_COMMIT_CREATED", + "description": format!("sha={} msg={}", commit_sha, parts[1]), + "git_commit": commit_sha, + "message": parts[1], + "git_ref": format!("refs/heads/{}", branch), + }), ); } } - // Chronicle event + // Chronicle: GOV_PUSH let (sha, _, _) = Self::git(&["rev-parse", "HEAD"]); - self.emit_chronicle( - "GOVERNED_PUSH", + let head_sha = sha.trim(); + chronicle.emit( + "GOV_PUSH", &actor_did, - &format!("{}@{} -> {}/{}", sha.trim(), branch, remote, branch), + head_sha, + serde_json::json!({ + "kind": "GOV_PUSH", + "description": format!("{}@{} -> {}/{}", head_sha, branch, remote, branch), + "git_commit": head_sha, + "git_ref": format!("refs/heads/{}", branch), + "remote": remote, + "branch": branch, + }), ); // Actual push @@ -257,7 +269,7 @@ impl GovernedGitCommands { if !out.is_empty() { println!("{}", out.trim()); } - println!("Chronicle: GOVERNED_PUSH recorded"); + println!("Chronicle: GOV_PUSH recorded"); Ok(()) } @@ -296,10 +308,20 @@ impl GovernedGitCommands { let data: serde_json::Value = resp.json().unwrap_or_default(); let pr_url = data["html_url"].as_str().unwrap_or("?"); println!("PR created: {}", pr_url); - self.emit_chronicle( - "PR_CREATED", - &format!("did:web:{}/user/operator", ctx.trust_domain), - &format!("PR: {} ({})", title, pr_url), + let actor_did = format!("did:web:{}/user/operator", ctx.trust_domain); + let (sha, _, _) = Self::git(&["rev-parse", "HEAD"]); + self.chronicle().emit( + "GOV_PR_CREATED", + &actor_did, + sha.trim(), + serde_json::json!({ + "kind": "GOV_PR_CREATED", + "description": format!("PR: {} ({})", title, pr_url), + "git_commit": sha.trim(), + "branch": branch, + "pr_url": pr_url, + "title": title, + }), ); } Ok(resp) => eprintln!("PR creation failed: {}", resp.status()),