refactor(git_commands): migrate Chronicle emission to CloudEvents
Replace fake Forgejo push webhook construction with structured CloudEvents 1.0 via ChronicleClient. Git commit SHAs are now used as CloudEvent ids for COMMIT_CREATED and PUSH events, enabling direct correlation between Chronicle entries and git history. Event renames: - REPO_CLONED -> GOV_REPO_CLONED - COMMIT_CREATED -> GOV_COMMIT_CREATED - GOVERNED_PUSH -> GOV_PUSH - PR_CREATED -> GOV_PR_CREATED Signed-off-by: Tyler King <tking@guildhouse.dev>
This commit is contained in:
parent
cf744dd909
commit
92464b07c5
1 changed files with 55 additions and 33 deletions
|
|
@ -3,6 +3,7 @@
|
||||||
//! Wraps git operations with accord validation, corpus score checks,
|
//! Wraps git operations with accord validation, corpus score checks,
|
||||||
//! and Chronicle attribution.
|
//! and Chronicle attribution.
|
||||||
|
|
||||||
|
use crate::chronicle_client::ChronicleClient;
|
||||||
use crate::session::SessionContext;
|
use crate::session::SessionContext;
|
||||||
use crate::traits::OrgCommands;
|
use crate::traits::OrgCommands;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
@ -84,21 +85,8 @@ impl GovernedGitCommands {
|
||||||
.map(|s| s.to_string())
|
.map(|s| s.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emit_chronicle(&self, kind: &str, actor_did: &str, message: &str) -> bool {
|
fn chronicle(&self) -> ChronicleClient {
|
||||||
let body = serde_json::json!({
|
ChronicleClient::from_legacy_webhook(&self.config.chronicle_webhook)
|
||||||
"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 cmd_status(&self, _ctx: &SessionContext) -> anyhow::Result<()> {
|
fn cmd_status(&self, _ctx: &SessionContext) -> anyhow::Result<()> {
|
||||||
|
|
@ -149,12 +137,18 @@ impl GovernedGitCommands {
|
||||||
if !out.is_empty() {
|
if !out.is_empty() {
|
||||||
println!("{}", out.trim());
|
println!("{}", out.trim());
|
||||||
}
|
}
|
||||||
self.emit_chronicle(
|
let actor_did = format!("did:web:{}/user/operator", ctx.trust_domain);
|
||||||
"REPO_CLONED",
|
self.chronicle().emit(
|
||||||
&format!("did:web:{}/user/operator", ctx.trust_domain),
|
"GOV_REPO_CLONED",
|
||||||
&format!("repo={}", repo),
|
&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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -226,27 +220,45 @@ impl GovernedGitCommands {
|
||||||
println!(" Actor: did:web:{}/user/operator", ctx.trust_domain);
|
println!(" Actor: did:web:{}/user/operator", ctx.trust_domain);
|
||||||
println!("--");
|
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 actor_did = format!("did:web:{}/user/operator", ctx.trust_domain);
|
||||||
|
let chronicle = self.chronicle();
|
||||||
let (commit_log, _, _) =
|
let (commit_log, _, _) =
|
||||||
Self::git(&["log", &format!("{}/{}..HEAD", remote, branch), "--format=%H|%s", "--no-merges"]);
|
Self::git(&["log", &format!("{}/{}..HEAD", remote, branch), "--format=%H|%s", "--no-merges"]);
|
||||||
for line in commit_log.lines().filter(|l| !l.is_empty()) {
|
for line in commit_log.lines().filter(|l| !l.is_empty()) {
|
||||||
let parts: Vec<&str> = line.splitn(2, '|').collect();
|
let parts: Vec<&str> = line.splitn(2, '|').collect();
|
||||||
if parts.len() >= 2 {
|
if parts.len() >= 2 {
|
||||||
self.emit_chronicle(
|
let commit_sha = parts[0].trim();
|
||||||
"COMMIT_CREATED",
|
chronicle.emit(
|
||||||
|
"GOV_COMMIT_CREATED",
|
||||||
&actor_did,
|
&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"]);
|
let (sha, _, _) = Self::git(&["rev-parse", "HEAD"]);
|
||||||
self.emit_chronicle(
|
let head_sha = sha.trim();
|
||||||
"GOVERNED_PUSH",
|
chronicle.emit(
|
||||||
|
"GOV_PUSH",
|
||||||
&actor_did,
|
&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
|
// Actual push
|
||||||
|
|
@ -257,7 +269,7 @@ impl GovernedGitCommands {
|
||||||
if !out.is_empty() {
|
if !out.is_empty() {
|
||||||
println!("{}", out.trim());
|
println!("{}", out.trim());
|
||||||
}
|
}
|
||||||
println!("Chronicle: GOVERNED_PUSH recorded");
|
println!("Chronicle: GOV_PUSH recorded");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -296,10 +308,20 @@ impl GovernedGitCommands {
|
||||||
let data: serde_json::Value = resp.json().unwrap_or_default();
|
let data: serde_json::Value = resp.json().unwrap_or_default();
|
||||||
let pr_url = data["html_url"].as_str().unwrap_or("?");
|
let pr_url = data["html_url"].as_str().unwrap_or("?");
|
||||||
println!("PR created: {}", pr_url);
|
println!("PR created: {}", pr_url);
|
||||||
self.emit_chronicle(
|
let actor_did = format!("did:web:{}/user/operator", ctx.trust_domain);
|
||||||
"PR_CREATED",
|
let (sha, _, _) = Self::git(&["rev-parse", "HEAD"]);
|
||||||
&format!("did:web:{}/user/operator", ctx.trust_domain),
|
self.chronicle().emit(
|
||||||
&format!("PR: {} ({})", title, pr_url),
|
"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()),
|
Ok(resp) => eprintln!("PR creation failed: {}", resp.status()),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue