lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

signing_evidence.rs (4172B)


      1 //! Exact signature facts bound to an already committed signing attempt.
      2 
      3 use radroots_event::SignedEvent;
      4 use radroots_event_codec::verify::{self, Nip01SignatureVerifier, RawEvent};
      5 
      6 use super::{
      7     AuthoredAtomicCommand, AuthoredAtomicOutcome, AuthoredAtomicReceipt, ClaimAuthoredTarget,
      8     ClaimAuthoredWork,
      9 };
     10 use crate::{
     11     Error,
     12     authored::{AuthoredArtifact, AuthoredArtifactId, WorkClaim},
     13     journal::OperationInstanceId,
     14 };
     15 
     16 /// Records an existing authored signature without granting scheduling authority.
     17 ///
     18 /// The backend must retrieve its own immutable receipt for [`Self::claim_command`]
     19 /// inside the recording transaction. A caller-supplied receipt is not durable
     20 /// attempt authority. The original claim may have expired or been superseded;
     21 /// new signing, admission and delivery still require their ordinary fences.
     22 #[derive(Clone, Debug, Eq, PartialEq)]
     23 pub struct RecordSignedArtifact {
     24     operation_id: OperationInstanceId,
     25     artifact_id: AuthoredArtifactId,
     26     claim: WorkClaim,
     27     event: SignedEvent,
     28     observed_at_unix_ms: u64,
     29 }
     30 
     31 impl RecordSignedArtifact {
     32     pub fn new(
     33         operation_id: OperationInstanceId,
     34         artifact_id: AuthoredArtifactId,
     35         claim: WorkClaim,
     36         event: SignedEvent,
     37         observed_at_unix_ms: u64,
     38     ) -> Result<Self, Error> {
     39         claim.validate()?;
     40         if observed_at_unix_ms < claim.acquired_at_unix_ms() {
     41             return Err(Error::AtomicWorkflowMismatch);
     42         }
     43         let id_verified = verify::id(RawEvent::new(event.envelope().clone()))
     44             .map_err(|_| Error::InvalidAuthoredArtifact)?;
     45         verify::signature(id_verified, &Nip01SignatureVerifier)
     46             .map_err(|_| Error::InvalidAuthoredArtifact)?;
     47         Ok(Self {
     48             operation_id,
     49             artifact_id,
     50             claim,
     51             event,
     52             observed_at_unix_ms,
     53         })
     54     }
     55 
     56     pub const fn operation_id(&self) -> OperationInstanceId {
     57         self.operation_id
     58     }
     59     pub const fn artifact_id(&self) -> AuthoredArtifactId {
     60         self.artifact_id
     61     }
     62     pub const fn claim(&self) -> &WorkClaim {
     63         &self.claim
     64     }
     65     pub const fn event(&self) -> &SignedEvent {
     66         &self.event
     67     }
     68     pub const fn observed_at_unix_ms(&self) -> u64 {
     69         self.observed_at_unix_ms
     70     }
     71 
     72     pub fn claim_command(&self) -> AuthoredAtomicCommand {
     73         AuthoredAtomicCommand::Claim(ClaimAuthoredWork::new(
     74             ClaimAuthoredTarget::ArtifactSigning(self.artifact_id),
     75             self.claim.clone(),
     76         ))
     77     }
     78 
     79     /// Applies facts only after exact backend-owned attempt provenance is checked.
     80     ///
     81     /// The first signed bytes are immutable. An identical result changes nothing;
     82     /// a different result conflicts. Cancellation and terminal failure survive.
     83     pub fn apply_to(
     84         &self,
     85         artifact: &mut AuthoredArtifact,
     86         original_claim: &AuthoredAtomicReceipt,
     87     ) -> Result<(), Error> {
     88         let AuthoredAtomicOutcome::Artifact(original) = original_claim.outcome() else {
     89             return Err(Error::AtomicWorkflowMismatch);
     90         };
     91         original.validate()?;
     92         artifact.validate()?;
     93         if !original_claim.matches_command(&self.claim_command())
     94             || original_claim.committed_at_unix_ms() != self.claim.acquired_at_unix_ms()
     95             || original.signing_claim() != Some(&self.claim)
     96             || original.operation_id() != self.operation_id
     97             || artifact.operation_id() != self.operation_id
     98             || original.artifact_id() != self.artifact_id
     99             || artifact.artifact_id() != self.artifact_id
    100             || original.plan() != artifact.plan()
    101             || original.origin() != artifact.origin()
    102             || original.ordinal() != artifact.ordinal()
    103             || original.created_at_unix_ms() != artifact.created_at_unix_ms()
    104             || original.revision() > artifact.revision()
    105             || original.updated_at_unix_ms() > artifact.updated_at_unix_ms()
    106         {
    107             return Err(Error::AtomicWorkflowMismatch);
    108         }
    109         artifact.record_signed_fact(self.event.clone(), self.observed_at_unix_ms)
    110     }
    111 }