use std::collections::BTreeMap;
use std::fs;
use std::path::{Path, PathBuf};
use std::time::SystemTime;
use crate::provider_v3::argv::{Bundle as ArgvBundle, Invocation, PlanRequest};
use crate::provider_v3::bundle::{Bundle, Claim, FILES_PREFIX};
use crate::provider_v3::plan::{EndState, PlanArtifact, PlanInputs};
use crate::provider_v3::{Error, Operation, Result, WireReason};
use crate::setup_core::backup::{BackupRef, Pool, SLOT_SCHEMA, SlotRecord};
use crate::setup_core::journal::{JOURNAL_SCHEMA, Journal, Phase};
use crate::setup_core::stamp::{DriftState, ProviderState, STATE_SCHEMA, StateReading};
use crate::setup_core::target::Target;
use crate::setup_core::{digest, lock};
use crate::harness_runtime::catalog::Setup;
use crate::harness_runtime::expiry;
use crate::harness_runtime::facts::{self, Foreign, Harness};
use crate::harness_runtime::software;
pub fn dispatch(harness: &Harness, invocation: Invocation) -> Result<serde_json::Value> {
match invocation {
Invocation::ProviderInfo => {
let info = harness.provider_info()?;
serde_json::to_value(info).map_err(|source| {
Error::declaration(format!("provider-info cannot be encoded: {source}"))
})
}
Invocation::Status {
target,
target_scope,
} => status(harness, &target, target_scope),
Invocation::ValidateBundle { bundle, .. } => Ok(validate_bundle(harness, &bundle)),
Invocation::PlanOperation { target, request } => plan(harness, &target, &request),
Invocation::ApplyOperation {
target,
plan_path,
plan_digest,
bundle,
prefix,
software_artifacts,
..
} => apply(
harness,
&target,
&plan_path,
&plan_digest,
bundle.as_ref(),
prefix.as_deref(),
&software_artifacts,
),
Invocation::RecoverOperation { target } => recover(harness, &target),
Invocation::Launch {
target,
prefix,
arguments,
} => software::launch(harness, &target, prefix.as_deref(), &arguments),
}
}
fn verified_bundle(harness: &Harness, bundle: &ArgvBundle, surface: Surface) -> Result<Bundle> {
let bytes = fs::read(&bundle.path).map_err(|source| {
Error::refuse(
WireReason::DigestMismatch,
format!(
"cannot read the bundle at {}: {source}",
bundle.path.display()
),
)
})?;
let verified = Bundle::read(
&bytes,
Claim {
bundle_format: &bundle.binding.bundle_format,
bundle_digest: &bundle.binding.bundle_digest,
artifact_digest: &bundle.binding.artifact_digest,
bundle_size: bundle.binding.bundle_size,
harness_id: harness.harness_id,
},
)?;
if verified.manifest.bundle_format == crate::provider_v3::bundle::BUNDLE_FORMAT {
let bound_scope = verified
.manifest
.projection_profile
.as_ref()
.map(|profile| profile.target_scope.as_str())
.ok_or_else(|| {
Error::refuse(
WireReason::AdaptationBindingMissing,
"bundle v2 has no projection_profile",
)
})?;
let bound_scope = match bound_scope {
"global" => None,
value => Some(crate::provider_v3::TargetScope::parse(value).ok_or_else(|| {
Error::refuse(
WireReason::ProjectionProfileMismatch,
format!("bundle v2 names unknown target scope {value:?}"),
)
})?),
};
if let Surface::At(requested) = surface
&& requested != bound_scope
{
return Err(Error::refuse(
WireReason::ProjectionProfileMismatch,
"bundle v2's bound scope differs from the requested target scope",
));
}
let profile = harness.projection_profile_for(bound_scope)?;
verified.require_projection_profile(&profile)?;
}
check_within_surface(harness, verified.files.keys(), surface)?;
check_declared_kinds(harness, &verified, surface)?;
Ok(verified)
}
fn check_declared_kinds(harness: &Harness, bundle: &Bundle, surface: Surface) -> Result<()> {
for entry in &bundle.manifest.conversion_report.entries {
if entry.component_type.is_empty() {
continue;
}
let known = match surface {
Surface::AnyDeclared => harness.implements_anywhere(&entry.component_type),
Surface::At(scope) => harness
.kinds_at(scope)
.iter()
.any(|kind| kind.as_str() == entry.component_type),
};
if !known {
return Err(Error::refuse(
WireReason::UnsupportedComponentKind,
format!(
"the bundle declares component {:?} as kind {:?}, which {} does not implement{}",
entry.stable_id,
entry.component_type,
harness.provider_id,
match surface {
Surface::AnyDeclared => String::new(),
Surface::At(scope) => format!(
" at {}",
scope.map_or("the global profile", crate::provider_v3::TargetScope::as_str)
),
}
),
));
}
}
Ok(())
}
#[derive(Debug, Clone, Copy)]
enum Surface {
AnyDeclared,
At(Option<crate::provider_v3::TargetScope>),
}
fn check_within_surface<'a>(
harness: &Harness,
paths: impl Iterator<Item = &'a String>,
surface: Surface,
) -> Result<()> {
for path in paths {
let owned = match surface {
Surface::AnyDeclared => harness.owns_anywhere(path),
Surface::At(scope) => harness.owns_at(path, scope),
};
if !owned {
return Err(Error::refuse(
WireReason::UnsupportedNativeSurface,
format!(
"the bundle writes {path:?}, which is outside the surface {} owns",
harness.provider_id
),
));
}
}
Ok(())
}
fn open(harness: &Harness, target: &Path) -> Result<(Target, std::path::PathBuf, Pool)> {
let resolved = Target::resolve(target, harness.control_directory)?;
let control = resolved.ensure_control_directory()?;
let pool = Pool::open(&control, facts::BACKUP_SLOTS)?;
Ok((resolved, control, pool))
}
fn observe(harness: &Harness, target: &Path) -> Result<(Target, std::path::PathBuf, Pool)> {
let resolved = Target::resolve(target, harness.control_directory)?;
let control = resolved.control_directory();
let pool = Pool::observe(&control, facts::BACKUP_SLOTS)?;
Ok((resolved, control, pool))
}
fn cleanup_owed(journal: Option<&Journal>) -> &'static str {
match journal.map(|entry| entry.phase) {
Some(Phase::Prepared) => "required",
Some(Phase::Committed) => "pending",
None => "none",
}
}
fn shadowed_here(harness: &Harness, root: &Path) -> Vec<serde_json::Value> {
harness
.shadowing_names
.iter()
.filter(|shadow| root.join(shadow.name).exists())
.map(|shadow| {
serde_json::json!({
"name": shadow.name,
"over": shadow.over,
"effect": shadow.effect,
})
})
.collect()
}
fn status(
harness: &Harness,
target: &Path,
asked: Option<crate::provider_v3::TargetScope>,
) -> Result<serde_json::Value> {
let (resolved, control, pool) = observe(harness, target)?;
let scope = scope_to_measure(harness, &resolved, asked)?;
let owned = owned_here(harness, &resolved, scope)?;
let identity = resolved.identity_of_owned(&as_paths(&owned), &harness.not_our_identity())?;
let journal = Journal::read(&control).ok().flatten();
status_of(harness, &resolved, &pool, &identity, journal)
}
fn scope_to_measure(
harness: &Harness,
resolved: &Target,
asked: Option<crate::provider_v3::TargetScope>,
) -> Result<Option<crate::provider_v3::TargetScope>> {
if let Some(named) = asked
&& harness.scoped_for(Some(named)).is_none()
{
return Err(Error::refuse(
WireReason::UnsupportedOperation,
format!(
"--target-scope {named} names a target this provider publishes no \
projection profile for"
),
));
}
Ok(asked.or_else(|| scope_recorded_at(harness, resolved)))
}
fn status_of(
harness: &Harness,
resolved: &Target,
pool: &Pool,
identity: &str,
journal: Option<Journal>,
) -> Result<serde_json::Value> {
let reading = ProviderState::read(resolved.root(), harness.state_file)?;
let is_empty =
fs::read_dir(resolved.root()).map_or(true, |mut entries| entries.next().is_none());
let state = match &reading {
StateReading::Current(_) => "managed",
_ if is_empty => "missing",
_ => "unmanaged",
};
let mut flat = serde_json::Map::new();
let provider_state = match reading {
StateReading::Absent => serde_json::json!({ "present": false }),
StateReading::ForeignSchema { found_schema } => serde_json::json!({
"present": true,
"readable": false,
"found_schema": found_schema,
"detail": "a schema this build does not write; status never migrates it",
}),
StateReading::Current(current) => {
let drift = if current.target_identity_digest == identity {
DriftState::Clean
} else {
DriftState::LocalDrift
};
if drift == DriftState::Clean {
flat = provenance_of(¤t, drift);
}
serde_json::json!({
"present": true,
"readable": true,
"setup_stable_id": current.setup_stable_id,
"setup_version": current.setup_version,
"operation_id": current.operation_id,
"backup_ref": current.backup_ref,
"recorded_identity": current.target_identity_digest,
"drift_state": drift,
})
}
};
let shadowed = shadowed_here(harness, resolved.root());
let cleanup_state = cleanup_owed(journal.as_ref());
let mut answer = serde_json::Map::new();
answer.extend(flat);
for (key, value) in [
("shadowed_by", serde_json::json!(shadowed)),
("cleanup_state", serde_json::json!(cleanup_state)),
("state", serde_json::json!(state)),
("target_digest", serde_json::json!(identity)),
(
"protocol_version",
serde_json::json!(crate::provider_v3::PROTOCOL_VERSION),
),
("provider_id", serde_json::json!(harness.provider_id)),
("harness_id", serde_json::json!(harness.harness_id)),
(
"canonical_target",
serde_json::json!(resolved.root().to_string_lossy()),
),
("target_identity_digest", serde_json::json!(identity)),
("provider_state", provider_state),
(
"journal",
match journal {
Some(entry) => serde_json::json!({
"phase": entry.phase.as_str(),
"operation": entry.operation,
"operation_id": entry.operation_id,
}),
None => serde_json::Value::Null,
},
),
("backups", {
let held = pool.held()?;
pool.list()?
.iter()
.map(|record| {
let holder = held
.iter()
.find(|(reference, _)| *reference == record.backup_ref);
serde_json::json!({
"backup_ref": record.backup_ref.as_str(),
"operation": record.operation,
"setup_id": record.setup_id,
"held": holder.is_some(),
"hold_reason": holder.map(|(_, reason)| reason.clone()),
})
})
.collect::<Vec<_>>()
.into()
}),
] {
answer.insert(key.to_owned(), value);
}
Ok(serde_json::Value::Object(answer))
}
fn provenance_of(
current: &crate::setup_core::stamp::ProviderState,
drift: DriftState,
) -> serde_json::Map<String, serde_json::Value> {
let mut flat = match serde_json::to_value(current) {
Ok(serde_json::Value::Object(map)) => map,
_ => serde_json::Map::new(),
};
flat.insert("drift_state".to_owned(), serde_json::json!(drift));
flat
}
fn validate_bundle(harness: &Harness, bundle: &ArgvBundle) -> serde_json::Value {
match verified_bundle(harness, bundle, Surface::AnyDeclared) {
Ok(_) => crate::provider_v3::plan::bundle_accepted(&bundle.binding),
Err(error) => {
let reason = error
.reason()
.unwrap_or(WireReason::UnsupportedBundleFormat);
crate::provider_v3::plan::rejected_with_detail(&bundle.binding, reason, Some(error.detail()))
}
}
}
fn honourable(harness: &Harness, request: &PlanRequest) -> Result<()> {
if let Some(named) = request.target_scope
&& harness.scoped_for(Some(named)).is_none()
{
return Err(Error::refuse(
WireReason::UnsupportedOperation,
format!(
"--target-scope {named} names a target this provider publishes no \
projection profile for; it declares {}",
if harness.scoped_projections.is_empty() {
"only the global one".to_owned()
} else {
harness
.scoped_projections
.iter()
.map(|scoped| scoped.target_scope.as_str())
.collect::<Vec<_>>()
.join(", ")
}
),
));
}
if !Operation::SOFTWARE.contains(&request.operation) {
if let Some(named) = request.prefix.as_deref() {
return Err(Error::refuse(
WireReason::UnsupportedOperation,
format!(
"{} configures a target and installs no program, so --prefix {} means \
nothing to it",
request.operation,
named.display()
),
));
}
if let Some(asked) = request.software_version.as_deref() {
return Err(Error::refuse(
WireReason::UnsupportedOperation,
format!(
"{} installs no program, so --software-version {asked:?} means nothing to it",
request.operation
),
));
}
}
if request.bundle.is_some()
&& !matches!(
request.operation,
Operation::Install | Operation::Replace | Operation::Remove
)
{
return Err(Error::refuse(
WireReason::UnsupportedOperation,
format!(
"{} reads no bundle, so one named for it would be echoed into \
the plan and never read; install, replace and remove are the \
operations that take one",
request.operation
),
));
}
if let Some(profile) = request.permission_profile.as_deref()
&& !harness.permission_profiles.contains(&profile)
{
return Err(Error::refuse(
WireReason::UnsupportedPermissionProfile,
format!(
"{profile:?} is not a permission profile {} declares; it offers {:?}",
harness.provider_id, harness.permission_profiles
),
));
}
if expiry::parse_utc_seconds(&request.expires_at).is_none() {
return Err(Error::refuse(
WireReason::Stale,
format!(
"the expiry {:?} is not the exact shape YYYY-MM-DDTHH:MM:SS.mmmZ, \
so no plan made from it could ever be applied",
request.expires_at
),
));
}
Ok(())
}
pub(crate) fn taken_before_writing(
harness: &Harness,
scope: Option<crate::provider_v3::TargetScope>,
) -> Vec<String> {
if harness.scoped_for(scope).is_some() {
return vec![
"only the files this provider recorded writing under this scope go; \
anything else under the same root is left alone"
.to_owned(),
];
}
vec![
format!(
"these entries go whole, not file by file: {}",
harness.native_namespaces.join(", ")
),
"whatever else is in them goes too -- your own keys in a file it names, \
your own files in a directory it names -- and the backup slot holds it"
.to_owned(),
]
}
fn bundle_effects(harness: &Harness, request: &PlanRequest) -> Result<Vec<String>> {
let Some(named) = request.bundle.as_ref() else {
return Err(Error::refuse(
WireReason::UnsupportedBundleFormat,
format!(
"{} arrives as a bundle, and none was named",
request.operation
),
));
};
let verified = verified_bundle(harness, named, Surface::At(request.target_scope))?;
let mut effects = vec!["capture the current target into a new backup slot".to_owned()];
effects.extend(taken_before_writing(harness, request.target_scope));
effects.push(format!(
"write the {} declared files over the entries this provider owns",
verified.files.len()
));
effects.extend(
verified
.files
.keys()
.take(16)
.map(|path| format!("write {path}")),
);
Ok(effects)
}
fn plan(harness: &Harness, target: &Path, request: &PlanRequest) -> Result<serde_json::Value> {
let (resolved, control, pool) = open(harness, target)?;
crate::setup_core::journal::require_clean_for_planning(
&control,
&control.join("transaction"),
&pool.partial_slots()?,
)?;
honourable(harness, request)?;
refuse_another_scopes_record(harness, &resolved, request.target_scope)?;
let owned = owned_here(harness, &resolved, request.target_scope)?;
let identity = resolved.identity_of_owned(&as_paths(&owned), &harness.not_our_identity())?;
let profile = harness.projection_profile_for(request.target_scope)?;
let build_digest = harness.build_digest()?;
if !matches!(
request.operation,
Operation::SoftwareInstall | Operation::SoftwareUpdate | Operation::SoftwareRemove
) {
refuse_a_neighbours_home(harness, &resolved, request.target_scope)?;
refuse_uncapturable(&resolved, &owned)?;
}
let mut software_artifacts = Vec::new();
let mut end_state = Vec::new();
let (effects, backup_ref, restore_target_digest) = match request.operation {
Operation::SoftwareInstall | Operation::SoftwareUpdate | Operation::SoftwareRemove => {
let (planned, effects) = software::plan(
harness,
request.prefix.as_deref(),
request.operation,
request.software_version.as_deref(),
)?;
software_artifacts = planned;
(effects, None, None)
}
Operation::Backup => (
vec![format!(
"capture {} into a new backup slot",
resolved.root().display()
)],
None,
None,
),
Operation::Restore => {
let record = chosen_backup(&pool, request.backup_ref.as_deref())?;
let payload = pool.payload_of(&record.backup_ref)?;
(
vec![
"capture the current target before restoring".to_owned(),
format!("restore the target from {}", record.backup_ref.as_str()),
],
Some(record.backup_ref.as_str().to_owned()),
Some(restore_target_identity(
harness,
&payload,
request.target_scope,
)?),
)
}
Operation::Remove => {
if let Removal::WouldTakeUnrecorded(present) =
classify_removal(harness, &resolved, request.target_scope)?
{
return Err(unrecorded_removal_refusal(harness, &resolved, &present));
}
let (lines, states) = removal_effects(harness, &resolved, request)?;
end_state = states;
(lines, None, None)
}
Operation::Install | Operation::Replace => (bundle_effects(harness, request)?, None, None),
other @ Operation::Launch => {
return Err(Error::refuse(
WireReason::UnsupportedOperation,
format!("{other} is not declared by this provider"),
));
}
};
PlanArtifact::new(PlanInputs {
provider_id: harness.provider_id,
provider_version: harness.version,
provider_build_digest: &build_digest,
provider_release_digest: &request.provider_release_digest,
operation_id: &request.operation_id,
operation: request.operation,
canonical_target: &resolved.root().to_string_lossy(),
expected_target_digest: &identity,
target_scope: request.target_scope,
projection_profile_digest: &profile.digest,
bundle: request.bundle.as_ref().map(|bundle| bundle.binding.clone()),
backup_ref,
restore_target_digest,
permission_profile: request.permission_profile.clone(),
expires_at: &request.expires_at,
software_artifacts,
end_state,
effects,
})?
.into_response()
}
pub(crate) enum Removal {
Recorded,
NothingHere,
WouldTakeUnrecorded(Vec<String>),
}
pub(crate) fn classify_removal(
harness: &Harness,
target: &Target,
scope: Option<crate::provider_v3::TargetScope>,
) -> Result<Removal> {
if harness.scoped_for(scope).is_some() {
return Ok(Removal::Recorded);
}
if matches!(
ProviderState::read(target.root(), harness.state_file)?,
StateReading::Current(_)
) {
return Ok(Removal::Recorded);
}
let present: Vec<String> = harness
.native_namespaces
.iter()
.filter(|namespace| target.root().join(namespace).symlink_metadata().is_ok())
.map(|namespace| (*namespace).to_owned())
.collect();
if present.is_empty() {
return Ok(Removal::NothingHere);
}
Ok(Removal::WouldTakeUnrecorded(present))
}
fn refuse_an_unrecorded_removal(
harness: &Harness,
resolved: &Target,
mutation: &Mutation<'_>,
) -> Result<()> {
if !matches!(
mutation.effect,
Effect::Remove | Effect::RemoveKeeping { .. }
) {
return Ok(());
}
if let Removal::WouldTakeUnrecorded(present) =
classify_removal(harness, resolved, mutation.target_scope)?
{
return Err(unrecorded_removal_refusal(harness, resolved, &present));
}
Ok(())
}
pub(crate) fn unrecorded_removal_refusal(
harness: &Harness,
target: &Target,
present: &[String],
) -> Error {
Error::refuse(
WireReason::UnsupportedOperation,
format!(
"{} has applied no setup at {} -- no state file, or one written before \
this build recorded what it wrote. Removing would take {} whole, and \
nothing here says this provider put them there. Install a setup first \
if you want one removed, or take what you put there yourself.",
harness.provider_id,
target.root().display(),
present.join(", ")
),
)
}
fn refuse_another_scopes_record(
harness: &Harness,
target: &Target,
asked: Option<crate::provider_v3::TargetScope>,
) -> Result<()> {
let Some(recorded) = scope_recorded_at(harness, target) else {
return Ok(());
};
if asked == Some(recorded) {
return Ok(());
}
Err(Error::refuse(
WireReason::UnsupportedOperation,
format!(
"{} is managed under target_scope {}, and this plan names {}; a plan \
under another scope would measure another inventory, so name the \
scope the target is managed under",
target.root().display(),
recorded.as_str(),
asked.map_or("the global profile", crate::provider_v3::TargetScope::as_str)
),
))
}
fn removal_effects(
harness: &Harness,
resolved: &Target,
request: &PlanRequest,
) -> Result<(Vec<String>, Vec<EndState>)> {
let mut lines = vec!["capture the current target before removing".to_owned()];
lines.extend(taken_before_writing(harness, request.target_scope));
let Some(named) = request.bundle.as_ref() else {
return Ok((lines, Vec::new()));
};
let verified = verified_bundle(harness, named, Surface::At(request.target_scope))?;
let states = end_states_of(harness, resolved, request.target_scope, &verified)?;
lines.push(format!(
"leave {} declared files behind at the bytes the bundle carries",
verified.files.len()
));
lines.extend(
verified
.files
.keys()
.take(16)
.map(|path| format!("leave {path}")),
);
Ok((lines, states))
}
fn end_states_of(
harness: &Harness,
target: &Target,
scope: Option<crate::provider_v3::TargetScope>,
verified: &Bundle,
) -> Result<Vec<EndState>> {
let taken: Vec<String> = if harness.scoped_for(scope).is_some() {
match ProviderState::read(target.root(), harness.state_file)? {
StateReading::Current(state) => state.written_paths,
StateReading::Absent | StateReading::ForeignSchema { .. } => Vec::new(),
}
} else {
harness
.native_namespaces
.iter()
.map(|namespace| (*namespace).to_owned())
.collect()
};
let mut entries: Vec<EndState> = taken
.iter()
.filter(|path| !verified.files.contains_key(*path))
.map(|path| EndState::removed(path))
.collect();
for path in verified.files.keys() {
let Some(record) = verified
.manifest
.files
.iter()
.find(|file| &file.path == path)
else {
return Err(Error::refuse(
WireReason::DigestMismatch,
format!("the bundle carries {path:?} and its manifest does not declare it"),
));
};
entries.push(EndState::final_bytes(
path,
&format!("{FILES_PREFIX}{path}"),
&record.digest,
record.byte_length,
));
}
Ok(entries)
}
fn end_states_in(artifact: &serde_json::Value) -> Result<Vec<EndState>> {
match artifact.get("end_state") {
None => Ok(Vec::new()),
Some(value) => serde_json::from_value(value.clone()).map_err(|source| {
Error::refuse(
WireReason::ProviderUnavailable,
format!("the approved plan's end_state member cannot be read: {source}"),
)
}),
}
}
fn removal_effect<'a>(
harness: &Harness,
artifact: &serde_json::Value,
bundle: Option<&ArgvBundle>,
verified: &'a mut Option<Bundle>,
applied: &mut Applied,
) -> Result<Effect<'a>> {
let planned = end_states_in(artifact)?;
if !planned.iter().any(EndState::survives) {
if bundle.is_some() {
return Err(Error::refuse(
WireReason::UnsupportedOperation,
"this remove plan leaves no bytes behind, so a bundle named at apply \
was never authorized; plan the removal with the bundle",
));
}
return Ok(Effect::Remove);
}
let Some(named) = bundle else {
return Err(Error::refuse(
WireReason::UnsupportedBundleFormat,
"this remove was planned with surviving bytes, and no bundle was \
named to carry them",
));
};
let ready = verified.insert(verified_bundle(
harness,
named,
Surface::At(scope_of(artifact)),
)?);
check_survivors(&planned, ready)?;
applied.bundle_format = Some(named.binding.bundle_format.clone());
applied.bundle_digest = Some(named.binding.bundle_digest.clone());
applied.artifact_digest = Some(named.binding.artifact_digest.clone());
Ok(Effect::RemoveKeeping {
files: &ready.files,
})
}
fn check_survivors(planned: &[EndState], ready: &Bundle) -> Result<()> {
for entry in planned.iter().filter(|entry| entry.survives()) {
let record = ready
.manifest
.files
.iter()
.find(|file| file.path == entry.path);
let agrees = record.is_some_and(|file| {
Some(&file.digest) == entry.sha256.as_ref()
&& Some(file.byte_length) == entry.byte_length
&& entry.member.as_deref() == Some(format!("{FILES_PREFIX}{}", file.path).as_str())
});
if !agrees {
return Err(Error::refuse(
WireReason::DigestMismatch,
format!(
"the plan leaves {:?} at bytes the bundle named for apply does not carry; \
no effect was made",
entry.path
),
));
}
}
let planned_survivors = planned.iter().filter(|entry| entry.survives()).count();
if planned_survivors != ready.files.len() {
return Err(Error::refuse(
WireReason::DigestMismatch,
format!(
"the plan leaves {planned_survivors} files behind and the bundle carries {}; \
no effect was made",
ready.files.len()
),
));
}
Ok(())
}
fn restore_target_identity(
harness: &Harness,
payload: &Path,
scope: Option<crate::provider_v3::TargetScope>,
) -> Result<String> {
let owned = if harness.scoped_for(scope).is_some() {
files_in_payload(payload)?
} else {
harness
.owned_projection(scope)
.iter()
.map(|path| (*path).to_owned())
.collect()
};
Ok(crate::setup_core::digest::of_owned(
payload,
&as_paths(&owned),
&harness.not_our_identity(),
)?)
}
fn files_in_payload(payload: &Path) -> Result<Vec<String>> {
let mut found = Vec::new();
for entry in fs::read_dir(payload).map_err(|error| {
crate::setup_core::Error::new(
crate::setup_core::ReasonCode::StateUnavailable,
format!("cannot list backup payload {}", payload.display()),
)
.with_source(error)
})? {
let entry = entry.map_err(|error| {
crate::setup_core::Error::new(
crate::setup_core::ReasonCode::StateUnavailable,
format!(
"cannot read an entry of backup payload {}",
payload.display()
),
)
.with_source(error)
})?;
let Some(name) = entry.file_name().to_str().map(str::to_owned) else {
return Err(Error::from(crate::setup_core::Error::new(
crate::setup_core::ReasonCode::StateUnavailable,
"a backup payload entry has an unrepresentable name",
)));
};
if entry.path().is_dir() {
found.extend(files_under(&entry.path(), &name)?);
} else {
found.push(name);
}
}
found.sort();
Ok(found)
}
pub(crate) fn chosen_backup(pool: &Pool, requested: Option<&str>) -> Result<SlotRecord> {
match requested {
Some(text) => {
let reference = BackupRef::parse(text)?;
pool.list()?
.into_iter()
.find(|record| record.backup_ref == reference)
.ok_or_else(|| {
Error::refuse(
WireReason::ProviderUnavailable,
format!("{text} is not a completed backup of this target"),
)
})
}
None => pool.latest()?.ok_or_else(|| {
Error::refuse(
WireReason::ProviderUnavailable,
"this target has no backup to restore",
)
}),
}
}
pub(crate) enum Effect<'a> {
Backup,
Restore {
backup_ref: Option<String>,
},
Remove,
RemoveKeeping {
files: &'a BTreeMap<String, (Vec<u8>, u32)>,
},
Materialize {
setup: &'a Setup,
},
Adopt {
stamp: std::path::PathBuf,
},
MaterializeBundle {
files: &'a BTreeMap<String, (Vec<u8>, u32)>,
},
}
pub(crate) struct Mutation<'a> {
pub operation: Operation,
pub operation_id: String,
pub plan_digest: String,
pub expected_target_digest: String,
pub target_scope: Option<crate::provider_v3::TargetScope>,
pub effect: Effect<'a>,
pub provenance: serde_json::Value,
pub applied: Applied,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(crate) struct Applied {
pub setup_id: Option<String>,
pub setup_definition_digest: Option<String>,
pub setup_version: Option<String>,
pub written_paths: Vec<String>,
pub bundle_format: Option<String>,
pub bundle_digest: Option<String>,
pub artifact_digest: Option<String>,
pub component_refs: Vec<String>,
}
fn scope_of(artifact: &serde_json::Value) -> Option<crate::provider_v3::TargetScope> {
artifact
.get("target_scope")
.and_then(serde_json::Value::as_str)
.and_then(crate::provider_v3::TargetScope::parse)
}
fn apply(
harness: &Harness,
target: &Path,
plan_path: &Path,
plan_digest: &str,
bundle: Option<&ArgvBundle>,
prefix: Option<&Path>,
downloaded: &[std::path::PathBuf],
) -> Result<serde_json::Value> {
let mut verified: Option<Bundle> = None;
let artifact = load_plan(plan_path, plan_digest)?;
let operation = operation_of(&artifact)?;
let expires_at = string_field(&artifact, "expires_at")?;
match expiry::parse_utc_seconds(&expires_at) {
None => {
return Err(Error::refuse(
WireReason::Stale,
format!(
"the plan's expiry {expires_at:?} is not the exact shape YYYY-MM-DDTHH:MM:SS.mmmZ, so no authorization could be read from it; no effect was made"
),
));
}
Some(_) if expiry::has_expired(&expires_at, SystemTime::now()) => {
return Err(Error::refuse(
WireReason::Stale,
"this plan expired before it was applied; no effect was made",
));
}
Some(_) => {}
}
if Operation::SOFTWARE.contains(&operation) {
return apply_software(harness, prefix, operation, plan_digest, downloaded);
}
if let Some(named) = prefix {
return Err(Error::refuse(
WireReason::UnsupportedOperation,
format!(
"{operation} configures a target and installs no program, so --prefix {} means \
nothing to it",
named.display()
),
));
}
let mut applied = Applied::default();
let effect = match operation {
Operation::Backup => Effect::Backup,
Operation::Restore => Effect::Restore {
backup_ref: artifact
.get("backup_ref")
.and_then(serde_json::Value::as_str)
.map(str::to_owned),
},
Operation::Remove => {
removal_effect(harness, &artifact, bundle, &mut verified, &mut applied)?
}
Operation::Install | Operation::Replace => {
let Some(named) = bundle.as_ref() else {
return Err(Error::refuse(
WireReason::UnsupportedBundleFormat,
format!("{operation} arrives as a bundle, and none was named"),
));
};
verified = Some(verified_bundle(
harness,
named,
Surface::At(scope_of(&artifact)),
)?);
let Some(ready) = verified.as_ref() else {
return Err(Error::refuse(
WireReason::ProviderUnavailable,
"bundle vanished",
));
};
record_bundle_provenance(&mut applied, named, ready);
applied.component_refs = ready
.manifest
.conversion_report
.entries
.iter()
.map(|entry| entry.stable_id.clone())
.filter(|stable_id| !stable_id.is_empty())
.collect();
Effect::MaterializeBundle {
files: &ready.files,
}
}
other => {
return Err(Error::refuse(
WireReason::UnsupportedOperation,
format!("{other} is not declared by this provider"),
));
}
};
perform(
harness,
target,
&Mutation {
operation,
operation_id: string_field(&artifact, "operation_id")?,
plan_digest: plan_digest.to_owned(),
expected_target_digest: string_field(&artifact, "expected_target_digest")?,
target_scope: scope_of(&artifact),
effect,
applied,
provenance: artifact,
},
)
}
pub(crate) fn perform(
harness: &Harness,
target: &Path,
mutation: &Mutation<'_>,
) -> Result<serde_json::Value> {
let (resolved, control, pool) = open(harness, target)?;
let mut guard = crate::setup_core::lock::TargetLock::acquire(&control)?;
guard.annotate(&format!(
"{} {}",
harness.provider_id, mutation.operation_id
))?;
let owned = owned_here(harness, &resolved, mutation.target_scope)?;
let identity = resolved.identity_of_owned(&as_paths(&owned), &harness.not_our_identity())?;
if identity != mutation.expected_target_digest {
return Err(Error::refuse(
WireReason::Stale,
"the target changed after the lock was taken; no effect was made",
));
}
crate::setup_core::journal::require_clean_for_planning(
&control,
&control.join("transaction"),
&pool.partial_slots()?,
)?;
let operation_id = mutation.operation_id.clone();
let operation_name = mutation.operation.as_str().to_owned();
let (previous_setup, previous_definition, previous_written) =
what_the_target_already_says(harness, &resolved)?;
refuse_a_neighbours_home(harness, &resolved, mutation.target_scope)?;
refuse_uncapturable(&resolved, &owned)?;
refuse_an_unrecorded_removal(harness, &resolved, mutation)?;
let captured = pool.capture(resolved.root(), &as_paths(&owned), |backup_ref| {
SlotRecord {
schema_version: SLOT_SCHEMA,
backup_ref,
operation: operation_name.clone(),
operation_id: operation_id.clone(),
target_identity_digest: identity.clone(),
setup_id: previous_setup.clone(),
setup_definition_digest: previous_definition.clone(),
}
})?;
let journal = Journal {
schema_version: JOURNAL_SCHEMA,
phase: Phase::Prepared,
operation_id: mutation.operation_id.clone(),
operation: mutation.operation.as_str().to_owned(),
plan_digest: mutation.plan_digest.clone(),
target_precondition_digest: identity.clone(),
backup_ref: Some(captured.backup_ref.as_str().to_owned()),
target_scope: mutation.target_scope.map(|scope| scope.as_str().to_owned()),
}
.publish_prepared(&control)?;
let mut applied = mutation.applied.clone();
let outcome = match &mutation.effect {
Effect::Backup => Ok(previous_written.clone()),
Effect::Restore { backup_ref } => {
let record = chosen_backup(&pool, backup_ref.as_deref())?;
let payload = pool.payload_of(&record.backup_ref)?;
applied.setup_id.clone_from(&record.setup_id);
applied
.setup_definition_digest
.clone_from(&record.setup_definition_digest);
replace_managed_from(harness, &resolved, &payload, mutation.target_scope)
}
Effect::Remove => {
remove_managed(harness, &resolved, mutation.target_scope).map(|()| vec![])
}
Effect::RemoveKeeping { files } => {
remove_keeping_files(harness, &resolved, mutation.target_scope, files)
}
Effect::Materialize { setup } => {
setup.check_within(harness)?;
replace_managed_from(harness, &resolved, &setup.payload, mutation.target_scope)
}
Effect::MaterializeBundle { files } => {
write_bundle_files(harness, &resolved, files, mutation.target_scope)
}
Effect::Adopt { stamp } => {
crate::harness_runtime::adopt::keep_aside(&control, stamp, harness.predecessor_state_file)
.map(|_| previous_written.clone())
}
};
applied.written_paths = outcome?;
let after_owned = if harness.scoped_for(mutation.target_scope).is_some() {
applied.written_paths.clone()
} else {
owned.clone()
};
let after = resolved.identity_of_owned(&as_paths(&after_owned), &harness.not_our_identity())?;
write_state(
harness, &resolved, mutation, &identity, &after, &captured, &applied,
)?;
journal.promote_to_committed(&control)?;
Journal::clear(&control)?;
Ok(serde_json::json!({
"state": "verified",
"operation": mutation.operation.as_str(),
"plan_digest": mutation.plan_digest,
"expected_target_digest": identity,
"target_identity_digest": after,
"backup_ref": captured.backup_ref.as_str(),
"setup_id": applied.setup_id,
}))
}
fn apply_software(
harness: &Harness,
prefix: Option<&Path>,
operation: Operation,
plan_digest: &str,
downloaded: &[std::path::PathBuf],
) -> Result<serde_json::Value> {
let mut answer = software::apply(harness, prefix, operation, downloaded)?;
if let Some(fields) = answer.as_object_mut() {
fields.insert(
"plan_digest".to_owned(),
serde_json::Value::String(plan_digest.to_owned()),
);
}
Ok(answer)
}
fn what_the_target_already_says(
harness: &Harness,
resolved: &Target,
) -> Result<(Option<String>, Option<String>, Vec<String>)> {
Ok(
match ProviderState::read(resolved.root(), harness.state_file)? {
StateReading::Current(current) => (
current.setup_stable_id,
current.setup_definition_digest,
current.written_paths,
),
_ => (None, None, Vec::new()),
},
)
}
fn record_bundle_provenance(applied: &mut Applied, named: &ArgvBundle, ready: &Bundle) {
applied.bundle_format = Some(named.binding.bundle_format.clone());
applied.bundle_digest = Some(named.binding.bundle_digest.clone());
applied.artifact_digest = Some(named.binding.artifact_digest.clone());
if !ready.passport.stable_id.is_empty() {
applied.setup_id = Some(ready.passport.stable_id.clone());
}
if !ready.passport.version.is_empty() {
applied.setup_version = Some(ready.passport.version.clone());
}
}
fn recover(harness: &Harness, target: &Path) -> Result<serde_json::Value> {
let (resolved, control, pool) = open(harness, target)?;
let _guard = crate::setup_core::lock::TargetLock::acquire(&control)?;
let Some(journal) = Journal::read(&control)? else {
return Ok(serde_json::json!({
"state": "verified",
"recovered": false,
"detail": "no journal is published; there is nothing to resolve",
}));
};
let scope = journal
.target_scope
.as_deref()
.and_then(crate::provider_v3::TargetScope::parse);
let owned = owned_here(harness, &resolved, scope)?;
match journal.phase {
Phase::Prepared => {
let Some(reference) = journal.backup_ref.as_deref() else {
return Err(Error::refuse(
WireReason::RecoveryRequired,
"the journal names no backup, so the pre-operation target cannot be restored",
));
};
let backup_ref = BackupRef::parse(reference)?;
let payload = pool.payload_of(&backup_ref)?;
replace_managed_from(harness, &resolved, &payload, scope)?;
Journal::clear(&control)?;
Ok(serde_json::json!({
"state": "verified",
"recovered": true,
"phase": Phase::Prepared.as_str(),
"restored_from": reference,
"target_identity_digest": resolved.identity_of_owned(&as_paths(&owned), &harness.not_our_identity())?,
}))
}
Phase::Committed => {
Journal::clear(&control)?;
Ok(serde_json::json!({
"state": "verified",
"recovered": true,
"phase": Phase::Committed.as_str(),
"target_identity_digest": resolved.identity_of_owned(&as_paths(&owned), &harness.not_our_identity())?,
}))
}
}
}
fn replace_managed_from(
harness: &Harness,
target: &Target,
payload: &Path,
scope: Option<crate::provider_v3::TargetScope>,
) -> Result<Vec<String>> {
if harness.scoped_for(scope).is_some() {
return replace_recorded_from(harness, target, payload, scope);
}
let mut written = Vec::new();
for namespace in harness.native_namespaces {
let destination = target.root().join(namespace);
let source = payload.join(namespace);
if harness.custody_namespaces.contains(namespace) && !source.exists() {
continue;
}
remove_keeping(&destination, target.root(), harness.never_touch)?;
if !source.exists() {
continue;
}
if source.is_dir() {
crate::setup_core::backup::copy_tree(&source, &destination, &[])?;
written.extend(files_under(&destination, namespace)?);
} else {
let bytes = fs::read(&source).map_err(|error| {
crate::setup_core::Error::new(
crate::setup_core::ReasonCode::StateUnavailable,
format!("cannot read {}", source.display()),
)
.with_source(error)
})?;
lock::atomic_write(&destination, &bytes)?;
written.push((*namespace).to_owned());
}
}
written.sort();
Ok(written)
}
fn replace_recorded_from(
harness: &Harness,
target: &Target,
payload: &Path,
scope: Option<crate::provider_v3::TargetScope>,
) -> Result<Vec<String>> {
for relative in &owned_here(harness, target, scope)? {
remove_path(&target.root().join(relative))?;
}
let mut written = Vec::new();
let entries = fs::read_dir(payload).map_err(|error| {
crate::setup_core::Error::new(
crate::setup_core::ReasonCode::StateUnavailable,
format!("cannot list {}", payload.display()),
)
.with_source(error)
})?;
for entry in entries {
let entry = entry.map_err(|error| {
crate::setup_core::Error::new(
crate::setup_core::ReasonCode::StateUnavailable,
format!("cannot read an entry of {}", payload.display()),
)
.with_source(error)
})?;
let Some(name) = entry.file_name().to_str().map(str::to_owned) else {
return Err(Error::from(crate::setup_core::Error::new(
crate::setup_core::ReasonCode::StateUnavailable,
format!(
"{} has a name this kernel cannot represent",
entry.path().display()
),
)));
};
let source = entry.path();
let destination = target.root().join(&name);
if source.is_dir() {
crate::setup_core::backup::copy_tree(&source, &destination, &[])?;
written.extend(files_under(&source, &name)?);
} else {
let bytes = fs::read(&source).map_err(|error| {
crate::setup_core::Error::new(
crate::setup_core::ReasonCode::StateUnavailable,
format!("cannot read {}", source.display()),
)
.with_source(error)
})?;
if let Some(parent) = destination.parent() {
fs::create_dir_all(parent).map_err(|error| {
crate::setup_core::Error::new(
crate::setup_core::ReasonCode::StateUnavailable,
format!("cannot create {}", parent.display()),
)
.with_source(error)
})?;
}
lock::atomic_write(&destination, &bytes)?;
written.push(name);
}
}
written.sort();
Ok(written)
}
fn files_under(root: &Path, namespace: &str) -> Result<Vec<String>> {
let mut found = Vec::new();
let mut pending = vec![(root.to_path_buf(), namespace.to_owned())];
while let Some((directory, prefix)) = pending.pop() {
let entries = fs::read_dir(&directory).map_err(|error| {
crate::setup_core::Error::new(
crate::setup_core::ReasonCode::StateUnavailable,
format!("cannot read {} back", directory.display()),
)
.with_source(error)
})?;
for entry in entries {
let entry = entry.map_err(|error| {
crate::setup_core::Error::new(
crate::setup_core::ReasonCode::StateUnavailable,
format!("cannot read an entry of {}", directory.display()),
)
.with_source(error)
})?;
let name = entry.file_name().to_string_lossy().into_owned();
let relative = format!("{prefix}/{name}");
if entry.path().is_dir() {
pending.push((entry.path(), relative));
} else {
found.push(relative);
}
}
}
Ok(found)
}
fn write_bundle_files(
harness: &Harness,
target: &Target,
files: &BTreeMap<String, (Vec<u8>, u32)>,
scope: Option<crate::provider_v3::TargetScope>,
) -> Result<Vec<String>> {
if harness.scoped_for(scope).is_some() {
for relative in &owned_here(harness, target, scope)? {
remove_path(&target.root().join(relative))?;
}
} else {
remove_managed(harness, target, None)?;
}
for (relative, (bytes, mode)) in files {
let destination = target.root().join(relative);
lock::atomic_write(&destination, bytes)?;
set_mode(&destination, *mode)?;
}
Ok(files.keys().cloned().collect())
}
#[cfg(unix)]
fn set_mode(path: &Path, mode: u32) -> Result<()> {
use std::os::unix::fs::PermissionsExt;
fs::set_permissions(path, fs::Permissions::from_mode(mode)).map_err(|error| {
Error::from(
crate::setup_core::Error::new(
crate::setup_core::ReasonCode::StateUnavailable,
format!("cannot set the mode of {}", path.display()),
)
.with_source(error),
)
})
}
#[cfg(not(unix))]
fn set_mode(_path: &Path, _mode: u32) -> Result<()> {
Ok(())
}
fn owned_here(
harness: &Harness,
target: &Target,
scope: Option<crate::provider_v3::TargetScope>,
) -> Result<Vec<String>> {
let Some(scoped) = harness.scoped_for(scope) else {
return Ok(harness
.native_namespaces
.iter()
.map(|name| (*name).to_owned())
.collect());
};
match ProviderState::read(target.root(), harness.state_file)? {
StateReading::Current(state) => Ok(state.written_paths),
StateReading::Absent => Ok(Vec::new()),
StateReading::ForeignSchema { .. } => Err(Error::refuse(
WireReason::UnsupportedOperation,
format!(
"an operation under target_scope {} acts on the files this provider \
recorded writing, and {} holds a state file written before this build \
recorded them. Refused rather than widened to {} whole, which under this \
scope is a root several products read. Reinstall to establish a record, \
or point --target at this product's own configuration home.",
scoped.target_scope.as_str(),
target.root().display(),
scoped.native_namespaces.join(", ")
),
)),
}
}
fn as_paths(owned: &[String]) -> Vec<&str> {
owned.iter().map(String::as_str).collect()
}
fn scope_recorded_at(harness: &Harness, target: &Target) -> Option<crate::provider_v3::TargetScope> {
let StateReading::Current(state) =
ProviderState::read(target.root(), harness.state_file).ok()?
else {
return None;
};
harness
.scoped_projections
.iter()
.find(|scoped| {
scoped.native_namespaces.len() == state.native_ownership.len()
&& scoped
.native_namespaces
.iter()
.all(|name| state.native_ownership.iter().any(|owned| owned == name))
})
.map(|scoped| scoped.target_scope)
}
fn remove_managed(
harness: &Harness,
target: &Target,
scope: Option<crate::provider_v3::TargetScope>,
) -> Result<()> {
if let Some(scoped) = harness.scoped_for(scope) {
let recorded = match ProviderState::read(target.root(), harness.state_file)? {
StateReading::Current(state) => state.written_paths,
StateReading::Absent | StateReading::ForeignSchema { .. } => {
return Err(Error::refuse(
WireReason::UnsupportedOperation,
format!(
"remove under target_scope {} is scoped to the files this \
provider recorded writing, and {} holds no readable record of them \
-- no state file, or one written before this build recorded them. \
Refused rather than widened to {} whole, which under this scope is \
a root several products read. Reinstall to establish a record, \
remove the components through the consumer, or point --target at \
this product's own configuration home.",
scoped.target_scope.as_str(),
target.root().display(),
scoped.native_namespaces.join(", ")
),
));
}
};
for relative in &recorded {
remove_path(&target.root().join(relative))?;
}
return Ok(());
}
for namespace in harness.native_namespaces {
remove_path(&target.root().join(namespace))?;
}
Ok(())
}
fn remove_keeping_files(
harness: &Harness,
target: &Target,
scope: Option<crate::provider_v3::TargetScope>,
files: &BTreeMap<String, (Vec<u8>, u32)>,
) -> Result<Vec<String>> {
remove_managed(harness, target, scope)?;
for (relative, (bytes, mode)) in files {
let destination = target.root().join(relative);
lock::atomic_write(&destination, bytes)?;
set_mode(&destination, *mode)?;
}
Ok(Vec::new())
}
fn remove_keeping(path: &Path, root: &Path, spared: &[&str]) -> Result<()> {
let keep: Vec<PathBuf> = spared.iter().map(|name| root.join(name)).collect();
if !keep.iter().any(|held| held.starts_with(path)) {
return remove_path(path);
}
let Ok(metadata) = fs::symlink_metadata(path) else {
return Ok(());
};
if !metadata.is_dir() {
return if keep.iter().any(|held| held == path) {
Ok(())
} else {
remove_path(path)
};
}
let Ok(entries) = fs::read_dir(path) else {
return Ok(());
};
for entry in entries.flatten() {
remove_keeping(&entry.path(), root, spared)?;
}
let _ = fs::remove_dir(path);
Ok(())
}
fn remove_path(path: &Path) -> Result<()> {
let Ok(metadata) = fs::symlink_metadata(path) else {
return Ok(());
};
let outcome = if metadata.is_dir() {
fs::remove_dir_all(path)
} else {
fs::remove_file(path)
};
outcome.map_err(|error| {
Error::from(
crate::setup_core::Error::new(
crate::setup_core::ReasonCode::StateUnavailable,
format!("cannot remove {}", path.display()),
)
.with_source(error),
)
})
}
fn refuse_a_neighbours_home(
harness: &Harness,
resolved: &Target,
scope: Option<crate::provider_v3::TargetScope>,
) -> Result<()> {
if harness.foreign_homes.is_empty() {
return Ok(());
}
if matches!(
ProviderState::read(resolved.root(), harness.state_file)?,
StateReading::Current(_)
) {
return Ok(());
}
if harness
.owned_projection(scope)
.iter()
.any(|name| resolved.root().join(name).exists())
{
return Ok(());
}
let found: Vec<&Foreign> = harness
.foreign_homes
.iter()
.filter(|foreign| resolved.root().join(foreign.marker).exists())
.collect();
let Some(first) = found.first() else {
return Ok(());
};
Err(Error::refuse(
WireReason::UnsupportedNativeSurface,
format!(
"{} holds {} and none of {}'s own files, which is what {}'s configuration \
home looks like. {} keeps its configuration in {}; this program configures \
{} in {}. Nothing has been changed. Name the target you meant.",
resolved.root().display(),
found
.iter()
.map(|foreign| foreign.marker)
.collect::<Vec<_>>()
.join(" and "),
harness.product,
first.product,
first.product,
first.home,
harness.product,
harness.documented_config_home,
),
))
}
fn refuse_uncapturable(resolved: &Target, owned: &[String]) -> Result<()> {
let refused = crate::setup_core::backup::uncapturable(resolved.root(), &as_paths(owned))?;
if refused.is_empty() {
return Ok(());
}
Err(Error::refuse(
WireReason::UnsupportedNativeSurface,
format!(
"a backup captures content, and these owned paths are links rather than \
content: {}. Nothing has been changed. Replace them with what they point \
at, or move them out of the namespaces this provider owns.",
refused.join(", ")
),
))
}
fn write_state(
harness: &Harness,
target: &Target,
mutation: &Mutation<'_>,
before: &str,
after: &str,
captured: &SlotRecord,
applied: &Applied,
) -> Result<()> {
let artifact = &mutation.provenance;
let previous = match ProviderState::read(target.root(), harness.state_file)? {
StateReading::Current(current) => Some(current.target_identity_digest),
_ => None,
};
ProviderState {
state_schema: STATE_SCHEMA,
protocol_version: crate::provider_v3::PROTOCOL_VERSION,
provider_id: harness.provider_id.to_owned(),
provider_version: harness.version.to_owned(),
provider_build_digest: harness.build_digest()?,
provider_release_digest: artifact
.get("provider_release_digest")
.and_then(serde_json::Value::as_str)
.map(str::to_owned),
harness_id: harness.harness_id.to_owned(),
canonical_target: target.root().to_string_lossy().into_owned(),
target_identity_digest: after.to_owned(),
setup_stable_id: applied.setup_id.clone(),
setup_version: applied.setup_version.clone(),
setup_version_passport_digest: None,
setup_definition_digest: applied.setup_definition_digest.clone(),
component_refs: applied.component_refs.clone(),
bundle_format: applied.bundle_format.clone(),
bundle_digest: applied.bundle_digest.clone(),
artifact_digest: applied.artifact_digest.clone(),
projection_profile_digest: Some(
harness
.projection_profile_for(mutation.target_scope)?
.digest,
),
provider_plan_digest: Some(mutation.plan_digest.clone()),
operation_id: string_field(artifact, "operation_id")?,
target_precondition_digest: before.to_owned(),
native_ownership: harness
.owned_projection(mutation.target_scope)
.iter()
.map(|n| (*n).to_owned())
.collect(),
written_paths: applied.written_paths.clone(),
backup_ref: Some(captured.backup_ref.as_str().to_owned()),
previous_verified_identity: previous,
drift_state: DriftState::Clean,
}
.write(target.root(), harness.state_file)
.map_err(Error::from)
}
fn load_plan(path: &Path, expected_digest: &str) -> Result<serde_json::Value> {
let bytes = fs::read(path).map_err(|error| {
Error::refuse(
WireReason::ProviderUnavailable,
format!(
"the approved plan at {} cannot be read: {error}",
path.display()
),
)
})?;
let artifact: serde_json::Value = serde_json::from_slice(&bytes).map_err(|error| {
Error::refuse(
WireReason::ProviderUnavailable,
format!("the approved plan is not JSON: {error}"),
)
})?;
let actual = digest::of_domain_canonical_json(crate::provider_v3::PLAN_DOMAIN, &artifact)?;
if actual != expected_digest {
return Err(Error::refuse(
WireReason::DigestMismatch,
"the approved plan artifact has another digest; no effect was made",
));
}
Ok(artifact)
}
fn operation_of(artifact: &serde_json::Value) -> Result<Operation> {
let name = string_field(artifact, "operation")?;
Operation::parse(&name).ok_or_else(|| {
Error::refuse(
WireReason::UnsupportedOperation,
format!("{name:?} is not an operation this protocol defines"),
)
})
}
fn string_field(artifact: &serde_json::Value, name: &str) -> Result<String> {
artifact
.get(name)
.and_then(serde_json::Value::as_str)
.map(str::to_owned)
.ok_or_else(|| {
Error::refuse(
WireReason::ProviderUnavailable,
format!("the plan artifact has no {name}"),
)
})
}
#[cfg(test)]
pub(crate) mod tests_support {
use crate::provider_v3::{ComponentKind, ProjectionKind};
use crate::harness_runtime::facts::Harness;
pub(crate) const TEST_PAYLOAD: &[u8] = b"#!/bin/sh\nexec echo test-harness 1.2.3\n";
pub(crate) const TEST_EARLIER_PAYLOAD: &[u8] = b"#!/bin/sh\nexec echo test-harness 1.2.2\n";
pub(crate) const TEST_ARTIFACTS: &[crate::setup_core::software::Artifact] = &[
test_artifact("linux/x86_64"),
test_artifact("linux/arm64"),
test_artifact("macos/x86_64"),
test_artifact("macos/arm64"),
test_artifact("windows/x86_64"),
test_artifact("windows/arm64"),
];
const fn test_artifact(platform: &'static str) -> crate::setup_core::software::Artifact {
crate::setup_core::software::Artifact {
platform,
url: "https://example.invalid/test-harness",
bytes: 39,
sha256: "sha256:0c7c47cc1bc9116feb15bd468d039e954093ccfca8d6246b32ea94d1ab2213ad",
shape: crate::setup_core::software::Shape::Raw,
member: "",
}
}
pub(crate) const TEST_PREVIOUS_ARTIFACTS: &[crate::setup_core::software::Artifact] = &[
earlier_artifact("linux/x86_64"),
earlier_artifact("linux/arm64"),
earlier_artifact("macos/x86_64"),
earlier_artifact("macos/arm64"),
earlier_artifact("windows/x86_64"),
earlier_artifact("windows/arm64"),
];
const fn earlier_artifact(platform: &'static str) -> crate::setup_core::software::Artifact {
crate::setup_core::software::Artifact {
platform,
url: "https://example.invalid/test-harness-1.2.2",
bytes: 39,
sha256: "sha256:42c3e0650b099f95955b0ff86c75499848e1343a6c40af6a7acd10f3c18ce226",
shape: crate::setup_core::software::Shape::Raw,
member: "",
}
}
pub(crate) const TEST_SOFTWARE: crate::setup_core::software::Software =
crate::setup_core::software::Software {
version: "1.2.3",
command: "test-harness",
delivery: crate::setup_core::software::Delivery::Artifacts(TEST_ARTIFACTS),
unsupported: &[],
previous: Some(crate::setup_core::software::Previous {
version: "1.2.2",
artifacts: TEST_PREVIOUS_ARTIFACTS,
}),
};
pub(crate) const TEST: Harness = Harness {
launch_binding: crate::harness_runtime::facts::LaunchBinding::Complete { how: "a fixture" },
software: Some(TEST_SOFTWARE),
predecessor_state_file: "NDDEV-TEST-SETUP.json",
embedded_setups: &[],
harness_id: "test",
provider_id: "test-setup-system",
version: "0.1.0",
product: "Test Product",
vendor: "NDDev",
documented_config_home: "~/.test",
config_home_env: "TEST_CONFIG_DIR",
updates_off_env: "",
config_home_note: "",
control_directory: ".test-setup-system",
state_file: "NDDEV-TEST-PROVIDER.json",
profile_id: "test/native-files/1",
native_namespaces: &["AGENTS.md", "settings.json", "skills"],
shadowing_names: &[],
custody_namespaces: &[],
never_touch: &[".credentials.json", "sessions"],
foreign_homes: &[],
permission_profiles: &["default"],
component_kinds: &[
ComponentKind::Instruction,
ComponentKind::Skill,
ComponentKind::Setting,
],
projection_kinds: &[ProjectionKind::NativeFiles],
scoped_projections: &[crate::harness_runtime::facts::Scoped {
target_scope: crate::provider_v3::TargetScope::UserRoot,
profile_id: "test/native-files/user-root/1",
component_kinds: &[ComponentKind::Skill],
projection_kinds: &[ProjectionKind::NativeFiles],
native_namespaces: &["shared"],
}],
max_files: 4096,
max_bytes: 64 * 1024 * 1024,
kit_identity: r#"{"aggregate_digest":"sha256:aa","protocol_version":3}"#,
};
}
#[cfg(test)]
mod tests {
#![allow(
clippy::unwrap_used,
clippy::panic,
clippy::disallowed_types,
reason = "tests drive real executables to check the shipped behaviour"
)]
use std::fs;
use std::path::{Path, PathBuf};
use crate::provider_v3::argv;
use super::*;
use crate::harness_runtime::facts::Shadow;
use crate::harness_runtime::wire::tests_support::{TEST, TEST_EARLIER_PAYLOAD, TEST_PAYLOAD};
const RELEASE: &str = "sha256:3333333333333333333333333333333333333333333333333333333333333333";
fn scratch(name: &str) -> PathBuf {
static NEXT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
let nth = NEXT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let base = std::env::temp_dir().join(format!(
"harness-runtime-{name}-{}-{nth}",
std::process::id()
));
let _ = fs::remove_dir_all(&base);
fs::create_dir_all(base.join("target")).unwrap();
fs::canonicalize(&base).unwrap()
}
fn seeded(name: &str) -> PathBuf {
let target = scratch(name).join("target");
fs::write(target.join("AGENTS.md"), "# first\n").unwrap();
fs::write(target.join("settings.json"), "{\"model\":\"first\"}").unwrap();
fs::create_dir_all(target.join("skills")).unwrap();
fs::write(target.join("skills").join("a.md"), "skill one").unwrap();
fs::write(target.join("unrelated.txt"), "keep me").unwrap();
fs::write(target.join(".credentials.json"), "SECRET").unwrap();
target
}
fn args(command: &str, target: &Path, extra: &[&str]) -> Vec<String> {
let mut tokens = vec![
command.to_owned(),
"--target".to_owned(),
target.to_string_lossy().into_owned(),
"--json".to_owned(),
];
tokens.extend(extra.iter().map(|s| (*s).to_owned()));
tokens
}
fn run(tokens: Vec<String>) -> serde_json::Value {
dispatch(&TEST, argv::parse(tokens).unwrap()).unwrap()
}
fn refuse(tokens: Vec<String>) -> crate::provider_v3::Error {
dispatch(&TEST, argv::parse(tokens).unwrap()).unwrap_err()
}
fn far_future() -> &'static str {
"2099-01-01T00:00:00.000Z"
}
#[test]
fn a_plan_names_the_namespaces_it_takes_whole_before_the_writes() {
let target = seeded("effects-name-what-goes");
assert_eq!(plan_then_apply(&target, "backup", &[])["state"], "verified");
let planned = run(args(
"plan-operation",
&target,
&[
"--operation",
"remove",
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01TEST",
"--expires-at",
far_future(),
],
));
let effects: Vec<String> = planned["plan"]["effects"]
.as_array()
.unwrap()
.iter()
.map(|line| line.as_str().unwrap().to_owned())
.collect();
let text = effects.join("\n");
for namespace in TEST.native_namespaces {
assert!(
text.contains(namespace),
"the plan never names {namespace}: {effects:?}"
);
}
assert!(
text.contains("go whole, not file by file"),
"the plan does not say the namespaces go whole: {effects:?}"
);
assert!(
text.contains("the backup slot holds it"),
"the plan does not say the capture holds what else was there: {effects:?}"
);
assert!(
!text.contains("withdraw every file this provider owns"),
"the false sentence survived: {effects:?}"
);
}
#[test]
fn the_state_a_scoped_operation_writes_names_the_scoped_profile() {
let info = dispatch(&TEST, argv::parse(["provider-info"]).unwrap()).unwrap();
let global = info["projection_profile"]["digest"].as_str().unwrap();
let scoped = info["scoped_projection_profiles"]
.as_array()
.unwrap()
.iter()
.find(|profile| profile["target_scope"] == "user_root")
.unwrap()["digest"]
.as_str()
.unwrap()
.to_owned();
assert_ne!(scoped, global, "the fixture's two profiles are identical");
let target = seeded("scoped-profile-state");
install_scoped(&target, "profile", "one", "# one\n");
let state: serde_json::Value =
serde_json::from_slice(&fs::read(target.join(TEST.state_file)).unwrap()).unwrap();
assert_eq!(
state["projection_profile_digest"], scoped,
"the state after a scoped install named the global profile"
);
}
#[test]
fn a_scoped_plan_names_the_scoped_profile_and_not_the_global_one() {
let info = dispatch(&TEST, argv::parse(["provider-info"]).unwrap()).unwrap();
let global = info["projection_profile"]["digest"].as_str().unwrap();
let scoped = info["scoped_projection_profiles"]
.as_array()
.unwrap()
.iter()
.find(|profile| profile["target_scope"] == "user_root")
.unwrap()["digest"]
.as_str()
.unwrap()
.to_owned();
assert_ne!(
scoped, global,
"the fixture's two profiles are identical, so this test cannot fail"
);
let target = seeded("scoped-profile-digest");
let planned = scoped_plan(&target, "remove", "operation_01SCOPEDPROFILE");
assert_eq!(
planned["plan"]["projection_profile_digest"], scoped,
"a user_root plan named a profile the consumer did not compile against"
);
}
#[test]
fn under_a_scope_the_plan_promises_the_recorded_files_and_not_the_namespaces() {
let target = seeded("effects-scoped");
let planned = run(args(
"plan-operation",
&target,
&[
"--operation",
"remove",
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01TEST",
"--expires-at",
far_future(),
"--target-scope",
"user_root",
],
));
let text = planned["plan"]["effects"].to_string();
assert!(
text.contains("only the files this provider recorded writing"),
"{text}"
);
assert!(
text.contains("left alone"),
"the scoped plan does not say a neighbour is left alone: {text}"
);
assert!(
!text.contains("go whole, not file by file"),
"the global sentence reached a scoped plan: {text}"
);
}
#[test]
fn a_removal_under_a_shared_root_is_refused_rather_than_performed() {
let target = seeded("shared-root-remove");
let planned = run(args(
"plan-operation",
&target,
&[
"--operation",
"remove",
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01TEST",
"--expires-at",
far_future(),
"--target-scope",
"user_root",
],
));
assert_eq!(planned["state"], "planned", "{planned}");
assert_eq!(planned["plan"]["target_scope"], "user_root", "{planned}");
let plan_path = target.join("..").join("plan.json");
fs::write(&plan_path, serde_json::to_vec(&planned["plan"]).unwrap()).unwrap();
let error = refuse(args(
"apply-operation",
&target,
&[
"--plan",
plan_path.to_str().unwrap(),
"--plan-digest",
planned["plan_digest"].as_str().unwrap(),
"--provider-release-digest",
RELEASE,
],
));
assert_eq!(error.reason(), Some(WireReason::UnsupportedOperation));
let said = error.to_string();
for wanted in ["user_root", "no readable record", "shared"] {
assert!(said.contains(wanted), "{said}");
}
assert!(
target.join("AGENTS.md").exists(),
"the refusal removed something anyway"
);
}
#[test]
fn a_removal_under_a_shared_root_takes_only_the_files_this_build_wrote() {
let target = seeded("shared-root-scoped");
fs::create_dir_all(target.join("skills").join("ours")).unwrap();
fs::write(
target.join("skills").join("ours").join("SKILL.md"),
b"ours\n",
)
.unwrap();
let captured = plan_then_apply(&target, "backup", &[]);
assert_eq!(captured["state"], "verified", "{captured}");
let restored = plan_then_apply(&target, "restore", &[]);
assert_eq!(restored["state"], "verified", "{restored}");
let theirs = target.join("skills").join("someone-elses");
fs::create_dir_all(&theirs).unwrap();
fs::write(theirs.join("SKILL.md"), b"another product's skill\n").unwrap();
let planned = run(args(
"plan-operation",
&target,
&[
"--operation",
"remove",
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01SCOPED",
"--expires-at",
far_future(),
"--target-scope",
"user_root",
],
));
assert_eq!(planned["state"], "planned", "{planned}");
let plan_path = target.join("..").join("plan-scoped.json");
fs::write(&plan_path, serde_json::to_vec(&planned["plan"]).unwrap()).unwrap();
let done = run(args(
"apply-operation",
&target,
&[
"--plan",
plan_path.to_str().unwrap(),
"--plan-digest",
planned["plan_digest"].as_str().unwrap(),
"--provider-release-digest",
RELEASE,
],
));
assert_eq!(done["state"], "verified", "{done}");
assert!(
theirs.join("SKILL.md").exists(),
"the removal took a file this build never wrote"
);
assert!(
!target.join("AGENTS.md").exists(),
"the removal left a file this build did write"
);
}
#[test]
fn a_backup_under_a_scope_captures_the_files_this_build_wrote_and_no_others() {
let target = seeded("scoped-capture");
install_scoped(&target, "cap", "ours", "# ours\n");
let theirs = target.join("shared").join("someone-elses");
fs::create_dir_all(&theirs).unwrap();
fs::write(theirs.join("SKILL.md"), b"another product's skill\n").unwrap();
let planned = scoped_plan(&target, "backup", "operation_01SCOPEDCAP");
assert_ne!(
planned["expected_target_digest"].as_str().unwrap(),
EMPTY_TREE,
"a target holding this provider's own files read as empty"
);
let done = scoped_apply(&target, &planned, "cap-backup");
assert_eq!(done["state"], "verified", "{done}");
let payload = target
.join(TEST.control_directory)
.join("backups")
.join(done["backup_ref"].as_str().unwrap())
.join("payload");
assert!(
payload
.join("shared")
.join("ours")
.join("SKILL.md")
.exists(),
"the capture took nothing this provider had written"
);
assert!(
!payload.join("shared").join("someone-elses").exists(),
"the capture took a neighbour's file into this provider's slot"
);
}
#[test]
fn a_restore_under_a_scope_does_not_revert_a_neighbours_file() {
let target = seeded("scoped-restore");
install_scoped(&target, "res", "ours", "# ours\n");
let theirs = target.join("shared").join("someone-elses");
fs::create_dir_all(&theirs).unwrap();
fs::write(theirs.join("SKILL.md"), b"before\n").unwrap();
let planned = scoped_plan(&target, "backup", "operation_01SCOPEDB");
let captured = scoped_apply(&target, &planned, "res-backup");
assert_eq!(captured["state"], "verified", "{captured}");
fs::write(theirs.join("SKILL.md"), b"after\n").unwrap();
fs::write(
target.join("shared").join("ours").join("SKILL.md"),
b"# damaged\n",
)
.unwrap();
let planned = scoped_plan(&target, "restore", "operation_01SCOPEDR");
let promised = planned["plan"]["restore_target_digest"]
.as_str()
.unwrap()
.to_owned();
let done = scoped_apply(&target, &planned, "res-restore");
assert_eq!(done["state"], "verified", "{done}");
assert_eq!(
fs::read_to_string(target.join("shared").join("ours").join("SKILL.md")).unwrap(),
"# ours\n",
"the restore did not return this provider's own file"
);
assert_eq!(
fs::read_to_string(theirs.join("SKILL.md")).unwrap(),
"after\n",
"the restore reverted a file this provider never wrote"
);
let status = run(args("status", &target, &["--target-scope", "user_root"]));
assert_eq!(
status["target_digest"], promised,
"restore produced bytes different from its BackupRef-bound promise"
);
}
#[test]
fn a_backup_leaves_the_inventory_it_found() {
let target = seeded("inventory-survives-backup");
install_scoped(&target, "inv", "ours", "# ours\n");
let before = recorded_written(&target);
assert!(!before.is_empty(), "the install recorded nothing");
let planned = scoped_plan(&target, "backup", "operation_01INVENTORY");
let done = scoped_apply(&target, &planned, "inv-backup");
assert_eq!(done["state"], "verified", "{done}");
assert_eq!(
recorded_written(&target),
before,
"the backup erased the record of what this provider had written"
);
}
#[test]
fn a_scope_this_provider_never_declared_is_refused() {
let target = seeded("undeclared-scope");
let mut harness = TEST;
harness.scoped_projections = &[];
let error = dispatch(
&harness,
argv::parse(args(
"plan-operation",
&target,
&[
"--operation",
"backup",
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01UNDECLARED",
"--expires-at",
far_future(),
"--target-scope",
"user_root",
],
))
.unwrap(),
)
.unwrap_err();
assert_eq!(error.reason(), Some(WireReason::UnsupportedOperation));
let said = error.to_string();
for wanted in ["user_root", "only the global one"] {
assert!(said.contains(wanted), "{said}");
}
}
const EMPTY_TREE: &str =
"sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
fn scoped_plan(target: &Path, operation: &str, operation_id: &str) -> serde_json::Value {
let planned = run(args(
"plan-operation",
target,
&[
"--operation",
operation,
"--provider-release-digest",
RELEASE,
"--operation-id",
operation_id,
"--expires-at",
far_future(),
"--target-scope",
"user_root",
],
));
assert_eq!(planned["state"], "planned", "{planned}");
planned
}
fn scoped_apply(target: &Path, planned: &serde_json::Value, tag: &str) -> serde_json::Value {
let plan_path = target.join("..").join(format!("plan-scoped-{tag}.json"));
fs::write(&plan_path, serde_json::to_vec(&planned["plan"]).unwrap()).unwrap();
run(args(
"apply-operation",
target,
&[
"--plan",
plan_path.to_str().unwrap(),
"--plan-digest",
planned["plan_digest"].as_str().unwrap(),
"--provider-release-digest",
RELEASE,
],
))
}
fn install_scoped(target: &Path, tag: &str, name: &str, body: &str) {
let relative = format!("shared/{name}/SKILL.md");
let (bytes, bundle_digest, artifact) = bundle_bytes_for(
&TEST,
Some(crate::provider_v3::TargetScope::UserRoot),
&[(&relative, body, 0o644)],
Some("skill"),
);
let artifact_path = target.join("..").join(format!("scoped-{tag}.zip"));
fs::write(&artifact_path, &bytes).unwrap();
let flags = bundle_flags(&artifact_path, &bundle_digest, &artifact, bytes.len());
let mut plan_args = vec![
"--operation".to_owned(),
"install".to_owned(),
"--provider-release-digest".to_owned(),
RELEASE.to_owned(),
"--operation-id".to_owned(),
format!("operation_01SCOPEDIN{}", tag.to_uppercase()),
"--expires-at".to_owned(),
far_future().to_owned(),
"--target-scope".to_owned(),
"user_root".to_owned(),
];
plan_args.extend(flags.clone());
let borrowed: Vec<&str> = plan_args.iter().map(String::as_str).collect();
let planned = run(args("plan-operation", target, &borrowed));
assert_eq!(planned["state"], "planned", "{planned}");
let plan_path = target.join("..").join(format!("scoped-{tag}-plan.json"));
fs::write(
&plan_path,
crate::setup_core::canonical::to_canonical_bytes(&planned["plan"]).unwrap(),
)
.unwrap();
let mut apply_args = vec![
"--plan".to_owned(),
plan_path.to_string_lossy().into_owned(),
"--plan-digest".to_owned(),
planned["plan_digest"].as_str().unwrap().to_owned(),
"--provider-release-digest".to_owned(),
RELEASE.to_owned(),
];
apply_args.extend(flags);
let borrowed: Vec<&str> = apply_args.iter().map(String::as_str).collect();
let applied = run(args("apply-operation", target, &borrowed));
assert_eq!(applied["state"], "verified", "{applied}");
}
fn recorded_written(target: &Path) -> Vec<String> {
let bytes = fs::read(target.join(TEST.state_file)).unwrap();
let value: serde_json::Value = serde_json::from_slice(&bytes).unwrap();
value["written_paths"]
.as_array()
.unwrap()
.iter()
.map(|entry| entry.as_str().unwrap().to_owned())
.collect()
}
#[test]
fn a_removal_without_a_scope_still_withdraws_the_namespaces() {
let target = seeded("unscoped-remove");
assert!(target.join("AGENTS.md").exists());
assert_eq!(plan_then_apply(&target, "backup", &[])["state"], "verified");
let done = plan_then_apply(&target, "remove", &[]);
assert_eq!(done["state"], "verified", "{done}");
assert!(
!target.join("AGENTS.md").exists(),
"remove left the instruction file"
);
}
fn plan_then_apply(target: &Path, operation: &str, extra: &[&str]) -> serde_json::Value {
let mut arguments = vec![
"--operation",
operation,
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01TEST",
"--expires-at",
far_future(),
];
arguments.extend_from_slice(extra);
let planned = run(args("plan-operation", target, &arguments));
assert_eq!(planned["state"], "planned", "plan refused: {planned}");
let plan_path = target.join("..").join(format!("plan-{operation}.json"));
fs::write(
&plan_path,
crate::setup_core::canonical::to_canonical_bytes(&planned["plan"]).unwrap(),
)
.unwrap();
run(args(
"apply-operation",
target,
&[
"--plan",
&plan_path.to_string_lossy(),
"--plan-digest",
planned["plan_digest"].as_str().unwrap(),
"--provider-release-digest",
RELEASE,
],
))
}
#[test]
fn provider_info_answers_without_a_target() {
let answer = dispatch(&TEST, argv::parse(["provider-info"]).unwrap()).unwrap();
assert_eq!(answer["provider_id"], TEST.provider_id);
assert_eq!(answer["protocol_version"], 3);
assert!(
answer["projection_profile"]["digest"]
.as_str()
.unwrap()
.starts_with("sha256:")
);
}
#[test]
fn a_component_kind_this_build_does_not_implement_is_refused() {
let target = seeded("kind");
let (bytes, digest, artifact) =
bundle_bytes_declaring(&[("AGENTS.md", "x", 0o644)], Some("quantum-manifest"));
let path = target.join("..").join("kind.zip");
fs::write(&path, &bytes).unwrap();
let flags = bundle_flags(&path, &digest, &artifact, bytes.len());
let answer = run(args(
"validate-bundle",
&target,
&flags.iter().map(String::as_str).collect::<Vec<_>>(),
));
assert_eq!(answer["rejected"], true, "{answer}");
assert_eq!(answer["reason"], "adaptation_binding_mismatch");
let (bytes, digest, artifact) =
bundle_bytes_declaring(&[("AGENTS.md", "x", 0o644)], Some("instruction"));
fs::write(&path, &bytes).unwrap();
let flags = bundle_flags(&path, &digest, &artifact, bytes.len());
let ok = run(args(
"validate-bundle",
&target,
&flags.iter().map(String::as_str).collect::<Vec<_>>(),
));
assert_eq!(ok["valid"], true, "{ok}");
}
#[test]
fn a_nested_namespace_is_owned_all_the_way_down() {
const NESTED: Harness = Harness {
native_namespaces: &[".agents/skills", "AGENTS.md"],
..TEST
};
assert!(NESTED.owns(".agents/skills"));
assert!(NESTED.owns(".agents/skills/review/SKILL.md"));
assert!(NESTED.owns("AGENTS.md"));
}
#[test]
fn a_nested_namespace_does_not_claim_its_parent() {
const NESTED: Harness = Harness {
native_namespaces: &[".agents/skills"],
..TEST
};
assert!(!NESTED.owns(".agents"));
assert!(!NESTED.owns(".agents/hooks.json"));
}
#[test]
fn a_namespace_does_not_swallow_a_neighbour_that_starts_with_it() {
const NEIGHBOURS: Harness = Harness {
native_namespaces: &["skills"],
..TEST
};
assert!(NEIGHBOURS.owns("skills/a/SKILL.md"));
assert!(!NEIGHBOURS.owns("skills-experimental/a/SKILL.md"));
assert!(!NEIGHBOURS.owns("skillsdata"));
}
#[test]
fn status_reports_an_untouched_target_without_changing_it() {
let target = seeded("status");
let before = fs::read_to_string(target.join("AGENTS.md")).unwrap();
let answer = run(args("status", &target, &[]));
assert_eq!(answer["state"], "unmanaged");
assert_eq!(answer["provider_state"]["present"], false);
assert!(answer["journal"].is_null());
assert_eq!(
fs::read_to_string(target.join("AGENTS.md")).unwrap(),
before
);
}
#[test]
fn replacement_spares_a_never_touch_path_inside_a_namespace_it_empties() {
const SPARES: Harness = Harness {
never_touch: &["skills/their-record.json"],
..TEST
};
let target = seeded("spared");
let inside = target.join("skills").join("their-record.json");
fs::write(&inside, b"a person's own file, inside a namespace we own").unwrap();
let beside = target.join("skills").join("nothing-spares-this.md");
fs::write(&beside, b"an ordinary sibling").unwrap();
let payload = scratch("spared-payload").join("target");
fs::create_dir_all(payload.join("skills")).unwrap();
fs::write(payload.join("skills").join("ours.md"), b"ours").unwrap();
let resolved = Target::resolve(&target, TEST.control_directory).unwrap();
replace_managed_from(&TEST, &resolved, &payload, None).unwrap();
assert!(!inside.exists(), "nothing spared it and it survived");
assert!(!beside.exists());
fs::write(&inside, b"a person's own file, inside a namespace we own").unwrap();
fs::write(&beside, b"an ordinary sibling").unwrap();
replace_managed_from(&SPARES, &resolved, &payload, None).unwrap();
assert!(inside.exists(), "the named path was taken anyway");
assert!(!beside.exists(), "a sibling nothing names survived");
assert!(
target.join("skills").join("ours.md").exists(),
"our own payload did not land beside it"
);
}
#[test]
fn status_names_a_file_the_product_reads_and_this_provider_does_not_own() {
const SHADOWED: Harness = Harness {
shadowing_names: &[Shadow {
name: "settings.jsonc",
over: "settings.json",
effect: "the product keeps the later of the two",
}],
..TEST
};
let target = seeded("shadowed");
let quiet = dispatch(
&SHADOWED,
argv::parse(args("status", &target, &[])).unwrap(),
)
.unwrap();
assert_eq!(
quiet["shadowed_by"].as_array().map(Vec::len),
Some(0),
"nothing shadows this target and status named something"
);
fs::write(target.join("settings.jsonc"), "{\"model\":\"theirs\"}").unwrap();
let loud = dispatch(
&SHADOWED,
argv::parse(args("status", &target, &[])).unwrap(),
)
.unwrap();
assert_eq!(loud["shadowed_by"][0]["name"], "settings.jsonc");
assert_eq!(loud["shadowed_by"][0]["over"], "settings.json");
assert_eq!(quiet["target_digest"], loud["target_digest"]);
assert_eq!(quiet["state"], loud["state"]);
}
#[test]
fn a_permission_profile_this_build_never_advertised_is_refused() {
let target = seeded("permission-profile");
let error = refuse(args(
"plan-operation",
&target,
&[
"--operation",
"backup",
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01TEST",
"--expires-at",
far_future(),
"--permission-profile",
"not-declared-anywhere",
],
));
assert_eq!(
error.reason(),
Some(WireReason::UnsupportedPermissionProfile)
);
assert!(
error.detail().contains("not-declared-anywhere"),
"{}",
error.detail()
);
let planned = run(args(
"plan-operation",
&target,
&[
"--operation",
"backup",
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01TEST",
"--expires-at",
far_future(),
"--permission-profile",
"default",
],
));
assert_eq!(planned["state"], "planned");
}
#[test]
fn an_unreadable_expiry_says_so_instead_of_claiming_the_plan_expired() {
let target = seeded("expiry-shape");
let error = refuse(args(
"plan-operation",
&target,
&[
"--operation",
"backup",
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01TEST",
"--expires-at",
"2026-08-23T22:21:39Z",
],
));
assert_eq!(error.reason(), Some(WireReason::Stale));
assert!(
error.detail().contains("not the exact shape"),
"{}",
error.detail()
);
}
#[test]
fn observing_a_target_leaves_nothing_behind() {
let empty = scratch("status-creates-nothing").join("target");
fs::create_dir_all(&empty).unwrap();
let answer = run(args("status", &empty, &[]));
assert_eq!(answer["state"], "missing");
let left = fs::read_dir(&empty).unwrap().count();
assert_eq!(
left, 0,
"status wrote into a target it was only reporting on"
);
}
#[test]
fn status_speaks_the_three_states_the_consumer_reads() {
let empty = scratch("status-missing").join("target");
let answer = run(args("status", &empty, &[]));
assert_eq!(answer["state"], "missing");
assert!(
answer["target_digest"]
.as_str()
.unwrap()
.starts_with("sha256:")
);
let seeded_target = seeded("status-unmanaged");
assert_eq!(
run(args("status", &seeded_target, &[]))["state"],
"unmanaged"
);
let foreign = scratch("status-foreign").join("target");
fs::create_dir_all(foreign.join("someone-elses")).unwrap();
fs::write(foreign.join("someone-elses/notes.md"), "not ours").unwrap();
assert_eq!(run(args("status", &foreign, &[]))["state"], "unmanaged");
plan_then_apply(&seeded_target, "backup", &[]);
assert_eq!(run(args("status", &seeded_target, &[]))["state"], "managed");
}
#[test]
fn a_clean_managed_target_publishes_the_provenance_it_persisted() {
let target = seeded("status-provenance");
plan_then_apply(&target, "backup", &[]);
let status = run(args("status", &target, &[]));
for field in crate::setup_core::stamp::PROVENANCE_FIELDS {
assert!(
status.get(*field).is_some(),
"status omits {field}, which state records and the consumer reads"
);
}
for field in [
"provider_version",
"provider_build_digest",
"projection_profile_digest",
"operation_id",
"target_identity_digest",
] {
assert!(
!status[field].is_null(),
"status publishes {field} as null on a target it calls managed"
);
}
assert_eq!(status["drift_state"], "clean");
assert_eq!(status["provider_id"], "test-setup-system");
}
#[test]
fn a_drifted_target_publishes_no_flat_provenance() {
let target = seeded("status-drifted");
plan_then_apply(&target, "backup", &[]);
assert!(run(args("status", &target, &[]))["provider_build_digest"].is_string());
fs::write(target.join("AGENTS.md"), "someone edited this\n").unwrap();
let status = run(args("status", &target, &[]));
assert_eq!(status["state"], "managed");
assert_eq!(status["provider_state"]["drift_state"], "local_drift");
for field in [
"provider_build_digest",
"provider_plan_digest",
"setup_definition_digest",
"operation_id",
"drift_state",
] {
assert!(
status.get(field).is_none(),
"{field} is published flat for a target that no longer matches it"
);
}
assert!(status["provider_state"]["recorded_identity"].is_string());
}
#[test]
fn an_unmanaged_target_publishes_no_flat_provenance() {
let target = seeded("status-unmanaged-flat");
let status = run(args("status", &target, &[]));
assert_eq!(status["state"], "unmanaged");
assert!(status.get("provider_build_digest").is_none());
assert!(status.get("operation_id").is_none());
assert_eq!(status["provider_state"]["present"], false);
}
#[test]
fn a_neighbours_file_is_not_part_of_this_targets_identity() {
let target = seeded("identity-overlay");
let before = run(args("status", &target, &[]))["target_identity_digest"].clone();
fs::write(target.join("unrelated.txt"), "the neighbour edited this").unwrap();
fs::create_dir_all(target.join("browser-profile/Default/Cache")).unwrap();
fs::write(
target.join("browser-profile/Default/Cache/blob"),
"20 GB, morally",
)
.unwrap();
fs::write(target.join(".credentials.json"), "ROTATED").unwrap();
let after = run(args("status", &target, &[]))["target_identity_digest"].clone();
assert_eq!(
before, after,
"a change outside every declared namespace moved this target's identity"
);
}
#[test]
fn a_change_inside_an_owned_namespace_moves_the_identity() {
let target = seeded("identity-owned");
let before = run(args("status", &target, &[]))["target_identity_digest"].clone();
fs::write(target.join("skills").join("a.md"), "edited").unwrap();
let edited = run(args("status", &target, &[]))["target_identity_digest"].clone();
assert_ne!(
before, edited,
"an edit inside skills left the identity alone"
);
fs::remove_dir_all(target.join("skills")).unwrap();
let removed = run(args("status", &target, &[]))["target_identity_digest"].clone();
assert_ne!(edited, removed, "deleting skills left the identity alone");
fs::create_dir_all(target.join("skills")).unwrap();
let empty = run(args("status", &target, &[]))["target_identity_digest"].clone();
assert_ne!(
removed, empty,
"a deleted namespace and an empty one hash the same"
);
}
#[test]
fn drift_inside_an_owned_namespace_is_still_reported() {
let target = seeded("identity-drift");
plan_then_apply(&target, "backup", &[]);
assert_eq!(
run(args("status", &target, &[]))["provider_state"]["drift_state"],
"clean"
);
fs::write(target.join("unrelated.txt"), "neighbour").unwrap();
assert_eq!(
run(args("status", &target, &[]))["provider_state"]["drift_state"],
"clean",
"a neighbour's write was reported as this provider's drift"
);
fs::write(target.join("AGENTS.md"), "# edited\n").unwrap();
assert_eq!(
run(args("status", &target, &[]))["provider_state"]["drift_state"],
"local_drift"
);
}
#[test]
fn a_zero_byte_setup_version_records_everything_a_populated_one_does() {
let target = seeded("empty-bundle");
let (bytes, bundle_digest, artifact) = bundle_bytes(&[("AGENTS.md", "", 0o644)]);
let artifact_path = target.parent().unwrap().join("empty.zip");
fs::write(&artifact_path, &bytes).unwrap();
let flags = bundle_flags(&artifact_path, &bundle_digest, &artifact, bytes.len());
let mut plan_args = vec![
"--operation".to_owned(),
"install".to_owned(),
"--provider-release-digest".to_owned(),
RELEASE.to_owned(),
"--operation-id".to_owned(),
"operation_01EMPTY".to_owned(),
"--expires-at".to_owned(),
far_future().to_owned(),
];
plan_args.extend(flags.clone());
let borrowed: Vec<&str> = plan_args.iter().map(String::as_str).collect();
let planned = run(args("plan-operation", &target, &borrowed));
assert_eq!(planned["state"], "planned", "{planned}");
let plan_path = target.join("..").join("empty-plan.json");
fs::write(
&plan_path,
crate::setup_core::canonical::to_canonical_bytes(&planned["plan"]).unwrap(),
)
.unwrap();
let mut apply_args = vec![
"--plan".to_owned(),
plan_path.to_string_lossy().into_owned(),
"--plan-digest".to_owned(),
planned["plan_digest"].as_str().unwrap().to_owned(),
"--provider-release-digest".to_owned(),
RELEASE.to_owned(),
];
apply_args.extend(flags);
let borrowed: Vec<&str> = apply_args.iter().map(String::as_str).collect();
let applied = run(args("apply-operation", &target, &borrowed));
assert_eq!(applied["state"], "verified", "{applied}");
let status = run(args("status", &target, &[]));
assert_eq!(status["state"], "managed");
assert_eq!(status["drift_state"], "clean");
for field in [
"bundle_format",
"bundle_digest",
"artifact_digest",
"provider_plan_digest",
"setup_stable_id",
"setup_version",
"projection_profile_digest",
"operation_id",
"provider_build_digest",
] {
assert!(
status[field].is_string(),
"an empty setup left {field} as {}",
status[field]
);
}
assert_eq!(status["bundle_digest"], bundle_digest);
assert_eq!(status["artifact_digest"], artifact);
assert_eq!(status["setup_version"], "3.1.0");
assert_eq!(status["target_digest"], status["target_identity_digest"]);
assert_eq!(
status["target_digest"],
status["provider_state"]["recorded_identity"]
);
assert!(status["backup_ref"].is_string());
assert_eq!(status["component_refs"], serde_json::json!([]));
}
#[test]
fn a_restore_is_exact_after_the_product_writes_its_own_runtime_files() {
let target = seeded("restore-overlay");
let before = run(args("status", &target, &[]))["target_identity_digest"].clone();
plan_then_apply(&target, "backup", &[]);
fs::write(target.join("AGENTS.md"), "# edited\n").unwrap();
assert_ne!(
run(args("status", &target, &[]))["target_identity_digest"],
before
);
fs::create_dir_all(target.join("sessions/2026-08-26")).unwrap();
fs::write(target.join("sessions/2026-08-26/log.jsonl"), "{}\n").unwrap();
fs::create_dir_all(target.join("cache/blobs")).unwrap();
fs::write(target.join("cache/blobs/a"), vec![7_u8; 4096]).unwrap();
fs::write(target.join("unrelated.txt"), "the neighbour moved too").unwrap();
let restored = plan_then_apply(&target, "restore", &[]);
assert_eq!(restored["state"], "verified");
let after = run(args("status", &target, &[]));
assert_eq!(
after["target_identity_digest"], before,
"a restore did not reach the identity the slot recorded, because \
something outside the owned namespaces moved"
);
assert_eq!(after["drift_state"], "clean");
assert!(target.join("sessions/2026-08-26/log.jsonl").is_file());
assert!(target.join("cache/blobs/a").is_file());
assert_eq!(
fs::read_to_string(target.join("unrelated.txt")).unwrap(),
"the neighbour moved too"
);
}
#[test]
#[cfg(unix)]
fn links_inside_an_owned_namespace_are_refused_before_anything_moves() {
let target = seeded("junction-preflight");
let elsewhere = target.parent().unwrap().join("elsewhere");
fs::create_dir_all(&elsewhere).unwrap();
fs::write(elsewhere.join("real.md"), "somewhere else").unwrap();
std::os::unix::fs::symlink(elsewhere.join("real.md"), target.join("skills/one.md"))
.unwrap();
std::os::unix::fs::symlink(&elsewhere, target.join("skills/two")).unwrap();
let error = refuse(args(
"plan-operation",
&target,
&[
"--operation",
"backup",
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01LINK",
"--expires-at",
far_future(),
],
));
assert_eq!(error.reason(), Some(WireReason::UnsupportedNativeSurface));
assert!(
error.detail().contains("skills/one.md"),
"{}",
error.detail()
);
assert!(error.detail().contains("skills/two"), "{}", error.detail());
let control = target.join(TEST.control_directory);
let slots = fs::read_dir(control.join("backups")).map_or(0, Iterator::count);
assert_eq!(slots, 0, "a refused plan left a backup slot behind");
assert!(
!control.join("journal.json").exists(),
"a refused plan left a journal behind"
);
assert_eq!(run(args("status", &target, &[]))["state"], "unmanaged");
fs::remove_file(target.join("skills/one.md")).unwrap();
fs::remove_file(target.join("skills/two")).unwrap();
std::os::unix::fs::symlink(elsewhere.join("real.md"), target.join("their-link")).unwrap();
assert_eq!(plan_then_apply(&target, "backup", &[])["state"], "verified");
}
#[test]
fn a_configuration_edit_strands_a_configuration_plan_and_not_a_program_one() {
let target = seeded("precondition-software");
let file = downloaded(&target, TEST_PAYLOAD);
let planned = software_plan(&target, "software_install");
let plan_path = target.join("..").join("precondition-plan.json");
fs::write(
&plan_path,
crate::setup_core::canonical::to_canonical_bytes(&planned["plan"]).unwrap(),
)
.unwrap();
fs::write(
target.join("AGENTS.md"),
"# edited between plan and apply\n",
)
.unwrap();
let prefix = target.join("..").join("precondition-prefix");
let applied = run(args(
"apply-operation",
&target,
&[
"--plan",
&plan_path.to_string_lossy(),
"--plan-digest",
planned["plan_digest"].as_str().unwrap(),
"--provider-release-digest",
RELEASE,
"--prefix",
&prefix.to_string_lossy(),
"--software-artifact",
&file.to_string_lossy(),
],
));
assert_eq!(
applied["state"], "verified",
"a configuration edit stranded a program install"
);
let other = seeded("precondition-configuration");
let config_plan = run(args(
"plan-operation",
&other,
&[
"--operation",
"backup",
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01PRECOND",
"--expires-at",
far_future(),
],
));
let config_path = other.join("..").join("precondition-config-plan.json");
fs::write(
&config_path,
crate::setup_core::canonical::to_canonical_bytes(&config_plan["plan"]).unwrap(),
)
.unwrap();
fs::write(other.join("AGENTS.md"), "# edited between plan and apply\n").unwrap();
let error = refuse(args(
"apply-operation",
&other,
&[
"--plan",
&config_path.to_string_lossy(),
"--plan-digest",
config_plan["plan_digest"].as_str().unwrap(),
"--provider-release-digest",
RELEASE,
],
));
assert_eq!(
error.reason(),
Some(WireReason::Stale),
"a configuration plan survived the target changing under it: {}",
error.detail()
);
}
struct Unreadable {
#[cfg(windows)]
_handle: fs::File,
#[cfg(unix)]
path: PathBuf,
}
impl Unreadable {
fn of(path: &Path) -> Option<Self> {
#[cfg(windows)]
{
use std::os::windows::fs::OpenOptionsExt;
let handle = fs::OpenOptions::new()
.read(true)
.share_mode(0)
.open(path)
.ok()?;
fs::File::open(path)
.is_err()
.then_some(Self { _handle: handle })
}
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
fs::set_permissions(path, fs::Permissions::from_mode(0o000)).ok()?;
if fs::File::open(path).is_ok() {
let _ = fs::set_permissions(path, fs::Permissions::from_mode(0o644));
return None;
}
Some(Self {
path: path.to_path_buf(),
})
}
}
}
impl Drop for Unreadable {
fn drop(&mut self) {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let _ = fs::set_permissions(&self.path, fs::Permissions::from_mode(0o644));
}
}
}
#[test]
fn a_file_this_process_cannot_read_stops_the_operation_and_leaves_nothing() {
let target = seeded("unreadable");
plan_then_apply(&target, "backup", &[]);
let control = target.join(TEST.control_directory);
let before = fs::read_dir(control.join("backups")).map_or(0, Iterator::count);
let locked = target.join("skills").join("held-open.md");
fs::write(&locked, "a product owns this").unwrap();
let Some(held) = Unreadable::of(&locked) else {
panic!(
"this process can still read a file it made unreadable, so this test \
would prove nothing; it needs to run as a user permissions apply to"
);
};
let error = refuse(args(
"plan-operation",
&target,
&[
"--operation",
"backup",
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01LOCKED",
"--expires-at",
far_future(),
],
));
assert!(
error.detail().contains("held-open.md"),
"the refusal does not name the file it could not read: {}",
error.detail()
);
assert_eq!(
fs::read_dir(control.join("backups")).map_or(0, Iterator::count),
before,
"a refused operation left a backup slot behind"
);
assert!(!control.join("journal.json").exists());
assert!(!control.join("transaction").exists());
drop(held);
assert_eq!(
plan_then_apply(&target, "backup", &[])["state"],
"verified",
"the target did not become usable again once the file could be read"
);
}
fn with_a_neighbour() -> Harness {
let mut harness = TEST;
harness.foreign_homes = &[
crate::harness_runtime::facts::Foreign {
marker: "config.yml",
product: "Oh My Pi",
home: "~/.omp/agent",
},
crate::harness_runtime::facts::Foreign {
marker: "models.yml",
product: "Oh My Pi",
home: "~/.omp/agent",
},
];
harness
}
fn refuse_for(harness: &Harness, tokens: Vec<String>) -> crate::provider_v3::Error {
dispatch(harness, argv::parse(tokens).unwrap()).unwrap_err()
}
fn run_for(harness: &Harness, tokens: Vec<String>) -> serde_json::Value {
dispatch(harness, argv::parse(tokens).unwrap()).unwrap()
}
fn backup_plan(target: &Path) -> Vec<String> {
args(
"plan-operation",
target,
&[
"--operation",
"backup",
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01NEIGHBOUR",
"--expires-at",
far_future(),
],
)
}
#[test]
fn a_target_that_is_a_neighbours_home_is_refused_and_says_whose() {
let harness = with_a_neighbour();
let target = scratch("neighbour-home").join("target");
fs::create_dir_all(&target).unwrap();
fs::write(target.join("config.yml"), "memory:\n backend: off\n").unwrap();
let error = refuse_for(&harness, backup_plan(&target));
assert_eq!(error.reason(), Some(WireReason::UnsupportedNativeSurface));
for expected in [
"config.yml",
"Oh My Pi",
"~/.omp/agent",
"Nothing has been changed",
] {
assert!(
error.detail().contains(expected),
"the refusal does not say {expected:?}: {}",
error.detail()
);
}
let control = target.join(harness.control_directory);
assert_eq!(
fs::read_dir(control.join("backups")).map_or(0, Iterator::count),
0
);
assert!(!control.join("journal.json").exists());
}
#[test]
fn a_target_that_is_ours_is_not_mistaken_for_a_neighbours() {
let harness = with_a_neighbour();
let both = scratch("neighbour-both").join("target");
fs::create_dir_all(&both).unwrap();
fs::write(both.join("config.yml"), "x").unwrap();
fs::write(both.join("settings.json"), "{}").unwrap();
assert_eq!(
run_for(&harness, backup_plan(&both))["state"],
"planned",
"a target holding our own file was refused as a neighbour's"
);
let managed = seeded("neighbour-managed");
plan_then_apply(&managed, "backup", &[]);
fs::write(managed.join("config.yml"), "arrived later").unwrap();
assert_eq!(
run_for(&harness, backup_plan(&managed))["state"],
"planned",
"a target we already manage was refused as a neighbour's"
);
let empty = scratch("neighbour-empty").join("target");
fs::create_dir_all(&empty).unwrap();
assert_eq!(run_for(&harness, backup_plan(&empty))["state"], "planned");
}
#[test]
fn a_harness_with_no_neighbour_refuses_nothing_on_this_ground() {
let target = scratch("neighbour-none").join("target");
fs::create_dir_all(&target).unwrap();
fs::write(target.join("config.yml"), "someone else's file").unwrap();
assert_eq!(run_for(&TEST, backup_plan(&target))["state"], "planned");
}
#[test]
fn two_status_calls_return_the_same_bytes() {
let target = seeded("status-repeatable");
plan_then_apply(&target, "backup", &[]);
let first = run(args("status", &target, &[]));
let second = run(args("status", &target, &[]));
assert_eq!(first, second);
}
#[test]
fn a_backup_captures_the_target_and_leaves_it_alone() {
let target = seeded("backup");
let applied = plan_then_apply(&target, "backup", &[]);
assert_eq!(applied["state"], "verified");
assert_eq!(
applied["expected_target_digest"],
applied["target_identity_digest"]
);
assert_eq!(
fs::read_to_string(target.join("AGENTS.md")).unwrap(),
"# first\n"
);
let status = run(args("status", &target, &[]));
assert_eq!(status["backups"].as_array().unwrap().len(), 1);
assert_eq!(status["provider_state"]["drift_state"], "clean");
}
#[test]
fn a_backup_never_copies_product_owned_credentials() {
let target = seeded("no-secrets");
plan_then_apply(&target, "backup", &[]);
let slot = target
.join(TEST.control_directory)
.join("backups")
.join("slot-000000000001")
.join("payload");
assert!(slot.join("AGENTS.md").exists());
assert!(
!slot.join(".credentials.json").exists(),
"a backup slot captured a secret"
);
}
#[test]
fn restore_returns_the_captured_state_and_keeps_unowned_files() {
let target = seeded("restore");
plan_then_apply(&target, "backup", &[]);
fs::write(target.join("AGENTS.md"), "# second\n").unwrap();
fs::write(target.join("skills").join("b.md"), "skill two").unwrap();
fs::write(target.join("unrelated.txt"), "still mine").unwrap();
let applied = plan_then_apply(&target, "restore", &[]);
assert_eq!(applied["state"], "verified");
assert_eq!(
fs::read_to_string(target.join("AGENTS.md")).unwrap(),
"# first\n"
);
assert!(!target.join("skills").join("b.md").exists());
assert_eq!(
fs::read_to_string(target.join("unrelated.txt")).unwrap(),
"still mine"
);
}
#[test]
fn restore_can_name_an_older_backup_than_the_last_one() {
let target = seeded("restore-chosen");
plan_then_apply(&target, "backup", &[]);
let first = run(args("status", &target, &[]))["backups"][0]["backup_ref"]
.as_str()
.unwrap()
.to_owned();
fs::write(target.join("AGENTS.md"), "# second\n").unwrap();
plan_then_apply(&target, "backup", &[]);
fs::write(target.join("AGENTS.md"), "# third\n").unwrap();
let applied = plan_then_apply(&target, "restore", &["--backup-ref", &first]);
assert_eq!(applied["state"], "verified");
assert_eq!(
fs::read_to_string(target.join("AGENTS.md")).unwrap(),
"# first\n"
);
}
#[test]
fn remove_withdraws_only_what_this_provider_owns() {
let target = seeded("remove");
assert_eq!(plan_then_apply(&target, "backup", &[])["state"], "verified");
assert_eq!(plan_then_apply(&target, "remove", &[])["state"], "verified");
assert!(!target.join("AGENTS.md").exists());
assert!(!target.join("settings.json").exists());
assert!(!target.join("skills").exists());
assert_eq!(
fs::read_to_string(target.join("unrelated.txt")).unwrap(),
"keep me"
);
assert_eq!(
fs::read_to_string(target.join(".credentials.json")).unwrap(),
"SECRET"
);
}
#[test]
fn an_expired_plan_has_no_effect() {
let target = seeded("expired");
let planned = run(args(
"plan-operation",
&target,
&[
"--operation",
"backup",
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01TEST",
"--expires-at",
"2000-01-01T00:00:00.000Z",
],
));
let plan_path = target.join("..").join("expired.json");
fs::write(
&plan_path,
crate::setup_core::canonical::to_canonical_bytes(&planned["plan"]).unwrap(),
)
.unwrap();
let error = refuse(args(
"apply-operation",
&target,
&[
"--plan",
&plan_path.to_string_lossy(),
"--plan-digest",
planned["plan_digest"].as_str().unwrap(),
"--provider-release-digest",
RELEASE,
],
));
assert_eq!(error.reason(), Some(WireReason::Stale));
assert_eq!(
run(args("status", &target, &[]))["backups"]
.as_array()
.unwrap()
.len(),
0
);
}
#[test]
fn a_plan_digest_that_does_not_bind_the_artifact_has_no_effect() {
let target = seeded("wrong-digest");
let planned = run(args(
"plan-operation",
&target,
&[
"--operation",
"backup",
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01TEST",
"--expires-at",
far_future(),
],
));
let plan_path = target.join("..").join("mismatched.json");
fs::write(
&plan_path,
crate::setup_core::canonical::to_canonical_bytes(&planned["plan"]).unwrap(),
)
.unwrap();
let error = refuse(args(
"apply-operation",
&target,
&[
"--plan",
&plan_path.to_string_lossy(),
"--plan-digest",
"sha256:0000000000000000000000000000000000000000000000000000000000000000",
"--provider-release-digest",
RELEASE,
],
));
assert_eq!(error.reason(), Some(WireReason::DigestMismatch));
assert_eq!(
run(args("status", &target, &[]))["backups"]
.as_array()
.unwrap()
.len(),
0
);
}
#[test]
fn planning_is_refused_while_a_journal_is_published() {
let target = seeded("journaled");
let control = target.join(TEST.control_directory);
fs::create_dir_all(&control).unwrap();
Journal {
schema_version: JOURNAL_SCHEMA,
phase: Phase::Prepared,
operation_id: "operation_01STUCK".to_owned(),
operation: "backup".to_owned(),
plan_digest: RELEASE.to_owned(),
target_precondition_digest: RELEASE.to_owned(),
backup_ref: None,
target_scope: None,
}
.publish_prepared(&control)
.unwrap();
let error = refuse(args(
"plan-operation",
&target,
&[
"--operation",
"backup",
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01TEST",
"--expires-at",
far_future(),
],
));
assert_eq!(error.reason(), Some(WireReason::RecoveryRequired));
}
#[test]
fn an_unsettled_operation_is_named_in_the_key_the_consumer_reads() {
fn recovery_fires(answer: &serde_json::Value) -> bool {
answer["state"] == "recovery_required"
|| matches!(
answer["cleanup_state"].as_str(),
Some("pending" | "required" | "in_progress")
)
}
let target = seeded("cleanup-state");
plan_then_apply(&target, "backup", &[]);
let settled = run(args("status", &target, &[]));
assert_eq!(settled["cleanup_state"], "none");
assert!(
!recovery_fires(&settled),
"a settled target asked for recovery"
);
let control = target.join(TEST.control_directory);
let entry = |phase| Journal {
schema_version: JOURNAL_SCHEMA,
phase,
operation_id: "operation_01INTERRUPTED".to_owned(),
operation: "restore".to_owned(),
plan_digest: RELEASE.to_owned(),
target_precondition_digest: RELEASE.to_owned(),
backup_ref: None,
target_scope: None,
};
entry(Phase::Prepared).publish_prepared(&control).unwrap();
let interrupted = run(args("status", &target, &[]));
assert_eq!(interrupted["cleanup_state"], "required");
assert_eq!(
interrupted["state"], "managed",
"state still describes the directory"
);
assert!(recovery_fires(&interrupted));
entry(Phase::Prepared)
.promote_to_committed(&control)
.unwrap();
let tail = run(args("status", &target, &[]));
assert_eq!(tail["cleanup_state"], "pending");
assert!(recovery_fires(&tail));
}
#[test]
fn recovery_from_prepared_returns_the_exact_pre_operation_target() {
let target = seeded("recover");
plan_then_apply(&target, "backup", &[]);
let reference = run(args("status", &target, &[]))["backups"][0]["backup_ref"]
.as_str()
.unwrap()
.to_owned();
let control = target.join(TEST.control_directory);
fs::write(target.join("AGENTS.md"), "# half written\n").unwrap();
Journal {
schema_version: JOURNAL_SCHEMA,
phase: Phase::Prepared,
operation_id: "operation_01INTERRUPTED".to_owned(),
operation: "restore".to_owned(),
plan_digest: RELEASE.to_owned(),
target_precondition_digest: RELEASE.to_owned(),
backup_ref: Some(reference.clone()),
target_scope: None,
}
.publish_prepared(&control)
.unwrap();
let recovered = run(args("recover-operation", &target, &[]));
assert_eq!(recovered["recovered"], true);
assert_eq!(recovered["phase"], "prepared");
assert_eq!(
fs::read_to_string(target.join("AGENTS.md")).unwrap(),
"# first\n"
);
let planned = run(args(
"plan-operation",
&target,
&[
"--operation",
"backup",
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01AFTER",
"--expires-at",
far_future(),
],
));
assert_eq!(planned["state"], "planned");
}
#[test]
fn recovery_with_no_journal_says_so_rather_than_inventing_work() {
let target = seeded("recover-clean");
assert_eq!(
run(args("recover-operation", &target, &[]))["recovered"],
false
);
}
#[test]
fn a_restore_plan_names_the_target_it_will_produce() {
let target = seeded("restore-shape");
plan_then_apply(&target, "backup", &[]);
let planned = run(args(
"plan-operation",
&target,
&[
"--operation",
"restore",
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01TEST",
"--expires-at",
far_future(),
],
));
assert!(
planned["plan"]["restore_target_digest"]
.as_str()
.unwrap()
.starts_with("sha256:")
);
assert!(planned["plan"]["backup_ref"].is_string());
}
fn bundle_bytes(files: &[(&str, &str, u32)]) -> (Vec<u8>, String, String) {
bundle_bytes_declaring(files, None)
}
fn bundle_bytes_declaring(
files: &[(&str, &str, u32)],
kind: Option<&str>,
) -> (Vec<u8>, String, String) {
bundle_bytes_for(&TEST, None, files, kind)
}
#[allow(
clippy::too_many_lines,
reason = "the fixture names every canonical v2 manifest and ZIP member in one place"
)]
fn bundle_bytes_for(
harness: &Harness,
scope: Option<crate::provider_v3::TargetScope>,
files: &[(&str, &str, u32)],
kind: Option<&str>,
) -> (Vec<u8>, String, String) {
use crate::provider_v3::bundle::{BUNDLE_DOMAIN, FILES_PREFIX, MANIFEST_MEMBER, REQUIRED_MEMBERS};
use crate::provider_v3::zip::build::{Entry, write};
let owner = "component_00000000000000000000000000";
let mut member_paths = files.iter().map(|(path, _, _)| *path).collect::<Vec<_>>();
member_paths.sort_unstable();
let profile = harness.projection_profile_for(scope).unwrap();
let records: Vec<serde_json::Value> = files
.iter()
.map(|(path, body, mode)| {
serde_json::json!({
"schema_version": 1,
"path": path,
"digest": crate::setup_core::digest::of_bytes(body.as_bytes()),
"byte_length": body.len(),
"mode": mode,
"owner": owner,
})
})
.collect();
let mut manifest = serde_json::json!({
"schema_version": 1,
"bundle_format": crate::provider_v3::bundle::BUNDLE_FORMAT,
"protocol_version": crate::provider_v3::bundle::BUNDLE_PROTOCOL_VERSION,
"harness_id": harness.harness_id,
"builder_version": "0.1.0",
"input_digest": "sha256:".to_owned() + &"3".repeat(64),
"projection_profile": {
"profile_id": profile.profile_id,
"profile_digest": profile.digest,
"target_scope": scope.map_or("global", crate::provider_v3::TargetScope::as_str),
},
"component_adaptations": [{
"stable_id": owner,
"version": "1.0",
"passport_digest": "sha256:".to_owned() + &"1".repeat(64),
"adaptation_id": "adaptation_".to_owned() + &"2".repeat(64),
"projection_artifact": {
"digest": "sha256:".to_owned() + &"3".repeat(64),
"size_bytes": 128,
},
"provider_component_kind": kind.unwrap_or("instruction"),
"projection_kind": "native_files",
"member_paths": member_paths,
}],
"managed_paths": files.iter().map(|(path, _, _)| *path).collect::<Vec<_>>(),
"files": records,
"limits": {
"max_files": 2000,
"max_file_bytes": 4 * 1024 * 1024,
"max_bundle_bytes": 64 * 1024 * 1024,
},
});
if let Some(scope) = scope {
manifest["target_scope"] = serde_json::json!(scope.as_str());
}
if let Some(kind) = kind {
manifest["conversion_report"] = serde_json::json!({
"complete": true,
"entries": [{
"stable_id": owner,
"component_type": kind,
"native_surface": files.first().map_or("", |(path, _, _)| path),
"state": "complete",
"losses": [],
}],
});
}
let bundle_digest =
crate::setup_core::digest::of_domain_canonical_json(BUNDLE_DOMAIN, &manifest).unwrap();
manifest["bundle_digest"] = serde_json::json!(bundle_digest);
let mut entries = vec![Entry {
name: MANIFEST_MEMBER.to_owned(),
data: crate::setup_core::canonical::to_canonical_bytes(&manifest).unwrap(),
mode: 0o644,
}];
for name in REQUIRED_MEMBERS.iter().skip(1) {
let data = if *name == "setup-passport.json" {
serde_json::to_vec(&serde_json::json!({
"stable_id": "setup_00000000000000000000000000",
"version": "3.1.0",
"harness_id": harness.harness_id,
}))
.unwrap()
} else {
b"{}".to_vec()
};
entries.push(Entry {
name: (*name).to_owned(),
data,
mode: 0o644,
});
}
for (path, body, mode) in files {
entries.push(Entry {
name: format!("{FILES_PREFIX}{path}"),
data: body.as_bytes().to_vec(),
mode: *mode,
});
}
let bytes = write(&entries);
let artifact = crate::setup_core::digest::of_bytes(&bytes);
(bytes, bundle_digest, artifact)
}
fn bundle_flags(path: &Path, bundle_digest: &str, artifact: &str, size: usize) -> Vec<String> {
vec![
"--bundle".to_owned(),
path.to_string_lossy().into_owned(),
"--bundle-format".to_owned(),
crate::provider_v3::bundle::BUNDLE_FORMAT.to_owned(),
"--bundle-digest".to_owned(),
bundle_digest.to_owned(),
"--artifact-digest".to_owned(),
artifact.to_owned(),
"--bundle-size".to_owned(),
size.to_string(),
]
}
#[test]
fn what_a_bundle_states_about_itself_is_recorded_in_provider_state() {
let target = seeded("bundle-components");
let (bytes, bundle_digest, artifact) =
bundle_bytes_declaring(&[("AGENTS.md", "# named\n", 0o644)], Some("instruction"));
let artifact_path = target.join("..").join("components.zip");
fs::write(&artifact_path, &bytes).unwrap();
let flags = bundle_flags(&artifact_path, &bundle_digest, &artifact, bytes.len());
let mut plan_args = vec![
"--operation".to_owned(),
"install".to_owned(),
"--provider-release-digest".to_owned(),
RELEASE.to_owned(),
"--operation-id".to_owned(),
"operation_01COMPONENT".to_owned(),
"--expires-at".to_owned(),
far_future().to_owned(),
];
plan_args.extend(flags.clone());
let borrowed: Vec<&str> = plan_args.iter().map(String::as_str).collect();
let planned = run(args("plan-operation", &target, &borrowed));
let plan_path = target.join("..").join("components-plan.json");
fs::write(
&plan_path,
crate::setup_core::canonical::to_canonical_bytes(&planned["plan"]).unwrap(),
)
.unwrap();
let mut apply_args = vec![
"--plan".to_owned(),
plan_path.to_string_lossy().into_owned(),
"--plan-digest".to_owned(),
planned["plan_digest"].as_str().unwrap().to_owned(),
"--provider-release-digest".to_owned(),
RELEASE.to_owned(),
];
apply_args.extend(flags);
let borrowed: Vec<&str> = apply_args.iter().map(String::as_str).collect();
assert_eq!(
run(args("apply-operation", &target, &borrowed))["state"],
"verified"
);
let state: serde_json::Value =
serde_json::from_str(&fs::read_to_string(target.join(TEST.state_file)).unwrap())
.unwrap();
assert_eq!(
state["component_refs"],
serde_json::json!(["component_00000000000000000000000000"])
);
assert_eq!(state["setup_stable_id"], "setup_00000000000000000000000000");
assert_eq!(state["setup_version"], "3.1.0");
assert!(state["setup_version_passport_digest"].is_null());
assert_eq!(
state["provider_plan_digest"], planned["plan_digest"],
"the state does not name the plan it was authorized by"
);
}
#[test]
fn a_setup_from_the_local_catalog_records_no_components_because_it_has_none() {
let target = seeded("catalog-components");
plan_then_apply(&target, "backup", &[]);
let state: serde_json::Value =
serde_json::from_str(&fs::read_to_string(target.join(TEST.state_file)).unwrap())
.unwrap();
assert_eq!(state["component_refs"], serde_json::json!([]));
}
#[test]
fn validate_bundle_accepts_a_consistent_bundle_and_echoes_what_it_was_given() {
let target = seeded("validate-ok");
let (bytes, bundle_digest, artifact) =
bundle_bytes(&[("AGENTS.md", "# from a bundle\n", 0o644)]);
let path = target.join("..").join("valid.zip");
fs::write(&path, &bytes).unwrap();
let flags = bundle_flags(&path, &bundle_digest, &artifact, bytes.len());
let borrowed: Vec<&str> = flags.iter().map(String::as_str).collect();
let answer = run(args("validate-bundle", &target, &borrowed));
assert_eq!(answer["valid"], true, "{answer}");
assert_eq!(answer["bundle_digest"], bundle_digest.as_str());
assert_eq!(answer["artifact_digest"], artifact.as_str());
}
#[test]
fn validate_bundle_refuses_with_a_reason_and_still_echoes_the_claim() {
let target = seeded("validate-bad");
let (bytes, bundle_digest, _) = bundle_bytes(&[("AGENTS.md", "# from a bundle\n", 0o644)]);
let path = target.join("..").join("wrong.zip");
fs::write(&path, &bytes).unwrap();
let lie = "sha256:0000000000000000000000000000000000000000000000000000000000000000";
let flags = bundle_flags(&path, &bundle_digest, lie, bytes.len());
let borrowed: Vec<&str> = flags.iter().map(String::as_str).collect();
let answer = run(args("validate-bundle", &target, &borrowed));
assert!(answer.get("valid").is_none(), "{answer}");
assert_eq!(answer["rejected"], true, "{answer}");
assert_eq!(answer["reason"], "digest_mismatch", "{answer}");
assert_eq!(
answer["artifact_digest"], lie,
"a refusal echoes the claim it was given, not the one it wished for"
);
assert!(
answer["detail"].as_str().is_some_and(|d| !d.is_empty()),
"a refusal carrying only a code says a bundle was wrong without \
saying which part: {answer}"
);
}
#[test]
fn a_bundle_on_an_operation_that_reads_none_is_refused_not_echoed() {
let target = seeded("bundle-on-backup");
let (bytes, bundle_digest, artifact) = bundle_bytes(&[("AGENTS.md", "x\n", 0o644)]);
let artifact_path = target.join("..").join("bundle-on-backup.zip");
fs::write(&artifact_path, &bytes).unwrap();
let mut plan_args = vec![
"--operation".to_owned(),
"backup".to_owned(),
"--provider-release-digest".to_owned(),
RELEASE.to_owned(),
"--operation-id".to_owned(),
"operation_01NOBUNDLE".to_owned(),
"--expires-at".to_owned(),
far_future().to_owned(),
];
plan_args.extend(bundle_flags(
&artifact_path,
&bundle_digest,
&artifact,
bytes.len(),
));
let borrowed: Vec<&str> = plan_args.iter().map(String::as_str).collect();
let error = refuse(args("plan-operation", &target, &borrowed));
assert_eq!(error.reason(), Some(WireReason::UnsupportedOperation));
assert!(
error.detail().contains("reads no bundle"),
"{}",
error.detail()
);
}
fn remove_keeping_plan(
target: &Path,
tag: &str,
files: &[(&str, &str, u32)],
scope: Option<&str>,
) -> (serde_json::Value, Vec<String>) {
let target_scope = scope.and_then(crate::provider_v3::TargetScope::parse);
let (bytes, bundle_digest, artifact) = bundle_bytes_for(
&TEST,
target_scope,
files,
Some(if target_scope.is_some() {
"skill"
} else {
"setting"
}),
);
let artifact_path = target.join("..").join(format!("keep-{tag}.zip"));
fs::write(&artifact_path, &bytes).unwrap();
let flags = bundle_flags(&artifact_path, &bundle_digest, &artifact, bytes.len());
let mut plan_args = vec![
"--operation".to_owned(),
"remove".to_owned(),
"--provider-release-digest".to_owned(),
RELEASE.to_owned(),
"--operation-id".to_owned(),
format!("operation_01KEEP{}", tag.to_uppercase()),
"--expires-at".to_owned(),
far_future().to_owned(),
];
if let Some(scope) = scope {
plan_args.push("--target-scope".to_owned());
plan_args.push(scope.to_owned());
}
plan_args.extend(flags.clone());
let borrowed: Vec<&str> = plan_args.iter().map(String::as_str).collect();
let planned = run(args("plan-operation", target, &borrowed));
assert_eq!(planned["state"], "planned", "{planned}");
let plan_path = target.join("..").join(format!("keep-{tag}-plan.json"));
fs::write(
&plan_path,
crate::setup_core::canonical::to_canonical_bytes(&planned["plan"]).unwrap(),
)
.unwrap();
let mut apply_args = vec![
"--plan".to_owned(),
plan_path.to_string_lossy().into_owned(),
"--plan-digest".to_owned(),
planned["plan_digest"].as_str().unwrap().to_owned(),
"--provider-release-digest".to_owned(),
RELEASE.to_owned(),
];
apply_args.extend(flags);
(planned, apply_args)
}
#[test]
fn a_remove_may_carry_the_bytes_a_path_keeps_and_leaves_them_behind() {
let target = seeded("remove-keeping");
assert_eq!(plan_then_apply(&target, "backup", &[])["state"], "verified");
let survivor = "{\"model\":\"mine, not the setup's\"}\n";
let (planned, apply_args) = remove_keeping_plan(
&target,
"global",
&[("settings.json", survivor, 0o644)],
None,
);
let states = planned["plan"]["end_state"].as_array().unwrap();
let of = |path: &str| {
states
.iter()
.find(|entry| entry["path"] == path)
.unwrap_or_else(|| panic!("no end state for {path}: {states:?}"))
};
assert_eq!(of("AGENTS.md")["end_state"], "removed");
assert_eq!(of("skills")["end_state"], "removed");
assert_eq!(of("settings.json")["end_state"], "final_bytes");
assert_eq!(of("settings.json")["member"], "files/settings.json");
assert_eq!(
of("settings.json")["sha256"],
crate::setup_core::digest::of_bytes(survivor.as_bytes())
);
assert_eq!(of("settings.json")["byte_length"], survivor.len());
assert_eq!(states.len(), 3, "{states:?}");
assert!(
planned["effects"]
.as_array()
.unwrap()
.iter()
.any(|line| line.as_str().unwrap().contains("leave settings.json")),
"{}",
planned["effects"]
);
let borrowed: Vec<&str> = apply_args.iter().map(String::as_str).collect();
let applied = run(args("apply-operation", &target, &borrowed));
assert_eq!(applied["state"], "verified", "{applied}");
assert_eq!(
fs::read_to_string(target.join("settings.json")).unwrap(),
survivor,
"the surviving file is not at the bytes the bundle carried"
);
assert!(!target.join("AGENTS.md").exists());
assert!(!target.join("skills").exists());
assert_eq!(
fs::read_to_string(target.join("unrelated.txt")).unwrap(),
"keep me"
);
assert_eq!(
fs::read_to_string(target.join(".credentials.json")).unwrap(),
"SECRET"
);
assert!(recorded_written(&target).is_empty());
let state: serde_json::Value =
serde_json::from_slice(&fs::read(target.join(TEST.state_file)).unwrap()).unwrap();
assert_eq!(state["bundle_digest"], planned["bundle_digest"]);
assert!(state["setup_stable_id"].is_null(), "{state}");
let after = run(args("status", &target, &[]));
assert_eq!(after["state"], "managed", "{after}");
assert_eq!(after["drift_state"], "clean", "{after}");
}
#[test]
fn a_remove_without_a_bundle_carries_no_end_state_member() {
let target = seeded("remove-bare-plan");
assert_eq!(plan_then_apply(&target, "backup", &[])["state"], "verified");
let planned = run(args(
"plan-operation",
&target,
&[
"--operation",
"remove",
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01BARE",
"--expires-at",
far_future(),
],
));
assert_eq!(planned["state"], "planned", "{planned}");
assert!(
planned["plan"].get("end_state").is_none(),
"{}",
planned["plan"]
);
}
#[test]
fn a_remove_apply_takes_exactly_the_bundle_its_plan_described() {
let target = seeded("remove-authorization");
assert_eq!(plan_then_apply(&target, "backup", &[])["state"], "verified");
let (bytes, bundle_digest, artifact) =
bundle_bytes(&[("settings.json", "{\"kept\":true}\n", 0o644)]);
let artifact_path = target.join("..").join("unplanned.zip");
fs::write(&artifact_path, &bytes).unwrap();
let flags = bundle_flags(&artifact_path, &bundle_digest, &artifact, bytes.len());
let planned = run(args(
"plan-operation",
&target,
&[
"--operation",
"remove",
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01UNPLANNED",
"--expires-at",
far_future(),
],
));
let plan_path = target.join("..").join("bare-plan.json");
fs::write(
&plan_path,
crate::setup_core::canonical::to_canonical_bytes(&planned["plan"]).unwrap(),
)
.unwrap();
let mut apply_args = vec![
"--plan".to_owned(),
plan_path.to_string_lossy().into_owned(),
"--plan-digest".to_owned(),
planned["plan_digest"].as_str().unwrap().to_owned(),
"--provider-release-digest".to_owned(),
RELEASE.to_owned(),
];
apply_args.extend(flags);
let borrowed: Vec<&str> = apply_args.iter().map(String::as_str).collect();
let error = refuse(args("apply-operation", &target, &borrowed));
assert_eq!(error.reason(), Some(WireReason::UnsupportedOperation));
assert!(
error.detail().contains("never authorized"),
"{}",
error.detail()
);
assert!(
target.join("AGENTS.md").exists(),
"a refusal made an effect"
);
let (_, with_bundle) =
remove_keeping_plan(&target, "unfed", &[("settings.json", "{}\n", 0o644)], None);
let bare: Vec<&str> = with_bundle.iter().take(6).map(String::as_str).collect();
let error = refuse(args("apply-operation", &target, &bare));
assert_eq!(error.reason(), Some(WireReason::UnsupportedBundleFormat));
assert!(
target.join("AGENTS.md").exists(),
"a refusal made an effect"
);
let (other_bytes, other_digest, other_artifact) =
bundle_bytes(&[("settings.json", "{\"other\":1}\n", 0o644)]);
let other_path = target.join("..").join("other.zip");
fs::write(&other_path, &other_bytes).unwrap();
let mut swapped: Vec<String> = with_bundle.iter().take(6).cloned().collect();
swapped.extend(bundle_flags(
&other_path,
&other_digest,
&other_artifact,
other_bytes.len(),
));
let borrowed: Vec<&str> = swapped.iter().map(String::as_str).collect();
let error = refuse(args("apply-operation", &target, &borrowed));
assert_eq!(error.reason(), Some(WireReason::DigestMismatch));
assert!(
error.detail().contains("does not carry"),
"{}",
error.detail()
);
assert!(
target.join("AGENTS.md").exists(),
"a refusal made an effect"
);
}
#[test]
fn status_and_a_scoped_plan_agree_on_the_identity_after_a_scoped_install() {
let target = seeded("scoped-status-agrees");
install_scoped(&target, "agree", "ours", "ours\n");
let observed = run(args("status", &target, &[]));
let planned = scoped_plan(&target, "remove", "operation_01AGREE");
assert_eq!(
planned["plan"]["expected_target_digest"], observed["target_digest"],
"status {observed}\nplan {planned}"
);
}
#[test]
fn status_and_a_project_plan_agree_on_the_identity_at_a_workspace() {
let harness = project_shaped();
let workspace = scratch("project-status-agrees").join("workspace");
fs::create_dir_all(workspace.join("src")).unwrap();
fs::write(workspace.join("src").join("main.rs"), "fn main() {}\n").unwrap();
fs::write(workspace.join("README.md"), "# theirs\n").unwrap();
let before = run_for(&harness, args("status", &workspace, &[]));
let (bytes, bundle_digest, artifact) = bundle_bytes_for(
&harness,
Some(crate::provider_v3::TargetScope::Project),
&[(".cursor/skills/probe/SKILL.md", "probe\n", 0o644)],
Some("skill"),
);
let artifact_path = workspace.join("..").join("project.zip");
fs::write(&artifact_path, &bytes).unwrap();
let flags = bundle_flags(&artifact_path, &bundle_digest, &artifact, bytes.len());
let mut plan_args = vec![
"--operation".to_owned(),
"install".to_owned(),
"--provider-release-digest".to_owned(),
RELEASE.to_owned(),
"--operation-id".to_owned(),
"operation_01PROJECTIN".to_owned(),
"--expires-at".to_owned(),
far_future().to_owned(),
"--target-scope".to_owned(),
"project".to_owned(),
];
plan_args.extend(flags.clone());
let borrowed: Vec<&str> = plan_args.iter().map(String::as_str).collect();
let planned = run_for(&harness, args("plan-operation", &workspace, &borrowed));
assert_eq!(planned["state"], "planned", "{planned}");
assert_eq!(
planned["plan"]["expected_target_digest"], before["target_digest"],
"install: status {before}\nplan {planned}"
);
let plan_path = workspace.join("..").join("project-plan.json");
fs::write(
&plan_path,
crate::setup_core::canonical::to_canonical_bytes(&planned["plan"]).unwrap(),
)
.unwrap();
let mut apply_args = vec![
"--plan".to_owned(),
plan_path.to_string_lossy().into_owned(),
"--plan-digest".to_owned(),
planned["plan_digest"].as_str().unwrap().to_owned(),
"--provider-release-digest".to_owned(),
RELEASE.to_owned(),
];
apply_args.extend(flags);
let borrowed: Vec<&str> = apply_args.iter().map(String::as_str).collect();
let applied = run_for(&harness, args("apply-operation", &workspace, &borrowed));
assert_eq!(applied["state"], "verified", "{applied}");
assert!(workspace.join(".cursor/skills/probe/SKILL.md").exists());
let after = run_for(&harness, args("status", &workspace, &[]));
let removal = run_for(
&harness,
args(
"plan-operation",
&workspace,
&[
"--operation",
"remove",
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01PROJECTRM",
"--expires-at",
far_future(),
"--target-scope",
"project",
],
),
);
assert_eq!(removal["state"], "planned", "{removal}");
assert_eq!(
removal["plan"]["expected_target_digest"], after["target_digest"],
"remove: status {after}\nplan {removal}"
);
}
#[test]
fn a_status_asked_about_a_scope_measures_that_scopes_inventory_before_any_record() {
let harness = project_shaped();
let workspace = scratch("project-status-asked").join("workspace");
fs::create_dir_all(workspace.join("skills")).unwrap();
fs::write(
workspace.join("skills/theirs.md"),
"# the repository's own\n",
)
.unwrap();
fs::write(workspace.join("README.md"), "# theirs\n").unwrap();
let unasked = run_for(&harness, args("status", &workspace, &[]));
let asked = run_for(
&harness,
args("status", &workspace, &["--target-scope", "project"]),
);
assert_ne!(
unasked["target_digest"], asked["target_digest"],
"the global set hashes the repository's skills/; the project set has no record and nothing of ours"
);
let (bytes, bundle_digest, artifact) = bundle_bytes_for(
&harness,
Some(crate::provider_v3::TargetScope::Project),
&[(".cursor/skills/probe/SKILL.md", "probe\n", 0o644)],
Some("skill"),
);
let artifact_path = workspace.join("..").join("asked.zip");
fs::write(&artifact_path, &bytes).unwrap();
let mut plan_args = vec![
"--operation".to_owned(),
"install".to_owned(),
"--provider-release-digest".to_owned(),
RELEASE.to_owned(),
"--operation-id".to_owned(),
"operation_01ASKED".to_owned(),
"--expires-at".to_owned(),
far_future().to_owned(),
"--target-scope".to_owned(),
"project".to_owned(),
];
plan_args.extend(bundle_flags(
&artifact_path,
&bundle_digest,
&artifact,
bytes.len(),
));
let borrowed: Vec<&str> = plan_args.iter().map(String::as_str).collect();
let planned = run_for(&harness, args("plan-operation", &workspace, &borrowed));
assert_eq!(planned["state"], "planned", "{planned}");
assert_eq!(
planned["plan"]["expected_target_digest"],
asked["target_digest"]
);
assert_ne!(
planned["plan"]["expected_target_digest"],
unasked["target_digest"]
);
let error = refuse_for(
&harness,
args("status", &workspace, &["--target-scope", "user_root"]),
);
assert_eq!(error.reason(), Some(WireReason::UnsupportedOperation));
}
#[test]
fn a_plan_whose_scope_contradicts_the_record_is_refused_by_name() {
let harness = project_shaped();
let workspace = scratch("project-scope-contradiction").join("workspace");
fs::create_dir_all(&workspace).unwrap();
let (bytes, bundle_digest, artifact) = bundle_bytes_for(
&harness,
Some(crate::provider_v3::TargetScope::Project),
&[(".cursor/skills/probe/SKILL.md", "probe\n", 0o644)],
Some("skill"),
);
let artifact_path = workspace.join("..").join("contra.zip");
fs::write(&artifact_path, &bytes).unwrap();
let flags = bundle_flags(&artifact_path, &bundle_digest, &artifact, bytes.len());
let mut plan_args = vec![
"--operation".to_owned(),
"install".to_owned(),
"--provider-release-digest".to_owned(),
RELEASE.to_owned(),
"--operation-id".to_owned(),
"operation_01CONTRAIN".to_owned(),
"--expires-at".to_owned(),
far_future().to_owned(),
"--target-scope".to_owned(),
"project".to_owned(),
];
plan_args.extend(flags.clone());
let borrowed: Vec<&str> = plan_args.iter().map(String::as_str).collect();
let planned = run_for(&harness, args("plan-operation", &workspace, &borrowed));
let plan_path = workspace.join("..").join("contra-plan.json");
fs::write(
&plan_path,
crate::setup_core::canonical::to_canonical_bytes(&planned["plan"]).unwrap(),
)
.unwrap();
let mut apply_args = vec![
"--plan".to_owned(),
plan_path.to_string_lossy().into_owned(),
"--plan-digest".to_owned(),
planned["plan_digest"].as_str().unwrap().to_owned(),
"--provider-release-digest".to_owned(),
RELEASE.to_owned(),
];
apply_args.extend(flags);
let borrowed: Vec<&str> = apply_args.iter().map(String::as_str).collect();
assert_eq!(
run_for(&harness, args("apply-operation", &workspace, &borrowed))["state"],
"verified"
);
let error = refuse_for(
&harness,
args(
"plan-operation",
&workspace,
&[
"--operation",
"remove",
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01CONTRARM",
"--expires-at",
far_future(),
],
),
);
assert_eq!(error.reason(), Some(WireReason::UnsupportedOperation));
assert!(
error
.detail()
.contains("managed under target_scope project")
&& error.detail().contains("names the global profile"),
"{}",
error.detail()
);
assert!(
workspace.join(".cursor/skills/probe/SKILL.md").exists(),
"a refusal made an effect"
);
}
#[test]
fn a_kind_declared_only_by_a_scope_validates_and_plans_under_that_scope() {
let mut harness = TEST;
harness.component_kinds = &[
crate::provider_v3::ComponentKind::Instruction,
crate::provider_v3::ComponentKind::Setting,
];
let target = seeded("scoped-only-kind");
let (bytes, bundle_digest, artifact) = bundle_bytes_for(
&harness,
Some(crate::provider_v3::TargetScope::UserRoot),
&[("shared/probe/SKILL.md", "probe\n", 0o644)],
Some("skill"),
);
let artifact_path = target.join("..").join("scoped-kind.zip");
fs::write(&artifact_path, &bytes).unwrap();
let flags = bundle_flags(&artifact_path, &bundle_digest, &artifact, bytes.len());
let borrowed: Vec<&str> = flags.iter().map(String::as_str).collect();
let validated = run_for(&harness, args("validate-bundle", &target, &borrowed));
assert_eq!(validated["valid"], true, "{validated}");
let mut scoped = vec![
"--operation".to_owned(),
"install".to_owned(),
"--provider-release-digest".to_owned(),
RELEASE.to_owned(),
"--operation-id".to_owned(),
"operation_01SCOPEDKIND".to_owned(),
"--expires-at".to_owned(),
far_future().to_owned(),
"--target-scope".to_owned(),
"user_root".to_owned(),
];
scoped.extend(flags.clone());
let borrowed: Vec<&str> = scoped.iter().map(String::as_str).collect();
let planned = run_for(&harness, args("plan-operation", &target, &borrowed));
assert_eq!(planned["state"], "planned", "{planned}");
let (bytes, bundle_digest, artifact) =
bundle_bytes_declaring(&[("skills/probe.md", "probe\n", 0o644)], Some("skill"));
let home_path = target.join("..").join("global-kind.zip");
fs::write(&home_path, &bytes).unwrap();
let mut global = vec![
"--operation".to_owned(),
"install".to_owned(),
"--provider-release-digest".to_owned(),
RELEASE.to_owned(),
"--operation-id".to_owned(),
"operation_01GLOBALKIND".to_owned(),
"--expires-at".to_owned(),
far_future().to_owned(),
];
global.extend(bundle_flags(
&home_path,
&bundle_digest,
&artifact,
bytes.len(),
));
let borrowed: Vec<&str> = global.iter().map(String::as_str).collect();
let error = refuse_for(&harness, args("plan-operation", &target, &borrowed));
assert_eq!(error.reason(), Some(WireReason::ProjectionProfileMismatch));
assert!(
error
.detail()
.contains("different provider projection profile"),
"{}",
error.detail()
);
}
#[test]
fn a_removal_answers_three_ways_when_no_record_says_what_this_build_wrote() {
let empty = scratch("remove-nothing-here").join("target");
fs::write(empty.join("unrelated.txt"), "theirs").unwrap();
let done = plan_then_apply(&empty, "remove", &[]);
assert_eq!(done["state"], "verified", "{done}");
assert_eq!(
fs::read_to_string(empty.join("unrelated.txt")).unwrap(),
"theirs"
);
let populated = seeded("remove-unrecorded");
let error = refuse(args(
"plan-operation",
&populated,
&[
"--operation",
"remove",
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01UNRECORDED",
"--expires-at",
far_future(),
],
));
assert_eq!(error.reason(), Some(WireReason::UnsupportedOperation));
assert!(
error.detail().contains("has applied no setup at")
&& error.detail().contains("AGENTS.md"),
"{}",
error.detail()
);
for kept in ["AGENTS.md", "settings.json", "unrelated.txt"] {
assert!(populated.join(kept).exists(), "the refusal took {kept}");
}
assert_eq!(
plan_then_apply(&populated, "backup", &[])["state"],
"verified"
);
assert_eq!(
plan_then_apply(&populated, "remove", &[])["state"],
"verified"
);
assert!(!populated.join("AGENTS.md").exists());
}
#[test]
fn a_human_removal_with_no_record_of_its_own_is_refused() {
let target = seeded("human-remove-unmanaged");
assert!(target.join(TEST.state_file).symlink_metadata().is_err());
let error = crate::harness_runtime::human::run(
&TEST,
crate::harness_runtime::human::Command::Remove {
target: target.clone(),
},
)
.unwrap_err();
assert_eq!(error.reason(), Some(WireReason::UnsupportedOperation));
assert!(
error.detail().contains("has applied no setup at"),
"{}",
error.detail()
);
for kept in ["AGENTS.md", "settings.json", "unrelated.txt"] {
assert!(target.join(kept).exists(), "the refusal took {kept}");
}
assert!(target.join("skills").is_dir(), "the refusal took skills/");
assert_eq!(plan_then_apply(&target, "backup", &[])["state"], "verified");
crate::harness_runtime::human::run(
&TEST,
crate::harness_runtime::human::Command::Remove {
target: target.clone(),
},
)
.unwrap();
assert!(!target.join("AGENTS.md").exists());
assert!(!target.join("skills").exists());
assert_eq!(
fs::read_to_string(target.join("unrelated.txt")).unwrap(),
"keep me"
);
}
fn project_shaped() -> Harness {
let mut harness = TEST;
harness.scoped_projections = &[crate::harness_runtime::facts::Scoped {
target_scope: crate::provider_v3::TargetScope::Project,
profile_id: "test/native-files/project/1",
component_kinds: &[
crate::provider_v3::ComponentKind::Skill,
crate::provider_v3::ComponentKind::Instruction,
],
projection_kinds: &[crate::provider_v3::ProjectionKind::NativeFiles],
native_namespaces: &[".cursor/skills", ".cursor/rules"],
}];
harness
}
#[test]
fn under_a_scope_a_file_left_behind_is_not_this_builds_to_take_next_time() {
let target = seeded("remove-keeping-scoped");
install_scoped(&target, "keep", "ours", "the setup's bytes\n");
assert_eq!(recorded_written(&target), vec!["shared/ours/SKILL.md"]);
let theirs = "the person's remaining bytes\n";
let (planned, apply_args) = remove_keeping_plan(
&target,
"scoped",
&[("shared/ours/SKILL.md", theirs, 0o644)],
Some("user_root"),
);
let states = planned["plan"]["end_state"].as_array().unwrap();
assert_eq!(states.len(), 1, "{states:?}");
assert_eq!(states[0]["end_state"], "final_bytes");
let borrowed: Vec<&str> = apply_args.iter().map(String::as_str).collect();
let applied = run(args("apply-operation", &target, &borrowed));
assert_eq!(applied["state"], "verified", "{applied}");
assert_eq!(
fs::read_to_string(target.join("shared").join("ours").join("SKILL.md")).unwrap(),
theirs
);
assert!(recorded_written(&target).is_empty());
let again = scoped_plan(&target, "remove", "operation_01AGAIN");
let done = scoped_apply(&target, &again, "again");
assert_eq!(done["state"], "verified", "{done}");
assert!(
target.join("shared").join("ours").join("SKILL.md").exists(),
"the second removal took the file the first one left to the person"
);
}
#[test]
fn a_bundle_installs_over_the_wire_and_leaves_unowned_files_alone() {
let target = seeded("bundle-install");
let (bytes, bundle_digest, artifact) = bundle_bytes(&[
("AGENTS.md", "# from a bundle\n", 0o644),
("skills/b.md", "two", 0o644),
]);
let artifact_path = target.join("..").join("bundle.zip");
fs::write(&artifact_path, &bytes).unwrap();
let flags = bundle_flags(&artifact_path, &bundle_digest, &artifact, bytes.len());
let mut plan_args = vec![
"--operation".to_owned(),
"install".to_owned(),
"--provider-release-digest".to_owned(),
RELEASE.to_owned(),
"--operation-id".to_owned(),
"operation_01BUNDLE".to_owned(),
"--expires-at".to_owned(),
far_future().to_owned(),
];
plan_args.extend(flags.clone());
let borrowed: Vec<&str> = plan_args.iter().map(String::as_str).collect();
let planned = run(args("plan-operation", &target, &borrowed));
assert_eq!(planned["state"], "planned", "{planned}");
assert_eq!(
planned["valid"], true,
"a plan carrying a bundle echoes its validity"
);
assert_eq!(planned["bundle_digest"], bundle_digest.as_str());
let plan_path = target.join("..").join("bundle-plan.json");
fs::write(
&plan_path,
crate::setup_core::canonical::to_canonical_bytes(&planned["plan"]).unwrap(),
)
.unwrap();
let mut apply_args = vec![
"--plan".to_owned(),
plan_path.to_string_lossy().into_owned(),
"--plan-digest".to_owned(),
planned["plan_digest"].as_str().unwrap().to_owned(),
"--provider-release-digest".to_owned(),
RELEASE.to_owned(),
];
apply_args.extend(flags);
let borrowed: Vec<&str> = apply_args.iter().map(String::as_str).collect();
let applied = run(args("apply-operation", &target, &borrowed));
assert_eq!(applied["state"], "verified");
assert_eq!(
fs::read_to_string(target.join("AGENTS.md")).unwrap(),
"# from a bundle\n"
);
assert_eq!(
fs::read_to_string(target.join("skills").join("b.md")).unwrap(),
"two"
);
assert!(!target.join("settings.json").exists());
assert_eq!(
fs::read_to_string(target.join("unrelated.txt")).unwrap(),
"keep me"
);
assert_eq!(
fs::read_to_string(target.join(".credentials.json")).unwrap(),
"SECRET"
);
}
#[test]
fn a_bundle_routed_to_a_scope_installs_into_that_scopes_namespace() {
let target = seeded("bundle-scoped");
let (bytes, bundle_digest, artifact) = bundle_bytes_for(
&TEST,
Some(crate::provider_v3::TargetScope::UserRoot),
&[("shared/review/SKILL.md", "# review\n", 0o644)],
Some("skill"),
);
let artifact_path = target.join("..").join("scoped-bundle.zip");
fs::write(&artifact_path, &bytes).unwrap();
let flags = bundle_flags(&artifact_path, &bundle_digest, &artifact, bytes.len());
let mut plan_args = vec![
"--operation".to_owned(),
"install".to_owned(),
"--provider-release-digest".to_owned(),
RELEASE.to_owned(),
"--operation-id".to_owned(),
"operation_01SCOPEDBUNDLE".to_owned(),
"--expires-at".to_owned(),
far_future().to_owned(),
"--target-scope".to_owned(),
"user_root".to_owned(),
];
plan_args.extend(flags.clone());
let borrowed: Vec<&str> = plan_args.iter().map(String::as_str).collect();
let planned = run(args("plan-operation", &target, &borrowed));
assert_eq!(planned["state"], "planned", "{planned}");
let plan_path = target.join("..").join("scoped-bundle-plan.json");
fs::write(
&plan_path,
crate::setup_core::canonical::to_canonical_bytes(&planned["plan"]).unwrap(),
)
.unwrap();
let mut apply_args = vec![
"--plan".to_owned(),
plan_path.to_string_lossy().into_owned(),
"--plan-digest".to_owned(),
planned["plan_digest"].as_str().unwrap().to_owned(),
"--provider-release-digest".to_owned(),
RELEASE.to_owned(),
];
apply_args.extend(flags);
let borrowed: Vec<&str> = apply_args.iter().map(String::as_str).collect();
let applied = run(args("apply-operation", &target, &borrowed));
assert_eq!(applied["state"], "verified", "{applied}");
assert_eq!(
fs::read_to_string(target.join("shared").join("review").join("SKILL.md")).unwrap(),
"# review\n"
);
assert!(
target.join("AGENTS.md").exists(),
"an install under a scope cleared the global target's files"
);
assert!(target.join("settings.json").exists());
let recorded: serde_json::Value =
serde_json::from_slice(&fs::read(target.join(TEST.state_file)).unwrap()).unwrap();
assert_eq!(
recorded["native_ownership"],
serde_json::json!(["shared"]),
"the state described the global namespaces at a scoped target"
);
}
#[test]
fn a_bundle_writing_outside_the_declared_surface_never_reaches_the_target() {
let target = seeded("bundle-outside");
let (bytes, bundle_digest, artifact) =
bundle_bytes(&[("AGENTS.md", "x", 0o644), ("elsewhere.txt", "y", 0o644)]);
let artifact_path = target.join("..").join("hostile.zip");
fs::write(&artifact_path, &bytes).unwrap();
let flags = bundle_flags(&artifact_path, &bundle_digest, &artifact, bytes.len());
let mut plan_args = vec![
"--operation".to_owned(),
"install".to_owned(),
"--provider-release-digest".to_owned(),
RELEASE.to_owned(),
"--operation-id".to_owned(),
"operation_01HOSTILE".to_owned(),
"--expires-at".to_owned(),
far_future().to_owned(),
];
plan_args.extend(flags);
let borrowed: Vec<&str> = plan_args.iter().map(String::as_str).collect();
let error = refuse(args("plan-operation", &target, &borrowed));
assert_eq!(error.reason(), Some(WireReason::UnsupportedNativeSurface));
assert!(!target.join("elsewhere.txt").exists());
}
#[test]
fn install_without_a_bundle_says_a_bundle_is_what_it_takes() {
let target = seeded("bundle-missing");
for operation in ["install", "replace"] {
let error = refuse(args(
"plan-operation",
&target,
&[
"--operation",
operation,
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01TEST",
"--expires-at",
far_future(),
],
));
assert_eq!(
error.reason(),
Some(WireReason::UnsupportedBundleFormat),
"{operation}"
);
assert!(
error.detail().contains("none was named"),
"{operation}: {error}"
);
}
let info = TEST.provider_info().unwrap();
assert!(info.declares(Operation::Install));
assert!(info.declares(Operation::Replace));
}
#[test]
fn launch_with_nothing_installed_says_to_install_first() {
let target = seeded("launch-empty");
let prefix = ready_prefix(&target);
let error = refuse(args("launch", &target, &["--prefix", &prefix]));
assert_eq!(error.reason(), Some(WireReason::ProviderUnavailable));
assert!(
error.detail().contains("software_install"),
"{}",
error.detail()
);
}
#[test]
fn launch_without_a_prefix_says_where_a_program_lives() {
let target = seeded("launch-noprefix");
let error = argv::parse(args("launch", &target, &[])).unwrap_err();
assert!(error.detail().contains("--prefix"), "{}", error.detail());
assert!(error.detail().contains("--help"), "{}", error.detail());
}
#[test]
fn a_build_that_cannot_point_the_product_at_a_target_does_not_declare_launch() {
let mut mute = TEST;
mute.config_home_env = "";
assert!(!mute.can_launch());
let info = mute.provider_info().unwrap();
assert!(!info.declares(Operation::Launch));
let error = software::launch(&mute, Path::new("/nowhere"), None, &[]).unwrap_err();
assert_eq!(error.reason(), Some(WireReason::UnsupportedOperation));
assert!(
error.detail().contains("configuration home"),
"{}",
error.detail()
);
}
#[test]
fn a_build_that_installs_nothing_does_not_declare_launch_either() {
let mut bare = TEST;
bare.software = None;
assert!(!bare.can_launch());
assert!(!bare.provider_info().unwrap().declares(Operation::Launch));
let error = software::launch(&bare, Path::new("/nowhere"), None, &[]).unwrap_err();
assert!(error.detail().contains("PATH"), "{}", error.detail());
}
#[test]
fn a_build_that_installs_and_can_be_pointed_declares_launch() {
assert!(TEST.can_launch());
let info = TEST.provider_info().unwrap();
assert!(info.declares(Operation::Launch));
assert!(info.supported_commands.iter().any(|c| c == "launch"));
}
#[test]
fn what_launch_starts_is_the_file_that_was_installed() {
let target = seeded("launch-installed");
let file = downloaded(&target, TEST_PAYLOAD);
let applied = plan_then_install(&target, "software_install", Some(&file));
let exposed = std::path::PathBuf::from(applied["executable"].as_str().unwrap());
let prefix = ready_prefix(&target);
assert_eq!(
exposed,
std::path::Path::new(&prefix)
.join("bin")
.join("test-harness")
);
assert!(exposed.symlink_metadata().is_ok());
#[cfg(unix)]
{
let output = run_once_it_is_not_busy(
std::process::Command::new(&exposed).env(TEST.config_home_env, &target),
);
assert_eq!(
String::from_utf8_lossy(&output.stdout).trim(),
"test-harness 1.2.3"
);
}
}
#[test]
fn a_restore_with_no_backup_to_read_refuses_rather_than_emptying_the_target() {
let target = seeded("restore-empty");
let error = refuse(args(
"plan-operation",
&target,
&[
"--operation",
"restore",
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01TEST",
"--expires-at",
far_future(),
],
));
assert!(error.detail().contains("no backup"));
assert_eq!(
fs::read_to_string(target.join("AGENTS.md")).unwrap(),
"# first\n"
);
}
fn prefix_for(target: &Path) -> std::path::PathBuf {
target.join("..").join("program")
}
fn downloaded(target: &Path, bytes: &[u8]) -> std::path::PathBuf {
let at = target.join("..").join("downloaded-artifact");
fs::write(&at, bytes).unwrap();
at
}
fn software_plan_args<'a>(operation: &'a str, prefix: &'a str) -> Vec<&'a str> {
vec![
"--operation",
operation,
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01SOFT",
"--expires-at",
far_future(),
"--prefix",
prefix,
]
}
fn ready_prefix(target: &Path) -> String {
let prefix = prefix_for(target);
fs::create_dir_all(&prefix).unwrap();
fs::canonicalize(&prefix)
.unwrap()
.to_string_lossy()
.into_owned()
}
fn apply_planned(
target: &Path,
prefix: &str,
operation: &str,
planned: &serde_json::Value,
artifact: Option<&Path>,
) -> serde_json::Value {
let plan_path = target.join("..").join(format!("plan-{operation}.json"));
fs::write(
&plan_path,
crate::setup_core::canonical::to_canonical_bytes(&planned["plan"]).unwrap(),
)
.unwrap();
let digest = planned["plan_digest"].as_str().unwrap().to_owned();
let path = plan_path.to_string_lossy().into_owned();
let mut extra = vec![
"--plan",
&path,
"--plan-digest",
&digest,
"--provider-release-digest",
RELEASE,
"--prefix",
prefix,
];
let held;
if let Some(file) = artifact {
held = file.to_string_lossy().into_owned();
extra.push("--software-artifact");
extra.push(&held);
}
run(args("apply-operation", target, &extra))
}
fn plan_then_install_at(
target: &Path,
operation: &str,
artifact: Option<&Path>,
version: Option<&str>,
) -> serde_json::Value {
let prefix = ready_prefix(target);
let mut arguments = software_plan_args(operation, &prefix);
if let Some(wanted) = version {
arguments.extend_from_slice(&["--software-version", wanted]);
}
let planned = run(args("plan-operation", target, &arguments));
assert_eq!(planned["state"], "planned", "plan refused: {planned}");
apply_planned(target, &prefix, operation, &planned, artifact)
}
fn plan_then_install(
target: &Path,
operation: &str,
artifact: Option<&Path>,
) -> serde_json::Value {
let prefix = ready_prefix(target);
let planned = run(args(
"plan-operation",
target,
&software_plan_args(operation, &prefix),
));
assert_eq!(planned["state"], "planned", "plan refused: {planned}");
let plan_path = target.join("..").join(format!("plan-{operation}.json"));
fs::write(
&plan_path,
crate::setup_core::canonical::to_canonical_bytes(&planned["plan"]).unwrap(),
)
.unwrap();
let digest = planned["plan_digest"].as_str().unwrap().to_owned();
let path = plan_path.to_string_lossy().into_owned();
let mut extra = vec![
"--plan",
&path,
"--plan-digest",
&digest,
"--provider-release-digest",
RELEASE,
"--prefix",
&prefix,
];
let held;
if let Some(file) = artifact {
held = file.to_string_lossy().into_owned();
extra.push("--software-artifact");
extra.push(&held);
}
run(args("apply-operation", target, &extra))
}
fn software_plan(target: &Path, operation: &str) -> serde_json::Value {
let prefix = ready_prefix(target);
run(args(
"plan-operation",
target,
&software_plan_args(operation, &prefix),
))
}
#[test]
fn a_software_plan_names_the_exact_bytes_before_any_network_is_open() {
let target = seeded("software-plan");
let planned = software_plan(&target, "software_install");
let artifacts = planned["plan"]["software_artifacts"].as_array().unwrap();
assert_eq!(artifacts.len(), 1);
let only = &artifacts[0];
let mut fields: Vec<&str> = only
.as_object()
.unwrap()
.keys()
.map(String::as_str)
.collect();
fields.sort_unstable();
assert_eq!(
fields,
vec!["byte_length", "entry_point", "platform", "sha256", "url"],
"the plan carries the agreed fields and no others"
);
assert_eq!(only["byte_length"], 39);
assert_eq!(
only["sha256"],
"sha256:0c7c47cc1bc9116feb15bd468d039e954093ccfca8d6246b32ea94d1ab2213ad"
);
assert_eq!(only["entry_point"], "bin/test-harness");
let effects = planned["effects"].as_array().unwrap();
assert!(
effects[0].as_str().unwrap().contains("download phase"),
"{effects:?}"
);
}
#[test]
fn a_software_operation_without_a_prefix_says_where_a_program_lives() {
let target = seeded("software-noprefix");
let error = refuse(args(
"plan-operation",
&target,
&[
"--operation",
"software_install",
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01SOFT",
"--expires-at",
far_future(),
],
));
assert!(error.detail().contains("--prefix"), "{}", error.detail());
}
#[test]
fn a_relative_prefix_is_refused_because_a_plan_cannot_be_bound_to_one() {
let target = seeded("software-relprefix");
let error = argv::parse(args(
"plan-operation",
&target,
&software_plan_args("software_install", "program"),
))
.unwrap_err();
assert!(error.detail().contains("absolute"), "{}", error.detail());
}
#[test]
fn a_prefix_on_an_operation_that_installs_nothing_is_refused_not_ignored() {
let target = seeded("software-strayprefix");
let elsewhere = std::env::temp_dir();
let elsewhere = elsewhere.to_string_lossy();
let error = refuse(args(
"plan-operation",
&target,
&[
"--operation",
"backup",
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01SOFT",
"--expires-at",
far_future(),
"--prefix",
&elsewhere,
],
));
assert!(
error.detail().contains("means nothing"),
"{}",
error.detail()
);
}
#[test]
fn a_version_this_build_does_not_pin_is_refused_rather_than_neighboured() {
let target = seeded("software-version");
let prefix = ready_prefix(&target);
let mut arguments = software_plan_args("software_install", &prefix);
arguments.extend_from_slice(&["--software-version", "9.9.9"]);
let error = refuse(args("plan-operation", &target, &arguments));
assert!(error.detail().contains("1.2.3"), "{}", error.detail());
let mut exact = software_plan_args("software_install", &prefix);
exact.extend_from_slice(&["--software-version", "1.2.3"]);
let planned = run(args("plan-operation", &target, &exact));
assert_eq!(planned["state"], "planned");
}
#[test]
fn the_version_pinned_before_this_one_can_be_planned_and_names_its_own_bytes() {
let target = seeded("software-previous");
let prefix = ready_prefix(&target);
let mut earlier = software_plan_args("software_install", &prefix);
earlier.extend_from_slice(&["--software-version", "1.2.2"]);
let planned = run(args("plan-operation", &target, &earlier));
assert_eq!(planned["state"], "planned");
let artifacts = planned["plan"]["software_artifacts"].as_array().unwrap();
assert_eq!(artifacts.len(), 1);
assert_eq!(
artifacts[0]["url"], "https://example.invalid/test-harness-1.2.2",
"the earlier version was named and the current bytes were planned"
);
assert_eq!(
artifacts[0]["sha256"],
"sha256:42c3e0650b099f95955b0ff86c75499848e1343a6c40af6a7acd10f3c18ce226"
);
let mut neither = software_plan_args("software_install", &prefix);
neither.extend_from_slice(&["--software-version", "9.9.9"]);
let error = refuse(args("plan-operation", &target, &neither));
assert!(error.detail().contains("1.2.3"), "{}", error.detail());
assert!(error.detail().contains("1.2.2"), "{}", error.detail());
}
#[test]
fn an_update_moves_the_command_between_two_versions_that_both_stay_on_disk() {
let target = seeded("software-update-across");
let prefix = ready_prefix(&target);
let root = Path::new(&prefix).to_path_buf();
let earlier_file = downloaded(&target, TEST_EARLIER_PAYLOAD);
let installed = plan_then_install_at(
&target,
"software_install",
Some(&earlier_file),
Some("1.2.2"),
);
assert_eq!(installed["state"], "verified", "{installed}");
assert_eq!(installed["version"], "1.2.2");
let mut arguments = software_plan_args("software_update", &prefix);
arguments.extend_from_slice(&["--software-version", "1.2.3"]);
let planned = run(args("plan-operation", &target, &arguments));
assert_eq!(planned["state"], "planned", "{planned}");
let effects = serde_json::to_string(&planned["plan"]["effects"]).unwrap();
assert!(
effects.contains("1.2.2") && effects.contains("1.2.3"),
"an update should name both ends of the move: {effects}"
);
let current_file = downloaded(&target, TEST_PAYLOAD);
let updated = apply_planned(
&target,
&prefix,
"software_update",
&planned,
Some(¤t_file),
);
assert_eq!(updated["state"], "verified", "{updated}");
assert_eq!(updated["version"], "1.2.3");
assert!(
root.join("1.2.2").is_dir(),
"the earlier tree was discarded"
);
assert!(root.join("1.2.3").is_dir(), "the new tree is missing");
let exposed = root.join("bin").join("test-harness");
assert_eq!(
fs::read(&exposed).unwrap(),
TEST_PAYLOAD,
"the exposed command still runs the version the update moved away from"
);
}
#[test]
fn apply_installs_the_release_the_bytes_belong_to_rather_than_the_one_named() {
let target = seeded("software-bytes-decide");
let prefix = ready_prefix(&target);
let earlier_file = downloaded(&target, TEST_EARLIER_PAYLOAD);
let applied = plan_then_install(&target, "software_install", Some(&earlier_file));
assert_eq!(applied["state"], "verified", "{applied}");
assert_eq!(
applied["version"], "1.2.2",
"the bytes were 1.2.2 and the install claimed otherwise"
);
assert!(Path::new(&prefix).join("1.2.2").is_dir());
assert!(!Path::new(&prefix).join("1.2.3").exists());
}
#[test]
fn an_artifact_from_no_release_this_build_names_is_refused_by_its_digest() {
let target = seeded("software-stranger");
let prefix = ready_prefix(&target);
let planned = run(args(
"plan-operation",
&target,
&software_plan_args("software_install", &prefix),
));
let plan_path = target.join("..").join("plan-stranger.json");
fs::write(
&plan_path,
crate::setup_core::canonical::to_canonical_bytes(&planned["plan"]).unwrap(),
)
.unwrap();
let digest = planned["plan_digest"].as_str().unwrap().to_owned();
let path = plan_path.to_string_lossy().into_owned();
let stranger = downloaded(&target, b"neither release, and not close\n");
let held = stranger.to_string_lossy().into_owned();
let error = refuse(args(
"apply-operation",
&target,
&[
"--plan",
&path,
"--plan-digest",
&digest,
"--provider-release-digest",
RELEASE,
"--prefix",
&prefix,
"--software-artifact",
&held,
],
));
assert_eq!(error.reason(), Some(WireReason::DigestMismatch));
let detail = error.detail();
assert!(
detail.contains("1.2.3") && detail.contains("1.2.2"),
"the refusal should name what this build does publish: {detail}"
);
}
#[test]
fn installing_places_a_command_and_leaves_the_configuration_alone() {
let target = seeded("software-install");
let before = run(args("status", &target, &[]))["target_identity_digest"].clone();
let file = downloaded(&target, TEST_PAYLOAD);
let applied = plan_then_install(&target, "software_install", Some(&file));
assert_eq!(applied["state"], "verified");
assert_eq!(applied["version"], "1.2.3");
let exposed = Path::new(&ready_prefix(&target))
.to_path_buf()
.join("bin")
.join("test-harness");
assert!(exposed.symlink_metadata().is_ok(), "no command was exposed");
assert_eq!(fs::read(&exposed).unwrap(), TEST_PAYLOAD);
assert!(
Path::new(&ready_prefix(&target))
.to_path_buf()
.join("1.2.3")
.join("test-harness")
.is_file()
);
let after = run(args("status", &target, &[]))["target_identity_digest"].clone();
assert_eq!(
before, after,
"installing software moved the target identity"
);
assert_eq!(
fs::read_to_string(target.join("AGENTS.md")).unwrap(),
"# first\n"
);
}
#[cfg(unix)]
fn run_once_it_is_not_busy(command: &mut std::process::Command) -> std::process::Output {
for _ in 0..50 {
match command.output() {
Err(error) if error.kind() == std::io::ErrorKind::ExecutableFileBusy => {
std::thread::sleep(std::time::Duration::from_millis(20));
}
other => return other.unwrap(),
}
}
panic!("it stayed busy for a second, which is longer than the fork race lasts");
}
#[test]
#[cfg(unix)]
fn what_was_installed_actually_runs() {
let target = seeded("software-runs");
let file = downloaded(&target, TEST_PAYLOAD);
let applied = plan_then_install(&target, "software_install", Some(&file));
let output = run_once_it_is_not_busy(&mut std::process::Command::new(
applied["executable"].as_str().unwrap(),
));
assert_eq!(
String::from_utf8_lossy(&output.stdout).trim(),
"test-harness 1.2.3"
);
}
#[test]
fn an_update_of_nothing_is_refused_rather_than_quietly_installing() {
let target = seeded("software-update-empty");
let prefix = ready_prefix(&target);
let error = refuse(args(
"plan-operation",
&target,
&software_plan_args("software_update", &prefix),
));
assert!(
error.detail().contains("software_install is the operation"),
"{}",
error.detail()
);
}
#[test]
fn a_plan_says_what_is_already_under_the_prefix() {
let target = seeded("software-plan-present");
let file = downloaded(&target, TEST_PAYLOAD);
plan_then_install(&target, "software_install", Some(&file));
let planned = software_plan(&target, "software_install");
let effects = planned["effects"].as_array().unwrap();
assert!(
effects[0].as_str().unwrap().contains("already installed"),
"{effects:?}"
);
let updating = software_plan(&target, "software_update");
assert_eq!(updating["state"], "planned");
}
#[test]
fn a_remove_names_the_versions_it_leaves_behind() {
let target = seeded("software-remove-others");
let file = downloaded(&target, TEST_PAYLOAD);
plan_then_install(&target, "software_install", Some(&file));
let prefix = ready_prefix(&target);
fs::create_dir_all(Path::new(&prefix).join("0.9.0")).unwrap();
let planned = software_plan(&target, "software_remove");
let effects: Vec<&str> = planned["effects"]
.as_array()
.unwrap()
.iter()
.map(|effect| effect.as_str().unwrap())
.collect();
assert!(
effects.iter().any(|effect| effect.contains("leave 0.9.0")),
"{effects:?}"
);
}
#[test]
fn every_software_apply_echoes_the_plan_digest_it_was_handed() {
for operation in ["software_install", "software_update", "software_remove"] {
let target = seeded(&format!("software-echo-{operation}"));
let file = downloaded(&target, TEST_PAYLOAD);
if operation != "software_install" {
plan_then_install(&target, "software_install", Some(&file));
}
let prefix = ready_prefix(&target);
let planned = software_plan(&target, operation);
assert_eq!(planned["state"], "planned", "plan refused: {planned}");
let digest = planned["plan_digest"].as_str().unwrap().to_owned();
let artifact = if operation == "software_remove" {
None
} else {
Some(file.as_path())
};
let applied = apply_planned(&target, &prefix, operation, &planned, artifact);
assert_eq!(applied["state"], "verified", "apply refused: {applied}");
assert_eq!(
applied["plan_digest"],
serde_json::Value::String(digest),
"{operation} answered without the plan echo the wire owes: {applied}"
);
}
}
#[test]
fn installing_software_spends_no_backup_slot() {
let target = seeded("software-slots");
let file = downloaded(&target, TEST_PAYLOAD);
plan_then_install(&target, "software_install", Some(&file));
let slots = target.join(TEST.control_directory).join("backups");
let taken = fs::read_dir(&slots).map_or(0, Iterator::count);
assert_eq!(
taken, 0,
"a software install captured a configuration backup"
);
}
#[test]
fn bytes_that_are_not_the_ones_the_plan_named_are_refused() {
let target = seeded("software-digest");
let mut tampered = TEST_PAYLOAD.to_vec();
tampered[0] = b'X';
let file = downloaded(&target, &tampered);
let prefix = ready_prefix(&target);
let planned = software_plan(&target, "software_install");
let plan_path = target.join("..").join("plan-tampered.json");
fs::write(
&plan_path,
crate::setup_core::canonical::to_canonical_bytes(&planned["plan"]).unwrap(),
)
.unwrap();
let error = refuse(args(
"apply-operation",
&target,
&[
"--plan",
&plan_path.to_string_lossy(),
"--plan-digest",
planned["plan_digest"].as_str().unwrap(),
"--provider-release-digest",
RELEASE,
"--prefix",
&prefix,
"--software-artifact",
&file.to_string_lossy(),
],
));
assert_eq!(error.reason(), Some(WireReason::DigestMismatch));
assert!(
!Path::new(&ready_prefix(&target))
.to_path_buf()
.join("bin")
.exists()
);
}
#[test]
fn an_install_with_no_artifact_says_what_is_missing() {
let target = seeded("software-missing");
let prefix = ready_prefix(&target);
let planned = software_plan(&target, "software_install");
let plan_path = target.join("..").join("plan-missing.json");
fs::write(
&plan_path,
crate::setup_core::canonical::to_canonical_bytes(&planned["plan"]).unwrap(),
)
.unwrap();
let error = refuse(args(
"apply-operation",
&target,
&[
"--plan",
&plan_path.to_string_lossy(),
"--plan-digest",
planned["plan_digest"].as_str().unwrap(),
"--provider-release-digest",
RELEASE,
"--prefix",
&prefix,
],
));
assert!(
error.detail().contains("--software-artifact"),
"{}",
error.detail()
);
}
#[test]
fn removing_takes_the_program_back_down() {
let target = seeded("software-remove");
let file = downloaded(&target, TEST_PAYLOAD);
plan_then_install(&target, "software_install", Some(&file));
assert!(
Path::new(&ready_prefix(&target))
.to_path_buf()
.join("bin/test-harness")
.symlink_metadata()
.is_ok()
);
let removed = plan_then_install(&target, "software_remove", None);
assert_eq!(removed["removed"], true);
assert!(
!Path::new(&ready_prefix(&target))
.to_path_buf()
.join("1.2.3")
.exists()
);
assert!(
Path::new(&ready_prefix(&target))
.to_path_buf()
.join("bin/test-harness")
.symlink_metadata()
.is_err()
);
}
#[test]
fn a_build_that_installs_software_declares_all_three_operations() {
let info = TEST.provider_info().unwrap();
assert!(info.declares(Operation::SoftwareInstall));
assert!(info.declares(Operation::SoftwareUpdate));
assert!(info.declares(Operation::SoftwareRemove));
}
#[test]
fn a_build_that_installs_no_software_declares_none_of_them() {
let mut bare = TEST;
bare.software = None;
let info = bare.provider_info().unwrap();
assert!(!info.declares(Operation::SoftwareInstall));
assert!(!info.declares(Operation::SoftwareUpdate));
assert!(!info.declares(Operation::SoftwareRemove));
let error = software::plan(
&bare,
Some(Path::new("/nowhere")),
Operation::SoftwareInstall,
None,
)
.unwrap_err();
assert_eq!(error.reason(), Some(WireReason::UnsupportedOperation));
}
#[test]
fn a_status_says_which_backups_are_held_and_why() {
let target = seeded("status-publishes-holds");
plan_then_apply(&target, "backup", &[]);
plan_then_apply(&target, "backup", &[]);
let control = target.join(TEST.control_directory);
let pool = crate::setup_core::backup::Pool::open(&control, facts::BACKUP_SLOTS).unwrap();
let first = pool.list().unwrap().last().unwrap().backup_ref.clone();
assert!(pool.hold(&first, "the evidence series baseline").unwrap());
let listed = run(args("status", &target, &[]));
let backups = listed["backups"].as_array().unwrap();
assert!(backups.len() >= 2, "{backups:?}");
let held: Vec<&serde_json::Value> = backups
.iter()
.filter(|entry| entry["held"] == serde_json::json!(true))
.collect();
assert_eq!(held.len(), 1, "exactly one slot was held: {backups:?}");
assert_eq!(held[0]["backup_ref"], serde_json::json!(first.as_str()));
assert_eq!(
held[0]["hold_reason"],
serde_json::json!("the evidence series baseline"),
"the reason travels with the fact, because a caller deciding \
whether to release one needs to know whose baseline it is"
);
for entry in backups {
if entry["backup_ref"] != serde_json::json!(first.as_str()) {
assert_eq!(entry["held"], serde_json::json!(false), "{entry:?}");
assert_eq!(entry["hold_reason"], serde_json::Value::Null, "{entry:?}");
}
}
}
}