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 serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::request::CeremonySubject;
|
||||
use crate::resolution::CeremonyResolution;
|
||||
|
||||
/// Operations on governance ceremonies.
|
||||
|
|
@ -54,11 +55,26 @@ impl RegistryArtifact for CeremonyResolution {
|
|||
}
|
||||
|
||||
fn canonical_bytes(&self) -> Vec<u8> {
|
||||
// Re-derive the canonical form from the resolution's fields.
|
||||
// The proof_hash was computed from JCS of the canonical form,
|
||||
// so we can use it as the canonical bytes for merkle anchoring.
|
||||
// This avoids re-canonicalizing and guarantees consistency.
|
||||
self.proof_hash.as_bytes().to_vec()
|
||||
// For git-originated ceremonies (PipelineMerge, SchematicPublish,
|
||||
// GitOpsSync), bind the git ref into the canonical form so the
|
||||
// Quartermaster merkle leaf transitively includes git's hash.
|
||||
// Format: proof_hash bytes || git ref bytes.
|
||||
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);
|
||||
}
|
||||
|
||||
#[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]
|
||||
fn verb_serialization() {
|
||||
let verb = CeremonyVerb::Approve;
|
||||
|
|
|
|||
Loading…
Reference in a new issue