made-core 0.8.0

Domain core of MADE: entities, value objects, events, ports. No IO.
Documentation
use made_core::entities::CeremonyEventReader;
use made_core::value_objects::{AuditEventType, EventSchemaVersion};
use serde_json::{json, Value};

fn transition() -> Value {
    let mut raw: Value = serde_json::from_str(include_str!(
        "fixtures/ceremony_events/v1/transition_applied.json"
    ))
    .unwrap();
    raw["transition"]["state_iteration"] = json!(2);
    raw["transition"]["state_visit"] = json!(3);
    raw["destination"] = json!({"state_visit": 4, "step_ids": ["a", "b"]});
    raw
}

#[test]
fn destination_reset_is_sealed_as_a_new_schema_and_rejects_contradictions() {
    let valid = transition();
    let event = CeremonyEventReader::read(
        AuditEventType::TransitionApplied,
        EventSchemaVersion::V3,
        valid.clone(),
    )
    .unwrap();
    assert_eq!(serde_json::to_value(event).unwrap(), valid);
    for version in [EventSchemaVersion::V1, EventSchemaVersion::V2] {
        assert!(CeremonyEventReader::read(
            AuditEventType::TransitionApplied,
            version,
            valid.clone()
        )
        .is_err());
    }
    for mutation in [
        "missing_source",
        "missing_destination",
        "wrong_next",
        "duplicate_steps",
        "zero_visit",
        "missing_iteration",
    ] {
        let mut raw = valid.clone();
        match mutation {
            "missing_source" => {
                raw["transition"]
                    .as_object_mut()
                    .unwrap()
                    .remove("state_visit");
            }
            "missing_destination" => {
                raw.as_object_mut().unwrap().remove("destination");
            }
            "wrong_next" => raw["destination"]["state_visit"] = json!(3),
            "duplicate_steps" => raw["destination"]["step_ids"] = json!(["a", "a"]),
            "zero_visit" => raw["destination"]["state_visit"] = json!(0),
            "missing_iteration" => {
                raw["transition"]
                    .as_object_mut()
                    .unwrap()
                    .remove("state_iteration");
            }
            _ => unreachable!(),
        }
        assert!(
            CeremonyEventReader::read(
                AuditEventType::TransitionApplied,
                EventSchemaVersion::V3,
                raw
            )
            .is_err(),
            "{mutation}"
        );
    }
}

#[test]
fn step_and_context_events_version_explicit_visits_without_reinterpreting_old_shapes() {
    for (kind, fixture, new_version) in [
        (
            AuditEventType::StepStarted,
            include_str!("fixtures/ceremony_events/v1/step_started.json"),
            EventSchemaVersion::V4,
        ),
        (
            AuditEventType::StepCompleted,
            include_str!("fixtures/ceremony_events/v1/step_completed.json"),
            EventSchemaVersion::V3,
        ),
        (
            AuditEventType::StepFailed,
            include_str!("fixtures/ceremony_events/v1/step_failed.json"),
            EventSchemaVersion::V3,
        ),
        (
            AuditEventType::ContextWritten,
            include_str!("fixtures/ceremony_events/v1/context_written.json"),
            EventSchemaVersion::V2,
        ),
    ] {
        let mut raw: Value = serde_json::from_str(fixture).unwrap();
        let legacy = CeremonyEventReader::read(kind, EventSchemaVersion::V1, raw.clone()).unwrap();
        assert_eq!(serde_json::to_value(legacy).unwrap(), raw);
        raw["state_iteration"] = json!(1);
        raw["state_visit"] = json!(2);
        let event = CeremonyEventReader::read(kind, new_version, raw.clone()).unwrap();
        assert_eq!(serde_json::to_value(event).unwrap(), raw);
        assert!(CeremonyEventReader::read(kind, EventSchemaVersion::V1, raw.clone()).is_err());
        raw.as_object_mut().unwrap().remove("state_iteration");
        assert!(CeremonyEventReader::read(kind, new_version, raw).is_err());
    }
}

#[test]
fn legacy_and_current_state_repeat_events_keep_their_distinct_shapes() {
    let mut raw = json!({"type": "state_iteration_started", "state_id": "A", "state_iteration": 2, "step_ids": ["a"], "started_at": "2026-09-18T09:00:00Z"});
    let legacy = CeremonyEventReader::read(
        AuditEventType::StateIterationStarted,
        EventSchemaVersion::V1,
        raw.clone(),
    )
    .unwrap();
    assert_eq!(serde_json::to_value(legacy).unwrap(), raw);
    raw["state_visit"] = json!(3);
    let event = CeremonyEventReader::read(
        AuditEventType::StateIterationStarted,
        EventSchemaVersion::V2,
        raw.clone(),
    )
    .unwrap();
    assert_eq!(serde_json::to_value(event).unwrap(), raw);
}