refactor(ceremony-engine): bind git commit hash into canonical_bytes
PipelineMerge ceremony resolutions now include the git commit SHA in their canonical form, binding the Quartermaster merkle leaf to git's merkle tree. SchematicPublish includes tree_hash, GitOpsSync includes target_revision. Non-git ceremony types (MutationIntent, Custom) unchanged — canonical_bytes still returns proof_hash alone. See cid-reconciliation-audit.md Site 8. Signed-off-by: Tyler King <tking@guildhouse.dev>
This commit is contained in:
parent
b1865a0627
commit
3d5e5485ec
1 changed files with 56 additions and 5 deletions
|
|
@ -7,6 +7,7 @@
|
||||||
use registry_protocol::{RegistryArtifact, MutationVerb};
|
use registry_protocol::{RegistryArtifact, MutationVerb};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::request::CeremonySubject;
|
||||||
use crate::resolution::CeremonyResolution;
|
use crate::resolution::CeremonyResolution;
|
||||||
|
|
||||||
/// Operations on governance ceremonies.
|
/// Operations on governance ceremonies.
|
||||||
|
|
@ -54,11 +55,26 @@ impl RegistryArtifact for CeremonyResolution {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn canonical_bytes(&self) -> Vec<u8> {
|
fn canonical_bytes(&self) -> Vec<u8> {
|
||||||
// Re-derive the canonical form from the resolution's fields.
|
// For git-originated ceremonies (PipelineMerge, SchematicPublish,
|
||||||
// The proof_hash was computed from JCS of the canonical form,
|
// GitOpsSync), bind the git ref into the canonical form so the
|
||||||
// so we can use it as the canonical bytes for merkle anchoring.
|
// Quartermaster merkle leaf transitively includes git's hash.
|
||||||
// This avoids re-canonicalizing and guarantees consistency.
|
// Format: proof_hash bytes || git ref bytes.
|
||||||
self.proof_hash.as_bytes().to_vec()
|
let mut bytes = self.proof_hash.as_bytes().to_vec();
|
||||||
|
match &self.subject {
|
||||||
|
CeremonySubject::PipelineMerge { commit_hash, .. } => {
|
||||||
|
bytes.extend_from_slice(commit_hash.as_bytes());
|
||||||
|
}
|
||||||
|
CeremonySubject::SchematicPublish { tree_hash, .. } => {
|
||||||
|
bytes.extend_from_slice(tree_hash.as_bytes());
|
||||||
|
}
|
||||||
|
CeremonySubject::GitOpsSync { target_revision, .. } => {
|
||||||
|
bytes.extend_from_slice(target_revision.as_bytes());
|
||||||
|
}
|
||||||
|
// Non-git ceremonies: proof_hash alone is the canonical form.
|
||||||
|
CeremonySubject::MutationIntent { .. }
|
||||||
|
| CeremonySubject::Custom { .. } => {}
|
||||||
|
}
|
||||||
|
bytes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -127,6 +143,41 @@ mod tests {
|
||||||
assert_eq!(b1, b2);
|
assert_eq!(b1, b2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn canonical_bytes_pipeline_merge_includes_commit_hash() {
|
||||||
|
let res = sample_resolution(); // PipelineMerge with commit_hash "abc"
|
||||||
|
let bytes = res.canonical_bytes();
|
||||||
|
let proof_bytes = res.proof_hash.as_bytes();
|
||||||
|
// Must contain proof_hash + commit_hash
|
||||||
|
assert!(bytes.len() > proof_bytes.len());
|
||||||
|
assert_eq!(&bytes[..proof_bytes.len()], proof_bytes);
|
||||||
|
assert_eq!(&bytes[proof_bytes.len()..], b"abc");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn canonical_bytes_non_git_ceremony_excludes_git_ref() {
|
||||||
|
let approvals = vec![CeremonyApproval {
|
||||||
|
approver_identity: "alice@ops".to_string(),
|
||||||
|
approver_role: "msp-ops".to_string(),
|
||||||
|
decision: ApprovalDecision::Approve,
|
||||||
|
comment: None,
|
||||||
|
decided_at: Utc::now(),
|
||||||
|
}];
|
||||||
|
let res = CeremonyResolution::from_ceremony(
|
||||||
|
"cer-custom",
|
||||||
|
GovernanceCeremonyStatus::Approved,
|
||||||
|
&CeremonySubject::Custom {
|
||||||
|
subject_type: "test".to_string(),
|
||||||
|
reference_id: "ref-1".to_string(),
|
||||||
|
description: "test ceremony".to_string(),
|
||||||
|
},
|
||||||
|
&approvals,
|
||||||
|
);
|
||||||
|
let bytes = res.canonical_bytes();
|
||||||
|
// Non-git: canonical_bytes == proof_hash bytes only
|
||||||
|
assert_eq!(bytes, res.proof_hash.as_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn verb_serialization() {
|
fn verb_serialization() {
|
||||||
let verb = CeremonyVerb::Approve;
|
let verb = CeremonyVerb::Approve;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue