use crate::checkpoint::CheckpointArtifact;
use crate::envelope::{Attestation, PublishEnvelope};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct PublishInput {
pub schema_version: String,
pub issued_at: String,
pub subject_id: String,
pub dataset_id: String,
pub dataset_version: String,
pub input_refs: Vec<String>,
pub output_refs: Vec<String>,
pub published_artifacts: Vec<CheckpointArtifact>,
pub primary_artifact_id: String,
pub checkpoint_manifest_ref: String,
pub checkpoint_manifest_hash: String,
pub checkpoint_id: String,
pub checkpoint_log_root_hash: String,
pub lineage_refs: Vec<String>,
pub verification_policy_id: String,
pub key_id: String,
pub stac_refs: Vec<String>,
pub reward_context_ref: Option<String>,
pub reward_context_hash: Option<String>,
pub provenance_start_mode: String,
pub bootstrap_origin_label: Option<String>,
pub reward_eligible: bool,
}
pub fn build_publish_envelope(input: &PublishInput, attestation: Attestation) -> PublishEnvelope {
PublishEnvelope {
schema_version: input.schema_version.clone(),
envelope_type: "publish".to_string(),
issued_at: input.issued_at.clone(),
subject_id: input.subject_id.clone(),
dataset_id: input.dataset_id.clone(),
dataset_version: input.dataset_version.clone(),
input_refs: input.input_refs.clone(),
output_refs: input.output_refs.clone(),
published_artifacts: input.published_artifacts.clone(),
primary_artifact_id: input.primary_artifact_id.clone(),
checkpoint_manifest_ref: input.checkpoint_manifest_ref.clone(),
checkpoint_manifest_hash: input.checkpoint_manifest_hash.clone(),
checkpoint_id: input.checkpoint_id.clone(),
checkpoint_log_root_hash: input.checkpoint_log_root_hash.clone(),
lineage_refs: input.lineage_refs.clone(),
verification_policy_id: input.verification_policy_id.clone(),
attestations: vec![attestation],
key_id: input.key_id.clone(),
stac_refs: input.stac_refs.clone(),
reward_context_ref: input.reward_context_ref.clone(),
reward_context_hash: input.reward_context_hash.clone(),
provenance_start_mode: input.provenance_start_mode.clone(),
bootstrap_origin_label: input.bootstrap_origin_label.clone(),
reward_eligible: input.reward_eligible,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn attestation() -> Attestation {
Attestation {
signer_id: "publisher".to_string(),
key_id: "key-1".to_string(),
signature: "sig".to_string(),
signed_at: "2026-01-01T00:00:00Z".to_string(),
}
}
fn input(reward_eligible: bool) -> PublishInput {
PublishInput {
schema_version: "1.0.0".to_string(),
issued_at: "2026-01-01T00:00:00Z".to_string(),
subject_id: "publish-1".to_string(),
dataset_id: "sst".to_string(),
dataset_version: "v1".to_string(),
input_refs: vec!["obj://zarr/1".to_string()],
output_refs: vec!["obj://release/1".to_string()],
published_artifacts: vec![CheckpointArtifact {
artifact_id: "artifact-1".to_string(),
content_root_hash: "root-1".to_string(),
content_descriptor_ref: None,
content_descriptor_hash: None,
media_type: "application/vnd+zarr".to_string(),
}],
primary_artifact_id: "artifact-1".to_string(),
checkpoint_manifest_ref: "checkpoint://1".to_string(),
checkpoint_manifest_hash: "checkpoint-hash".to_string(),
checkpoint_id: "checkpoint-1".to_string(),
checkpoint_log_root_hash: "checkpoint-log-root".to_string(),
lineage_refs: vec!["capture://1".to_string(), "transform://1".to_string()],
verification_policy_id: "verify-default".to_string(),
key_id: "key-1".to_string(),
stac_refs: vec![],
reward_context_ref: None,
reward_context_hash: None,
provenance_start_mode: "transport_capture".to_string(),
bootstrap_origin_label: None,
reward_eligible,
}
}
#[test]
fn build_publish_envelope_without_reward_proof_log_is_valid_when_not_reward_eligible() {
let env = build_publish_envelope(&input(false), attestation());
assert!(env.validate().is_ok());
}
#[test]
fn build_publish_envelope_with_reward_proof_log_is_valid() {
let env = build_publish_envelope(&input(true), attestation());
assert!(env.validate().is_ok());
}
}