diff --git a/crates/huginn-mind/src/fixtures.rs b/crates/huginn-mind/src/fixtures.rs index 3c84a09..56ff119 100644 --- a/crates/huginn-mind/src/fixtures.rs +++ b/crates/huginn-mind/src/fixtures.rs @@ -30,11 +30,19 @@ pub(crate) const NEAR_INSTANCE: &str = "yggdrasix"; /// A foreign instance carrying `INSTANCE` whole as its prefix, so a prefix /// test admits it. pub(crate) const PREFIXED_INSTANCE: &str = "yggdrasil-two"; -/// A foreign instance differing from `INSTANCE` in case alone. A slug label is -/// `[A-Za-z0-9_-]{1,64}`, so this is a name a client may declare and a mind -/// may hold: two minds whose names differ only in case are two minds, and a -/// comparison that folds case answers one of them for the other. +/// A foreign instance differing from `INSTANCE` in case alone. A `Slug` is +/// dot-joined labels, each `[A-Za-z0-9_-]{1,64}`, bounded to 64 bytes whole +/// (`epiphany_pipeline::dotted_text`), so this is a name a client may declare +/// and a mind may hold: two minds whose names differ only in case are two +/// minds, and a comparison that folds case answers one of them for the other. pub(crate) const CASED_INSTANCE: &str = "Yggdrasil"; +/// Two foreign instances differing from `OTHER_INSTANCE` (`thought-cage`) in +/// their separator alone: `_` and `.` are both legal in a `Slug` beside `-` +/// (`_` inside a label, `.` joining labels), so `thought-cage`, `thought_cage` +/// and `thought.cage` are three names, not one, and a comparison that folds a +/// separator into another answers one of them for another. +pub(crate) const UNDERSCORE_INSTANCE: &str = "thought_cage"; +pub(crate) const DOTTED_INSTANCE: &str = "thought.cage"; pub(crate) const CAMPAIGN: &str = "eureka-state"; pub(crate) const REPO: &str = "GameCult/Epiphany"; pub(crate) const OTHER_REPO: &str = "GameCult/Huginn"; diff --git a/crates/huginn-mind/src/mind.rs b/crates/huginn-mind/src/mind.rs index 94dc6cb..5fda6bf 100644 --- a/crates/huginn-mind/src/mind.rs +++ b/crates/huginn-mind/src/mind.rs @@ -417,7 +417,8 @@ mod tests { #[test] fn require_instance_is_the_one_check_admission_and_the_daemon_share() { use crate::fixtures::{ - CASED_INSTANCE, NEAR_INSTANCE, OTHER_INSTANCE, PREFIXED_INSTANCE, now, provenance, seeded, + CASED_INSTANCE, DOTTED_INSTANCE, NEAR_INSTANCE, OTHER_INSTANCE, PREFIXED_INSTANCE, UNDERSCORE_INSTANCE, + now, opened, provenance, seeded, }; use crate::receipt::Faculty; @@ -445,6 +446,24 @@ mod tests { documents: vec![instance(OTHER_INSTANCE)], }; assert_eq!(mind.admit(batch, now()), crate::admission::PipelineAdmissionOutcome::Refused(foreign)); + + // `INSTANCE` ("yggdrasil") carries no separator, so no fold of `_` or + // `.` into `-` can ever equate a declared name with it; a mind whose + // own name carries one is required to tell the fold apart. Opened as + // `OTHER_INSTANCE` ("thought-cage"), a declared name that folds to the + // same string under such a comparison must still be refused. + let separated = opened(MemoryStore::new(), OTHER_INSTANCE); + assert_eq!(UNDERSCORE_INSTANCE.replace('_', "-"), OTHER_INSTANCE, "differs from OTHER_INSTANCE by `_` alone"); + assert_eq!(DOTTED_INSTANCE.replace('.', "-"), OTHER_INSTANCE, "differs from OTHER_INSTANCE by `.` alone"); + assert_ne!(UNDERSCORE_INSTANCE, OTHER_INSTANCE); + assert_ne!(DOTTED_INSTANCE, OTHER_INSTANCE); + for declared in [UNDERSCORE_INSTANCE, DOTTED_INSTANCE] { + assert_eq!( + separated.require_instance(&slug(declared)).err(), + Some(MindRefusal::ForeignInstance { declared: declared.into(), mind: OTHER_INSTANCE.into() }), + "{declared} is not {OTHER_INSTANCE}" + ); + } } #[test]