fix(rc05): harden canary and resume controls
This commit is contained in:
@@ -25,6 +25,22 @@ TERMINAL_STATUSES = frozenset({"succeeded", "failed", "cancelled"})
|
||||
MAX_PREALLOCATED_ATTEMPTS = 280
|
||||
MAX_REPAIR_ATTEMPTS = 16
|
||||
REPAIR_OPERATIONS = frozenset({"repair_draft", "repair_review"})
|
||||
REAL_PROVIDER_NAME = "codex-cli-development"
|
||||
REAL_PROVIDER_MODEL = "gpt-5.6-sol"
|
||||
AFTER_FILES = {
|
||||
"draft": frozenset({"01-draft-after.json"}),
|
||||
"semantic_review": frozenset({"02-semantic-after.json"}),
|
||||
"repair_draft": frozenset({"03-repair-after.json"}),
|
||||
"repair_review": frozenset({"04-post-review-after.json"}),
|
||||
"specialist_review": frozenset({"02-specialist-after.json", "02-after.json"}),
|
||||
"intake": frozenset({"02-after.json"}),
|
||||
}
|
||||
JOB_OPERATIONS = {
|
||||
"contract_draft": frozenset({"draft", "repair_draft"}),
|
||||
"contract_draft_semantic_review": frozenset({"semantic_review", "repair_review"}),
|
||||
"contract_review": frozenset({"specialist_review"}),
|
||||
"contract_intake": frozenset({"intake"}),
|
||||
}
|
||||
LEDGER_SCHEMA = "multiscene-call-ledger.v2"
|
||||
CAMPAIGN_SCHEMA = "multiscene-call-campaign.v2"
|
||||
CONTROL_SCHEMA = "multiscene-release-control-state.v1"
|
||||
@@ -209,6 +225,7 @@ class CallLedger:
|
||||
|
||||
def control_snapshot(self) -> dict[str, object]:
|
||||
with self._locked():
|
||||
self._materialize_attempt_breakers_unlocked()
|
||||
return self._control_snapshot_unlocked()
|
||||
|
||||
def authorize_selection(self, selected: list[str], *, resume: bool,
|
||||
@@ -258,6 +275,7 @@ class CallLedger:
|
||||
if state["blocked_failure_classes"]:
|
||||
raise CallLedgerError("failed or unknown canary cannot authorize full dispatch")
|
||||
records = self._records()
|
||||
binding_sha256s: dict[str, str] = {}
|
||||
for case_id in canaries:
|
||||
relevant = [record for (current, _operation), record in records.items()
|
||||
if current == case_id]
|
||||
@@ -265,12 +283,15 @@ class CallLedger:
|
||||
or record["finish"]["status"] != "succeeded"
|
||||
for record in relevant):
|
||||
raise CallLedgerError("canary provider attempts are incomplete or unsuccessful")
|
||||
binding = _read_json(self.case_outcomes / f"{case_id}.json")
|
||||
binding = self._validated_persisted_outcome_unlocked(
|
||||
case_id, records, require_success=True)
|
||||
if binding.get("outcome_sha256") != normalized[case_id]:
|
||||
raise CallLedgerError("canary outcome does not match immutable binding")
|
||||
binding_sha256s[case_id] = _digest(binding)
|
||||
payload = {"schema_version": CONTROL_SCHEMA,
|
||||
"event": "canary_to_full", "ledger_identity_sha256": self.identity_sha256,
|
||||
"canary_case_ids": canaries, "outcome_sha256s": normalized}
|
||||
"canary_case_ids": canaries, "outcome_sha256s": normalized,
|
||||
"case_outcome_binding_sha256s": binding_sha256s}
|
||||
transition = self.control_events / "canary-to-full.json"
|
||||
if transition.exists():
|
||||
if _read_json(transition) != payload:
|
||||
@@ -302,11 +323,29 @@ class CallLedger:
|
||||
def _materialize_attempt_breakers_unlocked(self) -> None:
|
||||
if not self.controls_enabled:
|
||||
return
|
||||
for (case_id, operation), record in self._records().items():
|
||||
status = (record["finish"]["status"] if record["finish"] is not None
|
||||
else "unknown")
|
||||
if status == "succeeded":
|
||||
continue
|
||||
records = self._records()
|
||||
cases = sorted({case_id for case_id, _operation in records})
|
||||
for case_id in cases:
|
||||
relevant = [record for (current, _operation), record in records.items()
|
||||
if current == case_id]
|
||||
unfinished = any(record["finish"] is None for record in relevant)
|
||||
unsuccessful = next((record["finish"]["status"] for record in relevant
|
||||
if record["finish"] is not None
|
||||
and record["finish"]["status"] != "succeeded"), None)
|
||||
if unfinished:
|
||||
status, reason = "unknown", "ledger has an unfinished provider attempt"
|
||||
elif unsuccessful is not None:
|
||||
status, reason = unsuccessful, f"provider attempt finished as {unsuccessful}"
|
||||
else:
|
||||
try:
|
||||
self._validated_persisted_outcome_unlocked(
|
||||
case_id, records, require_success=True)
|
||||
except (OSError, UnicodeError, TypeError, ValueError,
|
||||
RuntimeError, CallLedgerError) as error:
|
||||
status = "unknown"
|
||||
reason = f"successful provider attempt has invalid case outcome: {error}"
|
||||
else:
|
||||
continue
|
||||
failure_class = self.release_controls["failure_classes"][case_id]
|
||||
path = self.control_events / f"failure-class--{failure_class}.json"
|
||||
if not path.exists():
|
||||
@@ -314,7 +353,7 @@ class CallLedger:
|
||||
"event": "failure_class_blocked",
|
||||
"ledger_identity_sha256": self.identity_sha256,
|
||||
"failure_class": failure_class, "case_id": case_id,
|
||||
"status": status, "reason": f"ledger attempt {operation} is {status}"})
|
||||
"status": status, "reason": reason[:1000]})
|
||||
|
||||
def _control_snapshot_unlocked(self) -> dict[str, object]:
|
||||
if not self.controls_enabled:
|
||||
@@ -323,6 +362,7 @@ class CallLedger:
|
||||
"canary_transition_sha256": None}
|
||||
blocked: list[str] = []
|
||||
transition_sha = None
|
||||
records = self._records()
|
||||
for path in self.control_events.iterdir():
|
||||
_reject_link_or_reparse(path, "control event")
|
||||
if not path.is_file():
|
||||
@@ -333,11 +373,48 @@ class CallLedger:
|
||||
or payload.get("ledger_identity_sha256") != self.identity_sha256):
|
||||
raise CallLedgerError("control event is invalid")
|
||||
if path.name == "canary-to-full.json" and payload.get("event") == "canary_to_full":
|
||||
expected_keys = {
|
||||
"schema_version", "event", "ledger_identity_sha256",
|
||||
"canary_case_ids", "outcome_sha256s",
|
||||
"case_outcome_binding_sha256s",
|
||||
}
|
||||
canaries = self.release_controls["canary_case_ids"]
|
||||
outcomes = payload.get("outcome_sha256s")
|
||||
bindings = payload.get("case_outcome_binding_sha256s")
|
||||
if (set(payload) != expected_keys
|
||||
or payload.get("canary_case_ids") != canaries
|
||||
or not isinstance(outcomes, dict) or set(outcomes) != set(canaries)
|
||||
or not isinstance(bindings, dict) or set(bindings) != set(canaries)):
|
||||
raise CallLedgerError("canary transition shape or inventory is invalid")
|
||||
for case_id in canaries:
|
||||
outcome_sha = _sha(outcomes[case_id], "canary transition outcome SHA-256")
|
||||
binding_sha = _sha(
|
||||
bindings[case_id], "canary transition binding SHA-256")
|
||||
binding = self._validated_persisted_outcome_unlocked(
|
||||
case_id, records, require_success=True)
|
||||
if (binding["outcome_sha256"] != outcome_sha
|
||||
or _digest(binding) != binding_sha):
|
||||
raise CallLedgerError(
|
||||
"canary transition differs from immutable outcome binding")
|
||||
transition_sha = _digest(payload)
|
||||
elif (re.fullmatch(r"failure-class--[A-Za-z0-9._-]+\.json", path.name)
|
||||
and payload.get("event") == "failure_class_blocked"):
|
||||
expected_keys = {
|
||||
"schema_version", "event", "ledger_identity_sha256",
|
||||
"failure_class", "case_id", "status", "reason",
|
||||
}
|
||||
if set(payload) != expected_keys:
|
||||
raise CallLedgerError("failure-class control event shape is invalid")
|
||||
failure_class = _identifier(payload.get("failure_class"), "failure class")
|
||||
if failure_class not in set(self.release_controls["failure_classes"].values()):
|
||||
case_id = _identifier(payload.get("case_id"), "failure event case ID")
|
||||
expected_name = f"failure-class--{failure_class}.json"
|
||||
if (path.name != expected_name or case_id not in self.case_limits
|
||||
or self.release_controls["failure_classes"][case_id] != failure_class
|
||||
or payload.get("status") not in {"failed", "cancelled", "unknown"}
|
||||
or not isinstance(payload.get("reason"), str)
|
||||
or not payload["reason"] or len(payload["reason"]) > 1000
|
||||
or failure_class not in set(
|
||||
self.release_controls["failure_classes"].values())):
|
||||
raise CallLedgerError("control event failure class is outside frozen controls")
|
||||
blocked.append(failure_class)
|
||||
else:
|
||||
@@ -454,11 +531,41 @@ class CallLedger:
|
||||
|
||||
def snapshot(self) -> dict:
|
||||
with self._locked():
|
||||
self._materialize_attempt_breakers_unlocked()
|
||||
records = self._records()
|
||||
successful_case_evidence: dict[str, bool] = {}
|
||||
for case_id in self.case_limits if self.controls_enabled else ():
|
||||
relevant = [record for (current, _operation), record in records.items()
|
||||
if current == case_id]
|
||||
if not relevant or any(record["finish"] is None
|
||||
or record["finish"]["status"] != "succeeded"
|
||||
for record in relevant):
|
||||
continue
|
||||
try:
|
||||
self._validated_persisted_outcome_unlocked(
|
||||
case_id, records, require_success=True)
|
||||
except (OSError, UnicodeError, TypeError, ValueError,
|
||||
RuntimeError, CallLedgerError):
|
||||
successful_case_evidence[case_id] = False
|
||||
else:
|
||||
successful_case_evidence[case_id] = True
|
||||
|
||||
def evidence_terminal(case_id: str, record: dict) -> bool:
|
||||
finish = record["finish"]
|
||||
if finish is None:
|
||||
return False
|
||||
if not self.controls_enabled:
|
||||
return True
|
||||
if finish["status"] != "succeeded":
|
||||
return True
|
||||
return successful_case_evidence.get(case_id, False)
|
||||
|
||||
operations = {}
|
||||
for operation in OPERATIONS:
|
||||
relevant = [record for (case, op), record in records.items() if op == operation]
|
||||
terminal = sum(record["finish"] is not None for record in relevant)
|
||||
relevant = [(case, record) for (case, op), record in records.items()
|
||||
if op == operation]
|
||||
terminal = sum(evidence_terminal(case, record)
|
||||
for case, record in relevant)
|
||||
operations[operation] = {
|
||||
"limit": self.operation_limits.get(operation, 0),
|
||||
"attempted": len(relevant), "terminal": terminal,
|
||||
@@ -467,14 +574,39 @@ class CallLedger:
|
||||
cases = {}
|
||||
for case_id, limit in sorted(self.case_limits.items()):
|
||||
relevant = [record for (case, _op), record in records.items() if case == case_id]
|
||||
terminal = sum(record["finish"] is not None for record in relevant)
|
||||
terminal = sum(evidence_terminal(case_id, record) for record in relevant)
|
||||
cases[case_id] = {"limit": limit, "attempted": len(relevant),
|
||||
"terminal": terminal, "unknown": len(relevant) - terminal}
|
||||
attempted = len(records)
|
||||
provider_terminal = sum(
|
||||
record["finish"] is not None for record in records.values())
|
||||
terminal = sum(evidence_terminal(case_id, record)
|
||||
for (case_id, _operation), record in records.items())
|
||||
campaign_records = self._campaign_records() if self.registry_root is not None else records
|
||||
campaign_attempted = len(campaign_records)
|
||||
campaign_terminal = sum(
|
||||
campaign_provider_terminal = sum(
|
||||
record["finish"] is not None for record in campaign_records.values())
|
||||
|
||||
def campaign_evidence_terminal(record: dict) -> bool:
|
||||
finish = record["finish"]
|
||||
if finish is None:
|
||||
return False
|
||||
if finish["status"] != "succeeded":
|
||||
return True
|
||||
claim = record["claim"]
|
||||
if self.registry_root is None:
|
||||
return evidence_terminal(claim["case_id"], record)
|
||||
if claim["plan_sha256"] != self.plan_sha256:
|
||||
# Foreign-plan attempts already fail the current candidate
|
||||
# gate and their output directory is deliberately not read.
|
||||
return True
|
||||
local = records.get((claim["case_id"], claim["operation"]))
|
||||
return bool(local is not None
|
||||
and evidence_terminal(claim["case_id"], local))
|
||||
|
||||
campaign_terminal = sum(
|
||||
campaign_evidence_terminal(record)
|
||||
for record in campaign_records.values())
|
||||
if self.registry_root is not None:
|
||||
current_plan_attempted = sum(
|
||||
record["claim"]["plan_sha256"] == self.plan_sha256
|
||||
@@ -544,14 +676,16 @@ class CallLedger:
|
||||
"campaign": {"attempted": campaign_attempted,
|
||||
"terminal": campaign_terminal,
|
||||
"unknown": campaign_attempted - campaign_terminal,
|
||||
"provider_terminal": campaign_provider_terminal,
|
||||
"current_plan_attempted": current_plan_attempted,
|
||||
"foreign_plan_attempted": (
|
||||
campaign_attempted - current_plan_attempted
|
||||
),
|
||||
"provider_outcomes": campaign_provider_outcomes},
|
||||
"attempted": attempted,
|
||||
"terminal": sum(record["finish"] is not None for record in records.values()),
|
||||
"unknown": sum(record["finish"] is None for record in records.values()),
|
||||
"terminal": terminal,
|
||||
"unknown": attempted - terminal,
|
||||
"provider_terminal": provider_terminal,
|
||||
"release_controls": controls}
|
||||
|
||||
def attempt_records(self) -> list[dict[str, object]]:
|
||||
@@ -659,6 +793,150 @@ class CallLedger:
|
||||
if payload != expected:
|
||||
raise CallLedgerError("case outcome or evidence changed after binding")
|
||||
|
||||
def _validated_persisted_outcome_unlocked(
|
||||
self,
|
||||
case_id: str,
|
||||
records: Mapping[tuple[str, str], dict],
|
||||
*,
|
||||
require_success: bool,
|
||||
) -> dict[str, object]:
|
||||
"""Validate a persisted case outcome and every file it binds.
|
||||
|
||||
This is intentionally independent of the caller-provided hashes used by
|
||||
``verify_case_outcome``. Resume authorization must rediscover the files
|
||||
from the ledger and fail closed after a crash or later mutation.
|
||||
"""
|
||||
case_id = _identifier(case_id, "persisted outcome case ID")
|
||||
binding_path = self.case_outcomes / f"{case_id}.json"
|
||||
_reject_link_or_reparse(binding_path, "case outcome binding")
|
||||
if not binding_path.is_file():
|
||||
raise CallLedgerError("case outcome binding is missing")
|
||||
payload = _read_json(binding_path)
|
||||
expected_keys = {
|
||||
"schema_version", "ledger_identity_sha256", "case_id",
|
||||
"outcome_file", "outcome_sha256", "case_attempts_sha256",
|
||||
"expected_operations", "case_operation_projection",
|
||||
"evidence_sha256s",
|
||||
}
|
||||
if not isinstance(payload, dict) or set(payload) != expected_keys:
|
||||
raise CallLedgerError("case outcome binding shape is invalid")
|
||||
outcome_file = payload.get("outcome_file")
|
||||
if outcome_file not in {"result.json", "failure.json"}:
|
||||
raise CallLedgerError("case outcome binding file is invalid")
|
||||
outcome_sha = _sha(payload.get("outcome_sha256"), "case outcome SHA-256")
|
||||
expected_operations = _operation_projection(payload.get("expected_operations"))
|
||||
evidence = _evidence_hashes(payload.get("evidence_sha256s"))
|
||||
projection = _case_attempt_projection(records, case_id)
|
||||
expected = {
|
||||
"schema_version": "multiscene-case-outcome.v2",
|
||||
"ledger_identity_sha256": self.identity_sha256,
|
||||
"case_id": case_id,
|
||||
"outcome_file": outcome_file,
|
||||
"outcome_sha256": outcome_sha,
|
||||
"expected_operations": expected_operations,
|
||||
"case_operation_projection": projection,
|
||||
"case_attempts_sha256": _digest(projection),
|
||||
"evidence_sha256s": evidence,
|
||||
}
|
||||
if payload != expected:
|
||||
raise CallLedgerError("case outcome binding no longer matches its attempts")
|
||||
|
||||
case_dir = self.output_dir / case_id
|
||||
_reject_link_or_reparse(case_dir, "case outcome directory")
|
||||
if not case_dir.is_dir():
|
||||
raise CallLedgerError("case outcome directory is missing")
|
||||
for candidate_name in ("result.json", "failure.json"):
|
||||
_reject_link_or_reparse(case_dir / candidate_name, "case outcome artifact")
|
||||
candidates = [case_dir / name for name in ("result.json", "failure.json")
|
||||
if (case_dir / name).is_file()]
|
||||
if len(candidates) != 1 or candidates[0].name != outcome_file:
|
||||
raise CallLedgerError("case outcome artifact is missing or ambiguous")
|
||||
outcome_path = candidates[0]
|
||||
if hashlib.sha256(outcome_path.read_bytes()).hexdigest() != outcome_sha:
|
||||
raise CallLedgerError("case outcome artifact digest mismatch")
|
||||
outcome = _read_json(outcome_path)
|
||||
if not isinstance(outcome, dict):
|
||||
raise CallLedgerError("case outcome artifact must be an object")
|
||||
calls = outcome.get("provider_call_count")
|
||||
if (outcome.get("case_id") != case_id
|
||||
or not isinstance(calls, int) or isinstance(calls, bool)
|
||||
or calls != len(projection)):
|
||||
raise CallLedgerError("case outcome artifact identity or call count is invalid")
|
||||
|
||||
actual_after_names: set[str] = set()
|
||||
for child in case_dir.iterdir():
|
||||
_reject_link_or_reparse(child, "case evidence artifact")
|
||||
if child.name.endswith("-after.json"):
|
||||
if not child.is_file():
|
||||
raise CallLedgerError("case evidence artifact is not a regular file")
|
||||
actual_after_names.add(child.name)
|
||||
if actual_after_names != set(evidence):
|
||||
raise CallLedgerError("case evidence inventory differs from immutable binding")
|
||||
evidence_by_operation: dict[str, dict] = {}
|
||||
for name, digest in evidence.items():
|
||||
evidence_path = case_dir / name
|
||||
if (not evidence_path.is_file()
|
||||
or hashlib.sha256(evidence_path.read_bytes()).hexdigest() != digest):
|
||||
raise CallLedgerError("case evidence artifact digest mismatch")
|
||||
evidence_payload = _read_json(evidence_path)
|
||||
if not isinstance(evidence_payload, dict):
|
||||
raise CallLedgerError("case evidence artifact must be an object")
|
||||
operation = evidence_payload.get("operation")
|
||||
if operation not in OPERATIONS or operation in evidence_by_operation:
|
||||
raise CallLedgerError("case evidence operation is invalid or duplicate")
|
||||
if name not in AFTER_FILES[operation]:
|
||||
raise CallLedgerError("case evidence file name does not match its operation")
|
||||
evidence_by_operation[operation] = evidence_payload
|
||||
|
||||
if require_success:
|
||||
if (outcome_file != "result.json" or outcome.get("status") != "completed"
|
||||
or outcome.get("passed") is not True
|
||||
or outcome.get("automated_quality_passed") is not True
|
||||
or outcome.get("automated_model_quality_release_passed") is not True
|
||||
or outcome.get("content_quality_release_passed") is not True):
|
||||
raise CallLedgerError("successful case outcome quality gate is incomplete")
|
||||
if any(item["status"] != "succeeded" for item in projection):
|
||||
raise CallLedgerError("successful case outcome has a non-successful provider attempt")
|
||||
actual_operations = [item["operation"] for item in projection]
|
||||
actual_set, expected_set = set(actual_operations), set(expected_operations)
|
||||
repair_actual = actual_set & REPAIR_OPERATIONS
|
||||
mandatory = expected_set - REPAIR_OPERATIONS
|
||||
if (not projection or len(actual_operations) != len(actual_set)
|
||||
or not actual_set <= expected_set or not mandatory <= actual_set
|
||||
or repair_actual not in (set(), set(REPAIR_OPERATIONS))
|
||||
or set(evidence_by_operation) != actual_set):
|
||||
raise CallLedgerError("successful case outcome operation coverage is incomplete")
|
||||
for item in projection:
|
||||
after = evidence_by_operation[item["operation"]]
|
||||
elapsed = after.get("execution_elapsed_seconds")
|
||||
job = after.get("job")
|
||||
audit = after.get("audit")
|
||||
if (not isinstance(elapsed, (int, float)) or isinstance(elapsed, bool)
|
||||
or elapsed < 0 or not isinstance(job, dict)
|
||||
or not isinstance(audit, dict)):
|
||||
raise CallLedgerError("case evidence measurements are invalid")
|
||||
job_id = job.get("id")
|
||||
audit_id = audit.get("id")
|
||||
audit_run_id = job.get("audit_run_id")
|
||||
job_operation = job.get("operation")
|
||||
if (after.get("case_id") != case_id
|
||||
or after.get("request_sha256") != item["request_sha256"]
|
||||
or after.get("response_sha256") != item["response_sha256"]
|
||||
or after.get("provider_invocation_count") != 1
|
||||
or job.get("status") != "succeeded"
|
||||
or job.get("provider_status") != "succeeded"
|
||||
or job_operation not in JOB_OPERATIONS
|
||||
or item["operation"] not in JOB_OPERATIONS[job_operation]
|
||||
or not isinstance(job_id, str) or not job_id
|
||||
or not isinstance(audit_id, str) or not audit_id
|
||||
or not isinstance(audit_run_id, str) or not audit_run_id
|
||||
or audit_id != audit_run_id
|
||||
or audit.get("provider") != REAL_PROVIDER_NAME
|
||||
or audit.get("model") != REAL_PROVIDER_MODEL
|
||||
or audit.get("provider_status") != "succeeded"):
|
||||
raise CallLedgerError("case evidence differs from immutable provider attempt")
|
||||
return payload
|
||||
|
||||
def _records(self) -> dict[tuple[str, str], dict]:
|
||||
_reject_link_or_reparse(self.root / "identity.json", "ledger identity")
|
||||
expected_identity = _read_json(self.root / "identity.json")
|
||||
|
||||
@@ -117,9 +117,25 @@ def _artifact_bytes(path: Path, root: Path, label: str) -> bytes:
|
||||
|
||||
|
||||
def _git(root: Path, *args: str, binary: bool = False):
|
||||
environment = {**os.environ, "GIT_CONFIG_NOSYSTEM": "1",
|
||||
"GIT_CONFIG_GLOBAL": os.devnull, "GIT_OPTIONAL_LOCKS": "0",
|
||||
"GIT_NO_REPLACE_OBJECTS": "1"}
|
||||
# Git's repository-selection variables override ``-C``. A release shell
|
||||
# that happens to export GIT_DIR/GIT_WORK_TREE (or Git's counted config
|
||||
# injection variables) must never redirect candidate verification to a
|
||||
# different clean checkout. Build a small execution environment instead
|
||||
# of trying to maintain an incomplete deny-list of GIT_* variables.
|
||||
inherited = (
|
||||
"PATH", "PATHEXT", "SystemRoot", "SYSTEMROOT", "WINDIR", "COMSPEC",
|
||||
"TEMP", "TMP", "TMPDIR",
|
||||
)
|
||||
environment = {name: os.environ[name] for name in inherited if name in os.environ}
|
||||
environment.update({
|
||||
"GIT_CONFIG_NOSYSTEM": "1",
|
||||
"GIT_CONFIG_GLOBAL": os.devnull,
|
||||
"GIT_OPTIONAL_LOCKS": "0",
|
||||
"GIT_NO_REPLACE_OBJECTS": "1",
|
||||
"GIT_TERMINAL_PROMPT": "0",
|
||||
"LANG": "C",
|
||||
"LC_ALL": "C",
|
||||
})
|
||||
completed = subprocess.run(
|
||||
["git", "-C", str(root), *args], capture_output=True,
|
||||
text=not binary, check=False, env=environment,
|
||||
|
||||
@@ -1152,19 +1152,6 @@ def execute(runner, plan_path, expected_sha, output_dir, factory, *, resume=Fals
|
||||
ledger.trip_failure_class(key, "failed", failure["error"][:1000])
|
||||
_bind_case_outcome(runner, out, ledger, case, "failure.json")
|
||||
break # Do not consume the remainder after a newly observed failure.
|
||||
if real and control_before["phase"] == "canary_required":
|
||||
canaries = plan["release_controls"]["canary_case_ids"]
|
||||
canary_rows = [_existing_case(runner, out, inventory[key], ledger.snapshot(), ledger)
|
||||
for key in canaries]
|
||||
if all(row.get("status") == "completed" and row.get("passed") is True
|
||||
and row.get("automated_quality_passed") is True
|
||||
and row.get("automated_model_quality_release_passed") is True
|
||||
and row.get("content_quality_release_passed") is True
|
||||
for row in canary_rows):
|
||||
ledger.complete_canary({
|
||||
key: runner.sha256((out / key / "result.json").read_bytes())
|
||||
for key in canaries
|
||||
})
|
||||
# A drift during the final case must not be hidden by a green summary.
|
||||
if real:
|
||||
final_binding, final_source_index = load_candidate_binding(
|
||||
@@ -1238,6 +1225,24 @@ def execute(runner, plan_path, expected_sha, output_dir, factory, *, resume=Fals
|
||||
)
|
||||
historical_release_eligible, historical_remediation = _historical_remediation(
|
||||
out, ledger, rows)
|
||||
canary_gate_passed = None
|
||||
if real and control_before["phase"] == "canary_required":
|
||||
canaries = plan["release_controls"]["canary_case_ids"]
|
||||
canary_gate_passed = bool(
|
||||
selected == canaries
|
||||
and current_selection_passed
|
||||
and selected_release_quality_passed
|
||||
)
|
||||
# Persist authorization only after provider/business outcomes, immutable
|
||||
# result/evidence bindings, mandatory operation coverage, and every
|
||||
# quality gate above have all been evaluated. A failing summary must
|
||||
# never leave behind a green transition for the next resume.
|
||||
if canary_gate_passed:
|
||||
ledger.complete_canary({
|
||||
key: runner.sha256((out / key / "result.json").read_bytes())
|
||||
for key in canaries
|
||||
})
|
||||
snapshot = ledger.snapshot()
|
||||
overall_release_passed = (matrix_execution_passed
|
||||
and automated_model_quality_release_passed
|
||||
and historical_release_eligible)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import subprocess
|
||||
@@ -78,6 +79,61 @@ def _controls(case_classes=None, canaries=None):
|
||||
"failure_classes": case_classes}
|
||||
|
||||
|
||||
def _write_json(path, value):
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(json.dumps(value, ensure_ascii=False, sort_keys=True,
|
||||
separators=(",", ":")), encoding="utf-8")
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def _bind_success(book, root, case_id, expected_operations):
|
||||
records = [record for record in book.attempt_records()
|
||||
if record["case_id"] == case_id]
|
||||
evidence = {}
|
||||
names = {
|
||||
"draft": "01-draft-after.json",
|
||||
"semantic_review": "02-semantic-after.json",
|
||||
"repair_draft": "03-repair-after.json",
|
||||
"repair_review": "04-post-review-after.json",
|
||||
"specialist_review": "02-specialist-after.json",
|
||||
"intake": "02-after.json",
|
||||
}
|
||||
jobs = {
|
||||
"draft": "contract_draft",
|
||||
"semantic_review": "contract_draft_semantic_review",
|
||||
"repair_draft": "contract_draft",
|
||||
"repair_review": "contract_draft_semantic_review",
|
||||
"specialist_review": "contract_review",
|
||||
"intake": "contract_intake",
|
||||
}
|
||||
for index, record in enumerate(records, 1):
|
||||
name = names[record["operation"]]
|
||||
run_id = f"audit-{index}"
|
||||
evidence[name] = _write_json(root / case_id / name, {
|
||||
"case_id": case_id,
|
||||
"operation": record["operation"],
|
||||
"request_sha256": record["request_sha256"],
|
||||
"response_sha256": record["response_sha256"],
|
||||
"provider_invocation_count": 1,
|
||||
"execution_elapsed_seconds": 0.25,
|
||||
"job": {"id": f"job-{index}", "audit_run_id": run_id,
|
||||
"operation": jobs[record["operation"]], "status": "succeeded",
|
||||
"provider_status": "succeeded"},
|
||||
"audit": {"id": run_id, "provider": "codex-cli-development",
|
||||
"model": "gpt-5.6-sol", "provider_status": "succeeded"},
|
||||
})
|
||||
outcome_sha = _write_json(root / case_id / "result.json", {
|
||||
"case_id": case_id, "status": "completed", "passed": True,
|
||||
"provider_call_count": len(records),
|
||||
"automated_quality_passed": True,
|
||||
"automated_model_quality_release_passed": True,
|
||||
"content_quality_release_passed": True,
|
||||
})
|
||||
book.bind_case_outcome(
|
||||
case_id, "result.json", outcome_sha, evidence, expected_operations)
|
||||
return outcome_sha
|
||||
|
||||
|
||||
def test_claim_precedes_call_and_finish_is_immutable(tmp_path):
|
||||
calls = []
|
||||
book = ledger(tmp_path)
|
||||
@@ -106,12 +162,10 @@ def test_canary_transition_is_explicit_persisted_and_required_before_full(tmp_pa
|
||||
assert state["phase"] == "canary_required"
|
||||
token = book.claim("purchase", "draft", SHA_A)
|
||||
book.finish(token, "succeeded", SHA_B)
|
||||
outcome = tmp_path / "purchase-result.json"
|
||||
outcome.write_text("{}", encoding="utf-8")
|
||||
book.bind_case_outcome("purchase", "result.json", SHA_A, {}, ["draft"])
|
||||
outcome_sha = _bind_success(book, tmp_path, "purchase", ["draft"])
|
||||
with pytest.raises(LEDGER.CallLedgerError, match="outcome does not match"):
|
||||
book.complete_canary({"purchase": SHA_B})
|
||||
book.complete_canary({"purchase": SHA_A})
|
||||
book.complete_canary({"purchase": outcome_sha})
|
||||
assert book.control_snapshot()["phase"] == "full_authorized"
|
||||
resumed = ledger(tmp_path, release_controls=_controls())
|
||||
assert resumed.control_snapshot()["phase"] == "full_authorized"
|
||||
@@ -126,8 +180,8 @@ def test_failure_class_breaker_persists_and_unknown_stays_in_denominator(tmp_pat
|
||||
book.authorize_selection(["purchase"], resume=False, explicit_selection=True)
|
||||
token = book.claim("purchase", "draft", SHA_A)
|
||||
book.finish(token, "succeeded", SHA_B)
|
||||
book.bind_case_outcome("purchase", "result.json", SHA_A, {}, ["draft"])
|
||||
book.complete_canary({"purchase": SHA_A})
|
||||
outcome_sha = _bind_success(book, tmp_path, "purchase", ["draft"])
|
||||
book.complete_canary({"purchase": outcome_sha})
|
||||
book.claim("service", "draft", SHA_A) # interrupted provider call => unknown
|
||||
resumed = ledger(tmp_path, case_limits={key: 1 for key in classes},
|
||||
operation_limits={"draft": 3},
|
||||
@@ -139,6 +193,173 @@ def test_failure_class_breaker_persists_and_unknown_stays_in_denominator(tmp_pat
|
||||
assert snapshot["release_controls"]["blocked_failure_classes"] == ["specialist"]
|
||||
|
||||
|
||||
def test_forged_canary_transition_with_missing_fields_is_rejected(tmp_path):
|
||||
book = ledger(tmp_path, release_controls=_controls())
|
||||
(book.control_events / "canary-to-full.json").write_text(json.dumps({
|
||||
"schema_version": LEDGER.CONTROL_SCHEMA,
|
||||
"event": "canary_to_full",
|
||||
"ledger_identity_sha256": book.identity_sha256,
|
||||
}), encoding="utf-8")
|
||||
with pytest.raises(LEDGER.CallLedgerError, match="shape or inventory"):
|
||||
book.control_snapshot()
|
||||
|
||||
|
||||
def test_hash_consistent_canary_transition_rejects_non_terminal_business_evidence(tmp_path):
|
||||
book = ledger(tmp_path, release_controls=_controls())
|
||||
token = book.claim("purchase", "draft", SHA_A)
|
||||
book.finish(token, "succeeded", SHA_B)
|
||||
outcome_sha = _bind_success(book, tmp_path, "purchase", ["draft"])
|
||||
binding_path = tmp_path / ".call-ledger/case-outcomes/purchase.json"
|
||||
binding = json.loads(binding_path.read_text(encoding="utf-8"))
|
||||
evidence_path = tmp_path / "purchase/01-draft-after.json"
|
||||
after = json.loads(evidence_path.read_text(encoding="utf-8"))
|
||||
after["job"]["status"] = "failed"
|
||||
evidence_sha = _write_json(evidence_path, after)
|
||||
binding["evidence_sha256s"][evidence_path.name] = evidence_sha
|
||||
_write_json(binding_path, binding)
|
||||
transition = {
|
||||
"schema_version": LEDGER.CONTROL_SCHEMA,
|
||||
"event": "canary_to_full",
|
||||
"ledger_identity_sha256": book.identity_sha256,
|
||||
"canary_case_ids": ["purchase"],
|
||||
"outcome_sha256s": {"purchase": outcome_sha},
|
||||
"case_outcome_binding_sha256s": {
|
||||
"purchase": LEDGER._digest(binding),
|
||||
},
|
||||
}
|
||||
_write_json(tmp_path / ".call-ledger/control-events/canary-to-full.json",
|
||||
transition)
|
||||
with pytest.raises(LEDGER.CallLedgerError, match="immutable provider attempt"):
|
||||
book.control_snapshot()
|
||||
|
||||
|
||||
def test_failure_class_event_requires_exact_shape_and_case_mapping(tmp_path):
|
||||
book = ledger(tmp_path, release_controls=_controls())
|
||||
(book.control_events / "failure-class--contract.json").write_text(json.dumps({
|
||||
"schema_version": LEDGER.CONTROL_SCHEMA,
|
||||
"event": "failure_class_blocked",
|
||||
"ledger_identity_sha256": book.identity_sha256,
|
||||
"failure_class": "contract", "case_id": "service",
|
||||
"status": "unknown", "reason": "forged", "unexpected": True,
|
||||
}), encoding="utf-8")
|
||||
with pytest.raises(LEDGER.CallLedgerError):
|
||||
book.control_snapshot()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mutation", ["extra", "order", "outcome", "binding"])
|
||||
def test_persisted_canary_transition_is_strict_and_revalidated(tmp_path, mutation):
|
||||
root = tmp_path / mutation
|
||||
classes = {"purchase": "contract", "service": "specialist"}
|
||||
book = ledger(root, operation_limits={"draft": 2},
|
||||
case_limits={key: 1 for key in classes},
|
||||
release_controls=_controls(classes, ["purchase", "service"]))
|
||||
book.authorize_selection(["purchase", "service"], resume=False,
|
||||
explicit_selection=True)
|
||||
outcomes = {}
|
||||
for case_id in ("purchase", "service"):
|
||||
token = book.claim(case_id, "draft", SHA_A)
|
||||
book.finish(token, "succeeded", SHA_B)
|
||||
outcomes[case_id] = _bind_success(book, root, case_id, ["draft"])
|
||||
book.complete_canary(outcomes)
|
||||
|
||||
transition_path = book.control_events / "canary-to-full.json"
|
||||
transition = json.loads(transition_path.read_text(encoding="utf-8"))
|
||||
if mutation == "extra":
|
||||
transition["unexpected"] = True
|
||||
transition_path.write_text(json.dumps(transition), encoding="utf-8")
|
||||
elif mutation == "order":
|
||||
transition["canary_case_ids"].reverse()
|
||||
transition_path.write_text(json.dumps(transition), encoding="utf-8")
|
||||
elif mutation == "outcome":
|
||||
transition["outcome_sha256s"]["purchase"] = SHA_A
|
||||
transition_path.write_text(json.dumps(transition), encoding="utf-8")
|
||||
else:
|
||||
binding_path = book.case_outcomes / "purchase.json"
|
||||
binding = json.loads(binding_path.read_text(encoding="utf-8"))
|
||||
binding["outcome_sha256"] = SHA_A
|
||||
binding_path.write_text(json.dumps(binding), encoding="utf-8")
|
||||
with pytest.raises(LEDGER.CallLedgerError):
|
||||
book.control_snapshot()
|
||||
|
||||
|
||||
def test_canary_missing_mandatory_operation_cannot_authorize_full(tmp_path):
|
||||
book = ledger(tmp_path, operation_limits={"draft": 1, "semantic_review": 1},
|
||||
case_limits={"purchase": 2},
|
||||
release_controls=_controls({"purchase": "contract"}, ["purchase"]))
|
||||
book.authorize_selection(["purchase"], resume=False, explicit_selection=True)
|
||||
token = book.claim("purchase", "draft", SHA_A)
|
||||
book.finish(token, "succeeded", SHA_B)
|
||||
outcome_sha = _bind_success(
|
||||
book, tmp_path, "purchase", ["draft", "semantic_review"])
|
||||
with pytest.raises(LEDGER.CallLedgerError, match="failed or unknown canary"):
|
||||
book.complete_canary({"purchase": outcome_sha})
|
||||
assert not (book.control_events / "canary-to-full.json").exists()
|
||||
assert book.snapshot()["unknown"] == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mutation", ["result", "evidence", "binding"])
|
||||
def test_resume_materializes_invalid_success_outcome_as_unknown_breaker(
|
||||
tmp_path, mutation,
|
||||
):
|
||||
root = tmp_path / mutation
|
||||
classes = {"purchase": "canary", "service": "specialist", "third": "specialist"}
|
||||
values = dict(operation_limits={"draft": 3},
|
||||
case_limits={key: 1 for key in classes},
|
||||
release_controls=_controls(classes, ["purchase"]))
|
||||
book = ledger(root, **values)
|
||||
book.authorize_selection(["purchase"], resume=False, explicit_selection=True)
|
||||
token = book.claim("purchase", "draft", SHA_A)
|
||||
book.finish(token, "succeeded", SHA_B)
|
||||
canary_sha = _bind_success(book, root, "purchase", ["draft"])
|
||||
book.complete_canary({"purchase": canary_sha})
|
||||
|
||||
token = book.claim("service", "draft", SHA_A)
|
||||
book.finish(token, "succeeded", SHA_B)
|
||||
_bind_success(book, root, "service", ["draft"])
|
||||
if mutation == "result":
|
||||
(root / "service/result.json").write_text("{}", encoding="utf-8")
|
||||
elif mutation == "evidence":
|
||||
next((root / "service").glob("*-after.json")).unlink()
|
||||
else:
|
||||
binding_path = book.case_outcomes / "service.json"
|
||||
binding = json.loads(binding_path.read_text(encoding="utf-8"))
|
||||
binding["case_attempts_sha256"] = SHA_A
|
||||
binding_path.write_text(json.dumps(binding), encoding="utf-8")
|
||||
|
||||
resumed = ledger(root, **values)
|
||||
with pytest.raises(LEDGER.CallLedgerError, match="blocked failure classes"):
|
||||
resumed.authorize_selection(["third"], resume=True, explicit_selection=True)
|
||||
snapshot = resumed.snapshot()
|
||||
assert snapshot["unknown"] == 1
|
||||
assert snapshot["terminal"] == 1
|
||||
assert snapshot["provider_terminal"] == 2
|
||||
assert snapshot["release_controls"]["blocked_failure_classes"] == ["specialist"]
|
||||
|
||||
|
||||
def test_concurrent_resume_fails_closed_between_provider_finish_and_outcome_binding(tmp_path):
|
||||
classes = {"purchase": "canary", "service": "specialist", "third": "specialist"}
|
||||
values = dict(operation_limits={"draft": 3},
|
||||
case_limits={key: 1 for key in classes},
|
||||
release_controls=_controls(classes, ["purchase"]))
|
||||
first = ledger(tmp_path, **values)
|
||||
first.authorize_selection(["purchase"], resume=False, explicit_selection=True)
|
||||
token = first.claim("purchase", "draft", SHA_A)
|
||||
first.finish(token, "succeeded", SHA_B)
|
||||
canary_sha = _bind_success(first, tmp_path, "purchase", ["draft"])
|
||||
first.complete_canary({"purchase": canary_sha})
|
||||
token = first.claim("service", "draft", SHA_A)
|
||||
first.finish(token, "succeeded", SHA_B)
|
||||
|
||||
concurrent = ledger(tmp_path, **values)
|
||||
with pytest.raises(LEDGER.CallLedgerError, match="blocked failure classes"):
|
||||
concurrent.authorize_selection(["third"], resume=True, explicit_selection=True)
|
||||
assert concurrent.snapshot()["unknown"] == 1
|
||||
# Even if the first writer subsequently finishes its local artifacts, the
|
||||
# persisted breaker cannot be erased by a race.
|
||||
_bind_success(first, tmp_path, "service", ["draft"])
|
||||
assert first.control_snapshot()["blocked_failure_classes"] == ["specialist"]
|
||||
|
||||
|
||||
def test_legacy_ledger_schema_is_explicitly_read_only(tmp_path):
|
||||
root = tmp_path / ".call-ledger"
|
||||
root.mkdir()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import hashlib
|
||||
import json
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -135,6 +136,34 @@ def test_git_candidate_gate_requires_exact_head_tag_and_clean_tree(monkeypatch,
|
||||
plan_module._verify_git_candidate(tmp_path, commit, "rc05-candidate", "test")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("injected", [
|
||||
{"GIT_DIR": "redirected.git", "GIT_WORK_TREE": "redirected-tree",
|
||||
"GIT_COMMON_DIR": "redirected-common"},
|
||||
{"GIT_INDEX_FILE": "alternate-index", "GIT_OBJECT_DIRECTORY": "alternate-objects",
|
||||
"GIT_ALTERNATE_OBJECT_DIRECTORIES": "more-objects"},
|
||||
{"GIT_CONFIG_COUNT": "1", "GIT_CONFIG_KEY_0": "core.bare",
|
||||
"GIT_CONFIG_VALUE_0": "true"},
|
||||
])
|
||||
def test_git_candidate_commands_drop_repository_and_config_injection(
|
||||
monkeypatch, tmp_path, injected,
|
||||
):
|
||||
captured = {}
|
||||
for name, value in injected.items():
|
||||
monkeypatch.setenv(name, value)
|
||||
|
||||
def run(argv, **kwargs):
|
||||
captured.update(kwargs["env"])
|
||||
return SimpleNamespace(returncode=0, stdout="", stderr="")
|
||||
|
||||
monkeypatch.setattr(plan_module.subprocess, "run", run)
|
||||
assert plan_module._git(tmp_path, "status", "--porcelain=v1") == ""
|
||||
assert not set(injected) & set(captured)
|
||||
assert captured["GIT_CONFIG_NOSYSTEM"] == "1"
|
||||
assert captured["GIT_CONFIG_GLOBAL"] == plan_module.os.devnull
|
||||
assert captured["GIT_NO_REPLACE_OBJECTS"] == "1"
|
||||
assert captured["GIT_TERMINAL_PROMPT"] == "0"
|
||||
|
||||
|
||||
def test_source_pins_are_repo_relative_existing_files_without_runtime_or_secrets(plan):
|
||||
assert plan["source_pins"]
|
||||
for relative, digest in plan["source_pins"].items():
|
||||
|
||||
@@ -438,6 +438,41 @@ def test_real_execution_rejects_unbound_candidate_and_initial_full_dispatch(
|
||||
assert calls == []
|
||||
|
||||
|
||||
def test_real_canary_does_not_transition_before_operation_and_evidence_gates(
|
||||
monkeypatch, tmp_path,
|
||||
):
|
||||
plan = _plan(tmp_path, count=1)
|
||||
plan["cases"][0].update(
|
||||
provider_call_budget=2,
|
||||
operations=["draft", "semantic_review"],
|
||||
)
|
||||
plan["operation_limits"] = {"draft": 1, "semantic_review": 1}
|
||||
calls = _install_modules(monkeypatch, plan)
|
||||
registry = tmp_path.parent / f"{tmp_path.name}-rc05-canary-registry"
|
||||
registry.mkdir()
|
||||
monkeypatch.setenv(EVAL.REAL_RUN_REGISTRY_ENV, str(registry))
|
||||
out = tmp_path / "canary"
|
||||
summary = EVAL.execute(
|
||||
_runner(tmp_path), tmp_path / "plan.json", "a" * 64, out, RealFactory,
|
||||
historical_usage=_historical_usage(), execution_mode="real",
|
||||
run_registry_dir=registry, case_ids=["case-0"],
|
||||
candidate_inputs=_candidate_inputs(),
|
||||
)
|
||||
assert calls == ["case-0"]
|
||||
assert summary["case_operation_coverage_passed"] is False
|
||||
assert summary["ledger"]["unknown"] == 1
|
||||
assert summary["ledger"]["campaign"]["unknown"] == 1
|
||||
assert summary["ledger"]["release_controls"]["phase"] == "canary_required"
|
||||
assert summary["ledger"]["release_controls"]["blocked_failure_classes"] == ["draft"]
|
||||
with pytest.raises(EVAL.CallLedgerError, match="blocked by a persisted failure class"):
|
||||
EVAL.execute(
|
||||
_runner(tmp_path), tmp_path / "plan.json", "a" * 64, out, RealFactory,
|
||||
historical_usage=_historical_usage(), execution_mode="real",
|
||||
run_registry_dir=registry, case_ids=["case-0"], resume=True,
|
||||
candidate_inputs=_candidate_inputs(),
|
||||
)
|
||||
|
||||
|
||||
def test_authoritative_campaign_registry_requires_explicit_empty_directory(
|
||||
monkeypatch, tmp_path,
|
||||
):
|
||||
|
||||
Reference in New Issue
Block a user