Remove review material byte caps and enable complete readonly evidence inspection
This commit is contained in:
@@ -12,8 +12,17 @@ ALLOWED_CODEX_MODELS = ('gpt-6.1-sol', 'gpt-6-astra')
|
||||
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. Claude commander/reviewer can inspect their source checkout with Read,Grep,Glob; reviewer validation materials are mirrored into .review-material within the frozen checkout. Other services keep their own tool sets. 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"]
|
||||
actions = {"ready": ["run", "wait"], "repair_ready": ["run", "wait"],
|
||||
"submitted": ["validate", "wait"], "repair_pending": ["request-repair", "wait"],
|
||||
"blocked": ["request-repair", "wait"], "review_pending": ["review", "wait"],
|
||||
"reviewed": ["prepare-integration", "wait"], "completed": ["wait"]}
|
||||
if state in actions:
|
||||
schema["properties"]["action"]["enum"] = actions[state]
|
||||
prompt = "Actual Runner state: " + state + "; valid actions: " + ", ".join(actions[state]) + ". Return a valid action; do not repeat completed transitions.\n" + prompt
|
||||
schema['required'] += ['codex_model', 'codex_model_reason']
|
||||
schema['properties']['codex_model'] = {'type': 'string', 'enum': list(ALLOWED_CODEX_MODELS)}
|
||||
schema['properties']['codex_model_reason'] = {'type': 'string', 'minLength': 1}
|
||||
@@ -69,7 +78,6 @@ def packet_with_actual_evidence(self, task_id, role, sha=None):
|
||||
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
|
||||
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"candidate": "bb34f443c4645a56d7798a142f1e2d8ddf615cae",
|
||||
"actual_complete_packet_bytes": 501277,
|
||||
"actual_size_guard_pass": false,
|
||||
"source_count": 18,
|
||||
"largest_sources": [
|
||||
[
|
||||
186171,
|
||||
"docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json"
|
||||
],
|
||||
[
|
||||
27520,
|
||||
"docs/plans/original-completion-2026-10-02/execution/p08-02/ROOT_REPAIR_INPUT-r04.json"
|
||||
],
|
||||
[
|
||||
20580,
|
||||
"tools/check_p08_migration_evidence.py"
|
||||
],
|
||||
[
|
||||
17336,
|
||||
"tests/fixtures/p08-02/mixed_world.gd"
|
||||
],
|
||||
[
|
||||
16368,
|
||||
"docs/plans/original-completion-2026-10-02/execution/p08-02/implementation.json"
|
||||
]
|
||||
],
|
||||
"diagnosis": "Coordinator supplied root input duplicates already preserved proposal/current history; no acceptance source may be omitted."
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"candidate": "bb34f443c4645a56d7798a142f1e2d8ddf615cae",
|
||||
"actual_writer_pid": 70289,
|
||||
"log_message": "build_facility: 先に階段の一つ下の階に通路を作ってください。",
|
||||
"checks": 30,
|
||||
"failures": 1,
|
||||
"initial_resource_failure_resolved_to_next_phase": true,
|
||||
"import_pid": 70134,
|
||||
"parse_pid": 70255,
|
||||
"all_remaining_cases_not_run": true,
|
||||
"no_original_unit_acceptance": true
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"target_commit": "bb34f443c4645a56d7798a142f1e2d8ddf615cae",
|
||||
"native_exit": 1,
|
||||
"storage_helper_sha256": "a5bdc806a82d3a9b52b93f4124dd86372a2397717597da644f1acfcbfb6b905c",
|
||||
"collector_sha256": "7e86737e6354a4734726ba8d3171ad5f58d6c0380aba5a0fa3c57879d580e579",
|
||||
"snapshots_or_timepoints_omitted": 0,
|
||||
"worker_source_changed": false,
|
||||
"phase_traces": [
|
||||
{
|
||||
"phase": "import",
|
||||
"ended_native_pid": 70134,
|
||||
"storage_only_changes": []
|
||||
},
|
||||
{
|
||||
"phase": "parse",
|
||||
"ended_native_pid": 70255,
|
||||
"storage_only_changes": []
|
||||
}
|
||||
],
|
||||
"original_unit_accepted": false
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"decision": {
|
||||
"action": "run",
|
||||
"task_id": "P08-02",
|
||||
"reason": "実状態はrepair_ready(通常repairは使用済み4回、上限null)。このためrequest-repairは無効で重複にもなり、有効なactionはrunかwaitだけ。オーナーの具体指示が揃っており、待つ理由となる依存・ロック・不足情報もないため、通常のrunを1回実行する。Workerへの具体指示: (1) docs/plans/original-completion-2026-10-02/execution/p08-02/ROOT_REPAIR_INPUT-r04.json(2282B、未コミットの変更あり)を最初に読み、前回までの修正指示として扱う。(2) 既存の階段の下にあるlandingの実前提を作る。登録済みの定義と既存の公開domain入口だけを使い、下の層から順に組み立てる。生産係数の新規追加や状態の直接注入はしない。(3) 近くのsetup前提を局所的に確認する。(4) 失敗時はkind/payload/detailsの内容だけを改善する。判定の緩和や受入条件の縮小はしない。(5) 変更後はtests/fixtures/p08-02のfixtureとmetadataのSHAを同期する。再実装しないもの: 現在の保持資源修正、mixed 3 stages、collector/checker、旧history参照。製品コード・仕様・Runner・価格・原30/88・受入分母は変更しない。allowed_paths内だけで作業し、git書き込み・別worker起動・cap変更はしない。条件: 18 source全件、packet 500k未満、timeout 900。完了後はsubmittedとして返し、未達の原条件と実際のcheck結果を列挙する。native/OS/full regressionはRoot側の担当で、このrunは承認・受入・main統合を意味しない。",
|
||||
"codex_model": "gpt-6.1-sol",
|
||||
"codex_model_reason": "今回の作業は局所的で、手順も具体的に決まっている。対象は既存fixtureのlanding前提を既存の公開domain入口で組み立てることと、失敗診断の改善、SHA同期に限られ、設計判断や広い再実装は対象外。前回までの修正で保持資源・3 stages・checkerは仕上がっているので、Astraに上げても品質の向上は小さく、時間と使用量が増えるだけ。既定のSolで十分と判断した。今回も同じ前提層で失敗が続き、原因の切り分けが難しいと分かった時点でAstraを再検討する。"
|
||||
},
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p08-02/runs/78ae5f8e63bc418f8cfe269620949928"
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"record_type": "preserved_coordinator_input_reference",
|
||||
"original_git_reference": {
|
||||
"commit": "bb34f443c4645a56d7798a142f1e2d8ddf615cae",
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p08-02/ROOT_REPAIR_INPUT-r04.json"
|
||||
},
|
||||
"original_raw_sha256": "d6977dd3e189bbb19d08d79733d70f7db8b25c082b64483ae2781177c91da519",
|
||||
"original_raw_bytes": 27521,
|
||||
"purpose": "Coordinator handoff input, not game acceptance proof. Full original retained in Git candidate already pushed.",
|
||||
"do_not_omit_acceptance_sources": true
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"candidate": "592809bfdaf6a1bbc5ec9cbaeff71a7bb072add9",
|
||||
"elapsed_seconds": 16.212463750038296,
|
||||
"actual_readonly_checker_deficits": [
|
||||
"OS proof: unsafe artifact path",
|
||||
"case off-positive: invalid literal for int() with base 10: 'Vector3i(0'",
|
||||
"case off-refusal: invalid literal for int() with base 10: 'Vector3i(0'",
|
||||
"case on-positive: invalid literal for int() with base 10: 'Vector3i(6'",
|
||||
"case on-refusal: invalid literal for int() with base 10: 'Vector3i(-12'",
|
||||
"final regression: unsafe artifact path"
|
||||
],
|
||||
"full_game_acceptance": false,
|
||||
"original_unit_completed": false,
|
||||
"index": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p08-02-native-mixed-r05/index.json"
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
[
|
||||
{
|
||||
"case": "off-positive",
|
||||
"original_world_revisions": {
|
||||
"world": 144,
|
||||
"occupancy": 144,
|
||||
"walkability": 99,
|
||||
"excavation": 0,
|
||||
"support": 142
|
||||
},
|
||||
"checkpoint_world_revisions": {
|
||||
"world": 150,
|
||||
"occupancy": 150,
|
||||
"walkability": 99,
|
||||
"excavation": 0,
|
||||
"support": 150
|
||||
}
|
||||
},
|
||||
{
|
||||
"case": "on-positive",
|
||||
"original_world_revisions": {
|
||||
"world": 144,
|
||||
"occupancy": 144,
|
||||
"walkability": 99,
|
||||
"excavation": 0,
|
||||
"support": 142
|
||||
},
|
||||
"checkpoint_world_revisions": {
|
||||
"world": 150,
|
||||
"occupancy": 150,
|
||||
"walkability": 99,
|
||||
"excavation": 0,
|
||||
"support": 150
|
||||
}
|
||||
},
|
||||
{
|
||||
"case": "off-refusal",
|
||||
"original_world_revisions": {
|
||||
"world": 144,
|
||||
"occupancy": 144,
|
||||
"walkability": 99,
|
||||
"excavation": 0,
|
||||
"support": 142
|
||||
},
|
||||
"checkpoint_world_revisions": {
|
||||
"world": 144,
|
||||
"occupancy": 144,
|
||||
"walkability": 99,
|
||||
"excavation": 0,
|
||||
"support": 142
|
||||
}
|
||||
},
|
||||
{
|
||||
"case": "on-refusal",
|
||||
"original_world_revisions": {
|
||||
"world": 144,
|
||||
"occupancy": 144,
|
||||
"walkability": 99,
|
||||
"excavation": 0,
|
||||
"support": 142
|
||||
},
|
||||
"checkpoint_world_revisions": {
|
||||
"world": 144,
|
||||
"occupancy": 144,
|
||||
"walkability": 99,
|
||||
"excavation": 0,
|
||||
"support": 142
|
||||
}
|
||||
}
|
||||
]
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"current_checker_candidate": "1545bd42d689a9a6382e2b003ee0550fa197f6e4",
|
||||
"actual_native_data_candidate": "592809bfdaf6a1bbc5ec9cbaeff71a7bb072add9",
|
||||
"scope": "read-only diagnostic preflight against actual retained data; NOT final exact-source-map acceptance",
|
||||
"rows": [
|
||||
{
|
||||
"case": "off-positive",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"case": "off-refusal",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"case": "on-positive",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"case": "on-refusal",
|
||||
"passed": true
|
||||
}
|
||||
]
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"bytes": 492771,
|
||||
"sources": 18,
|
||||
"size_guard_pass": true
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
[
|
||||
{
|
||||
"first_label": "original",
|
||||
"original_requests": [
|
||||
"supply.logistics_request.000000000001",
|
||||
"supply.logistics_request.000000000002"
|
||||
],
|
||||
"active_inventory_ids": [
|
||||
"cargo:staff_gina",
|
||||
"facility.inventory:facility_00000005"
|
||||
],
|
||||
"requests": [
|
||||
{
|
||||
"request_id": "supply.logistics_request.000000000001",
|
||||
"source_inventory_id": "facility.inventory:facility_00000005",
|
||||
"destination_inventory_id": "facility.inventory:facility_00000006",
|
||||
"item_requirements": {
|
||||
"ingredient/raw_food": 4
|
||||
},
|
||||
"priority": 50,
|
||||
"created_at": 728,
|
||||
"deadline": -1,
|
||||
"total_weight": 4,
|
||||
"reserved_stack_ids": [
|
||||
"stack_00000001"
|
||||
],
|
||||
"reservation_ids": [
|
||||
"reservation_00000001"
|
||||
],
|
||||
"assigned_carrier_id": "staff_gina",
|
||||
"state": "DELIVERING",
|
||||
"retry_count": 0,
|
||||
"state_reason": "",
|
||||
"state_changed_at": 735
|
||||
},
|
||||
{
|
||||
"request_id": "supply.logistics_request.000000000002",
|
||||
"source_inventory_id": "facility.inventory:facility_00000005",
|
||||
"destination_inventory_id": "facility.inventory:facility_00000006",
|
||||
"item_requirements": {
|
||||
"ingredient/raw_food": 4
|
||||
},
|
||||
"priority": 50,
|
||||
"created_at": 729,
|
||||
"deadline": -1,
|
||||
"total_weight": 4,
|
||||
"reserved_stack_ids": [
|
||||
"stack_00000001"
|
||||
],
|
||||
"reservation_ids": [
|
||||
"reservation_00000002"
|
||||
],
|
||||
"assigned_carrier_id": "",
|
||||
"state": "RESERVED",
|
||||
"retry_count": 0,
|
||||
"state_reason": "NO_AVAILABLE_PORTER_OR_ROUTE",
|
||||
"state_changed_at": 729
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"candidate": "592809bfdaf6a1bbc5ec9cbaeff71a7bb072add9",
|
||||
"scope": "Root readonly prototype only. Candidate/checker/evidence files unchanged, no formal acceptance.",
|
||||
"basis": "Authoritative SupplyLogisticsBridge.validate_crosslinks lines508-554 resolves facility inventory endpoints by facility ownership and allows unassigned RESERVED request with valid reservation; destination inventory materializes upon delivery.",
|
||||
"remaining_deficits": [
|
||||
"OS proof: unsafe artifact path",
|
||||
"final regression: unsafe artifact path",
|
||||
"unapproved typed migration delta off-positive/systems/runtime/world/revisions/support",
|
||||
"unapproved typed migration delta on-positive/systems/runtime/world/revisions/support"
|
||||
],
|
||||
"elapsed_seconds": 78.40071395831183
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"candidate": "592809bfdaf6a1bbc5ec9cbaeff71a7bb072add9",
|
||||
"actual_complete_packet_bytes": 449115,
|
||||
"source_count": 18,
|
||||
"sources_omitted": 0,
|
||||
"size_guard_pass": true,
|
||||
"acceptance": false
|
||||
}
|
||||
+20997
File diff suppressed because it is too large
Load Diff
+10
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"decision": {
|
||||
"action": "run",
|
||||
"task_id": "P08-02",
|
||||
"reason": "状態はrepair_readyで、実行できるactionはrunかwaitのみ。Rootのread-only checkerによる確定診断は3件。Rootのread-only prototypeは全native出力のうち、残りが支持(support)のwhitelist 2ケースとOS・回帰だけであることを示している。repairの入力も揃っている(ROOT_REPAIR_INPUT-r04.json、12020B)。修正範囲は検証側に限定できるので、通常のrepair runを行う。used5は保持し、上限・分母・仕様は変えない。\n\n【workerへの具体指示】\n0) 最初にdocs/plans/original-completion-2026-10-02/execution/p08-02/ROOT_REPAIR_INPUT-r04.jsonを全文読む。実際のサンプル・counter値・根拠行もここから取る。既存の成果は再実装しない。tools/check_p08_migration_evidence.pyは該当する関数だけ読む。domain契約はscripts内のSupplyLogisticsBridge 508-530行とWorldService 652-670行だけを読み取り専用で確認する。\n1) 型付き値のparse: Vector3i/Vector2i/Vector3/Vector2などのGodot型付き文字列を、構築子名・次元数・各成分の数値で厳密にparseする。eval、ast.literal_eval、括弧を外すだけの処理は使わない。次の不正はすべて検出して欠陥として出す:\n・構築子名の誤り\n・成分数の誤り\n・整数型に非整数(float)が入っている\n・空の成分\n・後ろに余計な文字が続く\n2) 物流の相互参照: facility.inventory:<facility_ID>形式のcanonical endpointは、宛先inventoryが初入庫まで作られない(lazy)場合でも有効とする。ただし、その施設IDがactual stateに実在することが条件。状態ごとに次を確認する:\n・RESERVED: carrierは空で、reservationが要求・荷物・数量と相互に一致する\n・DELIVERING: carrierが実在のactorを指し、そのactorが荷物を持ち、reservationと一致する\n・孤立したendpoint、予約の欠損、状態とcarrierの組合せ矛盾は欠陥とする\n3) 支持(support)のepoch: world.supportのrevisionは独立した+1 counterではなく、WorldServiceのglobal revision epochとして導出する。導出条件は根拠に基づいて厳密に書く。例えば、after側のsupport revisionがafter側のworld revisionと等しく、old側の値以上であり、占有の遷移と整合していること。根拠の実測値はold(world144/occupancy144/support142)とafter(150/150/150)。拒否ケースではworld/occupancy/supportが全部不変であることを要求する。wildcard、基準の緩和、キーを外す対応は禁止。\n4) tools/test_p08_migration_evidence.pyに追加するテスト:\n・現実形式(r04のactualサンプル)の正常例が通る\n・次の局所的な否定例がそれぞれ固有の欠陥で失敗する:不正Vector、孤立endpoint、RESERVEDの予約欠損、DELIVERINGのcarrier不整合、支持epoch矛盾、拒否時のrevision変化\n5) 変更しないもの: fixture、Runtime、native collector、原入力、6条件、30/88、README(試験中も含む)、run_all_tests.py、既存のmarker。\n6) 編集が終わったらsourceを固定し、python -Bでast.parseと局所testsを実行する。metadataを同期する場合は、テスト証拠が及ぶ範囲を明記する。古いSHAを最終版へ黙って付け替えない。既存の版を再利用するなら、input一致の証明と実SHAの保持を必須とする。\n7) statusはsubmittedで返す。未完了の項目を列挙すること:支持whitelist 2ケース、Rootによるwriterのsemantic早期検査、全native最終版1回、通常OS操作(manual path user://saves/manual_ui、private_project 2 fieldの可逆overrideのみ)、最終全回帰、独立Opus review、統合。自分で合格と宣言しない。",
|
||||
"codex_model": "gpt-6-astra",
|
||||
"codex_model_reason": "今回はgpt-6-astraを選ぶ。理由は3つ。\n・意味的な検査を複数同時に厳密に書く必要がある:RESERVED/DELIVERINGの状態別crosslink、lazy endpointの有効性条件、global revision epochからの導出条件。evalを使わない型付きparseも含む。\n・修正はすでに5回目。checkerの判断を誤ると、高コストなAPFS clone全native validateをもう1回無駄にする。\n・失敗を避けるには、基準を緩めずに厳密な条件を書けることが最も重要。\n修正範囲は検証側の2ファイルに限られ、入力もcompactなので、時間とusageの増加は限定的と判断した。"
|
||||
},
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p08-02/runs/c0b5e6a2085c4190a5ed3f6c3d67557c"
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"decision": {
|
||||
"action": "run",
|
||||
"task_id": "P08-02",
|
||||
"reason": "状態はrepair_readyで、上限はnull(used6)。正規validate db010bb7はreject済みで、原因は実旧nativeのsource-map/commit不一致と、OS・final regressionの未実施。これらはRoot側の工程なので、workerの残作業は1545bd42上のmetadataが古い問題の解消だけ。同じworktreeで通常runを行い、成果を保持する。\n\n【範囲】\n変更してよいのは docs/plans/original-completion-2026-10-02/execution/p08-02/ 配下だけ。対象は implementation.json、ROOT_REPAIR_INPUT-r04.json(今回はhandoffへの追記のみ)、CONDITION_MATRIX.md(必要な箇所だけ)。\ntools、tests、fixtures、README、.uid、completion_marker_contracts.json、Runtime、collector はすべて変更禁止。\n\n【手順】\n1) 最初に ROOT_REPAIR_INPUT-r04.json(約2683B)を読み、metadataの正しい出典として扱う。\n2) implementation.json を同期する。\n (a) outputs に残っている旧checkerのSHAを、1545bd42上の tools/check_p08_migration_evidence.py と tools/test_p08_migration_evidence.py の現在のraw SHA-256に置き換える。git show 1545bd42:path の生bytesで再計算すること。\n (b) local_checks の「2 tests 33.523」を、実際のAstra run結果「4 tests 61.547s・source固定PASS」に置き換え、scopeは局所checker testsに限ると明記する。\n (c) current generator ref のSHAを現在の値に更新する。\n (d) 旧inline exampleの全文は削除せず、Git 1545bd42 の raw SHA を参照する形で完全に保持する。そのうえで generator参照へのcompact化は可。\n (e) historical links にある歴史SHAは書き換えない。\n3) ROOT_REPAIR_INPUT-r04.json には次を記録する。\n - 2 toolの新SHA(exact)\n - Astra run と tests 61.547s の scope\n - 旧native 592 で retained 8 PASS(not final と明記)\n - 現在の1545実データでの preflight 4 PASS(not acceptance と明記)\n - Git 1545 に保全済みの全文の input SHA\n - 正規validate db010bb7 のreject理由:source-map/commit不一致と、未OS・未reg。旧SHAの付け替えはしていないことも明記する。\n4) CONDITION_MATRIX.md は、古い counter・crosslink・型の説明がある場合に限り、その説明文だけを同期する。条件、分母、判定は変えない。\n5) 新入力のSourceMapは docs を除外するので、metadata-only の変更では source refs が変わらないことを確認して記載する。\n\n【workerが行う検証】\n- 変更したJSONが全てparseできること\n- 記載したhashが git show の生bytesと一致すること\n- generator refs が実在すること\n- 完全な18 source packetが500k未満であることを実測すること\n\nchecker、native、full regression、Godot はworkerで重複実行しない。gitへの書き込みも禁止。\n\n【提出後(Root)】\nwriter semantic preflight → 最終native全体を1回 → OS収集 → final regression → independent review。受入分母・仕様・予算・main統合は変えない。",
|
||||
"codex_model": "gpt-6.1-sol",
|
||||
"codex_model_reason": "今回はmetadata-onlyのdoc同期で、作業はJSONのSHAと数値の置換、記述の小修正、hashの再計算に限られる。コードの設計や不具合解析は不要。難しい部分(checker修正と実データ検証)はAstraが1545bd42で完了済み。手順が明示的で機械的に検証できるので既定のsolで十分であり、時間と使用量も抑えられる。astraを選ぶ根拠はない。"
|
||||
},
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p08-02/runs/79a52e42ac7940148fcb6c79308b235f"
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"decision": {
|
||||
"action": "wait",
|
||||
"task_id": "P08-02",
|
||||
"reason": "状態はsubmittedのまま変えない。オーナー指示どおり、今回はwaitで計画だけを返す。Rootは592809bfdaf6a1bbc5ec9cbaeff71a7bb072add9のnative ON収集を続ける(OFF側success/refusal writer/reader 4件はPID 76298/76782/77272/77769でPASS済み)。formal validateは、ON・OS proof・最終full regressionがすべて揃ってから行う。\n\nOS手順の計画:\n(0) 事前確認(読取のみ): 実ソースのscenes/main/main.gdとSaveLoadMenuで次を確認し、manifestに行番号と抜粋のハッシュを記録する。読込▾のid1「旧寸法を修正して読み込む」、dimension_migration_dialog、「戻る」/「修正して読込」、manual保存先user://saves/manual。食い違いがあれば推測せずに止めて報告する。\n(1) stage: Controller native_OSのcopy-sourceでstageを作る。stage全体のSHAがcandidateのsource mapと完全一致することを確認し、current_source_pinsとも照合する。observerのP24-02 passive_os_controls_recorder_r05.gdはstageの外に置く。godot --scriptのSceneTreeとして起動してmain.tscnを実instantiateし、project.godot・autoload・製品sceneは変更しない。observerは別Nodeとして_input/_process、save_operation_completed、snapshot_publishedを受動的に記録し、root frameのPNGとModel bytesのハッシュを読むだけにする。UIやRuntimeのメソッドは呼ばない。\n(2) profile: fresh private profileを作り、protected default profileにはinventory before/afterで触れていないことを示す。native caseの旧保存Aの正式4memberを、新private profileのuser://saves/manualへコピーし、full SHAを照合する。別に保全した旧保存BのSHAも記録する。setup=controlled_old_saveと明示し、自然な製品経路とは書かない。\n(3) 起動: Godotは1プロセスだけ。新しいworkerは起動しない。PID、version、開始時刻、終了時刻、exit codeを記録する。\n(4) 入力: 実ウィンドウのPIDを対象に、CGEvent/osascriptでmouseとkeyboardを送る。各送信のtarget PIDとtimestamp_nsを記録し、observerの_input receiptと時刻窓で突き合わせる。操作順は以下で、各前後にscreen、Model hash、formal member hashを実ファイルで取る。\n - preview: 読込▾ → id1 → dialog表示。\n - cancel: 「戻る」。previewとcancelの前後でformal hashが同一であること。\n - confirm: 再度previewしてから「修正して読込」。formal saveとbackupの変化、save_operation_completedとsnapshot_publishedを記録する。\n - changed_source_rejection: 新しいprivate profileでAをpreviewした後、Rootがprivate manualを保全済みのBへ差し替える。差し替えはcontrolled inputとして記録する。OS confirmの直前と直後でBのformal hashが同一であること、main error statusが出て読込が起きない(load由来のsnapshot_publishedなし、Model不変)ことを記録する。\n(5) 後始末と結合: 自分が起動したprocessを片付け、終了後にinventoryを取り直す。manifestにcurrent commit、source map、input_method=native_os、actions順、各receipt/screen/model/formalのパスとSHAを入れ、p08-evidence index.jsonのOS proof欄に結合する。既存のnative receipt形式を再利用し、新しいobserverは作らない。\n\n許容性: observerは診断用helperであり、candidateの製品ソースもallowed_pathsも変更しない。ただしsource overrideにはしない。--scriptはstageの外から読み込み、stage全体のSHAとは別にobserverのSHAをmanifestに記録する。proofのPASSは自己申告のbooleanではなく、実ファイルとPIDで示す。全ケースが同じcandidate SHAで取られていることも確認する。signalで発火させたUIテストはhandlerの証拠としてのみ扱う。仕様、受入分母、予算、mainへの統合は変更せず、提案にも含めない。",
|
||||
"codex_model": "gpt-6.1-sol",
|
||||
"codex_model_reason": "今回はRootでの収集を待つだけで、Codex workerの実行も修理もない。上位モデルが必要になる難しさの根拠がないため、品質・時間・使用量を考えて既定のgpt-6.1-solのままにする。"
|
||||
},
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p08-02/runs/7af9f711058745788b0459e3db4859bf"
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"candidate": "592809bfdaf6a1bbc5ec9cbaeff71a7bb072add9",
|
||||
"manual_path_actual": "user://saves/manual_ui",
|
||||
"source": "scenes/main/main.gd:29",
|
||||
"private_profile": "Reuse approved private_project helper; only two private userdata project settings changed in isolated stage, whole original bytes restored and all other source verified. Record explicit reversible override, not canonical unchanged project setting claim.",
|
||||
"observer": "Root diagnostic outside stage, passive read/capture only; no UI/Runtime mutation."
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"source": "/Users/korizatou114/Projects/\u3068\u308a\u306b\u304f\u5de5\u623f\u30b2\u30fc\u30e0\u5236\u4f5c\u90e8/artifacts/original-completion-2026-10-02/p08-02-native-mixed-r05",
|
||||
"registered_allocation": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p08-02/.runtime/P08-02/build/p08-evidence",
|
||||
"candidate": "592809bfdaf6a1bbc5ec9cbaeff71a7bb072add9",
|
||||
"method": "APFS CoW separate inodes; every manifest file whole SHA/size verified; exact index bytes retained",
|
||||
"files": 2882,
|
||||
"elapsed_seconds": 34.907463249750435,
|
||||
"accepted": false
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"candidate": "592809bfdaf6a1bbc5ec9cbaeff71a7bb072add9",
|
||||
"scope": "Root readonly decoder prototype diagnostic; no candidate code or actual evidence altered; no acceptance",
|
||||
"observations": [
|
||||
{
|
||||
"case": "off-positive",
|
||||
"jobs": [
|
||||
{
|
||||
"id": "construction_00000015",
|
||||
"state": "BUILDING",
|
||||
"progress": 1266
|
||||
},
|
||||
{
|
||||
"id": "construction_00000016",
|
||||
"state": "PLANNED",
|
||||
"progress": 0
|
||||
}
|
||||
],
|
||||
"starting_job": "construction_00000016"
|
||||
},
|
||||
{
|
||||
"case": "off-refusal",
|
||||
"jobs": [
|
||||
{
|
||||
"id": "construction_00000015",
|
||||
"state": "BUILDING",
|
||||
"progress": 1266
|
||||
},
|
||||
{
|
||||
"id": "construction_00000016",
|
||||
"state": "PLANNED",
|
||||
"progress": 0
|
||||
}
|
||||
],
|
||||
"starting_job": "construction_00000016"
|
||||
},
|
||||
{
|
||||
"case": "on-positive",
|
||||
"jobs": [
|
||||
{
|
||||
"id": "construction_00000015",
|
||||
"state": "BUILDING",
|
||||
"progress": 1266
|
||||
},
|
||||
{
|
||||
"id": "construction_00000016",
|
||||
"state": "PLANNED",
|
||||
"progress": 0
|
||||
}
|
||||
],
|
||||
"starting_job": "construction_00000016"
|
||||
},
|
||||
{
|
||||
"case": "on-refusal",
|
||||
"jobs": [
|
||||
{
|
||||
"id": "construction_00000015",
|
||||
"state": "BUILDING",
|
||||
"progress": 1266
|
||||
},
|
||||
{
|
||||
"id": "construction_00000016",
|
||||
"state": "PLANNED",
|
||||
"progress": 0
|
||||
}
|
||||
],
|
||||
"starting_job": "construction_00000016"
|
||||
}
|
||||
],
|
||||
"remaining_deficits": [
|
||||
"OS proof: unsafe artifact path",
|
||||
"case off-positive: orphan cargo endpoint",
|
||||
"case off-refusal: orphan cargo endpoint",
|
||||
"case on-positive: orphan cargo endpoint",
|
||||
"case on-refusal: orphan cargo endpoint",
|
||||
"final regression: unsafe artifact path"
|
||||
],
|
||||
"elapsed_seconds": 16.21374141704291
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
"""Execute unchanged submitted P08 collector with storage-only closed-phase hook."""
|
||||
from pathlib import Path
|
||||
import importlib.util,argparse,json,sys,hashlib
|
||||
sys.dont_write_bytecode=True
|
||||
parser=argparse.ArgumentParser();parser.add_argument('--commit',required=True);parser.add_argument('--output',type=Path,required=True);args=parser.parse_args()
|
||||
root=Path('/Users/korizatou114/Projects/TheTree-managed-worktrees/p08-02/P08-02-implement')
|
||||
coordinator=Path(__file__).resolve().parent
|
||||
|
||||
def load(name,path):
|
||||
s=importlib.util.spec_from_file_location(name,path);m=importlib.util.module_from_spec(s);s.loader.exec_module(m);return m
|
||||
|
||||
driver=load('submitted_p08_collector',root/'tools/run_p08_mixed_migration.py')
|
||||
storage=load('closed_phase_storage',coordinator/'native_storage_guard.py')
|
||||
original_module=driver.module
|
||||
traces=[]
|
||||
|
||||
def module(name,path):
|
||||
m=original_module(name,path)
|
||||
if name=='p08_standard_runner':
|
||||
original=m.execute_phase
|
||||
def execute(*values,**kwargs):
|
||||
row=original(*values,**kwargs)
|
||||
print(json.dumps({'phase':values[3],'passed':row['passed'],'actual_pid':row.get('actual_pid'),'exit':row.get('exit_code'),'error':row.get('validation_error'),'guard_error':row.get('guard_error')}),flush=True)
|
||||
if row['passed']:
|
||||
changes=storage.compress_closed_phase(args.output,row)
|
||||
traces.append({'phase':values[3],'ended_native_pid':row.get('actual_pid'),'storage_only_changes':changes})
|
||||
print(json.dumps({'phase':values[3],'closed_proofs_preserved_and_compressed':len(changes)}),flush=True)
|
||||
return row
|
||||
m.execute_phase=execute
|
||||
return m
|
||||
|
||||
driver.module=module
|
||||
native=argparse.Namespace(project_root=root,output=args.output.resolve(),commit=args.commit,godot='/Applications/Godot.app/Contents/MacOS/Godot',collect=True)
|
||||
code=driver.collect(native)
|
||||
record={'target_commit':args.commit,'native_exit':code,'storage_helper_sha256':hashlib.sha256((coordinator/'native_storage_guard.py').read_bytes()).hexdigest(),'collector_sha256':hashlib.sha256((root/'tools/run_p08_mixed_migration.py').read_bytes()).hexdigest(),'snapshots_or_timepoints_omitted':0,'worker_source_changed':False,'phase_traces':traces,'original_unit_accepted':False}
|
||||
(coordinator/'NATIVE_STORAGE_COLLECTION_TRACE.json').write_text(json.dumps(record,ensure_ascii=False,indent=2)+'\n')
|
||||
raise SystemExit(code)
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
"""Coordinator storage only: transparently compress closed, owned phase proof files.
|
||||
|
||||
No snapshot/timepoint filtering. No changes to worker code or native outputs.
|
||||
Call only after execute_phase returned and actual engine_ended is true.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import hashlib,json,os,subprocess
|
||||
|
||||
def compress_closed_phase(out, phase_row):
|
||||
if phase_row.get('engine_ended') is not True:
|
||||
raise RuntimeError('Owned native child must actually have ended')
|
||||
out=Path(out).resolve()
|
||||
rows=[]
|
||||
candidates=[p for p in sorted((out/'pairs').rglob('*')) if p.is_file() and not p.is_symlink() and p.suffix in ('.var','.json') and p.stat().st_size>=65536 and not p.stat().st_flags&32] if (out/'pairs').exists() else []
|
||||
for offset in range(0,len(candidates),128):
|
||||
probe=subprocess.run(['/usr/sbin/lsof','-t','--',*[str(p) for p in candidates[offset:offset+128]]],capture_output=True,text=True)
|
||||
if probe.stdout.strip(): raise RuntimeError('Owned proof has open handles')
|
||||
for p in candidates:
|
||||
if not p.is_file() or p.is_symlink() or p.suffix not in ('.var','.json'):
|
||||
continue
|
||||
old=p.stat()
|
||||
if old.st_flags & 32 or old.st_size < 65536: continue
|
||||
data_sha=hashlib.sha256(p.read_bytes()).hexdigest()
|
||||
temp=p.with_name(p.name+'.owned-hfs-temporary')
|
||||
if temp.exists(): raise RuntimeError('Storage temporary collision')
|
||||
subprocess.run(['/usr/bin/ditto','--hfsCompression','--noclone',str(p),str(temp)],check=True)
|
||||
now=temp.stat()
|
||||
if hashlib.sha256(temp.read_bytes()).hexdigest()!=data_sha or (now.st_size,now.st_mode,now.st_uid,now.st_gid,now.st_mtime_ns)!=(old.st_size,old.st_mode,old.st_uid,old.st_gid,old.st_mtime_ns):
|
||||
raise RuntimeError('Content/metadata preservation failed')
|
||||
if hashlib.sha256(p.read_bytes()).hexdigest()!=data_sha:
|
||||
raise RuntimeError('Original proof changed before replacement')
|
||||
os.replace(temp,p)
|
||||
if hashlib.sha256(p.read_bytes()).hexdigest()!=data_sha:
|
||||
raise RuntimeError('Post-replacement content mismatch')
|
||||
rows.append({'path':str(p.relative_to(out)),'bytes':old.st_size,'sha256':data_sha,'physical_before':old.st_blocks*512,'physical_after':p.stat().st_blocks*512,'whole_bytes_and_mode_owner_mtime_preserved':True})
|
||||
return rows
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"candidate": "d1c7ce27d21a77be87081e12d20e7c8dd6ffa353",
|
||||
"original_unit_completed": false,
|
||||
"next": "final native with first-writer semantic preflight, normal OS, final regression, independent Opus, integration",
|
||||
"fixed_material_limit": null,
|
||||
"file_count_limit": null,
|
||||
"original_30_88_unchanged": true
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
"""Execute unchanged submitted P08 collector with storage-only closed-phase hook."""
|
||||
from pathlib import Path
|
||||
import importlib.util,argparse,json,sys,hashlib
|
||||
sys.dont_write_bytecode=True
|
||||
parser=argparse.ArgumentParser();parser.add_argument('--commit',required=True);parser.add_argument('--output',type=Path,required=True);args=parser.parse_args()
|
||||
root=Path('/Users/korizatou114/Projects/TheTree-managed-worktrees/p08-02/P08-02-implement')
|
||||
coordinator=Path(__file__).resolve().parent
|
||||
|
||||
def load(name,path):
|
||||
s=importlib.util.spec_from_file_location(name,path);m=importlib.util.module_from_spec(s);s.loader.exec_module(m);return m
|
||||
|
||||
driver=load('submitted_p08_collector',root/'tools/run_p08_mixed_migration.py')
|
||||
storage=load('closed_phase_storage',coordinator/'native_storage_guard.py')
|
||||
original_module=driver.module
|
||||
traces=[]
|
||||
|
||||
def module(name,path):
|
||||
m=original_module(name,path)
|
||||
if name=='p08_standard_runner':
|
||||
original=m.execute_phase
|
||||
def execute(*values,**kwargs):
|
||||
row=original(*values,**kwargs)
|
||||
print(json.dumps({'phase':values[3],'passed':row['passed'],'actual_pid':row.get('actual_pid'),'exit':row.get('exit_code'),'error':row.get('validation_error'),'guard_error':row.get('guard_error')}),flush=True)
|
||||
if row['passed']:
|
||||
if values[3] == 'off-positive-writer':
|
||||
checker=load('final_first_writer_semantic',root/'tools/check_p08_migration_evidence.py')
|
||||
pair=args.output/'pairs/off-positive'
|
||||
identity=checker.decode(checker.load(pair/'identity.json'))
|
||||
before=checker.decode(checker.load(pair/'original.json'))
|
||||
after=checker.decode(checker.load(pair/'writer-checkpoint.json'))
|
||||
checker.semantics(before,identity,initial=True)
|
||||
checker.semantics(after,identity)
|
||||
checker.migration_revisions(before['systems']['runtime'],after['systems']['runtime'],False)
|
||||
checker.semantics(checker.decode(checker.load(pair/'writer-minute-160.json')),identity)
|
||||
print(json.dumps({'early_actual_first_writer_semantics':'PASS','candidate':args.commit,'no_acceptance_criteria_omitted':True}),flush=True)
|
||||
changes=storage.compress_closed_phase(args.output,row)
|
||||
traces.append({'phase':values[3],'ended_native_pid':row.get('actual_pid'),'storage_only_changes':changes})
|
||||
print(json.dumps({'phase':values[3],'closed_proofs_preserved_and_compressed':len(changes)}),flush=True)
|
||||
return row
|
||||
m.execute_phase=execute
|
||||
return m
|
||||
|
||||
driver.module=module
|
||||
native=argparse.Namespace(project_root=root,output=args.output.resolve(),commit=args.commit,godot='/Applications/Godot.app/Contents/MacOS/Godot',collect=True)
|
||||
code=driver.collect(native)
|
||||
record={'target_commit':args.commit,'native_exit':code,'storage_helper_sha256':hashlib.sha256((coordinator/'native_storage_guard.py').read_bytes()).hexdigest(),'collector_sha256':hashlib.sha256((root/'tools/run_p08_mixed_migration.py').read_bytes()).hexdigest(),'snapshots_or_timepoints_omitted':0,'worker_source_changed':False,'phase_traces':traces,'original_unit_accepted':False}
|
||||
(coordinator/'NATIVE_STORAGE_COLLECTION_TRACE.json').write_text(json.dumps(record,ensure_ascii=False,indent=2)+'\n')
|
||||
raise SystemExit(code)
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
"""Coordinator storage only: transparently compress closed, owned phase proof files.
|
||||
|
||||
No snapshot/timepoint filtering. No changes to worker code or native outputs.
|
||||
Call only after execute_phase returned and actual engine_ended is true.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import hashlib,json,os,subprocess
|
||||
|
||||
def compress_closed_phase(out, phase_row):
|
||||
if phase_row.get('engine_ended') is not True:
|
||||
raise RuntimeError('Owned native child must actually have ended')
|
||||
out=Path(out).resolve()
|
||||
rows=[]
|
||||
candidates=[p for p in sorted((out/'pairs').rglob('*')) if p.is_file() and not p.is_symlink() and p.suffix in ('.var','.json') and p.stat().st_size>=65536 and not p.stat().st_flags&32] if (out/'pairs').exists() else []
|
||||
for offset in range(0,len(candidates),128):
|
||||
probe=subprocess.run(['/usr/sbin/lsof','-t','--',*[str(p) for p in candidates[offset:offset+128]]],capture_output=True,text=True)
|
||||
if probe.stdout.strip(): raise RuntimeError('Owned proof has open handles')
|
||||
for p in candidates:
|
||||
if not p.is_file() or p.is_symlink() or p.suffix not in ('.var','.json'):
|
||||
continue
|
||||
old=p.stat()
|
||||
if old.st_flags & 32 or old.st_size < 65536: continue
|
||||
data_sha=hashlib.sha256(p.read_bytes()).hexdigest()
|
||||
temp=p.with_name(p.name+'.owned-hfs-temporary')
|
||||
if temp.exists(): raise RuntimeError('Storage temporary collision')
|
||||
subprocess.run(['/usr/bin/ditto','--hfsCompression','--noclone',str(p),str(temp)],check=True)
|
||||
now=temp.stat()
|
||||
if hashlib.sha256(temp.read_bytes()).hexdigest()!=data_sha or (now.st_size,now.st_mode,now.st_uid,now.st_gid,now.st_mtime_ns)!=(old.st_size,old.st_mode,old.st_uid,old.st_gid,old.st_mtime_ns):
|
||||
raise RuntimeError('Content/metadata preservation failed')
|
||||
if hashlib.sha256(p.read_bytes()).hexdigest()!=data_sha:
|
||||
raise RuntimeError('Original proof changed before replacement')
|
||||
os.replace(temp,p)
|
||||
if hashlib.sha256(p.read_bytes()).hexdigest()!=data_sha:
|
||||
raise RuntimeError('Post-replacement content mismatch')
|
||||
rows.append({'path':str(p.relative_to(out)),'bytes':old.st_size,'sha256':data_sha,'physical_before':old.st_blocks*512,'physical_after':p.stat().st_blocks*512,'whole_bytes_and_mode_owner_mtime_preserved':True})
|
||||
return rows
|
||||
+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()
|
||||
+11
-1
@@ -2,7 +2,8 @@
|
||||
"requested_models": {
|
||||
"commander": "claude-opus-5-5",
|
||||
"reviewer": "claude-opus-5-5",
|
||||
"implementer": "gpt-6.1-sol"
|
||||
"implementer": "gpt-6.1-sol",
|
||||
"checker_repair_by_opus_decision": "gpt-6-astra"
|
||||
},
|
||||
"commands": [
|
||||
{
|
||||
@@ -22,6 +23,15 @@
|
||||
"actual_model": "gpt-6.1-sol",
|
||||
"record_sha256": "dc94188ab040365f0849478568056acccf60b533958fbedb94babfe0d341aeea",
|
||||
"record_path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p08-02/runs/c515ac662e484e8e8c90024b3c73ff53/command.json"
|
||||
},
|
||||
{
|
||||
"id": "7cd74f004a72415fb6f4503792d297b4",
|
||||
"role": "implementer",
|
||||
"status": "finished",
|
||||
"pid": 90408,
|
||||
"actual_model": "gpt-6-astra",
|
||||
"record_sha256": "6b27e49ebd8b48b6ecbecec3093a276a891a08310279048d051d5b227f6e9ecf",
|
||||
"record_path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p08-02/runs/7cd74f004a72415fb6f4503792d297b4/command.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
Opusがrepair_readyにrequest-repairを返したため、実行済み遷移を重複させずに済むよう現行OwnerCLIのcommander出力schemaを実際のtask状態の合法actionへ絞った。Runnerの通常遷移・記録済み判断だけのdispatchを維持し、判断の手動偽造はしない。repair_readyではrun/wait。提出済みではvalidate/wait。実装の重複を防ぐ運用入力改善。
|
||||
Reference in New Issue
Block a user