use super::{
make_attestation, verify_attestation, ArtifactRecord, Attestation, CaptureEnvelope,
PublishEnvelope, TransformEnvelope,
};
use crate::checkpoint::CheckpointArtifact;
fn attestation() -> Attestation {
Attestation {
signer_id: "signer-1".to_string(),
key_id: "key-1".to_string(),
signature: "sig".to_string(),
signed_at: "2026-01-01T00:00:00Z".to_string(),
}
}
fn capture() -> CaptureEnvelope {
CaptureEnvelope {
schema_version: "1.0.0".to_string(),
envelope_type: "capture".to_string(),
issued_at: "2026-01-01T00:00:00Z".to_string(),
subject_id: "capture-1".to_string(),
capture_role: "transport".to_string(),
capture_actor_id: "station-a".to_string(),
capture_system_id: "rx-1".to_string(),
capture_window: "2026-01-01T00:00:00Z/2026-01-01T00:05:00Z".to_string(),
segment_ids: vec!["seg-1".to_string()],
input_refs: vec![],
output_refs: vec!["obj://raw/1".to_string()],
segment_hashes: vec!["hash-1".to_string()],
rolling_hash_state: Some("rolling-1".to_string()),
content_root_hash: "root-1".to_string(),
content_commitment_profile: "blake3_root_v1".to_string(),
chunk_size: 3,
leaf_count: 1,
content_descriptor_ref: None,
content_descriptor_hash: None,
attestations: vec![attestation()],
key_id: "key-1".to_string(),
policy_profile_id: None,
}
}
fn transform() -> TransformEnvelope {
TransformEnvelope {
schema_version: "1.0.0".to_string(),
envelope_type: "transform".to_string(),
issued_at: "2026-01-01T00:00:00Z".to_string(),
subject_id: "transform-1".to_string(),
transform_job_id: "job-1".to_string(),
transform_stage: "nc_to_zarr".to_string(),
input_refs: vec!["obj://raw/1".to_string()],
output_refs: vec!["obj://zarr/1".to_string()],
input_artifact_roots: vec!["root-in".to_string()],
output_artifact_roots: vec!["root-out".to_string()],
input_artifacts: vec![ArtifactRecord {
artifact_id: "input-1".to_string(),
artifact_ref: "obj://raw/1".to_string(),
content_root_hash: "root-in".to_string(),
}],
output_artifacts: vec![ArtifactRecord {
artifact_id: "output-1".to_string(),
artifact_ref: "obj://zarr/1".to_string(),
content_root_hash: "root-out".to_string(),
}],
toolchain: "rust".to_string(),
parameters_ref: "cfg://1".to_string(),
parameters_hash: "cfg-hash".to_string(),
determinism_profile: "det-v1".to_string(),
runtime_env_ref: Some("oci://img".to_string()),
runtime_env_hash: Some("img-hash".to_string()),
provenance_start_mode: "transport_capture".to_string(),
source_manifest_ref: None,
source_manifest_hash: None,
source_root_hash: None,
transform_spec_ref: Some("spec://1".to_string()),
transform_spec_hash: Some("spec-hash".to_string()),
chunking_profile_ref: Some("chunk://1".to_string()),
chunking_profile_hash: Some("chunk-hash".to_string()),
execution_manifest_ref: Some("exec://1".to_string()),
execution_manifest_hash: Some("exec-hash".to_string()),
runtime_manifest_ref: Some("runtime://1".to_string()),
runtime_manifest_hash: Some("runtime-hash".to_string()),
c2pa_ingredients: vec![],
c2pa_actions: vec![],
attestations: vec![attestation()],
key_id: "key-1".to_string(),
}
}
fn publish() -> PublishEnvelope {
PublishEnvelope {
schema_version: "1.0.0".to_string(),
envelope_type: "publish".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(),
attestations: vec![attestation()],
key_id: "key-1".to_string(),
stac_refs: vec![],
ogc_refs: vec![],
c2pa_manifest_ref: None,
c2pa_manifest_hash: None,
c2pa_ingredients: vec![],
c2pa_actions: vec![],
reward_context_ref: None,
reward_context_hash: None,
provenance_start_mode: "transport_capture".to_string(),
bootstrap_origin_label: None,
reward_eligible: false,
}
}
#[test]
fn capture_envelope_validates() {
assert!(capture().validate().is_ok());
}
#[test]
fn capture_envelope_rejects_unknown_capture_role() {
let mut envelope = capture();
envelope.capture_role = "unknown".to_string();
let errors = envelope
.validate()
.expect_err("unknown capture role must fail validation");
assert!(errors.iter().any(|e| e.contains("capture_role")));
}
#[test]
fn transform_envelope_validates() {
assert!(transform().validate().is_ok());
}
#[test]
fn dataset_bootstrap_transform_requires_spec_and_source_manifest_fields() {
let mut env = transform();
env.provenance_start_mode = "dataset_bootstrap".to_string();
env.source_manifest_ref = None;
env.source_manifest_hash = None;
env.source_root_hash = None;
env.transform_spec_ref = None;
env.transform_spec_hash = None;
let errors = env
.validate()
.expect_err("bootstrap transform must require spec and source");
assert!(errors.iter().any(|e| e.contains("source_manifest_ref")));
assert!(errors.iter().any(|e| e.contains("source_manifest_hash")));
assert!(errors.iter().any(|e| e.contains("source_root_hash")));
assert!(errors.iter().any(|e| e.contains("transform_spec_ref")));
assert!(errors.iter().any(|e| e.contains("transform_spec_hash")));
}
#[test]
fn dataset_incremental_transform_requires_spec_and_source_manifest_fields() {
let mut env = transform();
env.provenance_start_mode = "dataset_incremental".to_string();
env.source_manifest_ref = None;
env.source_manifest_hash = None;
env.source_root_hash = None;
env.transform_spec_ref = None;
env.transform_spec_hash = None;
let errors = env
.validate()
.expect_err("incremental transform must require spec and source");
assert!(errors.iter().any(|e| e.contains("source_manifest_ref")));
assert!(errors.iter().any(|e| e.contains("source_manifest_hash")));
assert!(errors.iter().any(|e| e.contains("source_root_hash")));
assert!(errors.iter().any(|e| e.contains("transform_spec_ref")));
assert!(errors.iter().any(|e| e.contains("transform_spec_hash")));
}
#[test]
fn publish_envelope_validates() {
assert!(publish().validate().is_ok());
}
#[test]
fn dataset_bootstrap_publish_requires_origin_label() {
let mut env = publish();
env.provenance_start_mode = "dataset_bootstrap".to_string();
env.bootstrap_origin_label = None;
let errors = env
.validate()
.expect_err("bootstrap publish must require origin label");
assert!(errors.iter().any(|e| e.contains("bootstrap_origin_label")));
}
#[test]
fn make_attestation_uses_ed25519_signatures() {
const TEST_SIGNING_KEY_HEX: &str =
"4f3edf983ac636a65a842ce7c78d9aa706d3b113bce036f9a4f5762b76f70f18";
let payload = publish().canonical_attestation_payload_bytes();
let att = make_attestation(
"signer-1",
TEST_SIGNING_KEY_HEX,
"2026-01-01T00:00:00Z",
&payload,
)
.expect("build attestation");
assert!(verify_attestation(&att, &payload));
}