stack-ids 0.1.2

Shared identity, scope, trace, and digest primitives for the local-first AI systems stack
Documentation
use proptest::prelude::*;
use stack_ids::{ClaimId, TrialId};

#[test]
fn empty_whitespace_and_cross_family_ids_fail_parsing() {
    for invalid in ["", " ", "\t", "\n", "   "] {
        assert!(invalid.parse::<ClaimId>().is_err());
    }
    assert!("trial:018f6f4e".parse::<ClaimId>().is_err());
    assert!("claim:018f6f4e".parse::<TrialId>().is_err());
}

#[test]
fn only_canonical_family_qualified_ids_parse() {
    assert!("claim:018f6f4e".parse::<ClaimId>().is_ok());
    for invalid in [
        "Claim:018f6f4e",
        "claim:",
        "claim:has space",
        "claim:leading/slash",
        "claim:é",
    ] {
        assert!(invalid.parse::<ClaimId>().is_err(), "{invalid:?}");
    }
}

#[test]
fn generated_ids_are_family_qualified_and_round_trip() {
    let generated = ClaimId::generate();
    assert!(generated.as_str().starts_with("claim:"));
    assert_eq!(generated.as_str().parse::<ClaimId>().unwrap(), generated);
}

proptest! {
    #[test]
    fn whitespace_only_ids_are_always_rejected(
        characters in prop::collection::vec(
            prop_oneof![Just(' '), Just('\t'), Just('\n'), Just('\r')],
            0..32,
        )
    ) {
        let candidate: String = characters.into_iter().collect();
        prop_assert!(candidate.parse::<ClaimId>().is_err());
    }

    #[test]
    fn cross_family_substitution_is_always_rejected(
        payload in "[A-Za-z0-9][A-Za-z0-9._~-]{0,32}"
    ) {
        let trial = format!("trial:{}", payload);
        let claim = format!("claim:{}", payload);
        prop_assert!(trial.parse::<ClaimId>().is_err());
        prop_assert!(claim.parse::<TrialId>().is_err());
    }
}