prizmkit 1.1.137 → 1.1.138

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/dev-pipeline/prizmkit_runtime/runner_models.py +0 -1
  3. package/bundled/dev-pipeline/prizmkit_runtime/runner_prompts.py +0 -1
  4. package/bundled/dev-pipeline/prizmkit_runtime/runners.py +3 -5
  5. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +24 -39
  6. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +3 -5
  7. package/bundled/dev-pipeline/scripts/generate-recovery-prompt.py +4 -5
  8. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +3 -5
  9. package/bundled/dev-pipeline/scripts/prompt_framework.py +0 -5
  10. package/bundled/dev-pipeline/templates/bootstrap-prompt.md +3 -3
  11. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +24 -26
  12. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +5 -4
  13. package/bundled/dev-pipeline/templates/feature-list-schema.json +1 -1
  14. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +3 -2
  15. package/bundled/dev-pipeline/templates/sections/bugfix-mission.md +1 -1
  16. package/bundled/dev-pipeline/templates/sections/bugfix-phase-review.md +2 -10
  17. package/bundled/dev-pipeline/templates/sections/bugfix-reminders.md +2 -1
  18. package/bundled/dev-pipeline/templates/sections/context-budget-rules.md +2 -2
  19. package/bundled/dev-pipeline/templates/sections/phase-implement-agent.md +5 -5
  20. package/bundled/dev-pipeline/templates/sections/phase-implement-full.md +5 -5
  21. package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +2 -12
  22. package/bundled/dev-pipeline/templates/sections/phase-review-full.md +2 -10
  23. package/bundled/dev-pipeline/templates/sections/phase-specify-plan-full.md +1 -1
  24. package/bundled/dev-pipeline/templates/sections/refactor-mission.md +1 -1
  25. package/bundled/dev-pipeline/templates/sections/refactor-phase-implement.md +2 -2
  26. package/bundled/dev-pipeline/templates/sections/refactor-phase-review.md +2 -10
  27. package/bundled/dev-pipeline/templates/sections/refactor-reminders.md +2 -1
  28. package/bundled/dev-pipeline/templates/sections/subagent-timeout-recovery.md +5 -4
  29. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +10 -16
  30. package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +3 -1
  31. package/bundled/dev-pipeline/tests/test_python_runner_parity.py +1 -6
  32. package/bundled/dev-pipeline/tests/test_unified_cli.py +3 -5
  33. package/bundled/skills/_metadata.json +2 -2
  34. package/bundled/skills/prizmkit-test/SKILL.md +17 -20
  35. package/bundled/skills/prizmkit-test/assets/authoritative-records.schema.json +5 -73
  36. package/bundled/skills/prizmkit-test/assets/evidence-package-template.json +0 -2
  37. package/bundled/skills/prizmkit-test/references/boundary-coverage-protocol.md +2 -2
  38. package/bundled/skills/prizmkit-test/references/evidence-protocol.md +10 -10
  39. package/bundled/skills/prizmkit-test/references/evidence-request-protocol.md +8 -16
  40. package/bundled/skills/prizmkit-test/references/examples.md +3 -5
  41. package/bundled/skills/prizmkit-test/references/test-generation-steps.md +6 -8
  42. package/bundled/skills/prizmkit-test/references/test-report-template.md +4 -7
  43. package/bundled/skills/prizmkit-test/references/trusted-evidence-execution.md +5 -7
  44. package/bundled/skills/prizmkit-test/scripts/build_test_evidence.py +70 -196
  45. package/bundled/skills/prizmkit-test/scripts/validate_test_evidence.py +251 -167
  46. package/package.json +1 -1
@@ -8,7 +8,6 @@ Usage:
8
8
  python3 build_test_evidence.py --project-root ROOT --evidence-dir DIR prepare-tests --request REQUEST
9
9
  python3 build_test_evidence.py --project-root ROOT --evidence-dir DIR execute --request REQUEST
10
10
  python3 build_test_evidence.py --project-root ROOT --evidence-dir DIR execute --replay-receipt RECEIPT
11
- python3 build_test_evidence.py --project-root ROOT --evidence-dir DIR differential --request REQUEST
12
11
  python3 build_test_evidence.py --project-root ROOT --evidence-dir DIR finalize --request REQUEST
13
12
  python3 build_test_evidence.py --project-root ROOT --evidence-dir DIR render-report
14
13
  python3 build_test_evidence.py --project-root ROOT --evidence-dir DIR resume --manifest MANIFEST --inventory INVENTORY
@@ -30,7 +29,6 @@ import shutil
30
29
  import signal
31
30
  import subprocess
32
31
  import sys
33
- import tempfile
34
32
  import uuid
35
33
  from pathlib import Path
36
34
  from typing import Any
@@ -46,6 +44,9 @@ LAYERS = (
46
44
  "affected-module-regression", "regression-ring",
47
45
  )
48
46
  BLOCKED_EXTERNAL_CLASSES = {"production", "unknown"}
47
+ REPAIR_SCOPES = {
48
+ "test-infrastructure", "production", "runtime", "schema", "dependency", "public-interface",
49
+ }
49
50
  INVENTORY_RESERVED_ROOTS = (
50
51
  ".prizmkit/state",
51
52
  ".prizmkit/test/evidence",
@@ -413,9 +414,14 @@ def validate_execution_request(request: Any, *, plan: dict[str, Any] | None = No
413
414
  ensure_string_list(probe, f"tool probe {name}", allow_empty=False)
414
415
  has_ids = "test_ids" in request
415
416
  has_paths = "test_paths" in request
416
- auto_bind = request.get("auto_bind") is True
417
- if not has_ids and not has_paths and not auto_bind:
417
+ has_auto_bind = "auto_bind" in request
418
+ if has_auto_bind and not isinstance(request["auto_bind"], bool):
419
+ raise RequestError("execution request auto_bind must be boolean")
420
+ binding_modes = int(has_ids) + int(has_paths) + int(request.get("auto_bind") is True)
421
+ if binding_modes == 0:
418
422
  raise RequestError("execution request must provide test_ids, test_paths, or auto_bind=true")
423
+ if binding_modes != 1 or (has_auto_bind and request["auto_bind"] is False):
424
+ raise RequestError("execution request binding modes test_ids, test_paths, and auto_bind=true are mutually exclusive")
419
425
  if has_ids:
420
426
  test_ids = ensure_string_list(request["test_ids"], "test_ids", allow_empty=False)
421
427
  if len(set(test_ids)) != len(test_ids):
@@ -424,8 +430,6 @@ def validate_execution_request(request: Any, *, plan: dict[str, Any] | None = No
424
430
  test_paths = ensure_string_list(request["test_paths"], "test_paths", allow_empty=False)
425
431
  if len(set(test_paths)) != len(test_paths):
426
432
  raise RequestError("execution request test_paths contains duplicates")
427
- if "auto_bind" in request and not isinstance(request["auto_bind"], bool):
428
- raise RequestError("execution request auto_bind must be boolean")
429
433
  if request["layer"] not in LAYERS and request["layer"] != "lightweight":
430
434
  raise RequestError(f"unknown execution layer: {request['layer']}")
431
435
  if plan is not None and has_ids:
@@ -470,7 +474,17 @@ def command_scope_test_ids(project_root: Path, request: dict[str, Any], tests: l
470
474
  "-C", "--config", "--config-file", "--project", "--package", "-p",
471
475
  "--root", "--rootdir", "--cwd", "--testPathPattern", "--testNamePattern",
472
476
  }
477
+ scope_affecting_options = {
478
+ "-C", "--config", "--config-file", "--project", "--package", "--root", "--rootdir",
479
+ "--cwd", "--testPathPattern", "--testNamePattern", "--grep", "-k", "--filter", "-t",
480
+ }
473
481
  for token in command[1:]:
482
+ option_name = token.split("=", 1)[0]
483
+ if option_name in scope_affecting_options:
484
+ raise RequestError(
485
+ "command scope cannot be inferred when the native command uses a test-selection "
486
+ f"option: {option_name}"
487
+ )
474
488
  if skip_option_value:
475
489
  skip_option_value = False
476
490
  continue
@@ -497,7 +511,7 @@ def command_scope_test_ids(project_root: Path, request: dict[str, Any], tests: l
497
511
  if package_wide:
498
512
  return sorted({item["id"] for item in tests})
499
513
  raise RequestError(
500
- "auto_bind cannot infer native test scope from command; provide test_paths or use a recognized package-wide test command"
514
+ "command scope cannot be inferred from the native command; use a recognized package-wide command or an explicit test path in the command"
501
515
  )
502
516
 
503
517
  matched_paths: set[str] = set()
@@ -517,7 +531,7 @@ def command_scope_test_ids(project_root: Path, request: dict[str, Any], tests: l
517
531
  matched_paths.add(test_path)
518
532
  if not matched_paths:
519
533
  raise RequestError(
520
- "auto_bind command scope does not cover any prepared native test; provide test_paths explicitly"
534
+ "native command scope does not cover any prepared test"
521
535
  )
522
536
  return [item["id"] for item in tests if item["project_path"] in matched_paths]
523
537
 
@@ -529,19 +543,26 @@ def resolve_execution_test_ids(project_root: Path, evidence_dir: Path, request:
529
543
  raise RequestError("builder-generated test plan is required before path-based or auto-bound execution")
530
544
  return request
531
545
  if (evidence_dir / "lifecycle.json").is_file() and not (evidence_dir / "test-preparation.json").is_file():
532
- raise RequestError("canonical lifecycle requires prepare-tests before execute or differential")
546
+ raise RequestError("canonical lifecycle requires prepare-tests before execute")
533
547
  tests = test_plan_entries(plan)
534
548
  by_id = {item["id"]: item for item in tests}
535
549
  by_path = {item["project_path"]: item for item in tests}
536
550
  if "test_ids" in request:
537
- resolved = list(request["test_ids"])
551
+ declared = list(request["test_ids"])
538
552
  elif "test_paths" in request:
539
553
  unknown_paths = sorted(set(request["test_paths"]) - set(by_path))
540
554
  if unknown_paths:
541
555
  raise RequestError(f"execution request references unknown test paths: {', '.join(unknown_paths)}")
542
- resolved = [by_path[path]["id"] for path in request["test_paths"]]
556
+ declared = [by_path[path]["id"] for path in request["test_paths"]]
543
557
  else:
544
- resolved = command_scope_test_ids(project_root, request, tests)
558
+ declared = None
559
+ inferred = command_scope_test_ids(project_root, request, tests)
560
+ if declared is not None and set(declared) != set(inferred):
561
+ raise RequestError(
562
+ "execution request binding does not exactly match native command scope "
563
+ f"(declared: {', '.join(sorted(declared))}; inferred: {', '.join(sorted(inferred))})"
564
+ )
565
+ resolved = inferred
545
566
  if not resolved:
546
567
  raise RequestError("execution request resolved no prepared tests")
547
568
  unknown = sorted(set(resolved) - set(by_id))
@@ -553,6 +574,8 @@ def resolve_execution_test_ids(project_root: Path, evidence_dir: Path, request:
553
574
  )
554
575
  if invalid_layer:
555
576
  raise RequestError(f"execution layer is not planned for test IDs: {', '.join(invalid_layer)}")
577
+ request.pop("test_paths", None)
578
+ request.pop("auto_bind", None)
556
579
  request["test_ids"] = resolved
557
580
  return request
558
581
 
@@ -844,19 +867,20 @@ def execute_request(
844
867
  evidence_dir: Path,
845
868
  request_path: Path,
846
869
  *,
847
- execution_root: Path | None = None,
848
870
  replay_of: str | None = None,
849
- isolation: dict[str, Any] | None = None,
850
871
  selected_execution: bool = True,
851
872
  ) -> dict[str, Any]:
852
873
  request_value = load_json(request_path)
853
874
  original_test_ids = list(request_value["test_ids"]) if isinstance(request_value, dict) and isinstance(request_value.get("test_ids"), list) else None
875
+ original_binding_fields = {
876
+ key for key in ("test_ids", "test_paths", "auto_bind")
877
+ if isinstance(request_value, dict) and key in request_value
878
+ }
854
879
  request = validate_execution_request(request_value, plan=plan_for_execution(evidence_dir))
855
880
  request = resolve_execution_test_ids(project_root, evidence_dir, request)
856
- if original_test_ids != request["test_ids"]:
881
+ if original_binding_fields != {"test_ids"} or original_test_ids != request["test_ids"]:
857
882
  write_json_atomic(request_path, request)
858
- root = execution_root or project_root
859
- cwd = confined(root, request["cwd"] or ".", must_exist=True, directory=True)
883
+ cwd = confined(project_root, request["cwd"] or ".", must_exist=True, directory=True)
860
884
  complete_environment = {"PATH": os.environ.get("PATH", os.defpath)}
861
885
  if os.name == "nt" and os.environ.get("SYSTEMROOT"):
862
886
  complete_environment["SYSTEMROOT"] = os.environ["SYSTEMROOT"]
@@ -924,175 +948,12 @@ def execute_request(
924
948
  "started_at": started,
925
949
  "finished_at": finished,
926
950
  "replay_of": replay_of,
927
- "isolation": isolation,
951
+ "isolation": {"kind": "project-direct"},
928
952
  }
929
953
  append_receipt(evidence_dir, receipt)
930
954
  return receipt
931
955
 
932
956
 
933
- def materialize_request(evidence_dir: Path, name: str, request: dict[str, Any]) -> Path:
934
- path = evidence_dir / "requests" / name
935
- write_json_atomic(path, request)
936
- return path
937
-
938
-
939
- def validate_not_applicable(value: Any) -> dict[str, Any]:
940
- value = ensure_object(value, "differential N/A decision")
941
- required = {"reason", "rationale", "evidence", "considered_signals", "conflicts"}
942
- if set(value) != required:
943
- raise RequestError(f"differential N/A fields must be exactly {sorted(required)}")
944
- if value["reason"] not in {"new-behavior", "textual-contract"}:
945
- raise RequestError("differential N/A reason must be new-behavior or textual-contract")
946
- if not isinstance(value["rationale"], str) or len(value["rationale"].strip()) < 16:
947
- raise RequestError("differential N/A rationale is too short")
948
- ensure_string_list(value["evidence"], "differential N/A evidence", allow_empty=False)
949
- ensure_string_list(value["considered_signals"], "differential N/A considered_signals", allow_empty=False)
950
- if not isinstance(value["conflicts"], list):
951
- raise RequestError("differential N/A conflicts must be an array")
952
- if value["reason"] == "new-behavior" and "no stable pre-change observable" not in value["rationale"].lower():
953
- raise RequestError("new-behavior differential N/A must explain that no stable pre-change observable exists")
954
- if value["reason"] == "textual-contract" and "textual" not in value["rationale"].lower():
955
- raise RequestError("textual-contract differential N/A must explain why behavior is textual")
956
- return value
957
-
958
-
959
- def append_proof(evidence_dir: Path, proof: dict[str, Any]) -> None:
960
- proof_path = evidence_dir / "differential-proof.json"
961
- proof_record = load_json(proof_path) if proof_path.exists() else {"proofs": []}
962
- if not isinstance(proof_record, dict) or not isinstance(proof_record.get("proofs"), list):
963
- raise RequestError("differential-proof.json has invalid append target")
964
- if any(item.get("behavior_id") == proof.get("behavior_id") for item in proof_record["proofs"] if isinstance(item, dict)):
965
- raise RequestError(f"duplicate differential proof: {proof.get('behavior_id')}")
966
- proof_record["proofs"].append(proof)
967
- write_json_atomic(proof_path, proof_record)
968
-
969
-
970
- def run_differential(project_root: Path, evidence_dir: Path, request_path: Path) -> dict[str, Any]:
971
- request = ensure_object(load_json(request_path), "differential request")
972
- required = {"request_version", "behavior_id", "method", "execution_request", "baseline_commit", "mutation_patch_path", "test_overlay_paths", "expected_failure_signals"}
973
- na_required = {"request_version", "behavior_id", "method", "not_applicable"}
974
- if set(request) == na_required:
975
- if request["request_version"] != "1.0" or request["method"] != "not-applicable":
976
- raise RequestError("invalid differential N/A request")
977
- not_applicable = validate_not_applicable(request["not_applicable"])
978
- proof = {
979
- "behavior_id": request["behavior_id"], "classification": "NOT_APPLICABLE",
980
- "method": None, "differential_request_path": evidence_relative(evidence_dir, request_path),
981
- "differential_request_sha256": file_sha256(request_path), "baseline_commit": None,
982
- "baseline_execution_id": None, "mutation_execution_id": None,
983
- "current_execution_id": None, "failure_reason_matched": False, "cleanup_succeeded": True,
984
- "not_applicable": not_applicable,
985
- }
986
- append_proof(evidence_dir, proof)
987
- return proof
988
- if set(request) != required or request["request_version"] != "1.0":
989
- raise RequestError(f"differential request fields must be exactly {sorted(required)} with version 1.0")
990
- execution_request = resolve_execution_test_ids(
991
- project_root,
992
- evidence_dir,
993
- validate_execution_request(
994
- request["execution_request"], plan=plan_for_execution(evidence_dir),
995
- ),
996
- )
997
- request["execution_request"] = execution_request
998
- write_json_atomic(request_path, request)
999
- expected_failure_signals = ensure_string_list(request["expected_failure_signals"], "expected_failure_signals", allow_empty=False)
1000
- overlay_paths = ensure_string_list(request["test_overlay_paths"], "test_overlay_paths")
1001
- for relative in overlay_paths:
1002
- confined(project_root, relative, must_exist=True)
1003
- current_request_path = materialize_request(evidence_dir, f"differential-{uuid.uuid4().hex}.execution.json", execution_request)
1004
- baseline_receipt = None
1005
- mutation_receipt = None
1006
- current_receipt = None
1007
- cleanup_succeeded = False
1008
- worktree_path = None
1009
- try:
1010
- if request["method"] == "baseline":
1011
- worktree_path = tempfile.mkdtemp(prefix="prizmkit-baseline-")
1012
- result = subprocess.run(
1013
- ["git", "-C", str(project_root), "worktree", "add", "--detach", str(worktree_path), request["baseline_commit"]],
1014
- capture_output=True, check=False,
1015
- )
1016
- if result.returncode != 0:
1017
- raise RequestError(f"cannot create baseline worktree: {result.stderr.decode(errors='replace')}")
1018
- for relative in overlay_paths:
1019
- source = confined(project_root, relative, must_exist=True)
1020
- destination = Path(worktree_path) / relative
1021
- destination.parent.mkdir(parents=True, exist_ok=True)
1022
- shutil.copy2(source, destination)
1023
- baseline_receipt = execute_request(
1024
- project_root, evidence_dir, current_request_path, execution_root=Path(worktree_path),
1025
- isolation={"kind": "baseline", "baseline_commit": request["baseline_commit"]},
1026
- selected_execution=False,
1027
- )
1028
- elif request["method"] == "controlled-mutation":
1029
- worktree_path = tempfile.mkdtemp(prefix="prizmkit-mutation-")
1030
- result = subprocess.run(
1031
- ["git", "-C", str(project_root), "worktree", "add", "--detach", str(worktree_path), "HEAD"],
1032
- capture_output=True, check=False,
1033
- )
1034
- if result.returncode != 0:
1035
- raise RequestError(f"cannot create mutation worktree: {result.stderr.decode(errors='replace')}")
1036
- patch_value = request["mutation_patch_path"]
1037
- if not isinstance(patch_value, str) or not patch_value:
1038
- raise RequestError("controlled mutation requires mutation_patch_path")
1039
- patch_path = request_file(evidence_dir, patch_value)
1040
- applied = subprocess.run(
1041
- ["git", "apply", "--whitespace=nowarn", str(patch_path)],
1042
- cwd=worktree_path, capture_output=True, check=False,
1043
- )
1044
- if applied.returncode != 0:
1045
- raise RequestError(f"controlled mutation could not be applied: {applied.stderr.decode(errors='replace')}")
1046
- mutation_receipt = execute_request(
1047
- project_root, evidence_dir, current_request_path, execution_root=Path(worktree_path),
1048
- isolation={"kind": "controlled-mutation", "patch_sha256": file_sha256(patch_path)},
1049
- selected_execution=False,
1050
- )
1051
- else:
1052
- raise RequestError("differential method must be baseline or controlled-mutation")
1053
- current_receipt = execute_request(
1054
- project_root, evidence_dir, current_request_path, execution_root=project_root,
1055
- isolation={"kind": "project-direct"},
1056
- )
1057
- cleanup_succeeded = True
1058
- finally:
1059
- if worktree_path and Path(worktree_path).exists():
1060
- subprocess.run(
1061
- ["git", "-C", str(project_root), "worktree", "remove", "--force", str(worktree_path)],
1062
- capture_output=True, check=False,
1063
- )
1064
- try:
1065
- shutil.rmtree(worktree_path, ignore_errors=True)
1066
- except OSError:
1067
- pass
1068
- failing_receipt = baseline_receipt or mutation_receipt
1069
- failing_output = b""
1070
- if failing_receipt:
1071
- failing_output = (evidence_dir / failing_receipt["stdout_path"]).read_bytes() + (evidence_dir / failing_receipt["stderr_path"]).read_bytes()
1072
- failure_reason_matched = any(signal.encode() in failing_output for signal in expected_failure_signals)
1073
- proven = bool(
1074
- failing_receipt and current_receipt and failing_receipt["exit_code"] != 0
1075
- and current_receipt["exit_code"] == 0 and failure_reason_matched
1076
- and cleanup_succeeded
1077
- )
1078
- proof = {
1079
- "behavior_id": request["behavior_id"],
1080
- "classification": "PROVEN" if proven else "UNPROVEN",
1081
- "method": request["method"],
1082
- "differential_request_path": evidence_relative(evidence_dir, request_path),
1083
- "differential_request_sha256": file_sha256(request_path),
1084
- "baseline_commit": request["baseline_commit"],
1085
- "baseline_execution_id": baseline_receipt["execution_id"] if baseline_receipt else None,
1086
- "mutation_execution_id": mutation_receipt["execution_id"] if mutation_receipt else None,
1087
- "current_execution_id": current_receipt["execution_id"] if current_receipt else None,
1088
- "failure_reason_matched": failure_reason_matched,
1089
- "cleanup_succeeded": cleanup_succeeded,
1090
- "not_applicable": None,
1091
- }
1092
- append_proof(evidence_dir, proof)
1093
- return proof
1094
-
1095
-
1096
957
  def run_capture_change(
1097
958
  project_root: Path,
1098
959
  evidence_dir: Path,
@@ -1125,7 +986,6 @@ def run_init(project_root: Path, evidence_dir: Path, baseline: str | None, outpu
1125
986
  for directory in ("requests", "receipts", "raw", "generated-tests", "contracts", "runner"):
1126
987
  (evidence_dir / directory).mkdir(parents=True, exist_ok=True)
1127
988
  write_json_atomic(evidence_dir / "executions.json", [])
1128
- write_json_atomic(evidence_dir / "differential-proof.json", {"proofs": []})
1129
989
  write_json_atomic(evidence_dir / "lifecycle.json", {
1130
990
  "lifecycle_format": "prizmkit-canonical-lifecycle-v1",
1131
991
  "status": "initialized",
@@ -1176,8 +1036,6 @@ def run_render_report(evidence_dir: Path, output_name: str) -> Path:
1176
1036
  validation = ensure_object(load_json(validation_path), "validation") if validation_path.exists() else None
1177
1037
  lifecycle_path = evidence_dir / "lifecycle.json"
1178
1038
  lifecycle = ensure_object(load_json(lifecycle_path), "lifecycle") if lifecycle_path.exists() else {}
1179
- proofs_path = evidence_dir / "differential-proof.json"
1180
- proofs = load_json(proofs_path).get("proofs", []) if proofs_path.exists() else []
1181
1039
  executions = load_json(evidence_dir / "executions.json")
1182
1040
  artifact_dir = lifecycle.get("artifact_dir")
1183
1041
  lines = [
@@ -1195,7 +1053,6 @@ def run_render_report(evidence_dir: Path, output_name: str) -> Path:
1195
1053
  f"- Artifact Dir: {artifact_dir or 'not provided'}",
1196
1054
  "",
1197
1055
  f"Execution receipts: {len(executions) if isinstance(executions, list) else 0}",
1198
- f"Differential proofs: {len(proofs) if isinstance(proofs, list) else 0}",
1199
1056
  "The legacy test-report interface is not supported.",
1200
1057
  "",
1201
1058
  ]
@@ -1420,7 +1277,7 @@ def stage_output_owner(relative: str, change_class: str) -> str:
1420
1277
  return "INFRA_READY"
1421
1278
  if relative.startswith("generated-tests/"):
1422
1279
  return "TEST_BUILD"
1423
- if relative in {"executions.json", "differential-proof.json"} or relative.startswith("receipts/") or relative.startswith("raw/") or relative.startswith("runner/"):
1280
+ if relative == "executions.json" or relative.startswith("receipts/") or relative.startswith("raw/") or relative.startswith("runner/"):
1424
1281
  return "EXECUTE_PROVE"
1425
1282
  if relative in FINAL_RECORDS or relative == "lifecycle.json" or relative == "init.json" or relative == "finalize.json":
1426
1283
  return "EVIDENCE_PACKAGE"
@@ -1485,13 +1342,15 @@ def run_finalize(project_root: Path, evidence_dir: Path, request: dict[str, Any]
1485
1342
  raise RequestError(f"finalize request contains unsupported fields: {sorted(set(request) - allowed)}")
1486
1343
  if request.get("request_version", "1.0") != "1.0":
1487
1344
  raise RequestError("unsupported finalize request version")
1345
+ if request.get("repair_scope") is not None and request.get("repair_scope") not in REPAIR_SCOPES:
1346
+ raise RequestError("repair_scope must identify an evidence-backed repair domain")
1488
1347
  lifecycle = load_lifecycle(evidence_dir, required=True)
1489
1348
  if lifecycle.get("finalized") is True:
1490
1349
  raise RequestError("evidence identity is finalized; re-init a new package before changing scope or source")
1491
1350
  capture_path = evidence_dir / "change-capture.json"
1492
1351
  if not capture_path.exists():
1493
1352
  run_capture_change(project_root, evidence_dir, request.get("baseline_commit"), "change-capture.json")
1494
- capture = ensure_object(load_json(capture_path), "change capture")
1353
+ capture = load_change_capture(project_root, evidence_dir, require_fresh=True)
1495
1354
  baseline = capture.get("baseline_commit")
1496
1355
  patch_path = evidence_dir / "source-change.patch"
1497
1356
  if not isinstance(baseline, str) or not patch_path.is_file():
@@ -1541,7 +1400,7 @@ def run_finalize(project_root: Path, evidence_dir: Path, request: dict[str, Any]
1541
1400
  if change_class == "behavior":
1542
1401
  required_records.update({
1543
1402
  "behavior-risk-matrix.json", "test-plan.json", "test-preparation.json",
1544
- "infrastructure-changes.json", "differential-proof.json",
1403
+ "infrastructure-changes.json",
1545
1404
  })
1546
1405
  else:
1547
1406
  required_records.add("lightweight-verification.json")
@@ -1553,10 +1412,31 @@ def run_finalize(project_root: Path, evidence_dir: Path, request: dict[str, Any]
1553
1412
  blockers.append(
1554
1413
  "required evidence records are missing: " + ", ".join(missing_records)
1555
1414
  )
1556
- selected_failures = [item for item in executions if isinstance(item, dict) and item.get("selected_execution") is True and item.get("reliable") is True and item.get("exit_code") != 0]
1415
+ selected_unreliable = [
1416
+ item for item in executions
1417
+ if isinstance(item, dict)
1418
+ and item.get("selected_execution") is True
1419
+ and item.get("reliable") is not True
1420
+ ]
1421
+ for execution in selected_unreliable:
1422
+ blockers.append(
1423
+ "selected execution is unreliable: "
1424
+ + str(execution.get("execution_id", "unknown-execution"))
1425
+ )
1426
+ selected_failures = [
1427
+ item for item in executions
1428
+ if isinstance(item, dict)
1429
+ and item.get("selected_execution") is True
1430
+ and item.get("reliable") is True
1431
+ and item.get("exit_code") != 0
1432
+ ]
1557
1433
  if selected_failures and not request.get("repair_scope"):
1558
1434
  blockers.append("reliable failure exists but repair_scope is not evidence-backed")
1559
- verdict = "TEST_FAIL" if selected_failures and request.get("repair_scope") else ("TEST_BLOCKED" if blockers else "TEST_PASS")
1435
+ verdict = (
1436
+ "TEST_BLOCKED" if blockers
1437
+ else "TEST_FAIL" if selected_failures and request.get("repair_scope")
1438
+ else "TEST_PASS"
1439
+ )
1560
1440
  verdict_record: dict[str, Any] = {
1561
1441
  "verdict": verdict,
1562
1442
  "testing_domain_only": True,
@@ -1724,8 +1604,6 @@ def main() -> int:
1724
1604
  group = execute.add_mutually_exclusive_group(required=True)
1725
1605
  group.add_argument("--request")
1726
1606
  group.add_argument("--replay-receipt")
1727
- differential = subparsers.add_parser("differential")
1728
- differential.add_argument("--request", required=True)
1729
1607
  finalize = subparsers.add_parser("finalize")
1730
1608
  finalize.add_argument("--request", required=True, help="semantic finalization request")
1731
1609
  finalize.add_argument("--output", default="finalize.json")
@@ -1774,10 +1652,6 @@ def main() -> int:
1774
1652
  raise RequestError("recorded request hash no longer matches replay source")
1775
1653
  receipt = execute_request(project_root, evidence_dir, original_request, replay_of=prior.get("execution_id"))
1776
1654
  print(json.dumps(receipt, ensure_ascii=False))
1777
- elif args.subcommand == "differential":
1778
- require_mutable_lifecycle(evidence_dir)
1779
- proof = run_differential(project_root, evidence_dir, request_file(evidence_dir, args.request))
1780
- print(json.dumps(proof, ensure_ascii=False))
1781
1655
  elif args.subcommand == "render-report":
1782
1656
  load_lifecycle(evidence_dir)
1783
1657
  output = run_render_report(evidence_dir, args.output)