REQUIRED_STAGE_FIELDS = ["role","input_artifact_id","output_artifact_id","model_provider","model_id","policy_version","source_bundle_ref","claim_set_ref"]

def evaluate(c):
    # 1) decisive evidence record
    if not c.get("evidence_ref"):
        return out("HOLD_EVIDENCE_MISSING","NOT_RELEASED","HOLD","NOT_ALLOWED",False,"DECISIVE_EVIDENCE_MISSING")
    stages=c.get("stages",[])
    if len(stages)<5:
        return out("HOLD_LINEAGE_BROKEN","NOT_RELEASED","HOLD","NOT_ALLOWED",False,"STAGE_SEQUENCE_INCOMPLETE")
    # 2) model identity on AI stages
    for s in stages:
        if s.get("role")!="APPROVE" and (not s.get("model_provider") or not s.get("model_id")):
            return out("HOLD_IDENTITY_MISSING","NOT_RELEASED","HOLD","NOT_ALLOWED",False,"MODEL_IDENTITY_MISSING")
    # 3) artifact lineage
    for a,b in zip(stages, stages[1:]):
        # VERIFY output is a review artifact, while EDIT legitimately edits the reviewed DRAFT input.
        if a.get("role")=="VERIFY" and b.get("role")=="EDIT":
            expected=c.get("verified_artifact_id")
            if not expected or b.get("input_artifact_id")!=expected:
                return out("HOLD_LINEAGE_BROKEN","NOT_RELEASED","HOLD","NOT_ALLOWED",False,"EDIT_NOT_BOUND_TO_VERIFIED_ARTIFACT")
        elif b.get("input_artifact_id")!=a.get("output_artifact_id"):
            return out("HOLD_LINEAGE_BROKEN","NOT_RELEASED","HOLD","NOT_ALLOWED",False,"ARTIFACT_LINEAGE_BROKEN")
    # 4) verifier result and disposition
    verify=next((s for s in stages if s.get("role")=="VERIFY"),None)
    if not verify or not verify.get("review_result"):
        return out("HOLD_REVIEW_MISSING","NOT_RELEASED","HOLD","NOT_ALLOWED",False,"VERIFY_RESULT_MISSING")
    if verify.get("review_result") in ("OBJECT","FAIL") and not c.get("verifier_objection_disposition"):
        return out("DISPUTED_UNRESOLVED","NOT_RELEASED","DISPUTED","NOT_ALLOWED",True,"VERIFY_OBJECTION_UNRESOLVED")
    # 5) fallback drift
    if c.get("fallback_used") and c.get("source_linked_caveats_before",0) > c.get("source_linked_caveats_after",0):
        return out("REWORK_FALLBACK_DRIFT","NOT_RELEASED","REWORK","REVIEW_REQUIRED",True,"FALLBACK_DROPPED_SOURCE_CAVEATS")
    # 6) policy version and claim/source continuity
    for s in stages:
        if not s.get("policy_version") or not s.get("source_bundle_ref") or not s.get("claim_set_ref"):
            return out("HOLD_EVIDENCE_MISSING","NOT_RELEASED","HOLD","NOT_ALLOWED",False,"STAGE_EVIDENCE_INCOMPLETE")
    # 7) human approval bound to final artifact
    approve=next((s for s in stages if s.get("role")=="APPROVE"),None)
    final_artifact=stages[-2].get("output_artifact_id")
    if not c.get("human_anchor_id") or not approve or approve.get("approval_state")!="APPROVED" or approve.get("input_artifact_id")!=final_artifact:
        return out("HOLD_HUMAN_APPROVAL","NOT_RELEASED","HOLD","REVIEW_REQUIRED",True,"CURRENT_ARTIFACT_NOT_HUMAN_APPROVED")
    return out("RELEASED","RELEASED","RESOLVED","APPROVED",True,"CURRENT_LINEAGE_APPROVED")

def out(decision,release_state,disagreement_state,approval_required,trace_complete,reason_code):
    return {"decision":decision,"release_state":release_state,"disagreement_state":disagreement_state,"approval_required":approval_required,"trace_complete":trace_complete,"reason_code":reason_code}
