--- runner/core.before +++ runner/core.after @@ -939,38 +939,50 @@ require(not destination.exists(), "fresh review material directory required") destination.mkdir() rows = [] + excluded_sensitive = [] + approved_roots = ((self.root / "runs").resolve(), Path(self.allocation(task_id)["build_dir"]).resolve()) def copy_file(source, target): require(source.is_file() and not source.is_symlink(), "ordinary review proof") + resolved = source.resolve(strict=True) + approved = next((root for root in approved_roots if resolved.is_relative_to(root)), None) + require(approved is not None, "review proof outside registered evidence roots") + require(not secret_path(str(resolved.relative_to(approved))), "secret-named review proof rejected") + require(not target.exists(), "review proof target collision") target.parent.mkdir(parents=True, exist_ok=True) before = hashlib.sha256(source.read_bytes()).hexdigest() if sys.platform == "darwin": subprocess.run(["/bin/cp", "-pc", str(source), str(target)], check=True) else: shutil.copy2(source, target) - require(hashlib.sha256(target.read_bytes()).hexdigest() == before, "whole review proof mismatch") + require(hashlib.sha256(target.read_bytes()).hexdigest() == before + and hashlib.sha256(source.read_bytes()).hexdigest() == before, + "whole review proof mismatch or source changed") rows.append({"path": str(target.relative_to(worktree)), "original_path": str(source), "bytes": target.stat().st_size, "sha256": before}) - evidence = self.db.execute("SELECT kind,artifact FROM evidence WHERE task_id=? AND commit_sha=? AND passed=1", + evidence = self.db.execute("SELECT kind,artifact FROM evidence WHERE task_id=? AND commit_sha=? AND passed=1 AND kind=\'validation\'", (task_id, revision)).fetchall() for number, row in enumerate(evidence): artifact = Path(row["artifact"]) copy_file(artifact, destination / "receipts" / (str(number)+"-"+artifact.name)) if artifact.suffix == ".json": value = read_json(artifact) - for check in value.get("checks", []): + for check_number, check in enumerate(value.get("checks", [])): folder = Path(check["artifact"]) for name in ("stdout.log", "stderr.log", "command.json"): path = folder / name - if path.is_file(): - copy_file(path, destination / "validation-runs" / folder.name / name) + require(path.is_file(), "registered validation log missing: " + name) + copy_file(path, destination / "validation-runs" / str(number) / str(check_number) / folder.name / name) build = Path(self.allocation(task_id)["build_dir"]) require(build.resolve().is_relative_to(self.runtime(task_id).resolve()), "registered review build scope") if build.exists(): for source in sorted(build.rglob("*")): - if source.is_file() and not secret_path(str(source.relative_to(build))): + if source.is_file(): + if secret_path(str(source.relative_to(build))): + excluded_sensitive.append(str(source.relative_to(build))) + continue copy_file(source, destination / "build" / source.relative_to(build)) write_json(destination / "manifest.json", - {"candidate": revision, "task_id": task_id, "complete_registered_build_and_receipts": True, + {"candidate": revision, "task_id": task_id, "complete_nonsecret_registered_validation_material": True, "excluded_sensitive_paths": excluded_sensitive, "review_results_included": False, "file_count_limit": None, "byte_limit": None, "files": rows}) return {"path": ".review-material/manifest.json", "file_count": len(rows), "byte_limit": None} @@ -988,7 +1000,7 @@ try: material = self.review_materials(task_id, wt, task["submitted"]) prompt = self.packet(task_id, "reviewer", task["submitted"]) - prompt += "\\nRead complete registered validation receipts and build proof via " + json.dumps(material) + ". All files are available within this frozen checkout; inspect related sources and proof as needed." + prompt += "\nRead complete registered validation receipts and build proof via " + json.dumps(material) + ". All files are available within this frozen checkout; inspect related sources and proof as needed." value,artifact=self.llm(task_id,"reviewer","claude",prompt,wt,schema) c=json.loads(task["contract"]) passed=value["status"]=="pass" and set(value["acceptance_ids"])==set(c["acceptance_ids"]) and not any(f["severity"] in ("critical","high","medium") for f in value["findings"])