//! The conformance corpus of extensions/live-worlds.md §12: `live-conformance/worlds/`.
//!
//! [`generate`] writes it from fixed seeds: every key is SHA-256 of this corpus's label and an
//! index, every time is a constant, and Ed25519 signing is deterministic (RFC 8032), so the file
//! is the same bytes on every machine. A world is written as the tracks its actors publish: each
//! object an op batch (or, in a private world, a stand-in for its sealed bytes beside the batch
//! that opened from them), signed into its owner's receipt chain in the labelled form a room's
//! publisher signs, with the unmetered marker as its figure. Each vector states the outcome it
//! was written to have; the generator asserts that this module reaches it, and that the corpus
//! gives every reason of §11 a verifier raises.
//!
//! [`verify`] checks a corpus directory's files against this module.
//! `live-conformance/live_worlds_verify.py` checks the same files from the extension's text,
//! sharing no code with this module.

use super::*;
use crate::energy_binding::Coverage;
use crate::jwp_delivery::{GroupLabel, MerkleProof, ObjectLabel, ObjectReceipt};
use crate::live_events::{kind, Policy as EventPolicy};
use crate::live_room::{power, Caps, Grant, Limits, Recording, Review, Role, NO_CEILING};
use serde_json::{json, Map, Value};
use std::collections::BTreeSet;

const T0_US: u64 = 1_792_108_800_000_000;
const T0_S: u64 = T0_US / 1_000_000;
const STAMP: &str = "2026-10-15T00:00:00Z";
const ORIGIN: &str = "wai-corpus-world";
const ZERO: [u8; 32] = [0; 32];

const AUTHORITY: u8 = 0;
const CLOCK: u8 = 1;
const ALICE: u8 = 2;
const BOB: u8 = 3;
const CAROL: u8 = 4;
const VIEWER: u8 = 5;
const WATCHER: u8 = 6;
const PRINCIPALS: [(&str, u8); 7] =
    [("authority", AUTHORITY), ("clock", CLOCK), ("alice", ALICE), ("bob", BOB), ("carol", CAROL), ("viewer", VIEWER), ("watcher", WATCHER)];

fn key(i: u8) -> SigningKey {
    SigningKey::from_bytes(&sha256(&[b"wai:live-world/corpus/key\x00", &[i]]))
}

fn pk(i: u8) -> [u8; 32] {
    key(i).verifying_key().to_bytes()
}

fn unhex(s: &str) -> Option<Vec<u8>> {
    if !s.len().is_multiple_of(2) {
        return None;
    }
    (0..s.len()).step_by(2).map(|i| u8::from_str_radix(s.get(i..i + 2)?, 16).ok()).collect()
}

fn bits(ks: &[u8]) -> [u8; 32] {
    let mut b = [0u8; 32];
    for &k in ks {
        b[(k / 8) as usize] |= 1 << (k % 8);
    }
    b
}

/// The world every vector's statement names, unless it says otherwise.
fn header() -> ModelHeader {
    ModelHeader { model_id: [7; 32], seed: 0, width: 64, height: 64, fps: 24 }
}

// ---- the room ----

struct Room {
    descriptor: Vec<u8>,
    id: [u8; 32],
    grants: Vec<Vec<u8>>,
}

/// A room of two roles, a participant's and a viewer's, each subscribing with the seek power.
/// Alice, Bob and Carol are granted the attribute `world` = `act`; the viewer no attribute; the
/// watcher `world` = `watch`.
fn room() -> Room {
    let role = |name: &str| Role { name: name.into(), caps: Caps { publish: 0, subscribe: true, max_layer: 0, powers: power::SEEK }, events: bits(&[kind::CHAT]) };
    let d = Descriptor {
        authority: pk(AUTHORITY),
        clock: pk(CLOCK),
        t0_us: T0_US,
        tick_us: 1_000_000,
        origin: String::new(),
        limits: Limits { max_present: 16, max_publishers: 4, reval_s: 0, ceiling: EventPolicy::default() },
        roles: vec![role("participant"), role("viewer")],
        slots: vec![],
        profiles: vec![],
        review: Review { kinds: [0; 32], deadline_ms: 0, fallback: 0 },
        recording: Recording { mode: 0, retention_days: 0, commit_interval_ms: 0, jitter_ms: 0, regions: vec![] },
        sig: [0; 64],
    }
    .sign(&key(AUTHORITY));
    let id = d.room_id();
    let members: [(u8, u8, Option<&str>); 5] = [(ALICE, 0, Some(ACT)), (BOB, 0, Some(ACT)), (CAROL, 0, Some(ACT)), (VIEWER, 1, None), (WATCHER, 1, Some("watch"))];
    let grants = members
        .iter()
        .map(|&(who, r, world)| {
            Grant {
                room: id,
                principal: pk(who),
                prev: ZERO,
                effective: 0,
                nbf: T0_S - 3_600,
                exp: T0_S + 86_400,
                role: r,
                caps: d.roles[r as usize].caps,
                precedence: 0,
                once: [0; 16],
                viewer: [0; 16],
                policy: EventPolicy::with_kinds(&[kind::CHAT]),
                joules_ceiling: NO_CEILING,
                funds_ceiling: NO_CEILING,
                currency: [0; 3],
                profiles: vec![],
                origins: vec![],
                attrs: world.map(|v| vec![(ATTR.to_string(), v.to_string())]).unwrap_or_default(),
                sig: [0; 64],
            }
            .sign(&key(AUTHORITY))
            .to_bytes()
        })
        .collect();
    Room { descriptor: d.to_bytes(), id, grants }
}

fn statement(room: [u8; 32], max_op_bytes: u32, max_ops: u16) -> Statement {
    Statement::for_model(room, &header(), max_op_bytes, max_ops).sign(&key(AUTHORITY))
}

// ---- ops, batches and receipts ----

/// An action of `who`, at tick and `lamport` `l`, moving by (`dx`, `dy`).
fn act(who: u8, l: u64, dx: i32, dy: i32) -> Vec<u8> {
    world_model::sign_action(&key(who), l, l, dx, dy, 0, b"").to_bytes()
}

fn batch(ops: &[Vec<u8>]) -> Vec<u8> {
    ops.concat()
}

/// The receipts-track object of group `group` of `signer`'s world track, over `covered`, its
/// parent `parent`: the labelled group form a room's publisher signs (jwp-receipts §2.3), one
/// object, the unmetered marker its figure. Its link hash beside it.
fn receipt(signer: u8, alias: u64, group: u64, covered: &[u8], parent: Option<[u8; 32]>) -> (Vec<u8>, [u8; 32]) {
    let ch = crate::jwp_hash::content_hash(covered);
    let leaf = jwp::leaf_labelled(&ch, 0, &ObjectLabel::Unmetered, ORIGIN);
    let o = ObjectReceipt::new(alias, group, 0, ch, 0, ORIGIN, MerkleProof::build(&[leaf], 0), Some(ObjectLabel::Unmetered));
    let k = key(signer);
    let mut g = GroupReceipt {
        track_alias: alias,
        group_id: group,
        root_hash: jwp::root(&[leaf]),
        leaf_count: 1,
        joules_total: 0,
        group_start: STAMP.into(),
        group_end: STAMP.into(),
        signer_pubkey: k.verifying_key().to_bytes(),
        signer_id: ORIGIN.into(),
        sig: [0; 64],
        parent_receipt_hash: parent,
        label: Some(GroupLabel { class: None, coverage: Coverage::Whole }),
        hybrid: None,
    };
    g.sig = k.sign(&g.signing_payload()).to_bytes();
    let link = g.link_hash();
    (GroupReceipts { group: g, objects: vec![o] }.to_binary().expect("a labelled group has a binary form"), link)
}

/// A receipts-track object in the legacy form (jwp-receipts §2.3): a group whose total is
/// `total` over one object whose figure is 0, so that its figures do not sum to its total.
fn receipt_legacy_total(signer: u8, group: u64, covered: &[u8], parent: Option<[u8; 32]>, total: u64) -> Vec<u8> {
    let ch = crate::jwp_hash::content_hash(covered);
    let leaf = jwp::leaf_legacy(&ch);
    let o = ObjectReceipt::new(RECEIPT_ALIAS, group, 0, ch, 0, ORIGIN, MerkleProof::build(&[leaf], 0), None);
    let k = key(signer);
    let mut g = GroupReceipt {
        track_alias: RECEIPT_ALIAS,
        group_id: group,
        root_hash: jwp::root(&[leaf]),
        leaf_count: 1,
        joules_total: total,
        group_start: STAMP.into(),
        group_end: STAMP.into(),
        signer_pubkey: k.verifying_key().to_bytes(),
        signer_id: ORIGIN.into(),
        sig: [0; 64],
        parent_receipt_hash: parent,
        label: None,
        hybrid: None,
    };
    g.sig = k.sign(&g.signing_payload()).to_bytes();
    GroupReceipts { group: g, objects: vec![o] }.to_binary().expect("a legacy group has a binary form")
}

/// A stand-in for the sealed bytes an op batch opened from: the corpus holds no key, since
/// opening is confidentiality's (§9.1).
fn sealed(b: &[u8]) -> Vec<u8> {
    let mut x = blake3::Hasher::new();
    x.update(b"wai:live-world/corpus/sealed\x00");
    x.update(b);
    let mut out = vec![0u8; b.len() + 16];
    x.finalize_xof().fill(&mut out);
    out
}

/// One track being written: its owner, its objects, and the chain they are signed into.
#[derive(Clone)]
struct Writer {
    owner: u8,
    private: bool,
    next: u64,
    parent: Option<[u8; 32]>,
    objects: Vec<ObjectInput>,
}

impl Writer {
    fn new(owner: u8, private: bool) -> Writer {
        Writer { owner, private, next: 0, parent: None, objects: vec![] }
    }

    /// The next group, holding `batch`, signed by the owner.
    fn push(&mut self, batch: Vec<u8>) -> &mut Writer {
        let covered = if self.private { sealed(&batch) } else { batch.clone() };
        let (r, link) = receipt(self.owner, RECEIPT_ALIAS, self.next, &covered, self.parent);
        self.objects.push(ObjectInput { group: self.next, receipts: r, payload: covered, opened: self.private.then_some(batch) });
        self.parent = Some(link);
        self.next += 1;
        self
    }

    /// Groups the member reading this world never opened: signed, and left out of what it is
    /// given (a private world's member opens nothing sealed before it joined).
    fn unopened(&mut self, batch: Vec<u8>) -> &mut Writer {
        self.push(batch);
        self.objects.pop();
        self
    }

    fn track(&self) -> TrackInput {
        TrackInput { owner: pk(self.owner), objects: self.objects.clone() }
    }
}

// ---- vectors ----

struct Vector {
    name: &'static str,
    about: &'static str,
    input: Input,
    expect: Result<Outcome, Refusal>,
}

fn input(r: &Room, s: &Statement, private: bool, tracks: Vec<TrackInput>) -> Input {
    Input { descriptor: r.descriptor.clone(), grants: r.grants.clone(), statement: s.to_bytes(), private, tracks }
}

/// The ops of the public world: Alice's three moves and Bob's three, one of Bob's batches
/// holding two.
fn public_tracks() -> (Writer, Writer) {
    let mut a = Writer::new(ALICE, false);
    a.push(act(ALICE, 1, 1, 0)).push(act(ALICE, 2, 0, 1)).push(act(ALICE, 3, 1, 1));
    let mut b = Writer::new(BOB, false);
    b.push(act(BOB, 1, -1, 0)).push(batch(&[act(BOB, 2, 0, -1), act(BOB, 3, -1, -1)]));
    (a, b)
}

/// The outcome of the public world's ops, computed as §9.3 has it from the ops alone.
fn expected(ops: &[Vec<u8>]) -> Outcome {
    let mut ops: Vec<CausalOp> = ops.iter().map(|b| CausalOp::from_bytes(b).expect("an op").0).collect();
    ops.sort_by_key(|o| o.order_key());
    ops.dedup_by_key(|o| (o.actor, o.lamport));
    let mut actors = BTreeMap::new();
    for o in &ops {
        *actors.entry(o.actor).or_insert(0) += 1;
    }
    Outcome { session_hash: world_model::replay(&header(), &ops, true).expect("actions").session_hash, ops: ops.len(), actors }
}

fn public_ops() -> Vec<Vec<u8>> {
    vec![act(ALICE, 1, 1, 0), act(ALICE, 2, 0, 1), act(ALICE, 3, 1, 1), act(BOB, 1, -1, 0), act(BOB, 2, 0, -1), act(BOB, 3, -1, -1)]
}

fn refused(reason: Reason, at: Where) -> Result<Outcome, Refusal> {
    Err(Refusal { reason, at })
}

fn obj(track: usize, group: u64) -> Where {
    Where::Object { track, group }
}

fn build() -> Vec<Vector> {
    let r = room();
    let s = statement(r.id, 4096, 64);
    let (a, b) = public_tracks();
    let mut v = Vec::new();
    let mut add = |name: &'static str, about: &'static str, input: Input, expect: Result<Outcome, Refusal>| v.push(Vector { name, about, input, expect });

    // ---- accepted ----
    add("public_two_actors", "a public world: Alice's track and Bob's, every op its owner's, every group signed into its owner's chain; the session hash of the six ops", input(&r, &s, false, vec![a.track(), b.track()]), Ok(expected(&public_ops())));
    add("public_other_order", "the same tracks given Bob's first: the same session hash, whatever order the ops arrived in", input(&r, &s, false, vec![b.track(), a.track()]), Ok(expected(&public_ops())));
    {
        let mut a2 = a.clone();
        a2.push(act(ALICE, 2, 0, 1)).push(act(ALICE, 4, -1, 0));
        let mut ops = public_ops();
        ops.push(act(ALICE, 4, -1, 0));
        add("public_redelivery", "Alice sends her second op again, byte for byte, then a new one: the redelivery is admitted once, and the new op keeps the Lamport rule over the ops her track carried", input(&r, &s, false, vec![a2.track(), b.track()]), Ok(expected(&ops)));
    }
    {
        // A private world, read by Carol, who joined after Alice and Bob had each published:
        // she opens nothing sealed before, and each carries what it holds into her epoch.
        let mut pa = Writer::new(ALICE, true);
        pa.unopened(act(ALICE, 1, 1, 0)).unopened(act(ALICE, 2, 0, 1));
        let mut pb = Writer::new(BOB, true);
        pb.unopened(act(BOB, 1, -1, 0));
        let held = [act(ALICE, 1, 1, 0), act(ALICE, 2, 0, 1), act(BOB, 1, -1, 0)];
        let mut ordered: Vec<Vec<u8>> = held.to_vec();
        ordered.sort_by_key(|b| {
            let o = CausalOp::from_bytes(b).expect("an op").0;
            (o.actor, o.lamport)
        });
        pa.push(batch(&ordered)).push(act(ALICE, 3, 1, 1));
        pb.push(batch(&ordered)).push(act(BOB, 2, 0, -1));
        let mut pc = Writer::new(CAROL, true);
        pc.push(batch(&ordered)).push(act(CAROL, 1, 0, 0));
        let mut all = held.to_vec();
        all.extend([act(ALICE, 3, 1, 1), act(BOB, 2, 0, -1), act(CAROL, 1, 0, 0)]);
        add("private_carry", "a private world as Carol reads it: she opens nothing Alice and Bob sealed before she joined, and each carries every op it holds, its own and the other's, into her first epoch, before its next op; her own first object carries what she holds then. Her session hash is the whole world's", input(&r, &s, true, vec![pa.track(), pb.track(), pc.track()]), Ok(expected(&all)));
    }

    // ---- the statement ----
    let mut cut = input(&r, &s, false, vec![a.track()]);
    cut.statement.truncate(40);
    add("statement_truncated", "a statement whose bytes end before max_ops", cut, refused(Reason::ev(events::Reason::Truncated), Where::Statement));
    let mut magic = input(&r, &s, false, vec![a.track()]);
    magic.statement[3] = b'2';
    add("statement_bad_magic", "a statement whose magic is WLW2", magic, refused(Reason::ev(events::Reason::Malformed), Where::Statement));
    let mut ver = input(&r, &s, false, vec![a.track()]);
    ver.statement[4] = 2;
    add("statement_version_2", "a statement of version 2", ver, refused(Reason::ev(events::Reason::UnknownVersion), Where::Statement));
    let mut cap = s.clone();
    cap.capability = "wai.world.live".into();
    add("statement_capability_unknown", "a statement naming wai.world.live, which this revision does not carry", input(&r, &cap.sign(&key(AUTHORITY)), false, vec![a.track()]), refused(Reason::WorldCapabilityUnknown, Where::Statement));
    let mut fps = s.clone();
    fps.world = model_world(&ModelHeader { fps: 0, ..header() });
    add("statement_world_invalid", "a statement whose model header has a frame rate of 0", input(&r, &fps.sign(&key(AUTHORITY)), false, vec![a.track()]), refused(Reason::WorldStatementInvalid, Where::Statement));
    add("statement_ops_bound_invalid", "a statement whose max_op_bytes is 135, below an op's least size", input(&r, &statement(r.id, 135, 64), false, vec![a.track()]), refused(Reason::WorldStatementInvalid, Where::Statement));
    add("statement_other_room", "a statement of another room", input(&r, &statement([9; 32], 4096, 64), false, vec![a.track()]), refused(Reason::ev(events::Reason::WrongRoom), Where::Statement));
    add("statement_clock_signed", "a statement signed by the room's clock, not its authority", input(&r, &Statement::for_model(r.id, &header(), 4096, 64).sign(&key(CLOCK)), false, vec![a.track()]), refused(Reason::ev(events::Reason::BadSignature), Where::Statement));

    // ---- §9.2 step 1, 2 ----
    let mut viewer = Writer::new(VIEWER, false);
    viewer.push(act(VIEWER, 1, 1, 0));
    add("viewer_not_granted", "a world track of the viewer, whose grant holds no world attribute", input(&r, &s, false, vec![a.track(), viewer.track()]), refused(Reason::WorldNotGranted, obj(1, 0)));
    let mut watcher = Writer::new(WATCHER, false);
    watcher.push(act(WATCHER, 1, 1, 0));
    add("attribute_not_act", "a world track of the watcher, whose world attribute is watch, not act", input(&r, &s, false, vec![watcher.track()]), refused(Reason::WorldNotGranted, obj(0, 0)));
    let mut late = a.clone();
    late.objects.remove(0);
    add("public_starts_late", "Alice's track given from group 1: in a public world a track's first object is group 0", input(&r, &s, false, vec![late.track()]), refused(Reason::WorldGap, obj(0, 1)));
    let mut hole = a.clone();
    hole.objects.remove(1);
    add("public_gap", "Alice's track with group 1 missing", input(&r, &s, false, vec![hole.track()]), refused(Reason::WorldGap, obj(0, 2)));

    // ---- §9.2 step 3 ----
    let mut junk = a.clone();
    junk.objects[1].receipts = b"WJR1 not a receipt".to_vec();
    add("receipts_malformed", "group 1's receipts-track object does not read", input(&r, &s, false, vec![junk.track()]), refused(Reason::Receipt(jwp::Reason::Malformed), obj(0, 1)));
    let mut wrong_key = a.clone();
    wrong_key.objects[1].receipts = receipt(BOB, RECEIPT_ALIAS, 1, &wrong_key.objects[1].payload, None).0;
    add("receipts_other_signer", "group 1 of Alice's track receipted by Bob's key", input(&r, &s, false, vec![wrong_key.track()]), refused(Reason::Receipt(jwp::Reason::MoqtMismatch), obj(0, 1)));
    let mut alias = Writer::new(ALICE, false);
    let ab = act(ALICE, 1, 1, 0);
    alias.objects.push(ObjectInput { group: 0, receipts: receipt(ALICE, 2, 0, &ab, None).0, payload: ab, opened: None });
    add("receipts_other_alias", "Alice's group 0 receipted on track_alias 2, a media track's, not the world track's 4", input(&r, &s, false, vec![alias.track()]), refused(Reason::Receipt(jwp::Reason::MoqtMismatch), obj(0, 0)));
    let mut forged = a.clone();
    let n = forged.objects[1].receipts.len();
    forged.objects[1].receipts[n - 100] ^= 1;
    add("receipts_signature", "a byte of group 1's signature flipped", input(&r, &s, false, vec![forged.track()]), refused(Reason::Receipt(jwp::Reason::SignatureVerifyFailed), obj(0, 1)));
    let mut changed = a.clone();
    changed.objects[2].payload = act(ALICE, 3, 1, -1);
    add("payload_changed", "group 2's op batch changed after it was signed: it no longer hashes to its receipt", input(&r, &s, false, vec![changed.track()]), refused(Reason::Receipt(jwp::Reason::ContentHashMismatch), obj(0, 2)));
    let mut total = Writer::new(ALICE, false);
    let tb = act(ALICE, 1, 1, 0);
    total.objects.push(ObjectInput { group: 0, receipts: receipt_legacy_total(ALICE, 0, &tb, None, 7), payload: tb, opened: None });
    add("receipts_total_mismatch", "Alice's group 0 in the legacy form, its total 7 microjoules over one object whose figure is 0", input(&r, &s, false, vec![total.track()]), refused(Reason::Receipt(jwp::Reason::GroupTotalMismatch), obj(0, 0)));
    let mut restart = Writer::new(ALICE, false);
    restart.push(act(ALICE, 1, 1, 0));
    let b2 = act(ALICE, 2, 0, 1);
    restart.objects.push(ObjectInput { group: 1, receipts: receipt(ALICE, RECEIPT_ALIAS, 1, &b2, None).0, payload: b2, opened: None });
    add("chain_second_head", "Alice's group 1 names no parent: a second head of her chain", input(&r, &s, false, vec![restart.track()]), refused(Reason::Receipt(jwp::Reason::ChainBreak), obj(0, 1)));
    let mut stray = Writer::new(ALICE, false);
    stray.push(act(ALICE, 1, 1, 0));
    let b3 = act(ALICE, 2, 0, 1);
    stray.objects.push(ObjectInput { group: 1, receipts: receipt(ALICE, RECEIPT_ALIAS, 1, &b3, Some([0x5a; 32])).0, payload: b3, opened: None });
    add("chain_unknown_parent", "Alice's group 1 names a parent that is not her group 0", input(&r, &s, false, vec![stray.track()]), refused(Reason::Receipt(jwp::Reason::ParentNotHeld), obj(0, 1)));

    // ---- §9.2 step 4 ----
    let mut short = Writer::new(ALICE, false);
    let mut cut_op = act(ALICE, 1, 1, 0);
    cut_op.truncate(100);
    short.push(cut_op);
    add("op_truncated", "an op batch whose op ends 100 bytes in", input(&r, &s, false, vec![short.track()]), refused(Reason::OpMalformed, obj(0, 0)));
    let mut empty = Writer::new(ALICE, false);
    empty.push(vec![]);
    add("batch_empty", "an object that holds no op", input(&r, &s, false, vec![empty.track()]), refused(Reason::OpMalformed, obj(0, 0)));
    let mut three = Writer::new(ALICE, false);
    three.push(batch(&[act(ALICE, 1, 1, 0), act(ALICE, 2, 1, 0), act(ALICE, 3, 1, 0)]));
    add("batch_too_large", "a batch of three ops where the statement allows two", input(&r, &statement(r.id, 4096, 2), false, vec![three.track()]), refused(Reason::BatchTooLarge, obj(0, 0)));
    let mut big = Writer::new(ALICE, false);
    big.push(world_model::sign_action(&key(ALICE), 1, 1, 1, 0, 0, &[0x20; 100]).to_bytes());
    add("op_too_large", "an op of 252 bytes where the statement allows 200", input(&r, &statement(r.id, 200, 64), false, vec![big.track()]), refused(Reason::OpTooLarge, obj(0, 0)));

    // ---- §9.2 step 5 ----
    let mut badsig = Writer::new(ALICE, false);
    let mut op = act(ALICE, 1, 1, 0);
    let last = op.len() - 1;
    op[last] ^= 1;
    badsig.push(op);
    add("op_signature", "an op whose signature's last byte is flipped, its batch signed into the receipt as it stands", input(&r, &s, false, vec![badsig.track()]), refused(Reason::BadOpSignature, obj(0, 0)));
    let mut foreign = Writer::new(ALICE, false);
    foreign.push(act(ALICE, 1, 1, 0)).push(act(BOB, 1, -1, 0));
    add("public_foreign_op", "Alice's track carries an op of Bob's in a public world", input(&r, &s, false, vec![foreign.track()]), refused(Reason::ActorNotOwner, obj(0, 1)));
    let mut kind2 = Writer::new(ALICE, false);
    kind2.push(CausalOp::sign(&key(ALICE), 1, 1, vec![], [0; 16], 2, vec![0; 16]).to_bytes());
    add("op_not_an_action", "an op of mutation kind 2: not an action of wai.world.model", input(&r, &s, false, vec![kind2.track()]), refused(Reason::OpNotAction, obj(0, 0)));
    let mut deps = Writer::new(ALICE, false);
    deps.push(act(ALICE, 1, 1, 0));
    let d1 = crate::world::OpId { actor: pk(ALICE), lamport: 1 };
    deps.push(CausalOp::sign(&key(ALICE), 2, 2, vec![d1], [0; 16], world_model::MUT_ACTION, world_model::encode_action(1, 0, 0, b"")).to_bytes());
    add("op_with_dependency", "an action that names a dependency: an action of wai.world.model names none", input(&r, &s, false, vec![deps.track()]), refused(Reason::OpNotAction, obj(0, 1)));
    let mut regress = Writer::new(ALICE, false);
    regress.push(act(ALICE, 2, 1, 0)).push(act(ALICE, 1, 0, 1));
    add("lamport_regress", "Alice's op of lamport 1 after her op of lamport 2", input(&r, &s, false, vec![regress.track()]), refused(Reason::LamportRegress, obj(0, 1)));
    let mut equiv = Writer::new(ALICE, false);
    equiv.push(act(ALICE, 1, 1, 0)).push(act(ALICE, 1, -1, 0));
    add("equivocation", "Alice signs two ops of lamport 1 with other moves", input(&r, &s, false, vec![equiv.track()]), refused(Reason::Equivocation, obj(0, 1)));
    {
        // In a private world a carry may hold another's op; it may not hold another version of it.
        let mut pa = Writer::new(ALICE, true);
        pa.push(act(ALICE, 1, 1, 0));
        let mut pb = Writer::new(BOB, true);
        pb.push(batch(&[act(ALICE, 1, 1, 1), act(BOB, 1, -1, 0)]));
        add("private_carry_equivocation", "in a private world Bob's carry holds an op of Alice's lamport 1 other than the one Alice published", input(&r, &s, true, vec![pa.track(), pb.track()]), refused(Reason::Equivocation, obj(1, 0)));
    }
    v
}

// ---- the corpus document ----

fn hexes(v: &[Vec<u8>]) -> Value {
    Value::Array(v.iter().map(|b| json!(hex(b))).collect())
}

fn where_json(w: &Where) -> Value {
    match w {
        Where::Descriptor => json!("descriptor"),
        Where::Statement => json!("statement"),
        Where::Grant(i) => json!({ "grant": i }),
        Where::Object { track, group } => json!({ "track": track, "group": group }),
    }
}

fn expect_json(e: &Result<Outcome, Refusal>) -> Value {
    match e {
        Ok(o) => {
            let actors: Map<String, Value> = o.actors.iter().map(|(k, n)| (hex(k), json!(n))).collect();
            json!({ "accept": true, "session_hash": hex(&o.session_hash), "ops": o.ops, "actors": actors })
        }
        Err(r) => json!({ "accept": false, "reason": r.reason.code(), "at": where_json(&r.at) }),
    }
}

fn vector_json(v: &Vector) -> Value {
    let tracks: Vec<Value> = v
        .input
        .tracks
        .iter()
        .map(|t| {
            let objects: Vec<Value> = t
                .objects
                .iter()
                .map(|o| json!({ "group": o.group, "receipts": hex(&o.receipts), "payload": hex(&o.payload), "opened": o.opened.as_ref().map(|b| json!(hex(b))).unwrap_or(Value::Null) }))
                .collect();
            json!({ "owner": hex(&t.owner), "objects": objects })
        })
        .collect();
    json!({
        "name": v.name,
        "about": v.about,
        "descriptor": hex(&v.input.descriptor),
        "grants": hexes(&v.input.grants),
        "statement": hex(&v.input.statement),
        "private": v.input.private,
        "tracks": tracks,
        "expect": expect_json(&v.expect),
    })
}

/// The corpus: `vectors.json`. It panics if this module does not reach a vector's stated
/// outcome, or no vector gives one of §11's codes a verifier raises (`world_incomplete` is a
/// participant's report on its own copy, §7, and no verifier raises it).
pub fn generate() -> Vec<(String, String)> {
    let vs = build();
    let mut seen = BTreeSet::new();
    for v in &vs {
        let got = super::verify(&v.input);
        assert_eq!(got, v.expect, "{}: the module's outcome is not the one the vector states", v.name);
        if let Err(r) = &v.expect {
            seen.insert(r.reason.code());
        }
    }
    for r in Reason::ALL {
        if r != Reason::WorldIncomplete {
            assert!(seen.contains(r.code()), "no vector gives {}", r.code());
        }
    }
    let names: Map<String, Value> = PRINCIPALS.iter().map(|(n, i)| (n.to_string(), json!(hex(&pk(*i))))).collect();
    let doc = json!({
        "about": "extensions/live-worlds.md §12: rooms with a world statement and the world tracks their actors publish, each with the outcome a verifier reaches (§9.4) or its refusal, the reason and where",
        "principals": names,
        "key_seed": "SHA-256 of \"wai:live-world/corpus/key\" and a zero byte, then the principal's index (authority 0, clock 1, alice 2, bob 3, carol 4, viewer 5, watcher 6)",
        "sealed_stand_in": "in a private world, payload is BLAKE3 in XOF mode, over \"wai:live-world/corpus/sealed\", a zero byte and the op batch, of the batch's length plus 16 bytes: the receipt covers it, and opened is the batch",
        "vectors": vs.iter().map(vector_json).collect::<Vec<_>>(),
    });
    let mut body = serde_json::to_string_pretty(&doc).expect("JSON");
    body.push('\n');
    vec![("vectors.json".into(), body)]
}

fn hex_list(v: &Value) -> Option<Vec<Vec<u8>>> {
    v.as_array()?.iter().map(|h| unhex(h.as_str()?)).collect()
}

fn input_of(v: &Value) -> Option<Input> {
    let tracks = v["tracks"]
        .as_array()?
        .iter()
        .map(|t| {
            let owner: [u8; 32] = unhex(t["owner"].as_str()?)?.try_into().ok()?;
            let objects = t["objects"]
                .as_array()?
                .iter()
                .map(|o| {
                    Some(ObjectInput {
                        group: o["group"].as_u64()?,
                        receipts: unhex(o["receipts"].as_str()?)?,
                        payload: unhex(o["payload"].as_str()?)?,
                        opened: match &o["opened"] {
                            Value::Null => None,
                            x => Some(unhex(x.as_str()?)?),
                        },
                    })
                })
                .collect::<Option<Vec<_>>>()?;
            Some(TrackInput { owner, objects })
        })
        .collect::<Option<Vec<_>>>()?;
    Some(Input {
        descriptor: unhex(v["descriptor"].as_str()?)?,
        grants: hex_list(&v["grants"])?,
        statement: unhex(v["statement"].as_str()?)?,
        private: v["private"].as_bool()?,
        tracks,
    })
}

/// Every vector of the corpus in `files` gets the outcome it states.
pub fn verify(files: &BTreeMap<String, String>) -> Vec<String> {
    let mut fails = Vec::new();
    let Some(body) = files.get("vectors.json") else { return vec!["no vectors.json".into()] };
    let doc: Value = match serde_json::from_str(body) {
        Ok(d) => d,
        Err(e) => return vec![format!("vectors.json: {e}")],
    };
    for v in doc["vectors"].as_array().into_iter().flatten() {
        let name = v["name"].as_str().unwrap_or("?");
        let Some(input) = input_of(v) else {
            fails.push(format!("{name}: does not read"));
            continue;
        };
        let got = expect_json(&super::verify(&input));
        if got != v["expect"] {
            fails.push(format!("{name}: {got} where the corpus states {}", v["expect"]));
        }
    }
    fails
}
