diff --git a/docs/authority-map.md b/docs/authority-map.md index 7cf027b..270d592 100644 --- a/docs/authority-map.md +++ b/docs/authority-map.md @@ -55,7 +55,10 @@ A deployable repository publishes one strict declaration, normally - immutable artifact outputs, optionally pinned to an expected SHA-256; - a constrained launch contract; - one health-contract identity; -- an optional typed state contract with state schema generation and slots; +- an optional typed state contract with state schema generation and slots + (kinds `cultcache-file`, `external-file` and `external-directory`; the + external kinds are process-owned state an external runtime imposes, which + Idunn preserves and fences but never interprets); - provided capabilities, dependency kinds, startup requirements, capacity, and conflicts. diff --git a/src/deployment.rs b/src/deployment.rs index a67b4bc..a81dff4 100644 --- a/src/deployment.rs +++ b/src/deployment.rs @@ -184,6 +184,11 @@ pub enum StateKind { /// Process-owned state imposed by an external runtime contract. Idunn /// preserves and fences the file but never interprets its payload. ExternalFile, + /// A process-owned directory imposed by an external runtime contract (for + /// example Synapse's media store). Idunn preserves and fences it and never + /// interprets its contents. Same writer, recovery and startup rules as + /// `ExternalFile`. + ExternalDirectory, } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] @@ -2261,6 +2266,17 @@ nodes = ["yggdrasil"] assert!(declaration.write_lease_required()); } + #[test] + fn external_directory_state_is_lease_gated_like_external_file() { + let input = RECIPE.replace("kind = \"cultcache-file\"", "kind = \"external-directory\""); + let declaration = TargetDeclaration::parse(&input).unwrap(); + let slot = &declaration.state.as_ref().unwrap().slots[0]; + assert_eq!(slot.kind, StateKind::ExternalDirectory); + assert_eq!(slot.writer, StateWriter::ProcessBoundSingleWriter); + assert_eq!(slot.startup, StateStartup::CreateOrOpenAfterWriteLease); + assert!(declaration.write_lease_required()); + } + #[test] fn state_slots_cannot_claim_overlapping_paths() { let input = RECIPE.replace(