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, NativeCapture, 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::native_snapshot::{NativeBase, NativeSnapshot};
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)?;
status_of(harness, &resolved, &pool, &identity, journal, scope)
}
fn backup_status(
pool: &Pool,
resolved: &Target,
harness: &Harness,
scope: Option<crate::provider_v3::TargetScope>,
) -> Result<serde_json::Value> {
let held = pool.held()?;
let records = pool.list()?;
let current = if records
.iter()
.any(|record| record.native_snapshot.is_some())
{
inspect_native_surface(harness, resolved, scope)
.ok()
.map(|(_, snapshot)| snapshot)
} else {
None
};
let mut entries = Vec::new();
for record in records {
let holder = held
.iter()
.find(|(reference, _)| *reference == record.backup_ref);
let mut entry = 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()),
});
if let Some(snapshot) = &record.native_snapshot {
let verification = if pool.payload_of(&record.backup_ref).is_ok() {
"verified"
} else {
"unavailable"
};
let target_state = current.as_ref().map_or("unavailable", |current| {
if current == snapshot {
"matches"
} else {
"differs"
}
});
entry["native_snapshot"] = serde_json::json!({
"digest": snapshot.digest()?,
"base_root": snapshot.base_root,
"operation_id": record.operation_id,
"roots": snapshot.roots,
"excluded": snapshot.excluded,
"verification": verification,
"target_state": target_state,
});
}
entries.push(entry);
}
Ok(entries.into())
}
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"
),
));
}
if asked.is_some() {
return Ok(asked);
}
scope_recorded_at(harness, resolved)
}
fn status_of(
harness: &Harness,
resolved: &Target,
pool: &Pool,
identity: &str,
journal: Option<Journal>,
scope: Option<crate::provider_v3::TargetScope>,
) -> 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", backup_status(pool, resolved, harness, scope)?),
(
"instruction_region",
instruction_attachment_status(harness, resolved, scope),
),
] {
answer.insert(key.to_owned(), value);
}
Ok(serde_json::Value::Object(answer))
}
fn instruction_attachment_status(
harness: &Harness,
resolved: &Target,
scope: Option<crate::provider_v3::TargetScope>,
) -> serde_json::Value {
let Some(relative) = harness.instruction_region else {
return serde_json::Value::Null;
};
if scope.is_some() {
return serde_json::Value::Null;
}
let surface = resolved.root().join(relative);
let existing = crate::harness_runtime::instruction_region::read_utf8(&surface).unwrap_or_else(|_| String::new());
let section = if crate::harness_runtime::instruction_region::markers_well_formed(&existing) {
crate::harness_runtime::instruction_region::extract(&existing)
} else {
None
};
serde_json::json!({
"path": relative,
"present": surface.exists(),
"section_present": section.is_some(),
"section_sha256": section.map(|held| crate::setup_core::digest::of_bytes(held.as_bytes())),
})
}
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
),
));
}
}
honour_instruction_section(harness, request)?;
if let Some(named) = request.target_scope
&& matches!(
request.operation,
Operation::PatchInstructionRegion | Operation::DetachInstructionRegion
)
{
return Err(Error::refuse(
WireReason::UnsupportedOperation,
format!(
"{} acts on the user-global instruction surface; --target-scope \
{named} names a scoped target where this provider declares none",
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(())
}
fn honour_instruction_section(harness: &Harness, request: &PlanRequest) -> Result<()> {
if request.instruction_section.is_some()
&& request.operation != Operation::PatchInstructionRegion
{
return Err(Error::refuse(
WireReason::UnsupportedOperation,
format!(
"{} takes no instruction section; patch_instruction_region is the \
operation that reads --instruction-section",
request.operation
),
));
}
if request.operation != Operation::PatchInstructionRegion {
return Ok(());
}
if harness.instruction_region.is_none() {
return Err(Error::refuse(
WireReason::UnsupportedOperation,
format!(
"{} does not declare a user-global instruction surface",
harness.provider_id
),
));
}
if request
.instruction_section
.as_deref()
.is_none_or(str::is_empty)
{
return Err(Error::refuse(
WireReason::UnsupportedOperation,
"patch_instruction_region needs --instruction-section with marked bytes",
));
}
Ok(())
}
fn plan_instruction_patch(
harness: &Harness,
target: &Target,
request: &PlanRequest,
) -> Result<(Vec<String>, String, String, bool, String)> {
let Some(relative) = harness.instruction_region else {
return Err(Error::refuse(
WireReason::UnsupportedOperation,
format!(
"{} does not declare a user-global instruction surface",
harness.provider_id
),
));
};
let Some(section) = request.instruction_section.as_deref() else {
return Err(Error::refuse(
WireReason::UnsupportedOperation,
"patch_instruction_region needs --instruction-section with marked bytes",
));
};
if !crate::harness_runtime::instruction_region::markers_well_formed(section)
|| crate::harness_runtime::instruction_region::extract(section).is_none()
{
return Err(Error::refuse(
WireReason::UnsupportedOperation,
"instruction_section needs exactly one ordered :::begin-ai-stp and :::end-ai-stp pair",
));
}
let surface = target.root().join(relative);
let present = surface.exists();
let existing = read_instruction_text(&surface)?;
let observed = crate::setup_core::digest::of_bytes(existing.as_bytes());
let (updated, wrote) = crate::harness_runtime::instruction_region::patch(&existing, section);
let effects = if wrote {
vec![format!("patch instruction region at {relative}")]
} else {
vec![format!("instruction region at {relative} already matches")]
};
Ok((effects, relative.to_owned(), updated, present, observed))
}
fn plan_instruction_detach(
harness: &Harness,
target: &Target,
) -> Result<(Vec<String>, String, String, bool, String)> {
let Some(relative) = harness.instruction_region else {
return Err(Error::refuse(
WireReason::UnsupportedOperation,
format!(
"{} does not declare a user-global instruction surface",
harness.provider_id
),
));
};
let surface = target.root().join(relative);
let present = surface.exists();
let existing = read_instruction_text(&surface)?;
let observed = crate::setup_core::digest::of_bytes(existing.as_bytes());
let remainder = crate::harness_runtime::instruction_region::remove_region(&existing);
let effects = if remainder.is_some() {
vec![format!("detach instruction region at {relative}")]
} else {
vec![format!(
"instruction region at {relative} is already detached"
)]
};
Ok((
effects,
relative.to_owned(),
remainder.unwrap_or(existing),
present,
observed,
))
}
pub(crate) fn taken_before_writing(
_harness: &Harness,
_scope: Option<crate::provider_v3::TargetScope>,
) -> Vec<String> {
vec![
"only the files this provider recorded writing go; anything else under \
the same root is left alone"
.to_owned(),
]
}
fn taken_before_reset(harness: &Harness) -> Vec<String> {
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)
}
#[allow(clippy::too_many_lines)]
fn plan(harness: &Harness, target: &Path, request: &PlanRequest) -> Result<serde_json::Value> {
let (resolved, control, pool) = observe(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 native_capture = plan_native_capture(
harness,
&resolved,
request.target_scope,
request.operation,
request.capture_mode.as_deref(),
request.backup_ref.as_deref(),
&pool,
)?;
let identity_paths = if native_capture.is_some() {
owned.clone()
} else {
snapshot_if_unmanaged_backup(
harness,
&resolved,
request.target_scope,
&owned,
request.operation,
)?
};
let identity =
resolved.identity_of_owned(&as_paths(&identity_paths), &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)?;
let capture = match request.operation {
Operation::Reset => harness
.owned_projection(request.target_scope)
.iter()
.map(|name| (*name).to_owned())
.collect(),
Operation::Backup
if owned.is_empty()
&& matches!(
ProviderState::read(resolved.root(), harness.state_file)?,
StateReading::Absent
) =>
{
existing_under_projection(harness, &resolved, request.target_scope)?
}
Operation::PatchInstructionRegion | Operation::DetachInstructionRegion => {
let mut capture = owned.clone();
if let Some(path) = harness.instruction_region
&& !capture.iter().any(|held| held == path)
{
capture.push(path.to_owned());
}
capture
}
_ => owned.clone(),
};
refuse_uncapturable(&resolved, &capture)?;
}
let mut software_artifacts = Vec::new();
let mut software_prefix_held: Option<String> = None;
let mut software_version_held: Option<String> = None;
let mut end_state = Vec::new();
let mut instruction_path = None;
let mut instruction_text = None;
let mut instruction_present = None;
let mut instruction_observed = None;
let (effects, backup_ref, restore_target_digest) = match request.operation {
Operation::SoftwareInstall | Operation::SoftwareUpdate | Operation::SoftwareRemove => {
let (planned, effects, version) = software::plan(
harness,
request.prefix.as_deref(),
request.operation,
request.software_version.as_deref(),
)?;
software_artifacts = planned;
software_prefix_held = request
.prefix
.as_ref()
.map(|prefix| prefix.to_string_lossy().into_owned());
software_version_held = Some(version.to_owned());
(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, &record, &resolved,
)?),
)
}
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::Reset => {
match ProviderState::read(resolved.root(), harness.state_file)? {
StateReading::Current(_) => {}
StateReading::Absent | StateReading::ForeignSchema { .. } => {
return Err(Error::refuse(
WireReason::UnsupportedOperation,
format!(
"reset empties declared namespaces whole, and {} holds no \
record of this provider writing here; refused rather than \
guessing whose files those are",
resolved.root().display()
),
));
}
}
(taken_before_reset(harness), None, None)
}
Operation::Install | Operation::Replace => (bundle_effects(harness, request)?, None, None),
Operation::PatchInstructionRegion => {
let (lines, path, text, present, observed) =
plan_instruction_patch(harness, &resolved, request)?;
instruction_path = Some(path);
instruction_text = Some(text);
instruction_present = Some(present);
instruction_observed = Some(observed);
(lines, None, None)
}
Operation::DetachInstructionRegion => {
let (lines, path, remainder, present, observed) =
plan_instruction_detach(harness, &resolved)?;
instruction_path = Some(path);
instruction_text = Some(remainder);
instruction_present = Some(present);
instruction_observed = Some(observed);
(lines, None, None)
}
other @ Operation::Launch => {
return Err(Error::refuse(
WireReason::UnsupportedOperation,
format!("{other} is not declared by this provider"),
));
}
};
let mut effects = effects;
if let Some(native) = &native_capture {
effects.push(format!(
"preserve complete native configuration under {}",
native.roots.join(", ")
));
if native.restore_digest.is_some() {
effects.push("replace the complete captured surface, including later user additions and shared native files".to_owned());
}
}
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,
native_capture,
permission_profile: request.permission_profile.clone(),
expires_at: &request.expires_at,
software_artifacts,
software_prefix: software_prefix_held.as_deref(),
software_version: software_version_held.as_deref(),
end_state,
instruction_path,
instruction_text,
instruction_observed_digest: instruction_observed,
instruction_observed_present: instruction_present,
effects,
})?
.into_response()
}
pub(crate) enum Removal {
Recorded,
NothingHere,
#[allow(dead_code)]
WouldTakeUnrecorded(Vec<String>),
}
pub(crate) fn classify_removal(
harness: &Harness,
target: &Target,
_scope: Option<crate::provider_v3::TargetScope>,
) -> Result<Removal> {
match ProviderState::read(target.root(), harness.state_file)? {
StateReading::Current(_) => Ok(Removal::Recorded),
StateReading::Absent | StateReading::ForeignSchema { .. } => Ok(Removal::NothingHere),
}
}
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 = owned_here(harness, target, scope)?;
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,
record: &SlotRecord,
target: &Target,
) -> Result<String> {
let complete = record.native_snapshot.as_ref();
let owned = if complete.is_some() {
record.previous_written_paths.clone().unwrap_or_default()
} else {
files_in_payload(payload)?
};
let identity_root = if complete.is_some_and(|snapshot| snapshot.base_root == NativeBase::Parent)
{
payload.join(target.root().file_name().ok_or_else(|| {
Error::refuse(
WireReason::UnsupportedNativeSurface,
"the native target has no leaf directory",
)
})?)
} else {
payload.to_path_buf()
};
Ok(crate::setup_core::digest::of_owned(
&identity_root,
&as_paths(&owned),
&harness.not_our_identity(),
)?)
}
fn inspect_native_surface(
harness: &Harness,
target: &Target,
scope: Option<crate::provider_v3::TargetScope>,
) -> Result<(std::path::PathBuf, NativeSnapshot)> {
let (roots, excluded) = harness.preservation_surface(scope);
if harness.harness_id == "claude-code"
&& scope.is_none()
&& target
.root()
.file_name()
.is_some_and(|name| name == ".claude")
{
let root = target.root().parent().ok_or_else(|| {
Error::refuse(
WireReason::UnsupportedNativeSurface,
"the Claude target has no companion directory",
)
})?;
let mut roots: Vec<String> = roots.iter().map(|path| format!(".claude/{path}")).collect();
roots.push(".claude.json".to_owned());
let excluded: Vec<String> = excluded
.iter()
.map(|path| format!(".claude/{path}"))
.collect();
let mut snapshot = NativeSnapshot::inspect(root, &as_paths(&roots), &as_paths(&excluded))?;
snapshot.base_root = NativeBase::Parent;
Ok((root.to_path_buf(), snapshot))
} else {
Ok((
target.root().to_path_buf(),
NativeSnapshot::inspect(target.root(), &roots, &excluded)?,
))
}
}
pub(crate) fn plan_native_capture(
harness: &Harness,
target: &Target,
scope: Option<crate::provider_v3::TargetScope>,
operation: Operation,
capture_mode: Option<&str>,
backup_ref: Option<&str>,
pool: &Pool,
) -> Result<Option<NativeCapture>> {
if capture_mode.is_some()
&& (capture_mode != Some("complete_native")
|| !matches!(
operation,
Operation::Backup
| Operation::Install
| Operation::Replace
| Operation::Remove
| Operation::Reset
))
{
return Err(Error::refuse(
WireReason::UnsupportedOperation,
"capture-mode complete_native is supported only for native configuration mutations",
));
}
let restore = if operation == Operation::Restore {
let record = chosen_backup(pool, backup_ref)?;
pool.payload_of(&record.backup_ref)?;
record.native_snapshot
} else {
None
};
if capture_mode.is_none() && restore.is_none() {
return Ok(None);
}
let (_, current) = inspect_native_surface(harness, target, scope)?;
if let Some(saved) = &restore
&& (saved.base_root != current.base_root
|| saved.roots != current.roots
|| saved.excluded != current.excluded)
{
return Err(Error::refuse(
WireReason::UnsupportedNativeSurface,
"the saved native surface differs from this provider's declared coverage",
));
}
Ok(Some(NativeCapture {
base_root: current.base_root,
current_digest: current.digest()?,
restore_digest: restore.map(|snapshot| snapshot.digest()).transpose()?,
roots: current.roots,
excluded: current.excluded,
}))
}
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,
ResetNamespaces,
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)>,
},
PatchInstruction {
path: String,
text: String,
observed_digest: String,
observed_present: bool,
},
DetachInstruction {
path: String,
remainder: String,
observed_digest: String,
observed_present: bool,
},
}
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 setup_version_passport_digest: 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>,
}
pub(crate) fn applied_from_catalog(setup: &Setup) -> Applied {
Applied {
setup_id: Some(setup.manifest.id.clone()),
setup_definition_digest: Some(setup.definition_digest.clone()),
setup_version: setup.manifest.setup_version.clone(),
setup_version_passport_digest: setup.manifest.setup_passport_digest.clone(),
component_refs: setup
.manifest
.component_refs
.iter()
.map(|item| item.stable_id.clone())
.collect(),
..Applied::default()
}
}
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)
}
#[allow(clippy::too_many_lines)]
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,
&artifact,
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::Reset => Effect::ResetNamespaces,
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,
}
}
Operation::PatchInstructionRegion => {
let path = declared_instruction_path(harness, &artifact)?;
let text = string_field(&artifact, "instruction_text")?;
let (observed_digest, observed_present) = instruction_observation(&artifact)?;
Effect::PatchInstruction {
path,
text,
observed_digest,
observed_present,
}
}
Operation::DetachInstructionRegion => {
let path = declared_instruction_path(harness, &artifact)?;
let remainder = string_field(&artifact, "instruction_text")?;
let (observed_digest, observed_present) = instruction_observation(&artifact)?;
Effect::DetachInstruction {
path,
remainder,
observed_digest,
observed_present,
}
}
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,
},
)
}
#[allow(clippy::too_many_lines)]
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_paths = if mutation.provenance.get("native_capture").is_some() {
owned.clone()
} else {
snapshot_if_unmanaged_backup(
harness,
&resolved,
mutation.target_scope,
&owned,
mutation.operation,
)?
};
let identity =
resolved.identity_of_owned(&as_paths(&identity_paths), &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",
));
}
let native_capture: Option<NativeCapture> = mutation
.provenance
.get("native_capture")
.map(|value| serde_json::from_value(value.clone()))
.transpose()
.map_err(|_| Error::refuse(WireReason::Stale, "invalid native capture binding"))?;
let restoring = match &mutation.effect {
Effect::Restore { backup_ref } => backup_ref.as_deref(),
_ => None,
};
let native_expected = plan_native_capture(
harness,
&resolved,
mutation.target_scope,
mutation.operation,
if native_capture.is_some() && mutation.operation != Operation::Restore {
Some("complete_native")
} else {
None
},
restoring,
&pool,
)?;
if native_capture != native_expected {
return Err(Error::refuse(
WireReason::Stale,
"complete native state changed after planning; 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)?;
let capture = capture_inventory(
harness,
&resolved,
mutation.target_scope,
&owned,
&mutation.effect,
)?;
refuse_uncapturable(&resolved, &capture)?;
refuse_an_unrecorded_removal(harness, &resolved, mutation)?;
let previous_provider_state = match ProviderState::read(resolved.root(), harness.state_file)? {
StateReading::Current(state) => Some(state),
_ => None,
};
let record_capture = |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(),
native_snapshot: None,
previous_written_paths: Some(previous_written.clone()),
previous_provider_state: previous_provider_state.clone(),
};
let selected_hold = if native_capture.is_some() {
restoring
.map(|reference| {
let reference = BackupRef::parse(reference)?;
let added = pool.hold(
&reference,
&format!("restore operation {}", mutation.operation_id),
)?;
Ok::<_, Error>((reference, added))
})
.transpose()?
} else {
None
};
let captured = if let Some(binding) = &native_capture {
let (source_root, snapshot) =
inspect_native_surface(harness, &resolved, mutation.target_scope)?;
if snapshot.digest()? != binding.current_digest {
return Err(Error::refuse(
WireReason::Stale,
"native state changed before capture; no target effect was made",
));
}
pool.capture_native(&source_root, &snapshot, record_capture)?
} else {
pool.capture(resolved.root(), &as_paths(&capture), record_capture)?
};
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 => {
if let Some(state) = &previous_provider_state {
copy_applied_identity(&mut applied, state);
}
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);
if let Some(state) = &record.previous_provider_state {
copy_applied_identity(&mut applied, state);
}
if let Some(snapshot) = &record.native_snapshot {
let (source_root, _) =
inspect_native_surface(harness, &resolved, mutation.target_scope)?;
snapshot.restore(&payload, &source_root)?;
Ok(record.previous_written_paths.clone().unwrap_or_default())
} else {
replace_managed_from(harness, &resolved, &payload, mutation.target_scope, false)
}
}
Effect::Remove => {
remove_managed(harness, &resolved, mutation.target_scope).map(|()| vec![])
}
Effect::ResetNamespaces => reset_namespaces(harness, &resolved).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,
true,
)
}
Effect::MaterializeBundle { files } => {
write_bundle_files(harness, &resolved, files, mutation.target_scope)
}
Effect::PatchInstruction {
path,
text,
observed_digest,
observed_present,
} => {
let destination = resolved.root().join(path);
let now_present = destination.exists();
let now = crate::harness_runtime::instruction_region::read_utf8(&destination).map_err(|error| {
Error::refuse(
WireReason::ProviderUnavailable,
format!(
"cannot re-read instruction surface {}: {error}",
destination.display()
),
)
})?;
let now_digest = crate::setup_core::digest::of_bytes(now.as_bytes());
if *observed_present != now_present || *observed_digest != now_digest {
return Err(Error::refuse(
WireReason::Stale,
"the instruction surface changed after the plan; no effect was made",
));
}
if let Some(parent) = destination.parent() {
lock::refuse_linked_descent(resolved.root(), parent).map_err(Error::from)?;
fs::create_dir_all(parent).map_err(|error| {
Error::from(
crate::setup_core::Error::new(
crate::setup_core::ReasonCode::StateUnavailable,
format!("cannot create {}", parent.display()),
)
.with_source(error),
)
})?;
}
lock::atomic_write(&destination, text.as_bytes()).map_err(Error::from)?;
Ok(previous_written.clone())
}
Effect::DetachInstruction {
path,
remainder,
observed_digest,
observed_present,
} => {
let destination = resolved.root().join(path);
let now_present = destination.exists();
let now = crate::harness_runtime::instruction_region::read_utf8(&destination).map_err(|error| {
Error::refuse(
WireReason::ProviderUnavailable,
format!(
"cannot re-read instruction surface {}: {error}",
destination.display()
),
)
})?;
let now_digest = crate::setup_core::digest::of_bytes(now.as_bytes());
if *observed_present != now_present || *observed_digest != now_digest {
return Err(Error::refuse(
WireReason::Stale,
"the instruction surface changed after the plan; no effect was made",
));
}
if *remainder == now {
Ok(previous_written.clone())
} else {
if let Some(parent) = destination.parent() {
lock::refuse_linked_descent(resolved.root(), parent).map_err(Error::from)?;
}
if remainder.is_empty() {
lock::remove_file(&destination).map_err(|error| {
Error::from(
crate::setup_core::Error::new(
crate::setup_core::ReasonCode::StateUnavailable,
format!("cannot remove {}", destination.display()),
)
.with_source(error),
)
})?;
} else {
lock::atomic_write(&destination, remainder.as_bytes()).map_err(Error::from)?;
}
Ok(previous_written.clone())
}
}
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 = applied.written_paths.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)?;
if let Some((reference, true)) = selected_hold {
pool.release(&reference)?;
}
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,
plan: &serde_json::Value,
downloaded: &[std::path::PathBuf],
) -> Result<serde_json::Value> {
let planned_prefix = string_field(plan, "software_prefix")?;
let planned_version = string_field(plan, "software_version")?;
let argv_prefix = prefix.ok_or_else(|| {
Error::refuse(
WireReason::ProviderUnavailable,
format!(
"{operation} installs a program, which lives under --prefix, not under --target; \
name an absolute --prefix"
),
)
})?;
if argv_prefix != Path::new(&planned_prefix) {
return Err(Error::refuse(
WireReason::Stale,
format!(
"this plan is bound to --prefix {planned_prefix}; --prefix {} is a different \
resource; no effect was made",
argv_prefix.display()
),
));
}
let planned_artifacts = planned_software_artifacts(plan)?;
let mut answer = software::apply(
harness,
prefix,
operation,
&planned_version,
&planned_artifacts,
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 {
let quarantined = pool.quarantine_partial()?;
return Ok(serde_json::json!({
"state": "verified",
"recovered": quarantined != 0,
"detail": if quarantined == 0 { "no journal is published; there is nothing to resolve" }
else { "incomplete captures were retained in the backup archive; the target was not changed" },
}));
};
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)?;
let record = chosen_backup(&pool, Some(reference))?;
if let Some(state) = &record.previous_provider_state
&& (state.canonical_target != resolved.root().to_string_lossy()
|| state.provider_id != harness.provider_id
|| state.harness_id != harness.harness_id)
{
return Err(Error::refuse(
WireReason::RecoveryRequired,
"saved provider metadata belongs to another target",
));
}
if let Some(snapshot) = &record.native_snapshot {
let (source_root, current) = inspect_native_surface(harness, &resolved, scope)?;
if current.base_root != snapshot.base_root
|| current.roots != snapshot.roots
|| current.excluded != snapshot.excluded
{
return Err(Error::refuse(
WireReason::RecoveryRequired,
"native recovery surface differs from this provider",
));
}
snapshot.restore(&payload, &source_root)?;
} else {
replace_managed_from(harness, &resolved, &payload, scope, false)?;
}
if let Some(state) = &record.previous_provider_state {
state.write(resolved.root(), harness.state_file)?;
} else if record.previous_written_paths.is_some() {
let state_path = resolved.root().join(harness.state_file);
if state_path.exists() {
lock::remove_file(&state_path).map_err(|error| {
Error::refuse(
WireReason::RecoveryRequired,
format!("cannot restore absent provider metadata: {error}"),
)
})?;
}
}
let owned = owned_here(harness, &resolved, scope)?;
Journal::clear(&control)?;
Ok(serde_json::json!({
"state": "verified",
"recovered": true,
"phase": Phase::Prepared.as_str(),
"restored_from": reference,
"target_digest": resolved.identity_of_owned(&as_paths(&owned), &harness.not_our_identity())?,
"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_digest": resolved.identity_of_owned(&as_paths(&owned), &harness.not_our_identity())?,
"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>,
merge_json: bool,
) -> Result<Vec<String>> {
replace_recorded_from(harness, target, payload, scope, merge_json)
}
fn replace_recorded_from(
harness: &Harness,
target: &Target,
payload: &Path,
scope: Option<crate::provider_v3::TargetScope>,
merge_json: bool,
) -> Result<Vec<String>> {
for relative in &owned_here(harness, target, scope)? {
withdraw_written(harness, target, relative, merge_json)?;
}
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)
})?;
}
write_host_file(harness, target, &name, &bytes, merge_json)?;
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();
if lock::is_staging_name(&name) {
continue;
}
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>> {
let incoming: Vec<String> = files.keys().cloned().collect();
for relative in &owned_here(harness, target, scope)? {
if incoming.iter().any(|path| path == relative) && json_object_file(relative) {
continue;
}
withdraw_written(harness, target, relative, true)?;
}
for (relative, (bytes, mode)) in files {
write_host_file(harness, target, relative, bytes, true)?;
set_mode(&target.root().join(relative), *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(())
}
pub(crate) fn owned_here(
harness: &Harness,
target: &Target,
scope: Option<crate::provider_v3::TargetScope>,
) -> Result<Vec<String>> {
match ProviderState::read(target.root(), harness.state_file)? {
StateReading::Current(state) => Ok(state.written_paths),
StateReading::Absent => Ok(Vec::new()),
StateReading::ForeignSchema { .. } => {
let named = harness.owned_projection(scope).join(", ");
Err(Error::refuse(
WireReason::UnsupportedOperation,
format!(
"an operation acts on the files this provider recorded writing, and {} \
holds a state file written before this build recorded them. Refused \
rather than widened to {named} whole. Reinstall to establish a record, \
or point --target at this product's own configuration home.",
target.root().display()
),
))
}
}
}
fn as_paths(owned: &[String]) -> Vec<&str> {
owned.iter().map(String::as_str).collect()
}
fn scope_recorded_at(
harness: &Harness,
target: &Target,
) -> Result<Option<crate::provider_v3::TargetScope>> {
let StateReading::Current(state) = ProviderState::read(target.root(), harness.state_file)?
else {
return Ok(None);
};
Ok(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<()> {
for relative in &owned_here(harness, target, scope)? {
withdraw_written(harness, target, relative, true)?;
}
Ok(())
}
#[cfg_attr(not(test), allow(dead_code))]
fn reset_namespaces(harness: &Harness, target: &Target) -> Result<()> {
for namespace in harness.native_namespaces {
remove_keeping(
&target.root().join(namespace),
target.root(),
harness.never_touch,
)?;
}
forget_all_written_fields(harness, target);
Ok(())
}
fn capture_inventory(
harness: &Harness,
target: &Target,
scope: Option<crate::provider_v3::TargetScope>,
owned: &[String],
effect: &Effect<'_>,
) -> Result<Vec<String>> {
match effect {
Effect::ResetNamespaces => Ok(harness
.owned_projection(scope)
.iter()
.map(|name| (*name).to_owned())
.collect()),
Effect::Backup
if owned.is_empty()
&& matches!(
ProviderState::read(target.root(), harness.state_file)?,
StateReading::Absent
) =>
{
existing_under_projection(harness, target, scope)
}
Effect::MaterializeBundle { files } => {
let mut paths = owned.to_vec();
for path in files.keys() {
if target.root().join(path).exists() && !paths.iter().any(|held| held == path) {
paths.push(path.clone());
}
}
paths.sort();
Ok(paths)
}
Effect::Materialize { setup } => {
let mut paths = owned.to_vec();
overlay_payload_existing(target.root(), &setup.payload, &mut paths)?;
paths.sort();
Ok(paths)
}
_ => Ok(owned.to_vec()),
}
}
fn existing_under_projection(
harness: &Harness,
target: &Target,
scope: Option<crate::provider_v3::TargetScope>,
) -> Result<Vec<String>> {
let mut found = Vec::new();
for namespace in harness.owned_projection(scope) {
let path = target.root().join(namespace);
let meta = match fs::symlink_metadata(&path) {
Ok(meta) => meta,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => continue,
Err(error) => {
return Err(Error::from(
crate::setup_core::Error::new(
crate::setup_core::ReasonCode::StateUnavailable,
format!("cannot read {}", path.display()),
)
.with_source(error),
));
}
};
if meta.file_type().is_symlink() || !meta.is_dir() {
found.push((*namespace).to_owned());
} else {
found.extend(files_under_nofollow(&path, namespace)?);
}
}
found.sort();
Ok(found)
}
fn files_under_nofollow(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| {
Error::from(
crate::setup_core::Error::new(
crate::setup_core::ReasonCode::StateUnavailable,
format!("cannot read {}", directory.display()),
)
.with_source(error),
)
})?;
for entry in entries {
let entry = entry.map_err(|error| {
Error::from(
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();
if lock::is_staging_name(&name) {
continue;
}
let relative = format!("{prefix}/{name}");
let meta = fs::symlink_metadata(entry.path()).map_err(|error| {
Error::from(
crate::setup_core::Error::new(
crate::setup_core::ReasonCode::StateUnavailable,
format!("cannot read {}", entry.path().display()),
)
.with_source(error),
)
})?;
if meta.file_type().is_symlink() || !meta.is_dir() {
found.push(relative);
} else {
pending.push((entry.path(), relative));
}
}
}
Ok(found)
}
pub(crate) fn snapshot_if_unmanaged_backup(
harness: &Harness,
target: &Target,
scope: Option<crate::provider_v3::TargetScope>,
owned: &[String],
operation: Operation,
) -> Result<Vec<String>> {
if operation == Operation::Backup
&& owned.is_empty()
&& matches!(
ProviderState::read(target.root(), harness.state_file)?,
StateReading::Absent
)
{
existing_under_projection(harness, target, scope)
} else {
Ok(owned.to_vec())
}
}
fn overlay_payload_existing(root: &Path, payload: &Path, paths: &mut Vec<String>) -> Result<()> {
let entries = match fs::read_dir(payload) {
Ok(entries) => entries,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(()),
Err(error) => {
return Err(Error::from(
crate::setup_core::Error::new(
crate::setup_core::ReasonCode::StateUnavailable,
format!("cannot list {}", payload.display()),
)
.with_source(error),
));
}
};
for entry in entries.flatten() {
let Some(name) = entry.file_name().to_str().map(str::to_owned) else {
continue;
};
if root.join(&name).exists() && !paths.contains(&name) {
paths.push(name);
}
}
Ok(())
}
fn json_object_file(relative: &str) -> bool {
Path::new(relative)
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("json"))
}
fn json_top_keys(bytes: &[u8]) -> Option<Vec<String>> {
match serde_json::from_slice::<serde_json::Value>(bytes).ok()? {
serde_json::Value::Object(object) => Some(object.keys().cloned().collect()),
_ => None,
}
}
fn merge_json_objects(existing: &[u8], incoming: &[u8]) -> Option<Vec<u8>> {
let serde_json::Value::Object(mut base) = serde_json::from_slice(existing).ok()? else {
return None;
};
let serde_json::Value::Object(add) = serde_json::from_slice(incoming).ok()? else {
return None;
};
for (key, value) in add {
base.insert(key, value);
}
serde_json::to_vec(&serde_json::Value::Object(base)).ok()
}
fn written_fields_path(harness: &Harness, target: &Target) -> PathBuf {
target
.root()
.join(harness.control_directory)
.join("written-fields.json")
}
fn remember_written_fields(
harness: &Harness,
target: &Target,
relative: &str,
keys: Vec<String>,
) -> Result<()> {
let path = written_fields_path(harness, target);
if let Some(parent) = path.parent() {
lock::refuse_linked_descent(target.root(), parent).map_err(Error::from)?;
fs::create_dir_all(parent).map_err(|error| {
Error::from(
crate::setup_core::Error::new(
crate::setup_core::ReasonCode::StateUnavailable,
format!("cannot create {}", parent.display()),
)
.with_source(error),
)
})?;
}
let mut map = read_written_fields(&path)?;
map.insert(relative.to_owned(), keys);
let bytes = serde_json::to_vec(&map).map_err(|error| {
Error::from(
crate::setup_core::Error::new(
crate::setup_core::ReasonCode::StateUnavailable,
"cannot encode written-fields",
)
.with_source(error),
)
})?;
lock::atomic_write(&path, &bytes).map_err(Error::from)
}
fn read_written_fields(path: &Path) -> Result<BTreeMap<String, Vec<String>>> {
let bytes = match fs::read(path) {
Ok(bytes) => bytes,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
return Ok(BTreeMap::new());
}
Err(error) => {
return Err(Error::from(
crate::setup_core::Error::new(
crate::setup_core::ReasonCode::StateUnavailable,
format!("cannot read {}", path.display()),
)
.with_source(error),
));
}
};
serde_json::from_slice(&bytes).map_err(|error| {
Error::from(
crate::setup_core::Error::new(
crate::setup_core::ReasonCode::StateUnavailable,
format!("cannot parse {}", path.display()),
)
.with_source(error),
)
})
}
fn forget_written_fields(harness: &Harness, target: &Target, relative: &str) {
let path = written_fields_path(harness, target);
let Ok(mut map) = read_written_fields(&path) else {
return;
};
if map.remove(relative).is_some() {
if map.is_empty() {
let _ = lock::remove_file(&path);
} else if let Ok(bytes) = serde_json::to_vec(&map) {
if let Some(parent) = path.parent()
&& lock::refuse_linked_descent(target.root(), parent).is_err()
{
return;
}
let _ = lock::atomic_write(&path, &bytes);
}
}
}
fn forget_all_written_fields(harness: &Harness, target: &Target) {
let _ = lock::remove_file(&written_fields_path(harness, target));
}
fn write_host_file(
harness: &Harness,
target: &Target,
relative: &str,
bytes: &[u8],
merge_json: bool,
) -> Result<()> {
let destination = target.root().join(relative);
let outgoing = if merge_json && json_object_file(relative) && destination.exists() {
let existing = fs::read(&destination).map_err(|error| {
Error::from(
crate::setup_core::Error::new(
crate::setup_core::ReasonCode::StateUnavailable,
format!("cannot read {}", destination.display()),
)
.with_source(error),
)
})?;
match merge_json_objects(&existing, bytes) {
Some(merged) => merged,
None if serde_json::from_slice::<serde_json::Value>(&existing).is_err() => {
return Err(Error::refuse(
WireReason::ProviderUnavailable,
format!(
"{} does not parse as JSON; refused rather than overwriting it whole",
destination.display()
),
));
}
None => bytes.to_vec(),
}
} else {
bytes.to_vec()
};
let outgoing = if crate::harness_runtime::instruction_region::is_attachment(relative, harness.instruction_region)
{
let existing = read_instruction_text(&destination)?;
let incoming = std::str::from_utf8(&outgoing).map_err(|error| {
Error::refuse(
WireReason::ProviderUnavailable,
format!(
"instruction surface {} is not UTF-8: {error}",
destination.display()
),
)
})?;
if !crate::harness_runtime::instruction_region::markers_well_formed(incoming) {
return Err(Error::refuse(
WireReason::ProviderUnavailable,
format!(
"instruction surface {} has ambiguous markers",
destination.display()
),
));
}
crate::harness_runtime::instruction_region::preserve_in_replacement(&existing, incoming).into_bytes()
} else {
outgoing
};
if merge_json && let Some(keys) = json_top_keys(bytes) {
remember_written_fields(harness, target, relative, keys)?;
}
if let Some(parent) = destination.parent() {
lock::refuse_linked_descent(target.root(), parent).map_err(Error::from)?;
fs::create_dir_all(parent).map_err(|error| {
Error::from(
crate::setup_core::Error::new(
crate::setup_core::ReasonCode::StateUnavailable,
format!("cannot create {}", parent.display()),
)
.with_source(error),
)
})?;
}
lock::atomic_write(&destination, &outgoing).map_err(Error::from)
}
fn withdraw_written(
harness: &Harness,
target: &Target,
relative: &str,
preserve_json_keys: bool,
) -> Result<()> {
let destination = target.root().join(relative);
if preserve_json_keys && json_object_file(relative) {
let path = written_fields_path(harness, target);
if let Some(keys) = read_written_fields(&path)?.get(relative).cloned() {
if keys.is_empty() {
forget_written_fields(harness, target, relative);
return remove_keeping(&destination, target.root(), harness.never_touch);
}
if strip_json_keys(&destination, target.root(), &keys)? {
forget_written_fields(harness, target, relative);
return Ok(());
}
}
}
if preserve_json_keys {
forget_written_fields(harness, target, relative);
}
if crate::harness_runtime::instruction_region::is_attachment(relative, harness.instruction_region) {
let existing = read_instruction_text(&destination)?;
if let Some(region) = crate::harness_runtime::instruction_region::keep_region_on_withdraw(&existing) {
if let Some(parent) = destination.parent() {
lock::refuse_linked_descent(target.root(), parent).map_err(Error::from)?;
}
lock::atomic_write(&destination, region.as_bytes()).map_err(Error::from)?;
return Ok(());
}
}
remove_keeping(&destination, target.root(), harness.never_touch)
}
fn read_instruction_text(path: &Path) -> Result<String> {
let existing = crate::harness_runtime::instruction_region::read_utf8(path).map_err(|error| {
Error::refuse(
WireReason::ProviderUnavailable,
format!(
"cannot read instruction surface {}: {error}",
path.display()
),
)
})?;
if !crate::harness_runtime::instruction_region::markers_well_formed(&existing) {
return Err(Error::refuse(
WireReason::ProviderUnavailable,
format!(
"instruction surface {} has ambiguous markers",
path.display()
),
));
}
Ok(existing)
}
fn strip_json_keys(path: &Path, root: &Path, keys: &[String]) -> Result<bool> {
let bytes = match fs::read(path) {
Ok(bytes) => bytes,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(false),
Err(error) => {
return Err(Error::from(
crate::setup_core::Error::new(
crate::setup_core::ReasonCode::StateUnavailable,
format!("cannot read {}", path.display()),
)
.with_source(error),
));
}
};
let Ok(serde_json::Value::Object(mut object)) = serde_json::from_slice(&bytes) else {
return Err(Error::refuse(
WireReason::ProviderUnavailable,
format!(
"{} does not parse as a JSON object; refused rather than removing it whole",
path.display()
),
));
};
for key in keys {
object.remove(key);
}
if object.is_empty() {
remove_path(path, root)?;
} else {
let encoded = serde_json::to_vec(&serde_json::Value::Object(object)).map_err(|error| {
Error::from(
crate::setup_core::Error::new(
crate::setup_core::ReasonCode::StateUnavailable,
format!("cannot encode {}", path.display()),
)
.with_source(error),
)
})?;
if let Some(parent) = path.parent() {
lock::refuse_linked_descent(root, parent).map_err(Error::from)?;
}
lock::atomic_write(path, &encoded).map_err(Error::from)?;
}
Ok(true)
}
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);
if let Some(parent) = destination.parent() {
lock::refuse_linked_descent(target.root(), parent).map_err(Error::from)?;
}
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, root);
}
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, root)
};
}
let Ok(entries) = fs::read_dir(path) else {
return Ok(());
};
for entry in entries.flatten() {
remove_keeping(&entry.path(), root, spared)?;
}
if let Some(parent) = path.parent() {
lock::refuse_linked_descent(root, parent).map_err(Error::from)?;
}
let _ = lock::remove_dir(path);
Ok(())
}
fn remove_path(path: &Path, root: &Path) -> Result<()> {
if let Some(parent) = path.parent() {
lock::refuse_linked_descent(root, parent).map_err(Error::from)?;
}
let Ok(metadata) = fs::symlink_metadata(path) else {
return Ok(());
};
let outcome = if metadata.is_dir() {
lock::remove_dir_all(path)
} else {
lock::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 copy_applied_identity(applied: &mut Applied, state: &ProviderState) {
applied.setup_id.clone_from(&state.setup_stable_id);
applied.setup_version.clone_from(&state.setup_version);
applied
.setup_version_passport_digest
.clone_from(&state.setup_version_passport_digest);
applied
.setup_definition_digest
.clone_from(&state.setup_definition_digest);
applied.component_refs.clone_from(&state.component_refs);
applied.bundle_format.clone_from(&state.bundle_format);
applied.bundle_digest.clone_from(&state.bundle_digest);
applied.artifact_digest.clone_from(&state.artifact_digest);
}
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: applied.setup_version_passport_digest.clone(),
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}"),
)
})
}
fn declared_instruction_path(harness: &Harness, artifact: &serde_json::Value) -> Result<String> {
let path = string_field(artifact, "instruction_path")?;
let Some(declared) = harness.instruction_region else {
return Err(Error::refuse(
WireReason::UnsupportedOperation,
format!(
"{} does not declare a user-global instruction surface",
harness.provider_id
),
));
};
if path != declared {
return Err(Error::refuse(
WireReason::ProviderUnavailable,
format!(
"the plan artifact names instruction path {path:?}, not the \
{declared:?} this provider declares"
),
));
}
Ok(path)
}
fn instruction_observation(artifact: &serde_json::Value) -> Result<(String, bool)> {
let digest = string_field(artifact, "instruction_observed_digest")?;
let present = artifact
.get("instruction_observed_present")
.and_then(serde_json::Value::as_bool)
.ok_or_else(|| {
Error::refuse(
WireReason::ProviderUnavailable,
"the plan artifact has no instruction_observed_present",
)
})?;
Ok((digest, present))
}
fn planned_software_artifacts(
plan: &serde_json::Value,
) -> Result<Vec<crate::provider_v3::plan::SoftwareArtifact>> {
match plan.get("software_artifacts") {
None | Some(serde_json::Value::Null) => Ok(Vec::new()),
Some(value) => serde_json::from_value(value.clone()).map_err(|source| {
Error::refuse(
WireReason::ProviderUnavailable,
format!("the plan artifact has no usable software_artifacts: {source}"),
)
}),
}
}
#[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: &[],
preservation_surfaces: &[],
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}"#,
instruction_region: Some("AGENTS.md"),
};
}
#[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_for_default_remove_names_recorded_files_not_namespaces_whole() {
let target = seeded("effects-name-what-goes");
install_global(&target, "effects", &[("AGENTS.md", "# ours\n", 0o644)]);
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");
assert!(
text.contains("only the files this provider recorded writing"),
"the plan does not name recorded files: {effects:?}"
);
assert!(
!text.contains("go whole, not file by file"),
"default remove still claimed namespaces go whole: {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 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!(
target.join("AGENTS.md").exists(),
"unrecorded scoped remove took a file this provider never wrote"
);
}
#[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()
}
fn install_global(target: &Path, tag: &str, files: &[(&str, &str, u32)]) {
let (bytes, bundle_digest, artifact) =
bundle_bytes_for(&TEST, None, files, Some("instruction"));
let artifact_path = target.join("..").join(format!("global-{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_01GLOBALIN{}", tag.to_uppercase()),
"--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(format!("global-{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 replace_global(target: &Path, tag: &str, files: &[(&str, &str, u32)]) {
let (bytes, bundle_digest, artifact) =
bundle_bytes_for(&TEST, None, files, Some("instruction"));
let artifact_path = target.join("..").join(format!("global-{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(),
"replace".to_owned(),
"--provider-release-digest".to_owned(),
RELEASE.to_owned(),
"--operation-id".to_owned(),
format!("operation_01GLOBALRP{}", tag.to_uppercase()),
"--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!(
!planned["plan"]["effects"]
.to_string()
.contains("go whole, not file by file"),
"replace plan claimed namespaces go whole: {}",
planned["plan"]["effects"]
);
let plan_path = target.join("..").join(format!("global-{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}");
}
#[test]
#[allow(clippy::too_many_lines)]
fn global_composition_leaves_unrecorded_files_dirs_and_keys_and_reset_clears_them() {
let target = seeded("global-receipts");
fs::create_dir_all(target.join("skills").join("person")).unwrap();
fs::write(
target.join("skills").join("person").join("SKILL.md"),
"theirs\n",
)
.unwrap();
fs::write(
target.join("settings.json"),
r#"{"theme":"dark","model":"first"}"#,
)
.unwrap();
install_global(
&target,
"one",
&[
("AGENTS.md", "# ours\n", 0o644),
("settings.json", r#"{"model":"ours"}"#, 0o644),
],
);
assert_eq!(
fs::read_to_string(target.join("skills").join("person").join("SKILL.md")).unwrap(),
"theirs\n"
);
let settings: serde_json::Value =
serde_json::from_slice(&fs::read(target.join("settings.json")).unwrap()).unwrap();
assert_eq!(settings["theme"], "dark");
assert_eq!(settings["model"], "ours");
assert_eq!(
fs::read_to_string(target.join("unrelated.txt")).unwrap(),
"keep me"
);
replace_global(
&target,
"two",
&[
("AGENTS.md", "# next\n", 0o644),
("settings.json", r#"{"model":"next"}"#, 0o644),
],
);
assert_eq!(
fs::read_to_string(target.join("AGENTS.md")).unwrap(),
"# next\n"
);
assert_eq!(
fs::read_to_string(target.join("skills").join("person").join("SKILL.md")).unwrap(),
"theirs\n"
);
let settings: serde_json::Value =
serde_json::from_slice(&fs::read(target.join("settings.json")).unwrap()).unwrap();
assert_eq!(settings["theme"], "dark");
assert_eq!(settings["model"], "next");
let restored = plan_then_apply(&target, "restore", &[]);
assert_eq!(restored["state"], "verified", "{restored}");
assert_eq!(
fs::read_to_string(target.join("skills").join("person").join("SKILL.md")).unwrap(),
"theirs\n",
"restore after replace took an unrecorded nested file"
);
let removed = plan_then_apply(&target, "remove", &[]);
assert_eq!(removed["state"], "verified", "{removed}");
assert!(!target.join("AGENTS.md").exists());
assert!(
target
.join("skills")
.join("person")
.join("SKILL.md")
.exists(),
"remove took an unrecorded nested directory"
);
let settings: serde_json::Value =
serde_json::from_slice(&fs::read(target.join("settings.json")).unwrap()).unwrap();
assert_eq!(settings["theme"], "dark");
assert!(settings.get("model").is_none());
install_global(&target, "again", &[("AGENTS.md", "# again\n", 0o644)]);
let reset_plan = run(args(
"plan-operation",
&target,
&[
"--operation",
"reset",
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01RESETTEXT",
"--expires-at",
far_future(),
],
));
assert_eq!(reset_plan["state"], "planned", "{reset_plan}");
let reset_text = reset_plan["plan"]["effects"].to_string();
assert!(
reset_text.contains("go whole, not file by file"),
"reset plan did not name whole namespaces: {reset_text}"
);
let reset = plan_then_apply(&target, "reset", &[]);
assert_eq!(reset["state"], "verified", "{reset}");
assert!(
!target
.join("skills")
.join("person")
.join("SKILL.md")
.exists(),
"reset left an unrecorded nested directory"
);
assert!(
!target.join("settings.json").exists(),
"reset left the settings file"
);
assert_eq!(
fs::read_to_string(target.join("unrelated.txt")).unwrap(),
"keep me"
);
}
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 status_reports_the_instruction_attachment_or_its_absence() {
const BARE: Harness = Harness {
instruction_region: None,
..TEST
};
let target = seeded("status-attachment");
let bare = dispatch(&BARE, argv::parse(args("status", &target, &[])).unwrap()).unwrap();
assert!(
bare["instruction_region"].is_null(),
"a harness without the surface must answer null, not a state: {bare}"
);
fs::remove_file(target.join("AGENTS.md")).unwrap();
let missing = run(args("status", &target, &[]));
assert_eq!(missing["instruction_region"]["path"], "AGENTS.md");
assert_eq!(missing["instruction_region"]["present"], false);
assert_eq!(missing["instruction_region"]["section_present"], false);
assert!(missing["instruction_region"]["section_sha256"].is_null());
let target = seeded("status-attachment-file");
let plain = run(args("status", &target, &[]));
assert_eq!(plain["instruction_region"]["present"], true);
assert_eq!(plain["instruction_region"]["section_present"], false);
assert!(plain["instruction_region"]["section_sha256"].is_null());
plan_then_apply(
&target,
"patch_instruction_region",
&["--instruction-section", INSTRUCTION_SECTION],
);
let attached = run(args("status", &target, &[]));
assert_eq!(attached["instruction_region"]["section_present"], true);
let digest = attached["instruction_region"]["section_sha256"]
.as_str()
.unwrap();
assert_eq!(
digest,
crate::setup_core::digest::of_bytes(INSTRUCTION_SECTION.as_bytes()),
"section_sha256 must be the region digest: {attached}"
);
fs::write(
target.join("AGENTS.md"),
b":::end-ai-stp\n:::begin-ai-stp\n",
)
.unwrap();
let ambiguous = run(args("status", &target, &[]));
assert_eq!(ambiguous["instruction_region"]["present"], true);
assert_eq!(ambiguous["instruction_region"]["section_present"], false);
}
#[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, true).unwrap();
assert!(inside.exists(), "composition took an unrecorded host file");
assert!(beside.exists(), "composition took an unrecorded sibling");
assert!(
target.join("skills").join("ours.md").exists(),
"our own payload did not land beside it"
);
reset_namespaces(&TEST, &resolved).unwrap();
assert!(!inside.exists(), "reset left an unrecorded host file");
assert!(!beside.exists(), "reset left an unrecorded sibling");
fs::create_dir_all(inside.parent().unwrap()).unwrap();
fs::write(&inside, b"a person's own file, inside a namespace we own").unwrap();
fs::write(&beside, b"an ordinary sibling").unwrap();
reset_namespaces(&SPARES, &resolved).unwrap();
assert!(inside.exists(), "the named path was taken anyway");
assert!(!beside.exists(), "a sibling nothing names survived");
}
#[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");
install_global(&target, "drifted", &[("AGENTS.md", "# ours\n", 0o644)]);
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");
install_global(&target, "id", &[("AGENTS.md", "# ours\n", 0o644)]);
let before = run(args("status", &target, &[]))["target_identity_digest"].clone();
fs::write(target.join("AGENTS.md"), "# edited\n").unwrap();
let edited = run(args("status", &target, &[]))["target_identity_digest"].clone();
assert_ne!(
before, edited,
"an edit of a recorded file left the identity alone"
);
fs::write(target.join("skills").join("a.md"), "edited extra").unwrap();
let extra = run(args("status", &target, &[]))["target_identity_digest"].clone();
assert_eq!(edited, extra, "an unrecorded extra file moved the identity");
}
#[test]
fn drift_inside_an_owned_namespace_is_still_reported() {
let target = seeded("identity-drift");
install_global(&target, "drift", &[("AGENTS.md", "# ours\n", 0o644)]);
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("skills").join("extra.md"), "theirs").unwrap();
assert_eq!(
run(args("status", &target, &[]))["provider_state"]["drift_state"],
"clean",
"an unrecorded extra file 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");
install_global(&target, "overlay", &[("AGENTS.md", "# first\n", 0o644)]);
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));
let said = error.to_string();
assert!(said.contains("one.md"), "{said}");
assert!(said.contains("two"), "{said}");
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 = ready_prefix(&target);
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,
"--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");
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!(
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 complete_native_return_preserves_user_additions_and_restores_empty_directories() {
let target = seeded("complete-return");
plan_then_apply(&target, "backup", &[]);
fs::create_dir_all(target.join("skills/empty")).unwrap();
fs::write(target.join("skills/user.py"), b"print('user')\n").unwrap();
let baseline =
NativeSnapshot::inspect(&target, TEST.native_namespaces, &TEST.never_captured())
.unwrap();
let saved = plan_then_apply(&target, "backup", &["--capture-mode", "complete_native"]);
let reference = saved["backup_ref"].as_str().unwrap();
fs::write(target.join("skills/new.sh"), b"echo new\n").unwrap();
fs::write(target.join("AGENTS.md"), b"new instructions").unwrap();
fs::remove_dir(target.join("skills/empty")).unwrap();
let edited =
NativeSnapshot::inspect(&target, TEST.native_namespaces, &TEST.never_captured())
.unwrap();
let restored = plan_then_apply(&target, "restore", &["--backup-ref", reference]);
assert_eq!(restored["state"], "verified");
baseline.verify(&target).unwrap();
assert!(!target.join("skills/new.sh").exists());
assert_eq!(fs::read(target.join("unrelated.txt")).unwrap(), b"keep me");
let edited_ref = restored["backup_ref"].as_str().unwrap();
plan_then_apply(&target, "restore", &["--backup-ref", edited_ref]);
edited.verify(&target).unwrap();
}
#[test]
fn complete_native_capture_rejects_changes_outside_the_written_inventory() {
let target = seeded("complete-stale");
plan_then_apply(&target, "backup", &[]);
let planned = run(args(
"plan-operation",
&target,
&[
"--operation",
"backup",
"--capture-mode",
"complete_native",
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01NATIVE",
"--expires-at",
far_future(),
],
));
let path = target.parent().unwrap().join("native-plan.json");
fs::write(
&path,
crate::setup_core::canonical::to_canonical_bytes(&planned["plan"]).unwrap(),
)
.unwrap();
fs::write(target.join("skills/new.py"), b"user addition").unwrap();
let error = refuse(args(
"apply-operation",
&target,
&[
"--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(),
1
);
}
#[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(),
"restore took a file this provider never recorded writing"
);
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");
install_global(
&target,
"owned",
&[
("AGENTS.md", "# ours\n", 0o644),
("settings.json", r#"{"model":"ours"}"#, 0o644),
],
);
fs::write(target.join("skills").join("person.md"), "theirs\n").unwrap();
assert_eq!(plan_then_apply(&target, "remove", &[])["state"], "verified");
assert!(!target.join("AGENTS.md").exists());
assert!(
target.join("skills").join("person.md").exists(),
"remove took an unrecorded skill file"
);
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 complete_native_recovery_rewinds_a_state_written_before_journal_commit() {
for managed in [false, true] {
let target = seeded(if managed {
"recover-native-managed"
} else {
"recover-native-unmanaged"
});
if managed {
plan_then_apply(&target, "backup", &[]);
let StateReading::Current(mut state) =
ProviderState::read(&target, TEST.state_file).unwrap()
else {
panic!("state missing");
};
state.setup_version = Some("1.7".to_owned());
state.component_refs = vec!["component_original@1.0".to_owned()];
state.write(&target, TEST.state_file).unwrap();
}
let state_path = target.join(TEST.state_file);
let original_state = fs::read(&state_path).ok();
let original = fs::read(target.join("AGENTS.md")).unwrap();
let saved = plan_then_apply(&target, "backup", &["--capture-mode", "complete_native"]);
let StateReading::Current(mut state) =
ProviderState::read(&target, TEST.state_file).unwrap()
else {
panic!("state missing");
};
if managed {
assert_eq!(state.setup_version.as_deref(), Some("1.7"));
assert_eq!(state.component_refs, vec!["component_original@1.0"]);
}
state.setup_version = Some("9.9".to_owned());
state.written_paths = vec!["skills/unrecorded".to_owned()];
state.write(&target, TEST.state_file).unwrap();
fs::write(target.join("AGENTS.md"), b"half-written").unwrap();
fs::write(target.join("skills/unrecorded"), b"partial new file").unwrap();
Journal {
schema_version: JOURNAL_SCHEMA,
phase: Phase::Prepared,
operation_id: state.operation_id,
operation: "backup".to_owned(),
plan_digest: saved["plan_digest"].as_str().unwrap().to_owned(),
target_precondition_digest: saved["expected_target_digest"]
.as_str()
.unwrap()
.to_owned(),
backup_ref: Some(saved["backup_ref"].as_str().unwrap().to_owned()),
target_scope: None,
}
.publish_prepared(&target.join(TEST.control_directory))
.unwrap();
let recovered = run(args("recover-operation", &target, &[]));
assert_eq!(recovered["state"], "verified");
assert_eq!(recovered["target_digest"], saved["expected_target_digest"]);
assert_eq!(fs::read(&state_path).ok(), original_state);
assert_eq!(fs::read(target.join("AGENTS.md")).unwrap(), original);
assert!(!target.join("skills/unrecorded").exists());
}
}
#[test]
fn non_native_recovery_rewinds_the_state_written_before_journal_commit() {
let target = seeded("recover-written-managed");
plan_then_apply(&target, "backup", &[]);
plan_then_apply(&target, "restore", &[]);
let StateReading::Current(mut state) =
ProviderState::read(&target, TEST.state_file).unwrap()
else {
panic!("state missing");
};
state.setup_version = Some("1.7".to_owned());
state.component_refs = vec!["component_original@1.0".to_owned()];
state.write(&target, TEST.state_file).unwrap();
let state_path = target.join(TEST.state_file);
let original_state = fs::read(&state_path).unwrap();
let original = fs::read(target.join("AGENTS.md")).unwrap();
let saved = plan_then_apply(&target, "backup", &[]);
assert_eq!(saved["state"], "verified", "{saved}");
let StateReading::Current(mut drifted) =
ProviderState::read(&target, TEST.state_file).unwrap()
else {
panic!("state missing");
};
drifted.setup_version = Some("9.9".to_owned());
drifted.written_paths = vec!["skills/unrecorded".to_owned()];
drifted.write(&target, TEST.state_file).unwrap();
fs::write(target.join("AGENTS.md"), b"half-written").unwrap();
fs::write(target.join("skills/unrecorded"), b"partial new file").unwrap();
Journal {
schema_version: JOURNAL_SCHEMA,
phase: Phase::Prepared,
operation_id: drifted.operation_id,
operation: "backup".to_owned(),
plan_digest: saved["plan_digest"].as_str().unwrap().to_owned(),
target_precondition_digest: saved["expected_target_digest"]
.as_str()
.unwrap()
.to_owned(),
backup_ref: Some(saved["backup_ref"].as_str().unwrap().to_owned()),
target_scope: None,
}
.publish_prepared(&target.join(TEST.control_directory))
.unwrap();
let recovered = run(args("recover-operation", &target, &[]));
assert_eq!(recovered["state"], "verified");
assert_eq!(
fs::read(&state_path).unwrap(),
original_state,
"the pre-operation provider record did not come back with its payload"
);
assert_eq!(fs::read(target.join("AGENTS.md")).unwrap(), original);
assert!(!target.join("skills/unrecorded").exists());
}
#[test]
fn non_native_recovery_removes_a_state_the_pre_operation_target_never_had() {
let target = seeded("recover-written-unmanaged");
let saved = plan_then_apply(&target, "backup", &[]);
assert_eq!(saved["state"], "verified", "{saved}");
let state_path = target.join(TEST.state_file);
assert!(state_path.exists(), "a backup still writes provider state");
Journal {
schema_version: JOURNAL_SCHEMA,
phase: Phase::Prepared,
operation_id: "op_interrupted".to_owned(),
operation: "backup".to_owned(),
plan_digest: saved["plan_digest"].as_str().unwrap().to_owned(),
target_precondition_digest: saved["expected_target_digest"]
.as_str()
.unwrap()
.to_owned(),
backup_ref: Some(saved["backup_ref"].as_str().unwrap().to_owned()),
target_scope: None,
}
.publish_prepared(&target.join(TEST.control_directory))
.unwrap();
let recovered = run(args("recover-operation", &target, &[]));
assert_eq!(recovered["state"], "verified");
assert!(
!state_path.exists(),
"a record the pre-operation target never had survived recovery"
);
}
#[test]
fn planning_a_fresh_target_creates_nothing_in_it() {
let base = scratch("plan-reads-only");
let target = base.join("target");
let planned = run(args(
"plan-operation",
&target,
&[
"--operation",
"backup",
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01TEST",
"--expires-at",
far_future(),
],
));
assert_eq!(planned["state"], "planned", "{planned}");
assert_eq!(
fs::read_dir(&target).unwrap().count(),
0,
"asking left a directory behind"
);
}
#[test]
fn an_orphaned_staging_companion_does_not_move_the_target_identity() {
let target = seeded("staging-invisible");
let before = run(args("status", &target, &[]))["target_identity_digest"].clone();
fs::write(target.join(".AGENTS.md.staging"), b"half a write").unwrap();
fs::write(target.join("skills").join(".a.md.staging"), b"half a write").unwrap();
let after = run(args("status", &target, &[]))["target_identity_digest"].clone();
assert_eq!(
before, after,
"an interrupted write's leftover moved the identity it was measured under"
);
plan_then_apply(&target, "backup", &[]);
let StateReading::Current(state) = ProviderState::read(&target, TEST.state_file).unwrap()
else {
panic!("state missing");
};
assert!(
state
.written_paths
.iter()
.all(|path| !path.contains(".staging")),
"the capture recorded an in-flight write: {:?}",
state.written_paths
);
}
#[cfg(unix)]
#[test]
fn withdraw_refuses_to_delete_through_a_swapped_directory_link() {
let target = seeded("linked-withdraw");
plan_then_apply(&target, "backup", &[]);
plan_then_apply(&target, "restore", &[]);
let outside = target.join("..").join("outside");
fs::create_dir_all(&outside).unwrap();
fs::write(outside.join("a.md"), b"not ours").unwrap();
fs::remove_dir_all(target.join("skills")).unwrap();
std::os::unix::fs::symlink(&outside, target.join("skills")).unwrap();
let resolved = Target::resolve(&target, TEST.control_directory).unwrap();
let error = withdraw_written(&TEST, &resolved, "skills/a.md", false).unwrap_err();
assert_eq!(error.reason(), Some(WireReason::DigestMismatch));
assert_eq!(fs::read(outside.join("a.md")).unwrap(), b"not ours");
}
#[cfg(unix)]
#[test]
fn survivors_are_not_written_through_a_swapped_directory_link() {
let target = seeded("linked-survivors");
plan_then_apply(&target, "backup", &[]);
plan_then_apply(&target, "restore", &[]);
let outside = target.join("..").join("outside-survivors");
fs::create_dir_all(&outside).unwrap();
std::os::unix::fs::symlink(&outside, target.join("docs")).unwrap();
let resolved = Target::resolve(&target, TEST.control_directory).unwrap();
let files = BTreeMap::from([("docs/kept.md".to_string(), (b"theirs".to_vec(), 0o644_u32))]);
let error = remove_keeping_files(&TEST, &resolved, None, &files).unwrap_err();
assert_eq!(error.reason(), Some(WireReason::DigestMismatch));
assert!(!outside.join("kept.md").exists());
}
#[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 recovery_retains_unpublished_captures_and_unblocks_future_plans() {
let target = seeded("recover-unpublished");
let (_, control, pool) = open(&TEST, &target).unwrap();
let partial = control
.join(crate::setup_core::backup::POOL_DIRECTORY_NAME)
.join("slot-000000000001");
fs::create_dir_all(partial.join("payload")).unwrap();
fs::write(partial.join("payload/AGENTS.md"), b"interrupted copy").unwrap();
let original = fs::read(target.join("AGENTS.md")).unwrap();
assert_eq!(
run(args("recover-operation", &target, &[]))["recovered"],
true
);
assert!(pool.partial_slots().unwrap().is_empty());
assert_eq!(fs::read(target.join("AGENTS.md")).unwrap(), original);
assert_eq!(
fs::read(control.join("backups/incomplete/slot-000000000001-0/payload/AGENTS.md"))
.unwrap(),
b"interrupted copy"
);
plan_then_apply(&target, "backup", &[]);
assert_eq!(pool.list().unwrap().len(), 1);
}
#[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");
install_global(
&target,
"keep",
&[
("AGENTS.md", "# ours\n", 0o644),
("settings.json", r#"{"model":"setup"}"#, 0o644),
],
);
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("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(), 2, "{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").join("a.md").exists(),
"remove took an unrecorded skill file"
);
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_eq!(
unasked["target_digest"], asked["target_digest"],
"unmanaged global and project inventories are both empty receipts"
);
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"]
);
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_without_a_record_leaves_declared_entries_alone() {
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 done = plan_then_apply(&populated, "remove", &[]);
assert_eq!(done["state"], "verified", "{done}");
for kept in ["AGENTS.md", "settings.json", "unrelated.txt"] {
assert!(
populated.join(kept).exists(),
"unrecorded remove took {kept}"
);
}
}
#[test]
fn a_human_removal_with_no_record_of_its_own_is_a_noop() {
let target = seeded("human-remove-unmanaged");
assert!(target.join(TEST.state_file).symlink_metadata().is_err());
crate::harness_runtime::human::run(
&TEST,
crate::harness_runtime::human::Command::Remove {
target: target.clone(),
},
)
.unwrap();
for kept in ["AGENTS.md", "settings.json", "unrelated.txt"] {
assert!(target.join(kept).exists(), "unrecorded remove took {kept}");
}
assert!(
target.join("skills").is_dir(),
"unrecorded remove took skills/"
);
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(),
"install took an unrecorded host settings file"
);
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 (path, digest, held) = write_plan(target, operation, planned, artifact);
let mut extra = vec![
"--plan",
path.as_str(),
"--plan-digest",
digest.as_str(),
"--provider-release-digest",
RELEASE,
"--prefix",
prefix,
];
if let Some(file) = held.as_ref() {
extra.push("--software-artifact");
extra.push(file.as_str());
}
run(args("apply-operation", target, &extra))
}
fn write_plan(
target: &Path,
operation: &str,
planned: &serde_json::Value,
artifact: Option<&Path>,
) -> (String, String, Option<String>) {
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();
(
plan_path.to_string_lossy().into_owned(),
planned["plan_digest"].as_str().unwrap().to_owned(),
artifact.map(|file| file.to_string_lossy().into_owned()),
)
}
fn apply_args<'a>(
target: &'a Path,
prefix: &'a str,
operation: &'a str,
planned: &'a serde_json::Value,
artifact: Option<&'a Path>,
) -> Vec<String> {
let (path, digest, held) = write_plan(target, operation, planned, artifact);
let mut extra = vec![
"--plan".to_owned(),
path,
"--plan-digest".to_owned(),
digest,
"--provider-release-digest".to_owned(),
RELEASE.to_owned(),
"--prefix".to_owned(),
prefix.to_owned(),
];
if let Some(file) = held {
extra.push("--software-artifact".to_owned());
extra.push(file);
}
extra
}
fn refuse_apply(
target: &Path,
prefix: &str,
operation: &str,
artifact: Option<&Path>,
) -> crate::provider_v3::Error {
let planned = run(args(
"plan-operation",
target,
&software_plan_args(operation, prefix),
));
assert_eq!(planned["state"], "planned", "{planned}");
let extra = apply_args(target, prefix, operation, &planned, artifact);
let borrowed: Vec<&str> = extra.iter().map(String::as_str).collect();
refuse(args("apply-operation", target, &borrowed))
}
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");
assert_eq!(planned["plan"]["software_version"], "1.2.3");
assert_eq!(
planned["plan"]["software_prefix"],
ready_prefix(&target),
"the plan must bind the prefix apply will be given"
);
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_refuses_another_valid_pin_in_place_of_the_planned_artifact() {
let target = seeded("software-bytes-decide");
let prefix = ready_prefix(&target);
let earlier_file = downloaded(&target, TEST_EARLIER_PAYLOAD);
let error = refuse_apply(&target, &prefix, "software_install", Some(&earlier_file));
assert_eq!(error.reason(), Some(WireReason::DigestMismatch));
assert!(
error.detail().contains("1.2.3"),
"the refusal should name the planned version: {}",
error.detail()
);
assert!(!Path::new(&prefix).join("1.2.2").exists());
assert!(!Path::new(&prefix).join("1.2.3").exists());
}
#[test]
fn removing_the_previous_version_leaves_the_current_pin() {
let target = seeded("software-remove-previous");
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}");
let current_file = downloaded(&target, TEST_PAYLOAD);
let updated = plan_then_install_at(
&target,
"software_update",
Some(¤t_file),
Some("1.2.3"),
);
assert_eq!(updated["state"], "verified", "{updated}");
assert!(root.join("1.2.2").is_dir());
assert!(root.join("1.2.3").is_dir());
let removed = plan_then_install_at(&target, "software_remove", None, Some("1.2.2"));
assert_eq!(removed["state"], "verified", "{removed}");
assert_eq!(removed["version"], "1.2.2");
assert!(!root.join("1.2.2").exists(), "the planned version stayed");
assert!(
root.join("1.2.3").is_dir(),
"removing 1.2.2 took the current pin"
);
assert_eq!(
fs::read(root.join("bin").join("test-harness")).unwrap(),
TEST_PAYLOAD,
"the exposed command was taken with the unplanned version"
);
}
#[test]
fn apply_at_a_different_prefix_does_not_modify_that_prefix() {
let target = seeded("software-prefix-bind");
let planned_prefix = ready_prefix(&target);
let other = target.join("..").join("other-program");
fs::create_dir_all(&other).unwrap();
let other_prefix = fs::canonicalize(&other)
.unwrap()
.to_string_lossy()
.into_owned();
let file = downloaded(&target, TEST_PAYLOAD);
let planned = run(args(
"plan-operation",
&target,
&software_plan_args("software_install", &planned_prefix),
));
assert_eq!(planned["state"], "planned", "{planned}");
assert_eq!(planned["plan"]["software_prefix"], planned_prefix);
assert_eq!(planned["plan"]["software_version"], "1.2.3");
let extra = apply_args(
&target,
&other_prefix,
"software_install",
&planned,
Some(file.as_path()),
);
let borrowed: Vec<&str> = extra.iter().map(String::as_str).collect();
let error = refuse(args("apply-operation", &target, &borrowed));
assert_eq!(error.reason(), Some(WireReason::Stale));
assert!(
error.detail().contains(&planned_prefix),
"{}",
error.detail()
);
assert!(!Path::new(&other_prefix).join("bin").exists());
assert!(!Path::new(&planned_prefix).join("bin").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("sha256:"),
"the refusal should name the planned version and digest: {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));
assert!(info.declares(Operation::PatchInstructionRegion));
assert!(
info.declares(Operation::DetachInstructionRegion),
"the detach went out with the consumer release that accepts the name"
);
}
#[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));
assert!(
info.declares(Operation::PatchInstructionRegion),
"the instruction attachment is not a software lifecycle"
);
let error = software::plan(
&bare,
Some(Path::new("/nowhere")),
Operation::SoftwareInstall,
None,
)
.unwrap_err();
assert_eq!(error.reason(), Some(WireReason::UnsupportedOperation));
}
const INSTRUCTION_SECTION: &str = ":::begin-ai-stp\nhello from ai-stp\n:::end-ai-stp\n";
#[test]
fn patch_instruction_region_splices_markers_and_survives_a_recorded_withdraw() {
let target = seeded("patch-instruction-region");
let applied = plan_then_apply(
&target,
"patch_instruction_region",
&["--instruction-section", INSTRUCTION_SECTION],
);
assert_eq!(applied["state"], "verified", "{applied}");
let after_patch = fs::read_to_string(target.join("AGENTS.md")).unwrap();
assert!(
after_patch.starts_with("# first\n"),
"user bytes outside the markers were dropped: {after_patch:?}"
);
assert_eq!(
crate::harness_runtime::instruction_region::extract(&after_patch),
Some(INSTRUCTION_SECTION)
);
assert!(
!recorded_written(&target).contains(&"AGENTS.md".to_owned()),
"the attachment was recorded as a setup receipt: {:?}",
recorded_written(&target)
);
install_global(&target, "with-agents", &[("AGENTS.md", "# setup\n", 0o644)]);
let after_install = fs::read_to_string(target.join("AGENTS.md")).unwrap();
assert!(
after_install.contains("# setup"),
"the setup did not land: {after_install:?}"
);
assert_eq!(
crate::harness_runtime::instruction_region::extract(&after_install),
Some(INSTRUCTION_SECTION),
"install emptied the attachment: {after_install:?}"
);
assert!(
recorded_written(&target).contains(&"AGENTS.md".to_owned()),
"install did not record the setup file: {:?}",
recorded_written(&target)
);
let removed = plan_then_apply(&target, "remove", &[]);
assert_eq!(removed["state"], "verified", "{removed}");
let after_remove = fs::read_to_string(target.join("AGENTS.md")).unwrap();
assert_eq!(
crate::harness_runtime::instruction_region::extract(&after_remove),
Some(INSTRUCTION_SECTION),
"withdraw deleted the attachment: {after_remove:?}"
);
assert!(
!after_remove.contains("# setup"),
"withdraw left setup bytes beside the region: {after_remove:?}"
);
}
#[test]
fn instruction_patch_refuses_invalid_encoding_and_ambiguous_markers() {
let target = seeded("invalid-instruction-region");
let path = target.join("AGENTS.md");
for bytes in [
vec![0xff, 0xfe, b'X'],
b"keep\n:::begin-ai-stp\n".to_vec(),
b":::end-ai-stp\n:::begin-ai-stp\n".to_vec(),
format!("{INSTRUCTION_SECTION}{INSTRUCTION_SECTION}").into_bytes(),
] {
fs::write(&path, &bytes).unwrap();
let error = refuse(args(
"plan-operation",
&target,
&[
"--operation",
"patch_instruction_region",
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01TEST",
"--expires-at",
far_future(),
"--instruction-section",
INSTRUCTION_SECTION,
],
));
assert_eq!(error.reason(), Some(WireReason::ProviderUnavailable));
assert_eq!(fs::read(&path).unwrap(), bytes);
}
}
#[test]
fn instruction_patch_apply_refuses_a_surface_edited_since_the_plan() {
let arguments = [
"--operation",
"patch_instruction_region",
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01TEST",
"--expires-at",
far_future(),
"--instruction-section",
INSTRUCTION_SECTION,
];
for (name, deleted) in [("patch-edited", false), ("patch-deleted", true)] {
let target = seeded(name);
let path = target.join("AGENTS.md");
let plan_path = target.join("..").join(format!("plan-{name}.json"));
let planned = run(args("plan-operation", &target, &arguments));
assert_eq!(planned["state"], "planned", "plan refused: {planned}");
fs::write(
&plan_path,
crate::setup_core::canonical::to_canonical_bytes(&planned["plan"]).unwrap(),
)
.unwrap();
let tamper = if deleted {
fs::remove_file(&path).unwrap();
String::new()
} else {
let edited = "# first\n# edited by a person\n";
fs::write(&path, edited).unwrap();
edited.to_owned()
};
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), "{error}");
assert_eq!(
fs::read_to_string(&path).unwrap_or_default(),
tamper,
"a refused apply still wrote the surface"
);
}
}
#[test]
fn detach_instruction_region_removes_only_the_owned_section() {
let target = seeded("detach-instruction-region");
plan_then_apply(
&target,
"patch_instruction_region",
&["--instruction-section", INSTRUCTION_SECTION],
);
let attached = fs::read_to_string(target.join("AGENTS.md")).unwrap();
assert!(attached.starts_with("# first\n"), "{attached:?}");
let detached = plan_then_apply(&target, "detach_instruction_region", &[]);
assert_eq!(detached["state"], "verified", "{detached}");
let after = fs::read_to_string(target.join("AGENTS.md")).unwrap();
assert_eq!(
after, "# first\n",
"user bytes outside the markers changed: {after:?}"
);
assert_eq!(crate::harness_runtime::instruction_region::extract(&after), None);
assert!(
!recorded_written(&target).contains(&"AGENTS.md".to_owned()),
"the attachment was recorded as a setup receipt: {:?}",
recorded_written(&target)
);
}
#[test]
fn detach_instruction_region_deletes_a_file_holding_only_the_attachment() {
let target = seeded("detach-only-attachment");
let path = target.join("AGENTS.md");
fs::remove_file(&path).unwrap();
plan_then_apply(
&target,
"patch_instruction_region",
&["--instruction-section", INSTRUCTION_SECTION],
);
assert_eq!(
fs::read_to_string(&path).unwrap(),
INSTRUCTION_SECTION,
"patch should have written a file holding only the attachment"
);
let detached = plan_then_apply(&target, "detach_instruction_region", &[]);
assert_eq!(detached["state"], "verified", "{detached}");
assert!(
!path.exists(),
"a file that held only the attachment outlived its detach"
);
}
#[test]
fn detach_instruction_region_is_a_no_op_without_an_attachment() {
let target = seeded("detach-no-attachment");
let detached = plan_then_apply(&target, "detach_instruction_region", &[]);
assert_eq!(detached["state"], "verified", "{detached}");
assert_eq!(
fs::read_to_string(target.join("AGENTS.md")).unwrap(),
"# first\n",
"a no-op detach still changed the file"
);
}
#[test]
fn instruction_operations_refuse_a_scoped_target() {
for operation in ["patch_instruction_region", "detach_instruction_region"] {
let target = seeded("instruction-scoped");
let mut arguments = vec![
"--operation",
operation,
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01TEST",
"--expires-at",
far_future(),
"--target-scope",
"user_root",
];
if operation == "patch_instruction_region" {
arguments.extend(["--instruction-section", INSTRUCTION_SECTION]);
}
let error = refuse(args("plan-operation", &target, &arguments));
assert_eq!(
error.reason(),
Some(WireReason::UnsupportedOperation),
"{operation} under a scope must refuse: {error}"
);
}
}
#[test]
fn scoped_status_reports_no_instruction_region_statement() {
let target = seeded("scoped-instruction-status");
plan_then_apply(
&target,
"patch_instruction_region",
&["--instruction-section", INSTRUCTION_SECTION],
);
let global = run(args("status", &target, &[]));
assert_eq!(
global["instruction_region"]["section_present"], true,
"the attachment is there and the global answer does not see it: {global}"
);
let scoped = run(args("status", &target, &["--target-scope", "user_root"]));
assert!(
scoped["instruction_region"].is_null(),
"a scoped measure named a surface the declaration did not: {scoped}"
);
}
#[test]
fn detach_instruction_region_refuses_ambiguous_markers_and_a_section_flag() {
let target = seeded("detach-ambiguous");
let path = target.join("AGENTS.md");
fs::write(&path, b":::end-ai-stp\n:::begin-ai-stp\n").unwrap();
let arguments = [
"--operation",
"detach_instruction_region",
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01TEST",
"--expires-at",
far_future(),
];
let error = refuse(args("plan-operation", &target, &arguments));
assert_eq!(error.reason(), Some(WireReason::ProviderUnavailable));
fs::write(&path, "# first\n").unwrap();
let mut section = arguments.to_vec();
section.extend(["--instruction-section", INSTRUCTION_SECTION]);
let error = refuse(args("plan-operation", &target, §ion));
assert_eq!(
error.reason(),
Some(WireReason::UnsupportedOperation),
"detach must not accept --instruction-section: {error}"
);
}
#[test]
fn detach_instruction_region_apply_refuses_a_surface_edited_since_the_plan() {
for (name, deleted) in [("detach-edited", false), ("detach-deleted", true)] {
let target = seeded(name);
let path = target.join("AGENTS.md");
plan_then_apply(
&target,
"patch_instruction_region",
&["--instruction-section", INSTRUCTION_SECTION],
);
let arguments = [
"--operation",
"detach_instruction_region",
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01TEST",
"--expires-at",
far_future(),
];
let planned = run(args("plan-operation", &target, &arguments));
assert_eq!(planned["state"], "planned", "plan refused: {planned}");
let plan_path = target.join("..").join(format!("plan-{name}.json"));
fs::write(
&plan_path,
crate::setup_core::canonical::to_canonical_bytes(&planned["plan"]).unwrap(),
)
.unwrap();
let tamper = if deleted {
fs::remove_file(&path).unwrap();
String::new()
} else {
let edited = "# first\n# edited by a person\n";
fs::write(&path, edited).unwrap();
edited.to_owned()
};
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), "{error}");
assert_eq!(
fs::read_to_string(&path).unwrap_or_default(),
tamper,
"a refused apply still wrote the surface"
);
}
}
fn plan_mutate_apply(
target: &Path,
operation: &str,
name: &str,
mutate: impl Fn(&mut serde_json::Value),
) -> crate::provider_v3::Error {
let mut arguments = vec![
"--operation",
operation,
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01TEST",
"--expires-at",
far_future(),
];
if operation == "patch_instruction_region" {
arguments.extend(["--instruction-section", INSTRUCTION_SECTION]);
}
let planned = run(args("plan-operation", target, &arguments));
assert_eq!(planned["state"], "planned", "plan refused: {planned}");
let mut artifact = planned["plan"].clone();
mutate(&mut artifact);
let plan_path = target.join("..").join(format!("plan-{name}.json"));
fs::write(
&plan_path,
crate::setup_core::canonical::to_canonical_bytes(&artifact).unwrap(),
)
.unwrap();
let digest =
crate::setup_core::digest::of_domain_canonical_json(crate::provider_v3::PLAN_DOMAIN, &artifact)
.unwrap();
refuse(args(
"apply-operation",
target,
&[
"--plan",
&plan_path.to_string_lossy(),
"--plan-digest",
&digest,
"--provider-release-digest",
RELEASE,
],
))
}
#[test]
fn apply_refuses_a_plan_that_names_another_instruction_path() {
for operation in ["patch_instruction_region", "detach_instruction_region"] {
let target = seeded("foreign-instruction-path");
let error = plan_mutate_apply(&target, operation, "foreign", |artifact| {
artifact["instruction_path"] = serde_json::json!("unrelated.txt");
});
assert_eq!(
error.reason(),
Some(WireReason::ProviderUnavailable),
"{operation} accepted a foreign instruction path: {error}"
);
assert_eq!(
fs::read_to_string(target.join("unrelated.txt")).unwrap(),
"keep me",
"{operation} wrote a file outside the declared surface"
);
}
}
#[test]
fn apply_refuses_a_plan_without_an_instruction_observation() {
for operation in ["patch_instruction_region", "detach_instruction_region"] {
for dropped in [
"instruction_observed_digest",
"instruction_observed_present",
] {
let target = seeded("unobserved-instruction");
let error = plan_mutate_apply(&target, operation, "unobserved", |artifact| {
artifact.as_object_mut().unwrap().remove(dropped);
});
assert_eq!(
error.reason(),
Some(WireReason::ProviderUnavailable),
"{operation} applied with {dropped} missing: {error}"
);
}
}
let target = seeded("observed-absent-instruction");
fs::remove_file(target.join("AGENTS.md")).unwrap();
let detached = plan_then_apply(&target, "detach_instruction_region", &[]);
assert_eq!(detached["state"], "verified", "{detached}");
assert!(!target.join("AGENTS.md").exists());
}
#[test]
fn withdraw_refuses_a_corrupt_written_fields_ledger() {
let target = seeded("corrupt-fields-ledger");
let resolved = Target::resolve(&target, TEST.control_directory).unwrap();
let ledger = written_fields_path(&TEST, &resolved);
fs::create_dir_all(ledger.parent().unwrap()).unwrap();
fs::write(&ledger, b"{ not json").unwrap();
let error = withdraw_written(&TEST, &resolved, "settings.json", true).unwrap_err();
assert_eq!(error.reason(), Some(WireReason::ProviderUnavailable));
assert_eq!(
fs::read(target.join("settings.json")).unwrap(),
b"{\"model\":\"first\"}",
"a refused withdraw still touched the host file"
);
}
#[test]
fn withdraw_refuses_a_corrupt_json_host_file_instead_of_removing_it() {
let target = seeded("corrupt-json-host");
let resolved = Target::resolve(&target, TEST.control_directory).unwrap();
let ledger = written_fields_path(&TEST, &resolved);
fs::create_dir_all(ledger.parent().unwrap()).unwrap();
fs::write(
&ledger,
serde_json::to_vec(&serde_json::json!({"settings.json": ["model"]})).unwrap(),
)
.unwrap();
fs::write(target.join("settings.json"), b"{ not json").unwrap();
let error = withdraw_written(&TEST, &resolved, "settings.json", true).unwrap_err();
assert_eq!(error.reason(), Some(WireReason::ProviderUnavailable));
assert_eq!(
fs::read(target.join("settings.json")).unwrap(),
b"{ not json"
);
}
#[test]
fn write_refuses_to_merge_over_a_corrupt_json_host_file() {
let target = seeded("corrupt-json-merge");
let resolved = Target::resolve(&target, TEST.control_directory).unwrap();
fs::write(target.join("settings.json"), b"{ not json").unwrap();
let error = write_host_file(
&TEST,
&resolved,
"settings.json",
br#"{"ours": true}"#,
true,
)
.unwrap_err();
assert_eq!(error.reason(), Some(WireReason::ProviderUnavailable));
assert_eq!(
fs::read(target.join("settings.json")).unwrap(),
b"{ not json"
);
}
#[test]
fn status_refuses_a_corrupt_journal_instead_of_reporting_no_operation() {
let target = seeded("corrupt-journal-status");
let control = target.join(TEST.control_directory);
fs::create_dir_all(&control).unwrap();
fs::write(Journal::path(&control), b"{ not json").unwrap();
let error = refuse(args("status", &target, &[]));
assert_eq!(error.reason(), Some(WireReason::RecoveryRequired));
}
#[test]
fn plan_refuses_a_corrupt_state_instead_of_measuring_globally() {
let target = seeded("corrupt-state-scope");
fs::write(target.join(TEST.state_file), b"{ not json").unwrap();
let error = refuse(args(
"plan-operation",
&target,
&[
"--operation",
"remove",
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01TEST",
"--expires-at",
far_future(),
],
));
assert_eq!(error.reason(), Some(WireReason::ProviderUnavailable));
}
#[test]
fn setup_writes_and_withdrawal_refuse_ambiguous_instruction_bytes() {
let target = seeded("ambiguous-setup-instruction");
let path = target.join("AGENTS.md");
let bytes = b"user content\n:::begin-ai-stp\n";
fs::write(&path, bytes).unwrap();
let resolved = Target::resolve(&target, TEST.control_directory).unwrap();
let write_error =
write_host_file(&TEST, &resolved, "AGENTS.md", b"new setup\n", false).unwrap_err();
assert_eq!(write_error.reason(), Some(WireReason::ProviderUnavailable));
assert_eq!(fs::read(&path).unwrap(), bytes);
let remove_error = withdraw_written(&TEST, &resolved, "AGENTS.md", false).unwrap_err();
assert_eq!(remove_error.reason(), Some(WireReason::ProviderUnavailable));
assert_eq!(fs::read(&path).unwrap(), bytes);
}
#[test]
fn a_harness_without_an_instruction_surface_refuses_the_patch() {
let mut mute = TEST;
mute.instruction_region = None;
let target = seeded("no-instruction-surface");
let error = refuse_for(
&mute,
args(
"plan-operation",
&target,
&[
"--operation",
"patch_instruction_region",
"--provider-release-digest",
RELEASE,
"--operation-id",
"operation_01TEST",
"--expires-at",
far_future(),
"--instruction-section",
INSTRUCTION_SECTION,
],
),
);
assert_eq!(error.reason(), Some(WireReason::UnsupportedOperation));
assert!(
error
.detail()
.contains("does not declare a user-global instruction surface"),
"{}",
error.detail()
);
assert!(
!mute
.provider_info()
.unwrap()
.declares(Operation::PatchInstructionRegion)
);
}
#[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:?}");
}
}
}
}