P24-02: 操作と通知の全受入
This commit is contained in:
@@ -20,8 +20,20 @@ import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
|
||||
# This helper ships in S; it has only stdlib dependencies (no Main-only imports).
|
||||
_HELPER = Path(__file__).resolve().parents[1] / 'docs/plans/original-completion-2026-10-02/execution/p24-02/source-ready-repair5-20261017/composition.py'
|
||||
_spec = importlib.util.spec_from_file_location('p24_composition', _HELPER)
|
||||
composition = importlib.util.module_from_spec(_spec)
|
||||
_spec.loader.exec_module(composition)
|
||||
_spec = importlib.util.spec_from_file_location('p24_correspondence', _HELPER.with_name('correspondence.py'))
|
||||
correspondence = importlib.util.module_from_spec(_spec)
|
||||
_spec.loader.exec_module(correspondence)
|
||||
ROOT = Path(composition.git_bytes(Path(__file__).resolve().parents[1], 'rev-parse', '--show-toplevel').decode().strip())
|
||||
VERIFIED_ROOT = ROOT
|
||||
|
||||
REQUIRED_CONDITIONS = [
|
||||
"placement_and_invalid_drop",
|
||||
"modal_and_pause_resume",
|
||||
@@ -66,21 +78,12 @@ def compute_sha256(path: Path) -> str:
|
||||
|
||||
|
||||
def get_actual_git_head(root: Path = None) -> str:
|
||||
root = root if root is not None else Path(__file__).resolve().parents[1]
|
||||
root = root if root is not None else ROOT
|
||||
try:
|
||||
import subprocess
|
||||
res = subprocess.run(["git", "rev-parse", "HEAD"], cwd=root, capture_output=True, text=True, timeout=5)
|
||||
if res.returncode == 0:
|
||||
h = res.stdout.strip().lower()
|
||||
if HEX_COMMIT_PATTERN.match(h) and not (set(h) <= {"0"} or set(h) <= {"f"}):
|
||||
for name in ("CANDIDATE_COMMIT", "TARGET_HEAD"):
|
||||
if name in os.environ and os.environ[name].strip().lower() != h:
|
||||
print(f"ERROR: {name} mismatch with real git HEAD {h}", file=sys.stderr)
|
||||
return ""
|
||||
return h
|
||||
composition.root_identity(root, root)
|
||||
return composition.git_bytes(root, 'rev-parse', 'HEAD').decode().strip()
|
||||
except Exception:
|
||||
pass
|
||||
return ""
|
||||
return ""
|
||||
|
||||
|
||||
def get_required_suite_roster(root: Path) -> list[str]:
|
||||
@@ -93,7 +96,7 @@ def get_required_suite_roster(root: Path) -> list[str]:
|
||||
|
||||
def find_index_file(custom_path: str = None) -> Path:
|
||||
if custom_path:
|
||||
p = Path(custom_path)
|
||||
p = Path(custom_path) if Path(custom_path).is_absolute() else ROOT / custom_path
|
||||
if p.is_file():
|
||||
return p.resolve()
|
||||
if p.is_dir():
|
||||
@@ -114,9 +117,9 @@ def find_index_file(custom_path: str = None) -> Path:
|
||||
return candidate2.resolve()
|
||||
|
||||
candidates = [
|
||||
Path(".runtime/P24-02/build/p24-operations-evidence/index.json"),
|
||||
Path("docs/plans/original-completion-2026-10-02/execution/p24-02/evidence/index.json"),
|
||||
Path("artifacts/p24-02-operations/index.json"),
|
||||
ROOT / Path(".runtime/P24-02/build/p24-operations-evidence/index.json"),
|
||||
ROOT / Path("docs/plans/original-completion-2026-10-02/execution/p24-02/evidence/index.json"),
|
||||
ROOT / Path("artifacts/p24-02-operations/index.json"),
|
||||
]
|
||||
for c in candidates:
|
||||
if c.is_file():
|
||||
@@ -368,7 +371,7 @@ def _validate_condition_artifact_content(
|
||||
if not full_roster or not isinstance(full_roster, dict):
|
||||
return False, "Missing 'full_roster' summary in final validation review evidence"
|
||||
|
||||
repo_root = Path(".").resolve()
|
||||
repo_root = VERIFIED_ROOT
|
||||
required_suites = get_required_suite_roster(repo_root)
|
||||
total_required = len(required_suites)
|
||||
|
||||
@@ -428,7 +431,7 @@ def _validate_condition_artifact_content(
|
||||
return True, ""
|
||||
|
||||
|
||||
def verify_evidence(index_path: Path) -> bool:
|
||||
def _verify_evidence(index_path: Path) -> bool:
|
||||
print(f"Reading evidence index from: {index_path}")
|
||||
try:
|
||||
data = json.loads(index_path.read_text(encoding="utf-8"))
|
||||
@@ -487,13 +490,13 @@ def verify_evidence(index_path: Path) -> bool:
|
||||
print("ERROR: 'source_hashes' dictionary is required in evidence index", file=sys.stderr)
|
||||
return False
|
||||
|
||||
missing_scope_sources = [f for f in REQUIRED_SCOPE_FILES if f not in sources]
|
||||
missing_scope_sources = [f for f in REQUIRED_SCOPE_FILES if f not in sources and f not in data.get("inputs", {})]
|
||||
if missing_scope_sources:
|
||||
print(f"ERROR: 'source_hashes' omits required scope files: {missing_scope_sources}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
for rel_path, expected_hash in sources.items():
|
||||
p = Path(rel_path)
|
||||
p = composition.safe_path(VERIFIED_ROOT, rel_path)
|
||||
if not p.is_file():
|
||||
print(f"ERROR: Required source file does not exist on disk: {rel_path}", file=sys.stderr)
|
||||
return False
|
||||
@@ -530,9 +533,9 @@ def verify_evidence(index_path: Path) -> bool:
|
||||
print(f"ERROR: Artifact '{art_id}' path traversal or absolute path forbidden: {rel_art_path}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
art_file = evidence_dir / rel_art_path
|
||||
art_file = composition.safe_path(evidence_dir, rel_art_path)
|
||||
if not art_file.is_file():
|
||||
art_file = Path(rel_art_path)
|
||||
art_file = composition.safe_path(ROOT, rel_art_path)
|
||||
if not art_file.is_file():
|
||||
print(f"ERROR: Referenced artifact file missing on disk: {rel_art_path}", file=sys.stderr)
|
||||
return False
|
||||
@@ -712,25 +715,48 @@ def verify_evidence(index_path: Path) -> bool:
|
||||
print(f"ERROR: Condition '{cond}' artifact content verification failed: {last_err}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
# Reject self-declaration where conditions share a single generic artifact
|
||||
unique_referenced_artifacts = set()
|
||||
for aids in condition_to_artifacts.values():
|
||||
unique_referenced_artifacts.update(aids)
|
||||
|
||||
if len(unique_referenced_artifacts) < len(REQUIRED_CONDITIONS):
|
||||
print(f"ERROR: All 8 conditions require distinct evidence artifacts; found only {len(unique_referenced_artifacts)} unique artifacts across {len(REQUIRED_CONDITIONS)} conditions", file=sys.stderr)
|
||||
try:
|
||||
correspondence.verify(data, validated_artifacts, composition.root_context(evidence_dir, data), ROOT)
|
||||
except (ValueError, KeyError, TypeError, OSError, IndexError) as exc:
|
||||
print(f"ERROR: correspondence: {exc}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
print("SUCCESS: All 8 required conditions, candidate version, scope manifest, and OS execution artifacts strictly verified.")
|
||||
return True
|
||||
|
||||
|
||||
def verify_evidence(index_path: Path) -> bool:
|
||||
global VERIFIED_ROOT
|
||||
try:
|
||||
composition.root_identity(ROOT, Path.cwd())
|
||||
data = composition.strict_json(index_path.read_bytes())
|
||||
roles = composition.root_context(index_path.parent, data)
|
||||
composition.require(data.get('inputs', {}).get(composition.SPEC) == EXPECTED_SPEC_SHA256, 'frozen spec input SHA mismatch')
|
||||
required_helpers = {_HELPER.relative_to(ROOT).as_posix(), _HELPER.with_name('correspondence.py').relative_to(ROOT).as_posix()}
|
||||
composition.require(required_helpers <= set(data['inputs']), 'missing checker helper input pins')
|
||||
composition.verify_composition(ROOT, roles, data.get('source_hashes'), data.get('inputs'),
|
||||
EXPECTED_BASE_COMMIT, REQUIRED_SCOPE_FILES + ['docs/plans/original-completion-2026-10-02/execution/p24-02/**'])
|
||||
with composition.tested_checkout(ROOT, roles['T']) as view:
|
||||
VERIFIED_ROOT = view
|
||||
return _verify_evidence(index_path)
|
||||
except (ValueError, KeyError, TypeError, OSError, IndexError) as exc:
|
||||
print(f"ERROR: composition: {exc}", file=sys.stderr)
|
||||
return False
|
||||
finally:
|
||||
VERIFIED_ROOT = ROOT
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Check P24-02 Operations Evidence")
|
||||
parser.add_argument("--verify-only", action="store_true", help="Strict verification of existing evidence index only")
|
||||
parser.add_argument("--index", type=str, help="Path to evidence index.json", default=None)
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
composition.root_identity(ROOT, Path.cwd())
|
||||
except (ValueError, OSError) as exc:
|
||||
print(f"ERROR: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
index_file = find_index_file(args.index)
|
||||
if not index_file:
|
||||
print("ERROR: Could not locate p24-operations-evidence index.json.", file=sys.stderr)
|
||||
|
||||
@@ -416,7 +416,7 @@
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/ui/test_operations_notification.gd": {
|
||||
"source_sha256": "9c26386c529b8c8135639e8d0f0ed390da7139d64ff1e268975b05948cab9147",
|
||||
"source_sha256": "50172ab6daa031d221f6a78cac5cdbcb8884f295f8062b54656562e2412faff2",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": ["single"],
|
||||
"old_bounded_teardown": false,
|
||||
|
||||
Reference in New Issue
Block a user