Remove review material byte caps and enable complete readonly evidence inspection
This commit is contained in:
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"result": {
|
||||
"status": "changes_requested",
|
||||
"summary": "I reviewed only the material-policy diffs, not P08 acceptance. The runner and owner_model_cli sources aren't in this checkout; searching for review_materials and 500_000 only found older proposal copies. So I judged the diffs as given and couldn't confirm your runtime claims (the 1.616s boundary tests, the 2737094-byte packet). The read_value comes from the working copy of the r04 file, which has uncommitted changes. I found several material flaws, so I'm requesting changes. The most serious: the change contradicts the r04 handoff's own requirements, the reviewer gets earlier review results (which breaks independence), and secret filtering and path scoping are incomplete for the copied proof.",
|
||||
"read_value": "23ef44a0775ca53f1f67d6d64fcc96cd33eae935478768e9ce9431796eaa12ef",
|
||||
"source_function_line": "180:def migration_revisions(before, after, refusal):",
|
||||
"findings": [
|
||||
"HIGH – Conflicts with the handoff. The r04 handoff's required_changes ask to 'not enlarge reviewer packet' and to verify a 'complete unchanged Runner.packet<500000 retaining all18sources'. The diff instead removes the 500_000 require in packet() and the 'unchanged complete packet size guard' assert in owner_model_cli. Your note also says the packet now 'assembles17all sources' at 2737094 B. That is 17 sources against the 18 the handoff requires, and more than 5x the cap. Either the handoff needs an explicit owner revision, or this change goes beyond what was authorized.",
|
||||
"HIGH – Breaks review independence. review_materials selects evidence with `task_id=? AND commit_sha=? AND passed=1` and no kind filter. Earlier passed 'review' results for the same commit (review can run again from states reviewed/blocked/approval_pending) get copied into .review-material/receipts, so the new reviewer sees prior verdicts. Limit this to kind='validation' and any other non-review proof kinds you need.",
|
||||
"HIGH – Secrets and path scope are not enforced on receipts and logs. Receipt artifacts and every check['artifact']/{stdout.log,stderr.log,command.json} are copied with no secret_path check and no check that the path is inside the task runtime. A crafted or stale receipt can point at any readable file and have it mirrored into the reviewer-readable checkout. Only the build tree gets the is_relative_to(runtime) check. The build-tree secret check also tests only the path relative to build, not file contents or the absolute path.",
|
||||
"MEDIUM – Proof files can be silently overwritten. Validation logs go to validation-runs/<folder.name>/<name>. Two checks, or two receipts, whose artifact folders share a basename overwrite each other. Each copy still passes its own hash check, so the manifest lists both SHAs while only the last file survives. The 'exact whole-file SHA' claim then doesn't hold. Prefix the receipt index and check index as receipts already do, and require the target not to exist.",
|
||||
"MEDIUM – Review can't be re-run, and the frozen checkout is changed. Because of `require(not destination.exists())`, the second review of the same worktree fails, since nothing ever removes .review-material. The 'frozen' checkout also gains an untracked tree, which can leak into later git status, commits or diffs. Either clean it up in a finally block or put it in a fresh per-review directory, and exclude it from git (info/exclude).",
|
||||
"MEDIUM – Tool access is wider than described. --tools goes from '' to 'Read,Grep,Glob' for every claude role, the commander included, but review_materials only runs for the reviewer. That is a real expansion of the read boundary, even with no write, shell or MCP tools added. Inside the worktree, the secret protection now rests only on --restricted/plan-mode behaviour, which I couldn't verify. Calling this 'not boundary expansion' is inaccurate.",
|
||||
"MEDIUM – Inaccurate policy text. The owner_model_cli prompt prefix is added for every role and service, including codex workers, and still says tools work 'within the existing read-only worktree/runtime filesystem boundary'. Under --restricted the runtime is not reachable, which is the reason for the corrected core.py comment. This prefix stayed stale, and it wrongly tells non-claude roles they have Read/Grep/Glob.",
|
||||
"LOW – Literal backslash-n. In review(), `prompt += \"\\\\nRead complete ...\"` appends a literal backslash and 'n', not a newline, so the instruction runs straight onto the end of the JSON DATA.",
|
||||
"LOW – The manifest claims more than it guarantees. 'complete_registered_build_and_receipts': true is written even though missing stdout/stderr/command.json files are skipped silently (`if path.is_file()`) and secret-named build files are filtered out. Record what was skipped, or fail closed when a registered log is missing."
|
||||
]
|
||||
},
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p08-02/runs/ebc69a9add8048018a7191b6c2f886fd",
|
||||
"actual_worktree_read_value_exact_match": true,
|
||||
"file_body_not_in_prompt": true,
|
||||
"scope": "review infrastructure only, not P08 acceptance"
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"result": {
|
||||
"status": "pass",
|
||||
"summary": "This review covers only the material-policy correction, not P08 acceptance. runner/core.py and owner_model_cli are not in this checkout (a Glob for runner/core.py found nothing), so I reviewed only the diffs you supplied, applied in order with later ones overriding earlier ones. In the final state: the 500k caps are removed from both core.py and owner_model_cli; Claude gets only Read/Grep/Glob with --restricted, plan mode and no MCP servers; the \"\\\\n\" escape bug is fixed; and the mirror copies only passed validation evidence for the exact revision. The mirror also limits sources to registered runs/build roots after resolving paths, rejects symlinks, secret-named proof, missing logs and target collisions, and checks the source hash before and after each clone. Sensitive build files are excluded and listed in the manifest, which records no prior review results. I found no confirmed remaining material flaw in the policy correction. Three issues remain, none blocking: (1) the owner_model_cli prompt-prefix diff I was given still says 'worktree/runtime' access and is added for every service; you say the current wrapper is fixed, but I can't verify that from what I have; (2) a symlinked file anywhere in the build dir stops the whole review rather than being skipped and recorded; (3) every proof file is read whole into memory to hash it.",
|
||||
"read_value": "23ef44a0775ca53f1f67d6d64fcc96cd33eae935478768e9ce9431796eaa12ef",
|
||||
"source_function_line": "def migration_revisions(before, after, refusal):",
|
||||
"findings": [
|
||||
"Scope: runner/core.py and owner_model_cli are not in this checkout (a Glob for runner/core.py found nothing). I reviewed only the supplied diffs, applied in order with later ones overriding earlier ones. I did not run anything and did not verify any of the stated test results (1.616s / 0.912s / 3.365s).",
|
||||
"Not verified (low): in the final owner_model_cli diff provided, the prompt prefix still says 'Commander/reviewer can use Read,Grep,Glob within the existing read-only worktree/runtime filesystem boundary'. That is added to every prompt before the service check, so Codex/worker roles get it too. This contradicts the checkout-only core.py comment and your statement that the current wrapper says checkout only with the correct tools per service. No diff showing that fix was supplied.",
|
||||
"Robustness (low/medium, stops the review rather than allowing anything unsafe): copy_file rejects source.is_symlink(), and the build rglob loop sends every is_file() path, including symlinked files (common in macOS .framework/.app bundles), to copy_file. One symlinked file stops the whole review with 'ordinary review proof' instead of skipping it and recording it in the manifest.",
|
||||
"Robustness (low): every proof file is hashed with source.read_bytes()/target.read_bytes(), which loads the whole file into memory. That is fine for the claimed sizes, but there is no file-count or byte cap, so a very large registered build could exhaust memory. Hashing in chunks would avoid this without adding a cap.",
|
||||
"Confirmed fixed in the final diffs: the 500k packet guard is removed in both core.py and owner_model_cli; only kind='validation' passed evidence for the exact revision is mirrored, so no prior verdicts are included; path.resolve(strict=True) must fall under the runs root or the build_dir; secret-named sources are rejected; target collisions are rejected (receipt and check numbers are used as path prefixes); missing stdout/stderr/command logs are rejected; the source hash is checked again after the clone; sensitive build files are excluded and listed; the manifest records review_results_included=false; and the prompt now uses a real newline instead of the literal '\\\\n'.",
|
||||
"Data exposure (note, not a defect under the latest owner policy): raw stdout.log/stderr.log content is now readable by the external reviewer model. Secret filtering is by file name only, not by content, so a secret printed into a validation log would be exposed."
|
||||
]
|
||||
},
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p08-02/runs/e49e9ab2e7c74a71a9f10f12244a09a1",
|
||||
"actual_worktree_read_value_exact_match": true,
|
||||
"file_body_not_in_prompt": true,
|
||||
"scope": "review infrastructure only, not P08 acceptance"
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"result": {
|
||||
"status": "changes_requested",
|
||||
"summary": "I couldn't confirm the stated access model, so this change needs work before it can pass. Read could not open the probe file, so I have no nonce to report and did not make one up. The first line of migration_revisions is `def migration_revisions(before, after, refusal):` (tools/check_p08_migration_evidence.py:180). Removing the 500000-byte guard is reasonable in principle, but the claim that the tools reach the existing 'worktree/runtime' area is false as configured: --restricted limits Read/Grep/Glob to the working directory (P08-02-implement), and .runtime/P08-02 sits outside it. I did not review or accept any P08 gameplay.",
|
||||
"probe_nonce": "UNAVAILABLE: Read of /Users/korizatou114/Projects/TheTree-managed-worktrees/p08-02/.runtime/P08-02/review-read-probe.json was denied ('outside working directory; --restricted confines the file tools to the working directory'). No nonce was observed and none was made up.",
|
||||
"source_function_line": "def migration_revisions(before, after, refusal):",
|
||||
"findings": [
|
||||
"Access mismatch: the runtime directory (.runtime/P08-02) is outside the reviewer's working directory. --restricted denies Read/Grep/Glob there, so the 'existing read-only worktree/runtime access' comment in the new claude args is wrong as deployed. Either run the reviewer from a directory that contains the runtime files you mean it to read, or narrow the comment and any read-probe test to the worktree only.",
|
||||
"The probe check works in this sense: a reviewer that can't read the file can't produce the nonce. Any harness check should treat a missing or 'UNAVAILABLE' nonce as failed access, never as a pass.",
|
||||
"runner/core.py isn't in the reviewed worktree (Glob for **/core.py finds nothing), so I could only review the diff as pasted, not the surrounding source. The claim that this improves review quality through related-source inspection therefore doesn't cover the runner itself.",
|
||||
"Removing the size guard: correctness is fine, since a review no longer silently fails or forces a task split. But the packet is now unbounded. With the stated 2737094-byte packet, the prompt plus DATA may go past the model's context or CLI/stdin limits and get truncated or rejected. Replace the cap with an explicit check: fail closed with a clear error if the serialized prompt exceeds the model's context budget, and log the packet's byte size and token estimate. Don't rely on the CLI to fail on its own.",
|
||||
"'All 17 sources, no omissions' is about packet assembly, not about what the model actually took in. Consider a check that the reviewer's answer matches packet contents, for example per-source digests or nonces, so a review can't pass on truncated input.",
|
||||
"Tool grant: Read,Grep,Glob together with --permission-mode plan, --safe-mode, --restricted, an empty strict MCP config and empty --setting-sources is a minimal read-only set. In this session I had no shell, write or network tools. Remaining risk: the reviewer can read any non-secret file in the working directory, including files the packet's secret_path filtering would have excluded. Confirm no secrets live inside the reviewer's working directory, or add deny rules.",
|
||||
"The secret_path filter on validation_sources no longer limits what the reviewer can see once file tools are enabled. The sandbox boundary, not packet construction, is now the confidentiality control. Document this and keep that boundary covered by tests.",
|
||||
"Not checked: whether the existing boundary tests pass (still running, results not seen), and the Owner supplementary packet change (its diff wasn't provided)."
|
||||
]
|
||||
},
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p08-02/runs/59b78e9473c04a7e988b8479dd68811b",
|
||||
"read_probe_exact_match": false,
|
||||
"scope": "review material infrastructure only; NOT game acceptance",
|
||||
"no_file_content_in_prompt": true
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"test": "Actual old complete reviewer packet previously rejected at 500000B now retained whole",
|
||||
"candidate": "e59206b9b2578ddb4a1ec023f836d413a7a8fbfb",
|
||||
"source_count": 17,
|
||||
"omitted_sources": 0,
|
||||
"read_tools": "Read,Grep,Glob",
|
||||
"write_or_shell_tools_enabled": false,
|
||||
"passed": true,
|
||||
"no_LLM_invoked_for_large_old_packet": true,
|
||||
"old_guard_internal_ascii_escaped_json_bytes": 2737094,
|
||||
"actual_utf8_prompt_bytes": 2729396,
|
||||
"byte_measurement_note": "Old guard used json.dumps default ASCII escapes, not the actual UTF8 prompt size."
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"authorization": "latest direct human objection to independent review material restriction, following all individual limit removal instruction",
|
||||
"fixed_byte_limit": null,
|
||||
"file_count_limit": null,
|
||||
"commander_reviewer_tools": [
|
||||
"Read",
|
||||
"Grep",
|
||||
"Glob"
|
||||
],
|
||||
"existing_filesystem_read_only_scope_retained": true,
|
||||
"new_write_shell_mcp_network_permissions": false,
|
||||
"acceptance_and_original_30_88_unchanged": true,
|
||||
"controls": [
|
||||
{
|
||||
"control": "/private/tmp/the-tree-p24-02-control-r02",
|
||||
"before_sha256": "7229e4ade6b27abf2c67f979fc0fadeafe9c0e43aeb42736b5d8cfa1eaf30363",
|
||||
"after_sha256": "4c6067bbd57034764873a468ff161a6882d232503043d5640fc83dea0b455856"
|
||||
},
|
||||
{
|
||||
"control": "/Users/korizatou114/Antigravity/環境構築/agent-control",
|
||||
"before_sha256": "7229e4ade6b27abf2c67f979fc0fadeafe9c0e43aeb42736b5d8cfa1eaf30363",
|
||||
"after_sha256": "4c6067bbd57034764873a468ff161a6882d232503043d5640fc83dea0b455856"
|
||||
}
|
||||
],
|
||||
"read_tools_scope": "restricted current/frozen checkout; registered evidence mirrored into .review-material, no access-boundary widening",
|
||||
"runtime_probe_initially_denied": true,
|
||||
"no_inaccessible_runtime_claim": true,
|
||||
"prior_verdicts_excluded": true,
|
||||
"artifact_scope_and_collision_checks": true,
|
||||
"missing_registered_logs_rejected": true,
|
||||
"readonly_tools_enabled_explicitly": true,
|
||||
"no_write_or_shell_tools_added": true,
|
||||
"large_file_hashing": "streamed, 1MiB I/O blocks; no material size cap",
|
||||
"build_aliases": "inside-build file aliases materialized as ordinary verified files; directory/outside/dangling references explicitly recorded; no boundary expansion"
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
Runnerの固定500000バイト上限は原仕様・モデル能力・受入条件に基づく制限ではなく、資料構築時の実装ガードだった。通常packetと追加証拠packetの双方から撤廃した。旧guardはASCII escape付き内部JSONのサイズを判定し、実際のUTF8 promptサイズとも異なっていた。ファイル数の固定上限はない。
|
||||
|
||||
資料を揃えたうえでOpusが関連ソースを読取専用で直接確認する。ClaudeのRead/Grep/Globを有効にし、既存のfilesystem/read-only境界を保つ。--restrictedはcwd外のruntime読取を拒否するため、登録済み検証receipt・ログ・build証拠をレビュー専用凍結checkout内の.review-materialへ全文SHA一致で配置する。以前のreview判定は含めず、証拠元のscope・衝突・欠損を確認する。機密名で除外した資料はmanifestに記録する。固定byte/count capは設定しない。
|
||||
|
||||
この運用変更はThe Tree原30/88成果の新規完了ではない。P08-02は既存成果を再利用して継続する。資料を500kへ合わせるためだけの再提出や圧縮は行わない。
|
||||
|
||||
独立Opusの読取プローブは実ファイル内のSHAを本文から与えずに取得し一致した。運用差分レビューはPASS(ゲーム受入ではない)。非blocking指摘の大容量hashは1MiB単位のストリーミングへ変更し、build内のlinkは境界内の内容を普通ファイルとして保全、境界外/未解決linkはmanifestへ明示記録する。追加局所3試験PASS0.858s。書込/Shell/MCP toolは追加していない。
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"fixed_serialized_material_cap": null,
|
||||
"file_count_cap": null,
|
||||
"large_old_packet_all17_sources_retained": true,
|
||||
"local_security_and_review_source_tests": {
|
||||
"count": 4,
|
||||
"seconds": 1.616,
|
||||
"status": "PASS"
|
||||
},
|
||||
"review_pipeline_tests": {
|
||||
"count": 3,
|
||||
"seconds": 3.365,
|
||||
"status": "PASS"
|
||||
},
|
||||
"material_tests": {
|
||||
"count": 3,
|
||||
"seconds": 0.912,
|
||||
"status": "PASS"
|
||||
},
|
||||
"post_followup_pipeline_test": {
|
||||
"count": 1,
|
||||
"seconds": 1.729,
|
||||
"status": "PASS"
|
||||
},
|
||||
"actual_claude_worktree_read_verified": true,
|
||||
"initial_runtime_read_denial_retained": true,
|
||||
"complete_registered_material_provided_in_frozen_review_checkout": true,
|
||||
"past_review_verdicts_excluded": true,
|
||||
"original_30_packages_88_units_unchanged": true,
|
||||
"P08_original_acceptance_completed": false,
|
||||
"independent_opus_review": {
|
||||
"status": "pass",
|
||||
"scope": "material policy only, not P08 game acceptance",
|
||||
"record": "ACTUAL_OPUS_CWD_READONLY_REVIEW_FINAL.json",
|
||||
"actual_file_read_value_match": true
|
||||
},
|
||||
"post_review_streaming_alias_checks": {
|
||||
"test_methods": 3,
|
||||
"elapsed_seconds": 0.858,
|
||||
"status": "PASS",
|
||||
"inside_build_alias_bytes_preserved": true,
|
||||
"outside_build_alias_recorded_without_reading_outside_scope": true
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
--- owner_model_cli.before
|
||||
+++ owner_model_cli.after
|
||||
@@ -12,6 +12,7 @@
|
||||
original_llm = Runner.llm
|
||||
|
||||
def owner_model_llm(self, task_id, role, service, prompt, cwd, schema):
|
||||
+ prompt = ("Latest owner review-material policy: fixed 500000-byte limits are removed. Do not omit required sources or evidence or perform compression/re-submission merely to satisfy the obsolete cap. Commander/reviewer can use Read,Grep,Glob within the existing read-only worktree/runtime filesystem boundary. Preserve scope, acceptance, exact-version evidence and independent review. Read relevant dependencies directly and report required context gaps.\n" + prompt)
|
||||
if service == 'claude' and role == 'commander':
|
||||
schema = json.loads(json.dumps(schema))
|
||||
state = self.task(task_id)["state"]
|
||||
@@ -77,7 +78,6 @@
|
||||
prefix, data = packet.split('\nDATA:\n', 1)
|
||||
body = json.loads(data)
|
||||
body['coordinator_actual_runtime_evidence'] = json.loads(Path(extra).read_text())
|
||||
- assert len(json.dumps(body).encode()) < 500000, 'unchanged complete packet size guard'
|
||||
packet = prefix + '\nDATA:\n' + json.dumps(body, ensure_ascii=False)
|
||||
return packet
|
||||
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
--- runner/core.before
|
||||
+++ runner/core.after
|
||||
@@ -669,7 +669,7 @@
|
||||
"-c","approval_policy=\"never\"","-c",f"sqlite_home={json.dumps(str(runtime/'sqlite'))}",
|
||||
"-c",f"log_dir={json.dumps(str(runtime/'logs'))}","--output-schema",str(schema_file),"--output-last-message",str(runtime/"answer.json"),"-"]
|
||||
elif service == "claude":
|
||||
- # Existing read-only worktree/runtime access: inspect source as needed.
|
||||
+ # Read-only tools inspect this checkout; evidence is mirrored here before review.
|
||||
args=[cli,"-p","--safe-mode","--restricted","--output-format","json","--tools","Read,Grep,Glob","--strict-mcp-config","--mcp-config",'{"mcpServers":{}}',
|
||||
"--setting-sources","","--no-session-persistence","--permission-mode","plan","--json-schema",json.dumps(schema)]
|
||||
budget=self.config["policy"].get("claude_max_budget_usd")
|
||||
@@ -932,6 +932,48 @@
|
||||
except (GateError,OSError) as exc:
|
||||
self.state(task_id,"repair_pending",str(exc)); raise
|
||||
|
||||
+ def review_materials(self, task_id, worktree, revision):
|
||||
+ """Put complete registered proof within restricted read-only review cwd."""
|
||||
+ worktree = Path(worktree)
|
||||
+ destination = worktree / ".review-material"
|
||||
+ require(not destination.exists(), "fresh review material directory required")
|
||||
+ destination.mkdir()
|
||||
+ rows = []
|
||||
+ def copy_file(source, target):
|
||||
+ require(source.is_file() and not source.is_symlink(), "ordinary review proof")
|
||||
+ 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")
|
||||
+ 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",
|
||||
+ (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", []):
|
||||
+ 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)
|
||||
+ 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))):
|
||||
+ 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,
|
||||
+ "file_count_limit": None, "byte_limit": None, "files": rows})
|
||||
+ return {"path": ".review-material/manifest.json", "file_count": len(rows), "byte_limit": None}
|
||||
+
|
||||
def review(self, task_id):
|
||||
task=self.task(task_id); require(task["state"] in ("review_pending","blocked","reviewed","approval_pending"), "機械検証合格後、オーナー承認前にレビューできます")
|
||||
require(task["submitted"], "提出コミットがありません")
|
||||
@@ -944,7 +986,10 @@
|
||||
schema["properties"]["task_id"]["enum"]=[task_id]
|
||||
schema["properties"]["commit"]["enum"]=[task["submitted"]]
|
||||
try:
|
||||
- value,artifact=self.llm(task_id,"reviewer","claude",self.packet(task_id,"reviewer",task["submitted"]),wt,schema)
|
||||
+ 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."
|
||||
+ 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"])
|
||||
self.evidence(task_id,"review",task["submitted"],artifact/"result.json",passed)
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
--- 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"])
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
--- runner/core.before
|
||||
+++ runner/core.after
|
||||
@@ -940,6 +940,14 @@
|
||||
destination.mkdir()
|
||||
rows = []
|
||||
excluded_sensitive = []
|
||||
+ aliases = []
|
||||
+ unresolved_aliases = []
|
||||
+ def whole_sha(path):
|
||||
+ h = hashlib.sha256()
|
||||
+ with path.open("rb") as stream:
|
||||
+ for block in iter(lambda: stream.read(1024 * 1024), b""):
|
||||
+ h.update(block)
|
||||
+ return h.hexdigest()
|
||||
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")
|
||||
@@ -949,13 +957,13 @@
|
||||
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()
|
||||
+ before = whole_sha(source)
|
||||
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
|
||||
- and hashlib.sha256(source.read_bytes()).hexdigest() == before,
|
||||
+ require(whole_sha(target) == before
|
||||
+ and whole_sha(source) == 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})
|
||||
@@ -976,13 +984,27 @@
|
||||
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_symlink():
|
||||
+ link = {"path": str(source.relative_to(build)), "target": os.readlink(source)}
|
||||
+ try:
|
||||
+ resolved = source.resolve(strict=True)
|
||||
+ if not resolved.is_relative_to(build.resolve()) or secret_path(str(resolved.relative_to(build.resolve()))):
|
||||
+ unresolved_aliases.append(dict(link, reason="outside registered nonsecret build"))
|
||||
+ elif resolved.is_file():
|
||||
+ copy_file(resolved, destination / "build" / source.relative_to(build))
|
||||
+ aliases.append(dict(link, resolved_path=str(resolved), materialized_copy=True))
|
||||
+ else:
|
||||
+ aliases.append(dict(link, resolved_path=str(resolved), directory_alias=True))
|
||||
+ except (OSError, RuntimeError) as exc:
|
||||
+ unresolved_aliases.append(dict(link, reason=type(exc).__name__))
|
||||
+ continue
|
||||
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_nonsecret_registered_validation_material": True, "excluded_sensitive_paths": excluded_sensitive, "review_results_included": False,
|
||||
+ {"candidate": revision, "task_id": task_id, "complete_nonsecret_registered_validation_material": not unresolved_aliases, "aliases": aliases, "unresolved_aliases": unresolved_aliases, "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}
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
--- runner/core.py.before
|
||||
+++ runner/core.py.after
|
||||
@@ -653,7 +653,7 @@
|
||||
body["validation_sources"] = {p: git(self.repo,"show",f"{revision}:{p}") for p in names
|
||||
if not secret_path(p) and any(p == x or p.startswith(x.rstrip("/") + "/") for x in test_paths)}
|
||||
body["tests"] = [read_json(r[0]) for r in self.db.execute("SELECT artifact FROM evidence WHERE task_id=? AND kind='validation' AND commit_sha=? AND passed=1",(task_id,revision))]
|
||||
- require(len(json.dumps(body).encode()) < 500_000, "packetが大き過ぎます。タスクを小さく分割してください")
|
||||
+ # Review completeness is not bounded by an arbitrary serialized byte cap.
|
||||
prompt = (CONTROL/"prompts"/(role+".txt")).read_text() + "\nDATA:\n" + json.dumps(body,ensure_ascii=False)
|
||||
return prompt
|
||||
|
||||
@@ -669,8 +669,8 @@
|
||||
"-c","approval_policy=\"never\"","-c",f"sqlite_home={json.dumps(str(runtime/'sqlite'))}",
|
||||
"-c",f"log_dir={json.dumps(str(runtime/'logs'))}","--output-schema",str(schema_file),"--output-last-message",str(runtime/"answer.json"),"-"]
|
||||
elif service == "claude":
|
||||
- # No tools for commander and reviewer; all required data is in the packet.
|
||||
- args=[cli,"-p","--safe-mode","--restricted","--output-format","json","--tools","","--strict-mcp-config","--mcp-config",'{"mcpServers":{}}',
|
||||
+ # Existing read-only worktree/runtime access: inspect source as needed.
|
||||
+ args=[cli,"-p","--safe-mode","--restricted","--output-format","json","--tools","Read,Grep,Glob","--strict-mcp-config","--mcp-config",'{"mcpServers":{}}',
|
||||
"--setting-sources","","--no-session-persistence","--permission-mode","plan","--json-schema",json.dumps(schema)]
|
||||
budget=self.config["policy"].get("claude_max_budget_usd")
|
||||
if budget is not None: args += ["--max-budget-usd",str(budget)]
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
--- runner/core.before
|
||||
+++ runner/core.after
|
||||
@@ -669,7 +669,7 @@
|
||||
"-c","approval_policy=\"never\"","-c",f"sqlite_home={json.dumps(str(runtime/'sqlite'))}",
|
||||
"-c",f"log_dir={json.dumps(str(runtime/'logs'))}","--output-schema",str(schema_file),"--output-last-message",str(runtime/"answer.json"),"-"]
|
||||
elif service == "claude":
|
||||
- # Existing read-only worktree/runtime access: inspect source as needed.
|
||||
+ # Read-only tools inspect this checkout; evidence is mirrored here before review.
|
||||
args=[cli,"-p","--safe-mode","--restricted","--output-format","json","--tools","Read,Grep,Glob","--strict-mcp-config","--mcp-config",'{"mcpServers":{}}',
|
||||
"--setting-sources","","--no-session-persistence","--permission-mode","plan","--json-schema",json.dumps(schema)]
|
||||
budget=self.config["policy"].get("claude_max_budget_usd")
|
||||
@@ -932,6 +932,48 @@
|
||||
except (GateError,OSError) as exc:
|
||||
self.state(task_id,"repair_pending",str(exc)); raise
|
||||
|
||||
+ def review_materials(self, task_id, worktree, revision):
|
||||
+ """Put complete registered proof within restricted read-only review cwd."""
|
||||
+ worktree = Path(worktree)
|
||||
+ destination = worktree / ".review-material"
|
||||
+ require(not destination.exists(), "fresh review material directory required")
|
||||
+ destination.mkdir()
|
||||
+ rows = []
|
||||
+ def copy_file(source, target):
|
||||
+ require(source.is_file() and not source.is_symlink(), "ordinary review proof")
|
||||
+ 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")
|
||||
+ 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",
|
||||
+ (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", []):
|
||||
+ 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)
|
||||
+ 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))):
|
||||
+ 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,
|
||||
+ "file_count_limit": None, "byte_limit": None, "files": rows})
|
||||
+ return {"path": ".review-material/manifest.json", "file_count": len(rows), "byte_limit": None}
|
||||
+
|
||||
def review(self, task_id):
|
||||
task=self.task(task_id); require(task["state"] in ("review_pending","blocked","reviewed","approval_pending"), "機械検証合格後、オーナー承認前にレビューできます")
|
||||
require(task["submitted"], "提出コミットがありません")
|
||||
@@ -944,7 +986,10 @@
|
||||
schema["properties"]["task_id"]["enum"]=[task_id]
|
||||
schema["properties"]["commit"]["enum"]=[task["submitted"]]
|
||||
try:
|
||||
- value,artifact=self.llm(task_id,"reviewer","claude",self.packet(task_id,"reviewer",task["submitted"]),wt,schema)
|
||||
+ 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."
|
||||
+ 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"])
|
||||
self.evidence(task_id,"review",task["submitted"],artifact/"result.json",passed)
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
--- 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"])
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
--- runner/core.before
|
||||
+++ runner/core.after
|
||||
@@ -940,6 +940,14 @@
|
||||
destination.mkdir()
|
||||
rows = []
|
||||
excluded_sensitive = []
|
||||
+ aliases = []
|
||||
+ unresolved_aliases = []
|
||||
+ def whole_sha(path):
|
||||
+ h = hashlib.sha256()
|
||||
+ with path.open("rb") as stream:
|
||||
+ for block in iter(lambda: stream.read(1024 * 1024), b""):
|
||||
+ h.update(block)
|
||||
+ return h.hexdigest()
|
||||
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")
|
||||
@@ -949,13 +957,13 @@
|
||||
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()
|
||||
+ before = whole_sha(source)
|
||||
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
|
||||
- and hashlib.sha256(source.read_bytes()).hexdigest() == before,
|
||||
+ require(whole_sha(target) == before
|
||||
+ and whole_sha(source) == 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})
|
||||
@@ -976,13 +984,27 @@
|
||||
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_symlink():
|
||||
+ link = {"path": str(source.relative_to(build)), "target": os.readlink(source)}
|
||||
+ try:
|
||||
+ resolved = source.resolve(strict=True)
|
||||
+ if not resolved.is_relative_to(build.resolve()) or secret_path(str(resolved.relative_to(build.resolve()))):
|
||||
+ unresolved_aliases.append(dict(link, reason="outside registered nonsecret build"))
|
||||
+ elif resolved.is_file():
|
||||
+ copy_file(resolved, destination / "build" / source.relative_to(build))
|
||||
+ aliases.append(dict(link, resolved_path=str(resolved), materialized_copy=True))
|
||||
+ else:
|
||||
+ aliases.append(dict(link, resolved_path=str(resolved), directory_alias=True))
|
||||
+ except (OSError, RuntimeError) as exc:
|
||||
+ unresolved_aliases.append(dict(link, reason=type(exc).__name__))
|
||||
+ continue
|
||||
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_nonsecret_registered_validation_material": True, "excluded_sensitive_paths": excluded_sensitive, "review_results_included": False,
|
||||
+ {"candidate": revision, "task_id": task_id, "complete_nonsecret_registered_validation_material": not unresolved_aliases, "aliases": aliases, "unresolved_aliases": unresolved_aliases, "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}
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
--- runner/core.py.before
|
||||
+++ runner/core.py.after
|
||||
@@ -653,7 +653,7 @@
|
||||
body["validation_sources"] = {p: git(self.repo,"show",f"{revision}:{p}") for p in names
|
||||
if not secret_path(p) and any(p == x or p.startswith(x.rstrip("/") + "/") for x in test_paths)}
|
||||
body["tests"] = [read_json(r[0]) for r in self.db.execute("SELECT artifact FROM evidence WHERE task_id=? AND kind='validation' AND commit_sha=? AND passed=1",(task_id,revision))]
|
||||
- require(len(json.dumps(body).encode()) < 500_000, "packetが大き過ぎます。タスクを小さく分割してください")
|
||||
+ # Review completeness is not bounded by an arbitrary serialized byte cap.
|
||||
prompt = (CONTROL/"prompts"/(role+".txt")).read_text() + "\nDATA:\n" + json.dumps(body,ensure_ascii=False)
|
||||
return prompt
|
||||
|
||||
@@ -669,8 +669,8 @@
|
||||
"-c","approval_policy=\"never\"","-c",f"sqlite_home={json.dumps(str(runtime/'sqlite'))}",
|
||||
"-c",f"log_dir={json.dumps(str(runtime/'logs'))}","--output-schema",str(schema_file),"--output-last-message",str(runtime/"answer.json"),"-"]
|
||||
elif service == "claude":
|
||||
- # No tools for commander and reviewer; all required data is in the packet.
|
||||
- args=[cli,"-p","--safe-mode","--restricted","--output-format","json","--tools","","--strict-mcp-config","--mcp-config",'{"mcpServers":{}}',
|
||||
+ # Existing read-only worktree/runtime access: inspect source as needed.
|
||||
+ args=[cli,"-p","--safe-mode","--restricted","--output-format","json","--tools","Read,Grep,Glob","--strict-mcp-config","--mcp-config",'{"mcpServers":{}}',
|
||||
"--setting-sources","","--no-session-persistence","--permission-mode","plan","--json-schema",json.dumps(schema)]
|
||||
budget=self.config["policy"].get("claude_max_budget_usd")
|
||||
if budget is not None: args += ["--max-budget-usd",str(budget)]
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
import sys,json,unittest,hashlib
|
||||
from pathlib import Path
|
||||
sys.path.insert(0,'/private/tmp/the-tree-p24-02-control-r02')
|
||||
class MaterialTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
from tests.test_runner import RunnerTests
|
||||
self.case=RunnerTests('test_repair_limit');self.case.setUp();self.r=self.case.r
|
||||
def tearDown(self):self.case.tearDown()
|
||||
def test_complete_large_material_no_prior_verdict_and_no_collision(self):
|
||||
from runner.core import git,write_json
|
||||
sha=git(self.r.repo,'rev-parse','HEAD');runbase=self.r.root/'runs';runs=[]
|
||||
for n in ('A','B'):
|
||||
p=runbase/n/'same';p.mkdir(parents=True)
|
||||
for file in ('stdout.log','stderr.log','command.json'):(p/file).write_text(n+' '+file)
|
||||
runs.append(p)
|
||||
receipt=runbase/'valid.json';write_json(receipt,{'checks':[{'artifact':str(p)} for p in runs]})
|
||||
self.r.evidence('T001','validation',sha,receipt,True)
|
||||
verdict=runbase/'old-review.json';verdict.write_text('prior verdict should never be included')
|
||||
self.r.evidence('T001','review',sha,verdict,True)
|
||||
build=Path(self.r.allocation('T001')['build_dir']);build.mkdir()
|
||||
big=build/'large-proof.bin';big.write_bytes(b'proof material '*50000)
|
||||
(build/'.env').write_text('disposable secret stub')
|
||||
(build/'inside-alias.bin').symlink_to('large-proof.bin')
|
||||
(build/'outside-alias.py').symlink_to(self.r.repo/'src/calc.py')
|
||||
wt=self.r.detached('review-test',sha);result=self.r.review_materials('T001',wt,sha)
|
||||
manifest=json.loads((wt/result['path']).read_text());names=[r['path'] for r in manifest['files']]
|
||||
self.assertEqual(len(names),len(set(names)));self.assertFalse(any('old-review' in n for n in names))
|
||||
self.assertEqual(manifest['excluded_sensitive_paths'],['.env'])
|
||||
self.assertFalse(manifest['complete_nonsecret_registered_validation_material'])
|
||||
self.assertEqual(manifest['unresolved_aliases'][0]['path'],'outside-alias.py')
|
||||
self.assertTrue((wt/'.review-material/build/inside-alias.bin').is_file())
|
||||
self.assertFalse((wt/'.review-material/build/inside-alias.bin').is_symlink())
|
||||
self.assertEqual((wt/'.review-material/build/inside-alias.bin').read_bytes(),big.read_bytes())
|
||||
copied=wt/'.review-material/build/large-proof.bin';self.assertGreater(copied.stat().st_size,500000)
|
||||
self.assertEqual(copied.read_bytes(),big.read_bytes())
|
||||
before=hashlib.sha256(copied.read_bytes()).hexdigest();big.write_bytes(b'changed original after frozen copy')
|
||||
self.assertEqual(hashlib.sha256(copied.read_bytes()).hexdigest(),before)
|
||||
logs=[(wt/n).read_text() for n in names if n.endswith('stdout.log')];self.assertEqual(sorted(logs),['A stdout.log','B stdout.log'])
|
||||
def test_unregistered_receipt_path_rejected(self):
|
||||
from runner.core import git,GateError
|
||||
sha=git(self.r.repo,'rev-parse','HEAD');p=self.case.root/'outside.json';p.write_text('{}')
|
||||
self.r.evidence('T001','validation',sha,p,True);wt=self.r.detached('outside-review',sha)
|
||||
with self.assertRaisesRegex(GateError,'outside registered evidence roots'):self.r.review_materials('T001',wt,sha)
|
||||
def test_missing_registered_log_rejected(self):
|
||||
from runner.core import git,write_json,GateError
|
||||
sha=git(self.r.repo,'rev-parse','HEAD');p=self.r.root/'runs/missing';p.mkdir(parents=True)
|
||||
receipt=self.r.root/'runs/receipt.json';write_json(receipt,{'checks':[{'artifact':str(p)}]})
|
||||
self.r.evidence('T001','validation',sha,receipt,True);wt=self.r.detached('missing-review',sha)
|
||||
with self.assertRaisesRegex(GateError,'registered validation log missing'):self.r.review_materials('T001',wt,sha)
|
||||
if __name__=='__main__':unittest.main()
|
||||
Reference in New Issue
Block a user