use crypto::{Ed25519Signer, Signer, thread_operation::SignedOperation};
use objects::{
object::{
Attribution, Blob, Principal, Tree, TreeEntry, thread_replication::ThreadOperationBody,
},
store::pack::{ObjectType, PackBuilder, PackObjectId},
};
use super::*;
use crate::replication;
#[cfg(feature = "native")]
#[path = "import_staging_tests.rs"]
mod imported;
#[test]
fn partial_source_staging_retains_proof_without_complete_availability() {
let scratch = tempfile::tempdir().expect("scratch");
let (directory, mut ready, operations, state) = fixture(scratch.path(), false);
let pack = PackReader::open(
&directory.path().join("source.pack"),
&directory.path().join("source.idx"),
)
.expect("original pack");
let (_, bytes) = pack
.get_hashed_object(&state.tree)
.expect("tree read")
.expect("tree");
let tree = Tree::decode_canonical(&bytes).expect("salted tree");
let redacted = std::collections::HashSet::from([tree.v4_leaf_hash_at(0).expect("leaf")]);
let partial = objects::object::PartialTree::project(&tree, &redacted).expect("partial proof");
let mut builder = PackBuilder::for_repack(Default::default(), 0);
builder.add_id(
PackObjectId::StateId(state.id()),
ObjectType::State,
state.encode_current_msgpack().expect("canonical State"),
);
builder.add_id(
PackObjectId::Hash(state.tree),
ObjectType::Tree,
objects::object::encode_redacted_projection(&partial).expect("encode proof"),
);
let (bytes, index, _) = builder.build().expect("partial pack");
drop(pack);
std::fs::write(directory.path().join("source.pack"), &bytes).expect("write pack");
std::fs::write(directory.path().join("source.idx"), &index).expect("write index");
ready.full_closure_available = false;
let staged = validate(directory, ready.clone(), operations.clone(), vec![])
.expect("partial closure with unchanged original source signature");
assert!(!staged.is_complete());
assert_eq!(staged.partial_trees.len(), 1);
assert_eq!(staged.partial_trees[0].declared_root(), state.tree);
let destination = tempfile::tempdir().expect("destination");
let repository = repo::Repository::init_default(destination.path()).expect("repository");
staged
.install_source_objects(&repository)
.expect("install visible objects");
use objects::store::ObjectStore;
assert!(
repository
.store()
.get_tree(&state.tree)
.expect("full tree lookup")
.is_none(),
"a partial proof must never enter the full tree cache"
);
assert!(
repository
.store()
.has_partial_tree(&state.tree)
.expect("partial tree lookup")
);
let stored = repository
.store()
.get_state(&state.id())
.expect("state lookup")
.expect("state");
assert_eq!(
stored, state,
"source signature still binds the original State"
);
let directory = tempfile::tempdir_in(scratch.path()).expect("false full disclosure");
std::fs::write(directory.path().join("source.pack"), bytes).expect("write pack");
std::fs::write(directory.path().join("source.idx"), index).expect("write index");
ready.full_closure_available = true;
assert!(
validate(directory, ready, operations, vec![]).is_err(),
"partial proofs cannot be declared complete source"
);
}
#[test]
fn only_exact_portable_empty_seed_stages_without_source_operation() {
let scratch = tempfile::tempdir().expect("scratch");
let (_, mut ready, _, _) = crate::fetch::tests::fixture();
let signer = Ed25519Signer::from_seed(&[61; 32]).expect("creator");
let mut genesis = replication::opening::verify_genesis(
ready
.thread_genesis
.as_ref()
.expect("record")
.genesis
.as_ref()
.expect("signed"),
ready.thread.as_ref().expect("Thread"),
)
.expect("genesis");
let seed = objects::object::thread_replication::initial_base::synthetic_initial_base()
.expect("canonical seed");
genesis.base = seed.id();
let signed =
replication::opening::sign_genesis(&genesis, &signer).expect("signed seed genesis");
ready.thread_genesis.as_mut().expect("record").genesis = Some(signed);
ready.thread.as_mut().expect("Thread").id = Some(ThreadId {
value: genesis.id().expect("Thread ID").as_bytes().to_vec(),
});
ready.current.as_mut().expect("revision").revision = Some(revision_ref::Revision::State(
api::heddle::api::common::StateId {
value: seed.id().as_bytes().to_vec(),
},
));
let mut builder = PackBuilder::for_repack(Default::default(), 0);
builder.add_id(
PackObjectId::StateId(seed.id()),
ObjectType::State,
seed.encode_current_msgpack().expect("State"),
);
let tree = Tree::new();
builder.add_id(
PackObjectId::Hash(tree.hash()),
ObjectType::Tree,
tree.encode_canonical().expect("tree"),
);
let (pack, index, _) = builder.build().expect("source pack");
let directory = tempfile::Builder::new()
.prefix("seed-download-")
.tempdir_in(scratch.path())
.expect("staging");
std::fs::write(directory.path().join("source.pack"), pack).expect("pack");
std::fs::write(directory.path().join("source.idx"), index).expect("index");
let staged = validate(directory, ready.clone(), vec![], vec![]).expect("known seed");
assert_eq!(staged.state().id(), seed.id());
assert!(staged.operations().is_empty());
let mut wrong = ready;
let random = State::new_snapshot(
Tree::new().hash(),
vec![],
Attribution::human(Principal::new("Heddle", "init@heddle")),
);
wrong.current.as_mut().expect("revision").revision = Some(revision_ref::Revision::State(
api::heddle::api::common::StateId {
value: random.id().as_bytes().to_vec(),
},
));
let directory = tempfile::Builder::new()
.prefix("seed-download-negative-")
.tempdir_in(scratch.path())
.expect("staging");
std::fs::copy(
staged.artifact_paths()[0].clone(),
directory.path().join("source.pack"),
)
.expect("copy pack");
std::fs::copy(
staged.artifact_paths()[1].clone(),
directory.path().join("source.idx"),
)
.expect("copy index");
assert!(
validate(directory, wrong, vec![], vec![]).is_err(),
"random empty State is not an implicit source"
);
}
#[test]
fn fork_base_stages_only_with_parent_original_source() {
let scratch = tempfile::tempdir().expect("scratch");
let (directory, mut ready, operations, state) = fixture(scratch.path(), false);
let parent = ready.thread_genesis.clone().expect("parent original");
let parent_genesis = replication::opening::verify_genesis(
parent.genesis.as_ref().expect("signed parent"),
ready.thread.as_ref().expect("parent Thread"),
)
.expect("parent genesis");
let mut child = parent_genesis.clone();
child.parent = Some(parent_genesis.id().expect("parent ID"));
child.base = state.id();
child.name = "fork".into();
let signer = Ed25519Signer::from_seed(&[61; 32]).expect("creator");
let signed = replication::opening::sign_genesis(&child, &signer).expect("signed fork");
ready.thread_genesis.as_mut().expect("child record").genesis = Some(signed);
ready.thread.as_mut().expect("Thread").id = Some(ThreadId {
value: child.id().expect("child ID").as_bytes().to_vec(),
});
let staged = validate(
directory,
ready.clone(),
operations.clone(),
vec![parent.clone()],
)
.expect("parent original proves fork base");
assert_eq!(staged.state().id(), state.id());
assert_eq!(staged.dependency_geneses().len(), 1);
let directory = tempfile::Builder::new()
.prefix("fork-missing-parent-")
.tempdir_in(scratch.path())
.expect("staging");
std::fs::copy(
staged.artifact_paths()[0].clone(),
directory.path().join("source.pack"),
)
.expect("copy pack");
std::fs::copy(
staged.artifact_paths()[1].clone(),
directory.path().join("source.idx"),
)
.expect("copy index");
assert!(
validate(directory, ready, operations, vec![]).is_err(),
"fork base cannot discard the parent original and rely on its hash"
);
}
fn fixture(
scratch: &Path,
extra_blob: bool,
) -> (
tempfile::TempDir,
TransferReady,
Vec<SignedOperation>,
State,
) {
let (_, mut ready, _, _) = crate::fetch::tests::fixture();
let signer = Ed25519Signer::from_seed(&[61; 32]).expect("original creator");
let genesis = replication::opening::verify_genesis(
ready
.thread_genesis
.as_ref()
.expect("genesis")
.genesis
.as_ref()
.expect("signed genesis"),
ready.thread.as_ref().expect("Thread"),
)
.expect("original Thread");
let blob = Blob::new(b"selected source".to_vec());
let tree = Tree::from_entries_salted_v4(
vec![TreeEntry::file("main.rs", blob.hash(), false).expect("entry")],
vec![[22; 32]],
)
.expect("native salted source");
let state = State::new_snapshot(
tree.hash(),
vec![genesis.base],
Attribution::human(Principal::new("author", "author@example.test")),
);
ready.current.as_mut().expect("revision").revision = Some(revision_ref::Revision::State(
api::heddle::api::common::StateId {
value: state.id().as_bytes().to_vec(),
},
));
let operation = SignedOperation::sign(
&ThreadOperation {
version: 1,
thread: genesis.id().expect("Thread ID"),
parents: BTreeSet::new(),
publisher: signer.public_key().try_into().expect("key"),
body: ThreadOperationBody::Capture(
objects::object::thread_replication::AuthoredCapture::local(
state.encode_current_msgpack().expect("State").into(),
),
),
},
&signer,
)
.expect("original source signature");
let mut builder = PackBuilder::for_repack(Default::default(), 0);
builder.add_id(
PackObjectId::StateId(state.id()),
ObjectType::State,
state.encode_current_msgpack().expect("State"),
);
builder.add_id(
PackObjectId::Hash(tree.hash()),
ObjectType::Tree,
tree.encode_canonical().expect("tree"),
);
builder.add_id(
PackObjectId::Hash(blob.hash()),
ObjectType::Blob,
blob.into_content(),
);
if extra_blob {
let extra = Blob::new(b"private source outside the selected closure".to_vec());
builder.add_id(
PackObjectId::Hash(extra.hash()),
ObjectType::Blob,
extra.into_content(),
);
}
let (pack, index, _) = builder.build().expect("source artifacts");
let directory = tempfile::Builder::new()
.prefix("thread-download-")
.tempdir_in(scratch)
.expect("staging");
std::fs::write(directory.path().join("source.pack"), pack).expect("pack");
std::fs::write(directory.path().join("source.idx"), index).expect("index");
(directory, ready, vec![operation], state)
}
#[cfg(feature = "native")]
fn install_unwitnessed(
staged: StagedSource,
repository: &repo::Repository,
) -> Result<objects::object::StateId, Error> {
use repo::thread_replication::hosted_trust::{HostedTrust, SystemClock, TrustTransaction};
use crate::hybrid::authority::{
AcceptedHistory, SelectedAuthority,
tests::{bundle, selected},
};
let bundle = bundle();
let limits = heddleco_capability_verifier::VerificationLimits::new(3600).expect("limits");
let pinned = selected(&bundle, limits);
let history = AcceptedHistory::from_selected_spool(&bundle, &pinned, 1100, limits)
.expect("selected history");
let authority = SelectedAuthority::new(
history,
bundle,
|_: &crate::contract::ImportPublicProofBundleV1, _: i64, _: &TrustTransaction<'_>| Ok(()),
);
repo::thread_replication::hosted_trust::select_root(
repository.heddle_dir(),
&repo::thread_replication::hosted_trust::RootSelection {
authority: "https://weft.example.test".into(),
root_id: "unused-root".into(),
public_key: [1; 32],
},
)
.expect("independent root");
let trust = HostedTrust::open(
repository.heddle_dir(),
"https://weft.example.test",
SystemClock,
)
.expect("trust");
staged.install_hosted(repository, &trust, &authority, 1100)
}
#[cfg(feature = "native")]
fn installation_fixture(
scratch: &Path,
) -> (
tempfile::TempDir,
TransferReady,
Vec<SignedOperation>,
State,
) {
let (directory, mut ready, operations, state) = fixture(scratch, false);
let owner = Ed25519Signer::from_seed(&[61; 32]).expect("selected owner");
let recovery = Ed25519Signer::from_seed(&[62; 32]).expect("recovery");
let account = uuid::Uuid::from_bytes([9; 16]);
let root = repo::sign_custodial_owner_root(&owner, &recovery, *account.as_bytes(), [98; 32])
.expect("owner root");
let binding =
repo::sign_custodial_owner_binding(&owner, &root, [99; 32]).expect("owner binding");
let hash = binding.root_state_hash.clone();
let spool = ready
.thread
.as_ref()
.expect("Thread")
.spool
.as_ref()
.expect("Spool")
.id
.parse::<uuid::Uuid>()
.expect("UUID");
ready.ownership = Some(OwnerState {
owner: Some(PrincipalRef {
id: account.to_string(),
}),
root: Some(root.clone()),
binding: Some(binding),
version: hash.clone(),
resource_keyring: Some(CloneAuthorizationKeyring {
format_version: 1,
spool_uuid: spool.as_bytes().to_vec(),
canonical_spool_path_segments: vec!["selected".into(), "source".into()],
pin: Some(CloneOwnerPin {
kind: CloneOwnerPinKind::CloneTofu as i32,
expected_owner_id: root.root.as_ref().expect("root").owner_id.clone(),
first_seen_unix_seconds: 1,
}),
owner_root: Some(root),
accepted_state_hash: hash,
owner_genesis: ready.owner_genesis.clone(),
..Default::default()
}),
..Default::default()
});
(directory, ready, operations, state)
}
#[cfg(feature = "native")]
#[test]
fn verify_before_install_rejects_without_partial_repository_mutation() {
use objects::store::ObjectStore;
let scratch = tempfile::tempdir().expect("scratch");
let destination = tempfile::tempdir().expect("destination");
let repository = repo::Repository::init(destination.path()).expect("unseeded repository");
let (directory, ready, mut operations, state) = installation_fixture(scratch.path());
let stranger = Ed25519Signer::from_seed(&[63; 32]).expect("valid neighboring author");
let mut operation = operations[0].verify().expect("original operation");
operation.publisher = stranger.public_key().try_into().expect("key");
operations[0] = SignedOperation::sign(&operation, &stranger).expect("valid original signature");
let staged = validate(directory, ready, operations, vec![])
.expect("structural staging is not ownership authority");
assert!(install_unwitnessed(staged, &repository).is_err());
assert!(
!repository.heddle_dir().join("spool-id").exists(),
"rejection must precede Spool mutation"
);
assert!(
!repository
.heddle_dir()
.join("owner-authorization.bin")
.exists(),
"no owner enrollment on rejection"
);
assert!(
repository
.store()
.get_state(&state.id())
.expect("source lookup")
.is_none(),
"rejection must precede pack installation"
);
let (directory, ready, operations, _) = installation_fixture(scratch.path());
let staged =
validate(directory, ready, operations, vec![]).expect("valid structural local source");
assert!(matches!(
install_unwitnessed(staged, &repository),
Err(Error::HostedTrustRequired)
));
}
#[cfg(feature = "native")]
#[test]
fn structural_staging_never_authorizes_an_account_genesis() {
let scratch = tempfile::tempdir().expect("scratch");
let destination = tempfile::tempdir().expect("destination");
let repository = repo::Repository::init(destination.path()).expect("unseeded repository");
let (directory, mut ready, mut operations, _) = installation_fixture(scratch.path());
let wrapper = ready.thread_genesis.as_mut().expect("genesis carrier");
let record = wrapper.genesis.as_ref().expect("original");
let mut genesis = heddle_object_model::object::thread_replication::ThreadGenesis::decode(
&record.canonical_record,
)
.expect("genesis");
genesis.owner =
objects::object::thread_replication::GenesisOwner::Account(uuid::Uuid::from_bytes([9; 16]));
let signer = Ed25519Signer::from_seed(&[61; 32]).expect("creator");
wrapper.genesis =
Some(replication::opening::sign_genesis(&genesis, &signer).expect("original signature"));
wrapper.creator_authority = b"retained bytes are not an owner grant".to_vec();
ready
.thread
.as_mut()
.expect("Thread")
.id
.as_mut()
.expect("ID")
.value = genesis.id().expect("ID").as_bytes().to_vec();
let mut operation = operations[0].verify().expect("operation");
operation.thread = genesis.id().expect("ID");
operations[0] = SignedOperation::sign(&operation, &signer).expect("original signature");
let staged = validate(directory, ready, operations, vec![]).expect("structural originals");
assert!(matches!(
install_unwitnessed(staged, &repository),
Err(Error::HostedTrustRequired)
));
assert!(!repository.heddle_dir().join("spool-id").exists());
let (directory, ready, operations, _) = installation_fixture(scratch.path());
let staged = validate(directory, ready, operations, vec![]).expect("local originals");
install_unwitnessed(staged, &repository)
.expect_err("local originals on Weft still require hosted witness trust");
}
#[test]
fn source_staging_keeps_original_proofs_and_releases_artifacts_twice() {
let scratch = tempfile::tempdir().expect("scratch");
for _ in 0..2 {
let (directory, ready, operations, expected) = fixture(scratch.path(), false);
let original = operations.clone();
let staged = validate(directory, ready, operations, vec![]).expect("exact closure");
assert_eq!(staged.state().id(), expected.id());
assert_eq!(staged.operations(), original);
assert!(staged.artifact_paths().iter().all(|path| path.exists()));
drop(staged);
assert_eq!(
std::fs::read_dir(scratch.path())
.expect("scratch entries")
.count(),
0
);
}
}
#[test]
fn source_staging_rejects_unselected_content_before_installation() {
let scratch = tempfile::tempdir().expect("scratch");
let (directory, ready, operations, _) = fixture(scratch.path(), true);
let error = match validate(directory, ready, operations, vec![]) {
Ok(_) => panic!("unselected source must be rejected"),
Err(error) => error,
};
assert!(
error.to_string().contains("outside the selected revision"),
"{error}"
);
assert_eq!(
std::fs::read_dir(scratch.path())
.expect("scratch entries")
.count(),
0
);
}
#[test]
fn source_staging_requires_the_original_causal_parents() {
let scratch = tempfile::tempdir().expect("scratch");
let (directory, ready, mut operations, _) = fixture(scratch.path(), false);
let mut operation = operations[0].verify().expect("operation");
operation
.parents
.insert(ContentHash::compute(b"missing original parent"));
operations[0] = SignedOperation::sign(
&operation,
&Ed25519Signer::from_seed(&[61; 32]).expect("creator"),
)
.expect("valid signature");
assert!(matches!(
validate(directory, ready, operations, vec![]),
Err(Error::Invalid("incomplete source ancestry"))
));
assert_eq!(
std::fs::read_dir(scratch.path())
.expect("scratch entries")
.count(),
0
);
}
fn integrated_fixture(
scratch: &Path,
) -> (
tempfile::TempDir,
TransferReady,
Vec<SignedOperation>,
Vec<ThreadGenesisRecord>,
) {
use objects::object::{
VisibilityTier, thread_replication::local_integration::LocalIntegration,
};
let (directory, mut ready, _, original) = fixture(scratch, false);
let signer = Ed25519Signer::from_seed(&[61; 32]).expect("signer");
let target = replication::opening::verify_genesis(
ready
.thread_genesis
.as_ref()
.expect("genesis")
.genesis
.as_ref()
.expect("signed genesis"),
ready.thread.as_ref().expect("thread"),
)
.expect("target");
let mut source = target.clone();
source.name = "dependency".into();
source.nonce = vec![1];
let source_record =
replication::opening::sign_genesis(&source, &signer).expect("source genesis");
let source_state = State::new_snapshot(
original.tree,
vec![source.base],
Attribution::human(Principal::new("source", "")),
)
.with_intent("dependency source");
let source_op = ThreadOperation {
version: 1,
thread: source.id().expect("source Thread"),
parents: BTreeSet::new(),
publisher: signer.public_key().try_into().expect("key"),
body: ThreadOperationBody::Capture(
objects::object::thread_replication::AuthoredCapture::local(
source_state
.encode_current_msgpack()
.expect("source State")
.into(),
),
),
};
let result = State::new_merge(
original.tree,
vec![target.base, source_state.id()],
Attribution::human(Principal::new("integrator", "")),
);
let receipt = LocalIntegration {
author: heddle_object_model::object::thread_replication::SourceAuthor::LocalKey,
version: 1,
spool: source.spool.parse().expect("Spool"),
device: signer.public_key().try_into().expect("key"),
source_thread: source.id().expect("Thread"),
source_operation: source_op.id().expect("source operation"),
source_revision: source_state.id(),
target_thread: target.id().expect("target Thread"),
expected_target_frontier: BTreeSet::new(),
result: result.encode_current_msgpack().expect("result").into(),
result_visibility: VisibilityTier::Public,
initiating_request_proof: ContentHash::from_bytes([3; 32]),
local_policy_version: ContentHash::from_bytes([4; 32]),
executed_at_ms: 0,
};
let integration = ThreadOperation {
version: 1,
thread: target.id().expect("target"),
parents: BTreeSet::new(),
publisher: receipt.device,
body: ThreadOperationBody::LocalIntegration(receipt.encode().expect("receipt")),
};
ready.current.as_mut().expect("revision").revision = Some(revision_ref::Revision::State(
api::heddle::api::common::StateId {
value: result.id().as_bytes().to_vec(),
},
));
let mut builder = PackBuilder::for_repack(Default::default(), 0);
let reader = PackReader::open(
&directory.path().join("source.pack"),
&directory.path().join("source.idx"),
)
.expect("pack");
reader
.visit_objects(|id, kind, bytes| {
if kind != ObjectType::State {
builder.add_id(id, kind, bytes.to_vec());
}
Ok(())
})
.expect("reuse exact tree");
drop(reader);
builder.add_id(
PackObjectId::StateId(result.id()),
ObjectType::State,
result.encode_current_msgpack().expect("result"),
);
let (pack, index, _) = builder.build().expect("pack");
std::fs::write(directory.path().join("source.pack"), pack).expect("write pack");
std::fs::write(directory.path().join("source.idx"), index).expect("write index");
(
directory,
ready,
vec![
SignedOperation::sign(&integration, &signer).expect("integration signature"),
SignedOperation::sign(&source_op, &signer).expect("source signature"),
],
vec![ThreadGenesisRecord {
native_genesis_authority: None,
boundary_acceptances: Vec::new(),
ownership_claims: vec![],
ownership_claim_admissions: vec![],
ownership_resolutions: vec![],
ownership_resolution_admissions: vec![],
genesis: Some(source_record),
creator_authority: vec![],
admission: None,
}],
)
}
#[test]
fn source_staging_verifies_foreign_genesis_and_installs_dependency_first() {
let scratch = tempfile::tempdir().expect("scratch");
let (directory, ready, operations, dependencies) = integrated_fixture(scratch.path());
let dependency = operations[1].clone();
let staged = validate(directory, ready, operations, dependencies)
.expect("complete foreign source provenance");
assert_eq!(staged.dependency_geneses().len(), 1);
assert_eq!(staged.operations()[0], dependency);
assert!(
staged.operations()[1]
.verify()
.expect("integration")
.local_integration()
.expect("receipt")
.is_some()
);
}
#[test]
fn source_staging_rejects_missing_or_unrelated_foreign_source_provenance() {
let scratch = tempfile::tempdir().expect("scratch");
let (directory, ready, operations, _) = integrated_fixture(scratch.path());
assert!(
validate(directory, ready, operations, vec![]).is_err(),
"missing creator genesis cannot authorize a dependency"
);
let (directory, ready, mut operations, dependencies) = integrated_fixture(scratch.path());
operations.pop();
assert!(matches!(
validate(directory, ready, operations, dependencies),
Err(Error::Invalid(
"local integration original source proof absent"
))
));
}
fn publication_fixture(
scratch: &Path,
extra: bool,
) -> (
tempfile::TempDir,
PublishContentOpen,
crate::publication::PublicationOriginals,
State,
) {
let (directory, ready, operations, state) = fixture(scratch, extra);
let packs = ["source.pack", "source.idx"]
.into_iter()
.enumerate()
.map(|(index, name)| {
let bytes =
std::fs::read(directory.path().join(name)).expect("actual uploaded artifact");
let address = ObjectAddress {
algorithm: "blake3".into(),
digest: blake3::hash(&bytes).as_bytes().to_vec(),
};
PackExtent {
pack: Some(address.clone()),
offset: 0,
length: bytes.len() as u64,
extent_digest: Some(address),
kind: if index == 0 {
pack_extent::Kind::NativePack
} else {
pack_extent::Kind::NativeIndex
} as i32,
}
})
.collect();
let originals = crate::publication::PublicationOriginals {
geneses: vec![ready.thread_genesis.expect("original genesis")],
operations: vec![ReplicationOperations {
native_authority: None,
boundary_acceptances: Vec::new(),
authority_admissions: vec![],
import_authority: None,
operations: operations
.into_iter()
.map(|signed| {
let publisher = signed.verify().expect("original source").publisher;
SignedRecord {
format: heddle_object_model::object::thread_replication::OPERATION_FORMAT
.into(),
canonical_record: signed.canonical,
signatures: vec![RecordSignature {
public_key: publisher.to_vec(),
signature: signed.signature,
}],
}
})
.collect(),
}],
};
(
directory,
PublishContentOpen {
thread: ready.thread,
revision: ready.current,
packs,
..Default::default()
},
originals,
state,
)
}
#[test]
fn publication_staging_validates_originals_actual_artifacts_and_cleanup_twice() {
let scratch = tempfile::tempdir().expect("scratch");
for _ in 0..2 {
let (directory, opening, originals, state) = publication_fixture(scratch.path(), false);
let path = directory.path().to_owned();
let value = crate::publication::validate_source_artifacts(directory, &opening, originals)
.expect("validated publication");
assert_eq!(value.state().id(), state.id());
assert_eq!(value.operations().len(), 1);
assert_eq!(value.geneses().count(), 1);
assert!(path.exists());
drop(value);
assert!(
!path.exists(),
"completed staged publication owns scratch cleanup"
);
}
}
#[test]
fn publication_staging_rejects_wrong_inventory_and_unsupplied_originals() {
let scratch = tempfile::tempdir().expect("scratch");
let (directory, mut opening, originals, _) = publication_fixture(scratch.path(), false);
let path = directory.path().to_owned();
opening.packs[0].pack.as_mut().expect("address").digest[0] ^= 1;
opening.packs[0].extent_digest = opening.packs[0].pack.clone();
assert!(
matches!(
crate::publication::validate_source_artifacts(directory, &opening, originals),
Err(Error::Invalid("uploaded artifact digest differs"))
),
"actual artifact inventory must be checked"
);
assert!(!path.exists());
let (directory, opening, mut originals, _) = publication_fixture(scratch.path(), false);
originals.operations[0].operations[0].signatures[0].signature[0] ^= 1;
assert!(
crate::publication::validate_source_artifacts(directory, &opening, originals).is_err(),
"original source signatures are necessary"
);
let (directory, opening, originals, _) = publication_fixture(scratch.path(), true);
assert!(
crate::publication::validate_source_artifacts(directory, &opening, originals).is_err(),
"unselected private objects must not be staged as source"
);
}
#[test]
fn publication_staging_preserves_matched_account_admission_without_trusting_issuer() {
use objects::object::{
CollaborationActor,
thread_authority_admission::ThreadAuthorityAdmission,
thread_replication::{AuthoredCapture, SourceAuthor, integration::TrustedHostedExecutor},
};
let scratch = tempfile::tempdir().expect("scratch");
let (directory, opening, mut originals, state) = publication_fixture(scratch.path(), false);
let signer = Ed25519Signer::from_seed(&[61; 32]).expect("source author");
let mut operation = replication::decode_record(originals.operations[0].operations[0].clone())
.expect("source")
.verify()
.expect("signature");
let result = operation.source_result().expect("result").expect("capture");
let spool = uuid::Uuid::parse_str(
&opening
.thread
.as_ref()
.expect("Thread")
.spool
.as_ref()
.expect("Spool")
.id,
)
.expect("Spool UUID");
let actor = CollaborationActor {
principal_id: uuid::Uuid::from_u128(73),
agent_id: Some("original-agent".into()),
};
let capture = AuthoredCapture::account(
result,
spool,
actor.clone(),
b"first-admitted original author envelope".to_vec(),
)
.expect("signed author binding");
let SourceAuthor::Account {
authority_digest, ..
} = capture.author
else {
panic!("account author");
};
operation.body = ThreadOperationBody::Capture(capture);
let signed = SignedOperation::sign(&operation, &signer).expect("original source signature");
originals.operations[0].operations[0] = SignedRecord {
format: objects::object::thread_replication::OPERATION_FORMAT.into(),
canonical_record: signed.canonical.clone(),
signatures: vec![RecordSignature {
public_key: operation.publisher.to_vec(),
signature: signed.signature.clone(),
}],
};
let executor = Ed25519Signer::from_seed(&[74; 32]).expect("receipt issuer");
let statement = ThreadAuthorityAdmission { version: 3,
basis: heddle_object_model::object::original_boundary_acceptance::AdmissionBasis::OriginalAuthority, spool, spool_genesis: ContentHash::from_bytes([75;32]), thread: operation.thread,
subject: objects::object::thread_authority_admission::OriginalAuthoritySubject::Operation(operation.id().expect("operation ID")), actor, publisher: operation.publisher, authority_digest,
executor: executor.public_key().try_into().expect("executor key"), admitted_at_ms: 100 };
let receipt =
crypto::thread_authority_admission::SignedAuthorityAdmission::sign(&statement, &executor)
.expect("historical testimony");
originals.operations[0].authority_admissions =
vec![crate::authority_admission::encode(&receipt).expect("portable receipt")];
let validated = crate::publication::validate_source_artifacts(directory, &opening, originals)
.expect("matched structurally valid source");
assert_eq!(validated.state().id(), state.id());
assert_eq!(
validated
.authority_admissions()
.get(&statement.subject.id()),
Some(&receipt)
);
let wrong_trust = TrustedHostedExecutor {
spool,
spool_genesis: statement.spool_genesis,
executor: [76; 32],
};
assert!(
validated.authority_admissions()[&statement.subject.id()]
.verify(&signed, &wrong_trust)
.is_err(),
"structural staging never enrolls its issuer"
);
}
#[test]
fn source_staging_retains_signed_claim_cutoff_beyond_selected_revision() {
use objects::object::{
CollaborationActor,
thread_replication::{
AuthoredCapture, SourceAuthor, ownership_claim::ThreadOwnershipClaim,
},
};
let scratch = tempfile::tempdir().expect("scratch");
for omit_cutoff in [false, true] {
let (directory, mut ready, mut operations, selected) = fixture(scratch.path(), false);
let genesis = replication::opening::verify_genesis_record(
ready.thread_genesis.as_ref().expect("genesis"),
ready.thread.as_ref().expect("Thread"),
)
.expect("original genesis");
let local = Ed25519Signer::from_seed(&[61; 32]).expect("local owner");
let account = Ed25519Signer::from_seed(&[69; 32]).expect("accepting account key");
let future = State::new_snapshot(
selected.tree,
vec![selected.id()],
Attribution::human(Principal::new("owner", "owner@example.test")),
);
let next = SignedOperation::sign(
&ThreadOperation {
version: 1,
thread: genesis.id().expect("Thread"),
parents: BTreeSet::from([operations[0]
.verify()
.expect("source")
.id()
.expect("source ID")]),
publisher: genesis.creator,
body: ThreadOperationBody::Capture(AuthoredCapture::local(
future
.encode_current_msgpack()
.expect("cutoff source")
.into(),
)),
},
&local,
)
.expect("future source original");
let claim = ThreadOwnershipClaim {
version: 1,
thread: genesis.id().expect("Thread"),
prior_local_key: genesis.creator,
accepting_publisher: account.public_key().try_into().expect("acceptor"),
acceptance: SourceAuthor::account(
genesis.spool.parse().expect("Spool"),
CollaborationActor {
principal_id: uuid::Uuid::from_u128(69),
agent_id: Some("delegate".into()),
},
b"independently verified only on installation".to_vec(),
)
.expect("signed acceptance binding"),
source_frontier: BTreeSet::from([next
.verify()
.expect("future original")
.id()
.expect("cutoff ID")]),
};
let proof =
crypto::thread_ownership_claim::SignedOwnershipClaim::sign(&claim, &local, &account)
.expect("both ownership signatures");
ready
.thread_genesis
.as_mut()
.expect("genesis")
.ownership_claims =
vec![crate::thread_ownership::encode(&proof).expect("portable original")];
if !omit_cutoff {
operations.push(next.clone());
}
let result = validate(directory, ready, operations, vec![]);
if omit_cutoff {
assert!(
result
.err()
.unwrap_or_else(|| panic!("cutoff closure required"))
.to_string()
.contains("ownership claim cutoff source proof absent or foreign"),
"claim must not install with missing cutoff evidence"
);
} else {
let staged = result
.expect("later signed ownership cutoff retained with historical selected source");
assert_eq!(staged.state().id(), selected.id());
assert_eq!(
staged.operations().last(),
Some(&next),
"original cutoff follows causal selected source"
);
}
}
}
#[test]
fn source_staging_binds_signed_entry_privacy_to_selected_salted_closure() {
use objects::object::{
EntryVisibilityEntry, VisibilityTier, thread_replication::CaptureVisibility,
};
let scratch = tempfile::tempdir().expect("scratch");
let signer = Ed25519Signer::from_seed(&[61; 32]).expect("author");
for variant in 0..3 {
let (directory, ready, mut operations, state) = fixture(scratch.path(), false);
let reader = PackReader::open(
&directory.path().join("source.pack"),
&directory.path().join("source.idx"),
)
.expect("pack");
let (_, bytes) = reader
.get_hashed_object(&state.tree)
.expect("read tree")
.expect("tree");
let tree = Tree::decode_canonical(&bytes).expect("salted tree");
let mut entry = EntryVisibilityEntry {
tree_id: tree.hash(),
leaf_hash: tree.v4_leaf_hash_for("main.rs").expect("leaf"),
tier: VisibilityTier::Private {
scope_label: "security".into(),
},
};
if variant == 1 {
entry.tree_id = ContentHash::from_bytes([44; 32]);
}
if variant == 2 {
entry.leaf_hash = ContentHash::from_bytes([55; 32]);
}
let mut original = operations[0].verify().expect("original");
let ThreadOperationBody::Capture(capture) = &mut original.body else {
panic!("source")
};
capture.result.visibility = Some(CaptureVisibility {
state: None,
embargo_until: None,
entries: vec![entry],
});
operations[0] = SignedOperation::sign(&original, &signer).expect("signed declaration");
drop(reader);
let result = validate(directory, ready, operations, vec![]);
if variant == 0 {
let retained = result.expect("exact selected entry admitted");
let operation = retained.operations()[0]
.verify()
.expect("retained original");
assert_eq!(operation, original, "privacy survives actual staging");
} else {
let error = match result {
Ok(_) => panic!("foreign entry binding must fail {variant}"),
Err(error) => error,
};
let expected = if variant == 1 {
"entry visibility tree is outside"
} else {
"entry visibility leaf is absent"
};
assert!(
error.to_string().contains(expected),
"specific binding failure: {error}"
);
}
}
}