use ed25519_dalek::VerifyingKey;
use triblespace::core::collection::descriptor;
use triblespace::core::collection::records::CollectionHandle;
use triblespace::core::id::Id;
use triblespace::core::inline::Inline;
use triblespace::core::metadata;
use triblespace::core::query::register::{maximal, ObservationOrder};
use triblespace::core::query::TriblePattern;
use triblespace::prelude::*;
pub use crate::schemas::config::{COLLECTION_NAME, DEFAULT_SCOPE_ID};
pub fn handle(authority: VerifyingKey) -> CollectionHandle {
descriptor::root_handle_to_read(
COLLECTION_NAME,
crate::collection_names::private_policy(authority),
)
}
pub fn configured_in<P>(facts: &P, scope: Id) -> anyhow::Result<Option<CollectionHandle>>
where
P: TriblePattern + Sync,
{
let order = ObservationOrder::new(facts, metadata::supersedes.id());
let mut heads: Vec<CollectionHandle> = find!(
(
state: Id,
selected: Inline<inlineencodings::Handle<blobencodings::SimpleArchive>>
),
and!(
pattern!(facts, [{ ?state @
crate::schemas::config::config::anchor: scope,
crate::schemas::config::config::selects: ?selected,
}]),
maximal(state, &order),
)
)
.map(|(_, selected)| selected)
.collect();
heads.sort_unstable();
heads.dedup();
match heads.as_slice() {
[] => Ok(None),
[only] => Ok(Some(*only)),
several => {
let listed = several
.iter()
.map(|handle| format!("blake3:{}", hex::encode(handle.raw)))
.collect::<Vec<_>>()
.join(", ");
anyhow::bail!(
"the {COLLECTION_NAME} collection holds {} concurrent configurations for scope \
{scope:X} and none supersedes the others: {listed}. Write one state that \
supersedes them rather than letting a reader pick.",
several.len()
)
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Source {
Environment,
Stored,
Agreed,
EnvironmentOverridesStored,
}
pub fn decide(from_env: bool, from_stored: bool, equal: bool) -> Option<Source> {
match (from_env, from_stored) {
(true, true) if equal => Some(Source::Agreed),
(true, true) => Some(Source::EnvironmentOverridesStored),
(true, false) => Some(Source::Environment),
(false, true) => Some(Source::Stored),
(false, false) => None,
}
}
pub fn report_override_divergence(variable: &str, from_env: &str, from_stored: &str) {
eprintln!(
"{}",
override_divergence_note(variable, from_env, from_stored)
);
}
pub fn override_divergence_note(variable: &str, from_env: &str, from_stored: &str) -> String {
format!(
"note: {variable} is set to {from_env} but this pile's {COLLECTION_NAME} collection says \
{from_stored}; using the environment. A process environment is a copy taken when the \
process started and does not update when the configuration changes -- if this is not \
deliberate, start a fresh shell."
)
}
#[cfg(test)]
mod tests {
use super::*;
fn key(byte: u8) -> VerifyingKey {
ed25519_dalek::SigningKey::from_bytes(&[byte; 32]).verifying_key()
}
#[test]
fn the_handle_is_a_pure_function_of_the_key() {
assert_eq!(handle(key(1)), handle(key(1)));
assert_ne!(
handle(key(1)),
handle(key(2)),
"one pile's configuration must not be another's"
);
}
fn descriptor(byte: u8) -> CollectionHandle {
Inline::new([byte; 32])
}
fn state(id: &Id, scope: Id, selected: CollectionHandle, supersedes: &[Id]) -> TribleSet {
let mut facts = entity! { ExclusiveId::force_ref(id) @
crate::schemas::config::config::anchor: scope,
crate::schemas::config::config::selects: selected,
}
.facts()
.clone();
for earlier in supersedes {
facts.union(
entity! { ExclusiveId::force_ref(id) @
metadata::supersedes: ExclusiveId::force_ref(earlier),
}
.facts()
.clone(),
);
}
facts
}
#[test]
fn writing_the_same_configuration_twice_collapses_to_one_state() {
let scope = genid().id;
let once = entity! {
crate::schemas::config::config::anchor: scope,
crate::schemas::config::config::selects: descriptor(0xAB),
};
let twice = entity! {
crate::schemas::config::config::anchor: scope,
crate::schemas::config::config::selects: descriptor(0xAB),
};
assert_eq!(
once.root(),
twice.root(),
"the same statement must mint the same entity"
);
let mut facts = once.facts().clone();
facts.union(twice.facts().clone());
assert_eq!(
facts.len(),
once.facts().len(),
"the union of one statement with itself adds nothing"
);
assert_eq!(
configured_in(&facts, scope).unwrap(),
Some(descriptor(0xAB))
);
}
#[test]
fn reverting_would_collide_unless_supersession_is_in_the_core() {
let scope = genid().id;
let original = entity! {
crate::schemas::config::config::anchor: scope,
crate::schemas::config::config::selects: descriptor(0xAB),
};
let reverted_without_edges_in_core = entity! {
crate::schemas::config::config::anchor: scope,
crate::schemas::config::config::selects: descriptor(0xAB),
};
assert_eq!(
original.root(),
reverted_without_edges_in_core.root(),
"a revert that states only (anchor, selects) IS the earlier state"
);
}
#[test]
fn selecting_a_different_collection_mints_a_different_state() {
let scope = genid().id;
let one = entity! {
crate::schemas::config::config::anchor: scope,
crate::schemas::config::config::selects: descriptor(0xAB),
};
let other = entity! {
crate::schemas::config::config::anchor: scope,
crate::schemas::config::config::selects: descriptor(0xCD),
};
assert_ne!(one.root(), other.root());
}
#[test]
fn an_unconfigured_faculty_resolves_to_nothing_rather_than_failing() {
let facts = TribleSet::new();
assert_eq!(configured_in(&facts, genid().id).unwrap(), None);
}
#[test]
fn one_state_is_the_configuration() {
let scope = genid().id;
let other = genid().id;
let facts = state(&genid().id, scope, descriptor(0xAB), &[]);
assert_eq!(
configured_in(&facts, scope).unwrap(),
Some(descriptor(0xAB))
);
assert_eq!(
configured_in(&facts, other).unwrap(),
None,
"one faculty's register must not answer for another's"
);
}
#[test]
fn a_superseding_state_wins_and_the_old_one_stays_readable() {
let scope = genid().id;
let first = genid().id;
let second = genid().id;
let mut facts = state(&first, scope, descriptor(0xAB), &[]);
facts.union(state(&second, scope, descriptor(0xCD), &[first]));
assert_eq!(
configured_in(&facts, scope).unwrap(),
Some(descriptor(0xCD)),
"the head is the state nothing supersedes"
);
}
#[test]
fn concurrent_states_selecting_the_same_collection_still_resolve() {
let scope = genid().id;
let mut facts = state(&genid().id, scope, descriptor(0xAB), &[]);
facts.union(state(&genid().id, scope, descriptor(0xAB), &[]));
assert_eq!(
configured_in(&facts, scope).unwrap(),
Some(descriptor(0xAB))
);
}
#[test]
fn concurrent_states_that_disagree_are_an_error_not_a_guess() {
let scope = genid().id;
let mut facts = state(&genid().id, scope, descriptor(0xAB), &[]);
facts.union(state(&genid().id, scope, descriptor(0xCD), &[]));
let error = configured_in(&facts, scope).unwrap_err();
let text = format!("{error:#}");
assert!(text.contains("concurrent"), "{text}");
assert!(text.contains("supersede"), "{text}");
}
#[test]
fn the_environment_wins_but_a_disagreement_is_reported() {
assert_eq!(decide(true, true, true), Some(Source::Agreed));
assert_eq!(
decide(true, true, false),
Some(Source::EnvironmentOverridesStored)
);
assert_eq!(decide(true, false, false), Some(Source::Environment));
assert_eq!(decide(false, true, false), Some(Source::Stored));
assert_eq!(decide(false, false, false), None);
}
#[test]
fn the_note_carries_both_values_whole() {
let note = override_divergence_note("TRIBLESPACE_COLLECTION_MESSAGE", "aaaa", "bbbb");
assert!(note.contains("TRIBLESPACE_COLLECTION_MESSAGE"), "{note}");
assert!(note.contains("aaaa") && note.contains("bbbb"), "{note}");
assert!(
note.contains("copy taken when the process started"),
"{note}"
);
}
}