feat(kernel): implement run-attempt binding shell
This commit is contained in:
parent
d0d7df3857
commit
f8d496ff9e
1 changed files with 620 additions and 0 deletions
620
generate/run_attempt_binding.py
Normal file
620
generate/run_attempt_binding.py
Normal file
|
|
@ -0,0 +1,620 @@
|
|||
"""Inert CandidateAttempt run-binding shell per ADR-0232."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum, unique
|
||||
|
||||
from generate.candidate_operator import CandidateOperatorResult
|
||||
from generate.geometric_search_run import (
|
||||
BudgetCharge,
|
||||
CandidateAttempt,
|
||||
CandidateReplayStatus,
|
||||
GeometricSearchRun,
|
||||
)
|
||||
from generate.kernel_facts import SourceSpan
|
||||
|
||||
CANDIDATE_ATTEMPT_RUN_BINDING_POLICY_VERSION = "candidate_attempt_run_binding.v1"
|
||||
|
||||
|
||||
@unique
|
||||
class RunAttemptBindingRefusalReason(str, Enum):
|
||||
INVALID_BINDING_INPUT = "invalid_binding_input"
|
||||
UNSUPPORTED_BINDING_POLICY = "unsupported_binding_policy"
|
||||
RUN_RESULT_MISMATCH = "run_result_mismatch"
|
||||
ATTEMPT_RESULT_MISMATCH = "attempt_result_mismatch"
|
||||
RECONSTRUCTION_RESULT_MISMATCH = "reconstruction_result_mismatch"
|
||||
REPLAY_STATUS_NOT_PENDING = "replay_status_not_pending"
|
||||
REPLAY_BLOCKERS_PRESENT = "replay_blockers_present"
|
||||
DUPLICATE_ATTEMPT_INDEX = "duplicate_attempt_index"
|
||||
DUPLICATE_ATTEMPT_ID = "duplicate_attempt_id"
|
||||
DUPLICATE_CANDIDATE_DIGEST = "duplicate_candidate_digest"
|
||||
BUDGET_CHARGE_EXCEEDS_REMAINING = "budget_charge_exceeds_remaining"
|
||||
OPERATOR_SET_MISMATCH = "operator_set_mismatch"
|
||||
RUN_IDENTITY_MISMATCH = "run_identity_mismatch"
|
||||
MALFORMED_EVIDENCE_SPAN = "malformed_evidence_span"
|
||||
UNSUPPORTED_SCHEMA_VERSION = "unsupported_schema_version"
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class CandidateAttemptRunBindingInput:
|
||||
input_digest: str
|
||||
binding_policy_version: str
|
||||
original_run_id: str
|
||||
original_run_policy_version: str
|
||||
original_run_input_digest: str
|
||||
operator_result_id: str
|
||||
operator_policy_version: str
|
||||
candidate_attempt_id: str
|
||||
attempt_index: int
|
||||
candidate_digest: str
|
||||
candidate_reconstruction_digest: str
|
||||
operator_set_id: str
|
||||
operator_set_version: str
|
||||
budget_id: str
|
||||
gate_decision_id: str
|
||||
residual_ids: tuple[str, ...]
|
||||
problem_frame_digest: str
|
||||
original_contract_assessment_id: str
|
||||
schema_versions: tuple[tuple[str, str], ...]
|
||||
policy_versions: tuple[tuple[str, str], ...]
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class CandidateAttemptRunBinding:
|
||||
binding_id: str
|
||||
binding_policy_version: str
|
||||
input_digest: str
|
||||
original_run_id: str
|
||||
operator_result_id: str
|
||||
candidate_attempt_id: str
|
||||
attempt_index: int
|
||||
candidate_digest: str
|
||||
candidate_reconstruction_digest: str
|
||||
candidate_attempt_ref: str
|
||||
budget_charge: BudgetCharge
|
||||
depth: int
|
||||
step_index: int
|
||||
run_attempt_membership: str
|
||||
evidence_spans: tuple[SourceSpan, ...]
|
||||
reason_codes: tuple[str, ...]
|
||||
explanation: str
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class CandidateAttemptRunBindingRefusal:
|
||||
binding_refusal_id: str
|
||||
binding_policy_version: str
|
||||
input_digest: str | None
|
||||
original_run_id: str | None
|
||||
operator_result_id: str | None
|
||||
candidate_attempt_id: str | None
|
||||
reason_codes: tuple[str, ...]
|
||||
explanation: str
|
||||
|
||||
|
||||
CandidateAttemptRunBindingOutcome = (
|
||||
CandidateAttemptRunBinding | CandidateAttemptRunBindingRefusal
|
||||
)
|
||||
|
||||
|
||||
def _canonical_digest(payload: dict[str, object]) -> str:
|
||||
encoded = json.dumps(
|
||||
payload,
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
).encode("utf-8")
|
||||
return hashlib.sha256(encoded).hexdigest()
|
||||
|
||||
|
||||
def _span_payload(span: SourceSpan) -> dict[str, object]:
|
||||
return {
|
||||
"text": span.text,
|
||||
"start": span.start,
|
||||
"end": span.end,
|
||||
"sentence_index": span.sentence_index,
|
||||
}
|
||||
|
||||
|
||||
def _budget_charge_payload(charge: BudgetCharge) -> dict[str, int]:
|
||||
return {"candidates": charge.candidates, "steps": charge.steps}
|
||||
|
||||
|
||||
def _candidate_attempt_payload(attempt: CandidateAttempt) -> dict[str, object]:
|
||||
return {
|
||||
"attempt_id": attempt.attempt_id,
|
||||
"attempt_index": attempt.attempt_index,
|
||||
"parent_attempt_id": attempt.parent_attempt_id,
|
||||
"operator_id": attempt.operator_id,
|
||||
"operator_version": attempt.operator_version,
|
||||
"input_digest": attempt.input_digest,
|
||||
"candidate_digest": attempt.candidate_digest,
|
||||
"budget_charge": _budget_charge_payload(attempt.budget_charge),
|
||||
"depth": attempt.depth,
|
||||
"step_index": attempt.step_index,
|
||||
"replay_status": attempt.replay_status.value,
|
||||
"replay_blockers": list(attempt.replay_blockers),
|
||||
"evidence_spans": [_span_payload(span) for span in attempt.evidence_spans],
|
||||
}
|
||||
|
||||
|
||||
def _version_pairs_payload(versions: tuple[tuple[str, str], ...]) -> list[list[str]]:
|
||||
return [[name, version] for name, version in versions]
|
||||
|
||||
|
||||
def _valid_version_pairs(value: object) -> bool:
|
||||
if not isinstance(value, tuple):
|
||||
return False
|
||||
names: list[str] = []
|
||||
for entry in value:
|
||||
if not isinstance(entry, tuple) or len(entry) != 2:
|
||||
return False
|
||||
name, version = entry
|
||||
if not isinstance(name, str) or not name:
|
||||
return False
|
||||
if not isinstance(version, str) or not version:
|
||||
return False
|
||||
names.append(name)
|
||||
return len(names) == len(set(names)) and names == sorted(names)
|
||||
|
||||
|
||||
def _valid_span(span: object) -> bool:
|
||||
return (
|
||||
isinstance(span, SourceSpan)
|
||||
and isinstance(span.text, str)
|
||||
and type(span.start) is int
|
||||
and type(span.end) is int
|
||||
and span.start >= 0
|
||||
and span.end >= span.start
|
||||
and (span.sentence_index is None or type(span.sentence_index) is int)
|
||||
)
|
||||
|
||||
|
||||
def _valid_spans(spans: object) -> bool:
|
||||
return isinstance(spans, tuple) and all(_valid_span(span) for span in spans)
|
||||
|
||||
|
||||
def _binding_input_payload(
|
||||
*,
|
||||
original_run: GeometricSearchRun,
|
||||
candidate_operator_result: CandidateOperatorResult,
|
||||
schema_versions: tuple[tuple[str, str], ...],
|
||||
policy_versions: tuple[tuple[str, str], ...],
|
||||
) -> dict[str, object]:
|
||||
attempt = candidate_operator_result.candidate_attempt
|
||||
reconstruction = candidate_operator_result.candidate_reconstruction
|
||||
return {
|
||||
"binding_policy_version": CANDIDATE_ATTEMPT_RUN_BINDING_POLICY_VERSION,
|
||||
"original_run_id": original_run.run_id,
|
||||
"original_run_policy_version": original_run.run_policy_version,
|
||||
"original_run_input_digest": original_run.input_digest,
|
||||
"operator_result_id": candidate_operator_result.operator_result_id,
|
||||
"operator_policy_version": candidate_operator_result.operator_policy_version,
|
||||
"candidate_attempt_id": attempt.attempt_id,
|
||||
"attempt_index": attempt.attempt_index,
|
||||
"candidate_digest": attempt.candidate_digest,
|
||||
"candidate_reconstruction_digest": reconstruction.candidate_reconstruction_digest,
|
||||
"operator_set_id": original_run.operator_set_id,
|
||||
"operator_set_version": original_run.operator_set_version,
|
||||
"budget_id": original_run.budget_id,
|
||||
"gate_decision_id": original_run.gate_decision_id,
|
||||
"residual_ids": list(original_run.residual_ids),
|
||||
"problem_frame_digest": original_run.problem_frame_digest,
|
||||
"original_contract_assessment_id": original_run.contract_assessment_id,
|
||||
"schema_versions": _version_pairs_payload(schema_versions),
|
||||
"policy_versions": _version_pairs_payload(policy_versions),
|
||||
}
|
||||
|
||||
|
||||
def _refusal_id_payload(
|
||||
*,
|
||||
input_digest: str | None,
|
||||
original_run_id: str | None,
|
||||
operator_result_id: str | None,
|
||||
candidate_attempt_id: str | None,
|
||||
reason_codes: tuple[str, ...],
|
||||
) -> dict[str, object]:
|
||||
return {
|
||||
"binding_refusal_id": "",
|
||||
"binding_policy_version": CANDIDATE_ATTEMPT_RUN_BINDING_POLICY_VERSION,
|
||||
"input_digest": input_digest,
|
||||
"original_run_id": original_run_id,
|
||||
"operator_result_id": operator_result_id,
|
||||
"candidate_attempt_id": candidate_attempt_id,
|
||||
"reason_codes": list(reason_codes),
|
||||
}
|
||||
|
||||
|
||||
def _refusal(
|
||||
*,
|
||||
reason: RunAttemptBindingRefusalReason,
|
||||
input_digest: str | None = None,
|
||||
original_run_id: str | None = None,
|
||||
operator_result_id: str | None = None,
|
||||
candidate_attempt_id: str | None = None,
|
||||
) -> CandidateAttemptRunBindingRefusal:
|
||||
reason_codes = (reason.value,)
|
||||
binding_refusal_id = _canonical_digest(
|
||||
_refusal_id_payload(
|
||||
input_digest=input_digest,
|
||||
original_run_id=original_run_id,
|
||||
operator_result_id=operator_result_id,
|
||||
candidate_attempt_id=candidate_attempt_id,
|
||||
reason_codes=reason_codes,
|
||||
)
|
||||
)
|
||||
return CandidateAttemptRunBindingRefusal(
|
||||
binding_refusal_id=binding_refusal_id,
|
||||
binding_policy_version=CANDIDATE_ATTEMPT_RUN_BINDING_POLICY_VERSION,
|
||||
input_digest=input_digest,
|
||||
original_run_id=original_run_id,
|
||||
operator_result_id=operator_result_id,
|
||||
candidate_attempt_id=candidate_attempt_id,
|
||||
reason_codes=reason_codes,
|
||||
explanation="CandidateAttempt run binding refused: " + reason.value + ".",
|
||||
)
|
||||
|
||||
|
||||
def _binding_id_payload(
|
||||
*,
|
||||
input_digest: str,
|
||||
original_run_id: str,
|
||||
operator_result_id: str,
|
||||
candidate_attempt_id: str,
|
||||
attempt_index: int,
|
||||
candidate_digest: str,
|
||||
candidate_reconstruction_digest: str,
|
||||
candidate_attempt_ref: str,
|
||||
budget_charge: BudgetCharge,
|
||||
depth: int,
|
||||
step_index: int,
|
||||
evidence_spans: tuple[SourceSpan, ...],
|
||||
) -> dict[str, object]:
|
||||
return {
|
||||
"binding_id": "",
|
||||
"binding_policy_version": CANDIDATE_ATTEMPT_RUN_BINDING_POLICY_VERSION,
|
||||
"input_digest": input_digest,
|
||||
"original_run_id": original_run_id,
|
||||
"operator_result_id": operator_result_id,
|
||||
"candidate_attempt_id": candidate_attempt_id,
|
||||
"attempt_index": attempt_index,
|
||||
"candidate_digest": candidate_digest,
|
||||
"candidate_reconstruction_digest": candidate_reconstruction_digest,
|
||||
"candidate_attempt_ref": candidate_attempt_ref,
|
||||
"budget_charge": _budget_charge_payload(budget_charge),
|
||||
"depth": depth,
|
||||
"step_index": step_index,
|
||||
"run_attempt_membership": "structurally_bound",
|
||||
"evidence_spans": [_span_payload(span) for span in evidence_spans],
|
||||
"reason_codes": [],
|
||||
}
|
||||
|
||||
|
||||
def _operator_provenance_has(
|
||||
provenance: tuple[tuple[str, str], ...], key: str, value: str
|
||||
) -> bool:
|
||||
return (key, value) in provenance
|
||||
|
||||
|
||||
def bind_candidate_attempt_to_run(
|
||||
*,
|
||||
original_run: GeometricSearchRun,
|
||||
candidate_operator_result: CandidateOperatorResult,
|
||||
schema_versions: tuple[tuple[str, str], ...] = (),
|
||||
policy_versions: tuple[tuple[str, str], ...] = (),
|
||||
explanation: str = "",
|
||||
) -> CandidateAttemptRunBindingOutcome:
|
||||
"""Bind an existing candidate operator result to one immutable run episode."""
|
||||
|
||||
if not isinstance(original_run, GeometricSearchRun):
|
||||
return _refusal(reason=RunAttemptBindingRefusalReason.INVALID_BINDING_INPUT)
|
||||
if not isinstance(candidate_operator_result, CandidateOperatorResult):
|
||||
return _refusal(
|
||||
reason=RunAttemptBindingRefusalReason.INVALID_BINDING_INPUT,
|
||||
original_run_id=original_run.run_id,
|
||||
)
|
||||
|
||||
attempt = candidate_operator_result.candidate_attempt
|
||||
reconstruction = candidate_operator_result.candidate_reconstruction
|
||||
|
||||
if not isinstance(attempt, CandidateAttempt):
|
||||
return _refusal(
|
||||
reason=RunAttemptBindingRefusalReason.INVALID_BINDING_INPUT,
|
||||
original_run_id=original_run.run_id,
|
||||
operator_result_id=candidate_operator_result.operator_result_id,
|
||||
)
|
||||
|
||||
if not _valid_version_pairs(schema_versions) or not _valid_version_pairs(
|
||||
policy_versions
|
||||
):
|
||||
return _refusal(
|
||||
reason=RunAttemptBindingRefusalReason.UNSUPPORTED_SCHEMA_VERSION,
|
||||
original_run_id=original_run.run_id,
|
||||
operator_result_id=candidate_operator_result.operator_result_id,
|
||||
candidate_attempt_id=attempt.attempt_id,
|
||||
)
|
||||
|
||||
if candidate_operator_result.geometric_search_run_id != original_run.run_id:
|
||||
return _refusal(
|
||||
reason=RunAttemptBindingRefusalReason.RUN_RESULT_MISMATCH,
|
||||
original_run_id=original_run.run_id,
|
||||
operator_result_id=candidate_operator_result.operator_result_id,
|
||||
candidate_attempt_id=attempt.attempt_id,
|
||||
)
|
||||
|
||||
if candidate_operator_result.attempt_id != attempt.attempt_id:
|
||||
return _refusal(
|
||||
reason=RunAttemptBindingRefusalReason.ATTEMPT_RESULT_MISMATCH,
|
||||
original_run_id=original_run.run_id,
|
||||
operator_result_id=candidate_operator_result.operator_result_id,
|
||||
candidate_attempt_id=attempt.attempt_id,
|
||||
)
|
||||
if candidate_operator_result.attempt_index != attempt.attempt_index:
|
||||
return _refusal(
|
||||
reason=RunAttemptBindingRefusalReason.ATTEMPT_RESULT_MISMATCH,
|
||||
original_run_id=original_run.run_id,
|
||||
operator_result_id=candidate_operator_result.operator_result_id,
|
||||
candidate_attempt_id=attempt.attempt_id,
|
||||
)
|
||||
if candidate_operator_result.candidate_digest != attempt.candidate_digest:
|
||||
return _refusal(
|
||||
reason=RunAttemptBindingRefusalReason.ATTEMPT_RESULT_MISMATCH,
|
||||
original_run_id=original_run.run_id,
|
||||
operator_result_id=candidate_operator_result.operator_result_id,
|
||||
candidate_attempt_id=attempt.attempt_id,
|
||||
)
|
||||
if candidate_operator_result.input_digest != attempt.input_digest:
|
||||
return _refusal(
|
||||
reason=RunAttemptBindingRefusalReason.ATTEMPT_RESULT_MISMATCH,
|
||||
original_run_id=original_run.run_id,
|
||||
operator_result_id=candidate_operator_result.operator_result_id,
|
||||
candidate_attempt_id=attempt.attempt_id,
|
||||
)
|
||||
if candidate_operator_result.operator_name != attempt.operator_id:
|
||||
return _refusal(
|
||||
reason=RunAttemptBindingRefusalReason.ATTEMPT_RESULT_MISMATCH,
|
||||
original_run_id=original_run.run_id,
|
||||
operator_result_id=candidate_operator_result.operator_result_id,
|
||||
candidate_attempt_id=attempt.attempt_id,
|
||||
)
|
||||
if candidate_operator_result.operator_version != attempt.operator_version:
|
||||
return _refusal(
|
||||
reason=RunAttemptBindingRefusalReason.ATTEMPT_RESULT_MISMATCH,
|
||||
original_run_id=original_run.run_id,
|
||||
operator_result_id=candidate_operator_result.operator_result_id,
|
||||
candidate_attempt_id=attempt.attempt_id,
|
||||
)
|
||||
|
||||
if reconstruction.candidate_digest != candidate_operator_result.candidate_digest:
|
||||
return _refusal(
|
||||
reason=RunAttemptBindingRefusalReason.RECONSTRUCTION_RESULT_MISMATCH,
|
||||
original_run_id=original_run.run_id,
|
||||
operator_result_id=candidate_operator_result.operator_result_id,
|
||||
candidate_attempt_id=attempt.attempt_id,
|
||||
)
|
||||
if (
|
||||
reconstruction.candidate_reconstruction_digest
|
||||
!= candidate_operator_result.candidate_reconstruction_digest
|
||||
):
|
||||
return _refusal(
|
||||
reason=RunAttemptBindingRefusalReason.RECONSTRUCTION_RESULT_MISMATCH,
|
||||
original_run_id=original_run.run_id,
|
||||
operator_result_id=candidate_operator_result.operator_result_id,
|
||||
candidate_attempt_id=attempt.attempt_id,
|
||||
)
|
||||
|
||||
if attempt.replay_status is not CandidateReplayStatus.REPLAY_PENDING:
|
||||
return _refusal(
|
||||
reason=RunAttemptBindingRefusalReason.REPLAY_STATUS_NOT_PENDING,
|
||||
original_run_id=original_run.run_id,
|
||||
operator_result_id=candidate_operator_result.operator_result_id,
|
||||
candidate_attempt_id=attempt.attempt_id,
|
||||
)
|
||||
if attempt.replay_blockers != ():
|
||||
return _refusal(
|
||||
reason=RunAttemptBindingRefusalReason.REPLAY_BLOCKERS_PRESENT,
|
||||
original_run_id=original_run.run_id,
|
||||
operator_result_id=candidate_operator_result.operator_result_id,
|
||||
candidate_attempt_id=attempt.attempt_id,
|
||||
)
|
||||
|
||||
if reconstruction.source_residual_id not in original_run.residual_ids:
|
||||
return _refusal(
|
||||
reason=RunAttemptBindingRefusalReason.RUN_IDENTITY_MISMATCH,
|
||||
original_run_id=original_run.run_id,
|
||||
operator_result_id=candidate_operator_result.operator_result_id,
|
||||
candidate_attempt_id=attempt.attempt_id,
|
||||
)
|
||||
if reconstruction.problem_frame_digest != original_run.problem_frame_digest:
|
||||
return _refusal(
|
||||
reason=RunAttemptBindingRefusalReason.RUN_IDENTITY_MISMATCH,
|
||||
original_run_id=original_run.run_id,
|
||||
operator_result_id=candidate_operator_result.operator_result_id,
|
||||
candidate_attempt_id=attempt.attempt_id,
|
||||
)
|
||||
if (
|
||||
reconstruction.original_contract_assessment_id
|
||||
!= original_run.contract_assessment_id
|
||||
):
|
||||
return _refusal(
|
||||
reason=RunAttemptBindingRefusalReason.RUN_IDENTITY_MISMATCH,
|
||||
original_run_id=original_run.run_id,
|
||||
operator_result_id=candidate_operator_result.operator_result_id,
|
||||
candidate_attempt_id=attempt.attempt_id,
|
||||
)
|
||||
|
||||
if not _operator_provenance_has(
|
||||
reconstruction.operator_provenance,
|
||||
"operator_set_id",
|
||||
original_run.operator_set_id,
|
||||
) or not _operator_provenance_has(
|
||||
reconstruction.operator_provenance,
|
||||
"operator_set_version",
|
||||
original_run.operator_set_version,
|
||||
):
|
||||
return _refusal(
|
||||
reason=RunAttemptBindingRefusalReason.OPERATOR_SET_MISMATCH,
|
||||
original_run_id=original_run.run_id,
|
||||
operator_result_id=candidate_operator_result.operator_result_id,
|
||||
candidate_attempt_id=attempt.attempt_id,
|
||||
)
|
||||
|
||||
if not _valid_spans(attempt.evidence_spans):
|
||||
return _refusal(
|
||||
reason=RunAttemptBindingRefusalReason.MALFORMED_EVIDENCE_SPAN,
|
||||
original_run_id=original_run.run_id,
|
||||
operator_result_id=candidate_operator_result.operator_result_id,
|
||||
candidate_attempt_id=attempt.attempt_id,
|
||||
)
|
||||
if candidate_operator_result.evidence_spans != attempt.evidence_spans:
|
||||
return _refusal(
|
||||
reason=RunAttemptBindingRefusalReason.MALFORMED_EVIDENCE_SPAN,
|
||||
original_run_id=original_run.run_id,
|
||||
operator_result_id=candidate_operator_result.operator_result_id,
|
||||
candidate_attempt_id=attempt.attempt_id,
|
||||
)
|
||||
if reconstruction.evidence_spans != attempt.evidence_spans:
|
||||
return _refusal(
|
||||
reason=RunAttemptBindingRefusalReason.MALFORMED_EVIDENCE_SPAN,
|
||||
original_run_id=original_run.run_id,
|
||||
operator_result_id=candidate_operator_result.operator_result_id,
|
||||
candidate_attempt_id=attempt.attempt_id,
|
||||
)
|
||||
|
||||
for existing in original_run.candidate_attempts:
|
||||
if existing.attempt_index == attempt.attempt_index:
|
||||
return _refusal(
|
||||
reason=RunAttemptBindingRefusalReason.DUPLICATE_ATTEMPT_INDEX,
|
||||
original_run_id=original_run.run_id,
|
||||
operator_result_id=candidate_operator_result.operator_result_id,
|
||||
candidate_attempt_id=attempt.attempt_id,
|
||||
)
|
||||
if existing.attempt_id == attempt.attempt_id:
|
||||
return _refusal(
|
||||
reason=RunAttemptBindingRefusalReason.DUPLICATE_ATTEMPT_ID,
|
||||
original_run_id=original_run.run_id,
|
||||
operator_result_id=candidate_operator_result.operator_result_id,
|
||||
candidate_attempt_id=attempt.attempt_id,
|
||||
)
|
||||
if existing.candidate_digest == attempt.candidate_digest:
|
||||
return _refusal(
|
||||
reason=RunAttemptBindingRefusalReason.DUPLICATE_CANDIDATE_DIGEST,
|
||||
original_run_id=original_run.run_id,
|
||||
operator_result_id=candidate_operator_result.operator_result_id,
|
||||
candidate_attempt_id=attempt.attempt_id,
|
||||
)
|
||||
|
||||
consumed = original_run.budget_consumed
|
||||
charge = attempt.budget_charge
|
||||
if charge.candidates < 0 or charge.steps < 0 or attempt.depth < 0:
|
||||
return _refusal(
|
||||
reason=RunAttemptBindingRefusalReason.BUDGET_CHARGE_EXCEEDS_REMAINING,
|
||||
original_run_id=original_run.run_id,
|
||||
operator_result_id=candidate_operator_result.operator_result_id,
|
||||
candidate_attempt_id=attempt.attempt_id,
|
||||
)
|
||||
if attempt.step_index < 0:
|
||||
return _refusal(
|
||||
reason=RunAttemptBindingRefusalReason.BUDGET_CHARGE_EXCEEDS_REMAINING,
|
||||
original_run_id=original_run.run_id,
|
||||
operator_result_id=candidate_operator_result.operator_result_id,
|
||||
candidate_attempt_id=attempt.attempt_id,
|
||||
)
|
||||
if consumed.candidates_considered + charge.candidates > consumed.max_candidates:
|
||||
return _refusal(
|
||||
reason=RunAttemptBindingRefusalReason.BUDGET_CHARGE_EXCEEDS_REMAINING,
|
||||
original_run_id=original_run.run_id,
|
||||
operator_result_id=candidate_operator_result.operator_result_id,
|
||||
candidate_attempt_id=attempt.attempt_id,
|
||||
)
|
||||
if consumed.steps_used + charge.steps > consumed.max_steps:
|
||||
return _refusal(
|
||||
reason=RunAttemptBindingRefusalReason.BUDGET_CHARGE_EXCEEDS_REMAINING,
|
||||
original_run_id=original_run.run_id,
|
||||
operator_result_id=candidate_operator_result.operator_result_id,
|
||||
candidate_attempt_id=attempt.attempt_id,
|
||||
)
|
||||
if max(consumed.depth_reached, attempt.depth) > consumed.max_depth:
|
||||
return _refusal(
|
||||
reason=RunAttemptBindingRefusalReason.BUDGET_CHARGE_EXCEEDS_REMAINING,
|
||||
original_run_id=original_run.run_id,
|
||||
operator_result_id=candidate_operator_result.operator_result_id,
|
||||
candidate_attempt_id=attempt.attempt_id,
|
||||
)
|
||||
if consumed.parallelism_used > consumed.max_parallelism:
|
||||
return _refusal(
|
||||
reason=RunAttemptBindingRefusalReason.BUDGET_CHARGE_EXCEEDS_REMAINING,
|
||||
original_run_id=original_run.run_id,
|
||||
operator_result_id=candidate_operator_result.operator_result_id,
|
||||
candidate_attempt_id=attempt.attempt_id,
|
||||
)
|
||||
|
||||
input_payload = _binding_input_payload(
|
||||
original_run=original_run,
|
||||
candidate_operator_result=candidate_operator_result,
|
||||
schema_versions=schema_versions,
|
||||
policy_versions=policy_versions,
|
||||
)
|
||||
input_digest = _canonical_digest(input_payload)
|
||||
binding_input = CandidateAttemptRunBindingInput(
|
||||
input_digest=input_digest,
|
||||
residual_ids=original_run.residual_ids,
|
||||
schema_versions=schema_versions,
|
||||
policy_versions=policy_versions,
|
||||
**{
|
||||
key: value
|
||||
for key, value in input_payload.items()
|
||||
if key not in {"residual_ids", "schema_versions", "policy_versions"}
|
||||
},
|
||||
)
|
||||
candidate_attempt_ref = _canonical_digest(_candidate_attempt_payload(attempt))
|
||||
binding_id = _canonical_digest(
|
||||
_binding_id_payload(
|
||||
input_digest=binding_input.input_digest,
|
||||
original_run_id=original_run.run_id,
|
||||
operator_result_id=candidate_operator_result.operator_result_id,
|
||||
candidate_attempt_id=attempt.attempt_id,
|
||||
attempt_index=attempt.attempt_index,
|
||||
candidate_digest=attempt.candidate_digest,
|
||||
candidate_reconstruction_digest=reconstruction.candidate_reconstruction_digest,
|
||||
candidate_attempt_ref=candidate_attempt_ref,
|
||||
budget_charge=charge,
|
||||
depth=attempt.depth,
|
||||
step_index=attempt.step_index,
|
||||
evidence_spans=attempt.evidence_spans,
|
||||
)
|
||||
)
|
||||
return CandidateAttemptRunBinding(
|
||||
binding_id=binding_id,
|
||||
binding_policy_version=CANDIDATE_ATTEMPT_RUN_BINDING_POLICY_VERSION,
|
||||
input_digest=binding_input.input_digest,
|
||||
original_run_id=original_run.run_id,
|
||||
operator_result_id=candidate_operator_result.operator_result_id,
|
||||
candidate_attempt_id=attempt.attempt_id,
|
||||
attempt_index=attempt.attempt_index,
|
||||
candidate_digest=attempt.candidate_digest,
|
||||
candidate_reconstruction_digest=reconstruction.candidate_reconstruction_digest,
|
||||
candidate_attempt_ref=candidate_attempt_ref,
|
||||
budget_charge=charge,
|
||||
depth=attempt.depth,
|
||||
step_index=attempt.step_index,
|
||||
run_attempt_membership="structurally_bound",
|
||||
evidence_spans=attempt.evidence_spans,
|
||||
reason_codes=(),
|
||||
explanation=explanation
|
||||
or "CandidateAttempt structurally bound to GeometricSearchRun.",
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"CANDIDATE_ATTEMPT_RUN_BINDING_POLICY_VERSION",
|
||||
"RunAttemptBindingRefusalReason",
|
||||
"CandidateAttemptRunBindingInput",
|
||||
"CandidateAttemptRunBinding",
|
||||
"CandidateAttemptRunBindingRefusal",
|
||||
"CandidateAttemptRunBindingOutcome",
|
||||
"bind_candidate_attempt_to_run",
|
||||
]
|
||||
Loading…
Reference in a new issue