Skip to main content

codetether_agent/github_pr/
provenance_block.rs

1//! Generate provenance verification blocks for PR bodies.
2
3use crate::provenance::{ExecutionProvenance, sign_provenance};
4
5const SIG_PREVIEW_CHARS: usize = 16;
6
7/// Build a markdown verification block from provenance metadata.
8pub fn provenance_markdown_block(provenance: &ExecutionProvenance) -> String {
9    let signature = sign_provenance(provenance);
10    let sig_label = signature.as_deref().unwrap_or("UNSIGNED");
11    let sig_preview = &sig_label[..sig_label.len().min(SIG_PREVIEW_CHARS)];
12    format!(
13        "### 🔐 Provenance\n\
14         \n\
15         | Field | Value |\n\
16         |-------|-------|\n\
17         | Agent | `{agent}` |\n\
18         | Origin | `{origin}` |\n\
19         | Run ID | `{run}` |\n\
20         | Provenance ID | `{prov}` |\n\
21         | Signature | `{sig}` |\n\
22         \n\
23         > Verify: `codetether verify-pr {prov}`",
24        agent = provenance.identity.agent_name,
25        origin = provenance.identity.origin.as_str(),
26        run = provenance.run_id.as_deref().unwrap_or("N/A"),
27        prov = provenance.provenance_id,
28        sig = sig_preview,
29    )
30}
31
32#[cfg(test)]
33mod tests {
34    use super::provenance_markdown_block;
35    use crate::provenance::{ExecutionOrigin, ExecutionProvenance};
36
37    #[test]
38    fn renders_block_with_required_fields() {
39        unsafe {
40            std::env::remove_var("CODETETHER_SIGNING_SECRET");
41        }
42        let mut provenance = ExecutionProvenance::for_operation("ralph", ExecutionOrigin::Ralph);
43        provenance.set_run_id("run-42");
44        let block = provenance_markdown_block(&provenance);
45        assert!(block.contains("### 🔐 Provenance"));
46        assert!(block.contains("| Agent | `ralph` |"));
47        assert!(block.contains("| Run ID | `run-42` |"));
48        assert!(block.contains(&provenance.provenance_id));
49        assert!(block.contains("`UNSIGNED`"));
50    }
51}