P24-02: 操作と通知の全受入

This commit is contained in:
Agent Control Runner
2026-10-17 14:49:39 +09:00
parent bd3a134051
commit 78f7cba7a9
30 changed files with 4729 additions and 38 deletions
+56 -30
View File
@@ -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)
+1 -1
View File
@@ -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,