P08-03: 配置の通常操作受入

This commit is contained in:
Agent Control Runner
2026-10-14 20:45:46 +09:00
parent 7297a4c050
commit fd643f55a2
9 changed files with 138 additions and 9 deletions
@@ -3690,6 +3690,17 @@
"path": "docs/spec/acceptance/P08-03.json", "path": "docs/spec/acceptance/P08-03.json",
"scope": "normal placement; native and OS pending" "scope": "normal placement; native and OS pending"
} }
},
{
"test": "tests/transport/test_runtime_build_preview.gd",
"template": "Runtime build preview: <checks> checks, <failures> failures",
"required": true,
"fixed_check_count_used": false,
"source_sha256_at_schema_audit": "c881b60bce7276047884303a6823da9abfaaf8c1e85dbc5f2c0ff13f0d742449",
"authority": {
"path": "docs/spec/acceptance/P08-03.json",
"scope": "normal placement; native and OS pending"
}
} }
], ],
"unknown_roster_suite_policy": "refuse, explicitly review its source/schema before accepting", "unknown_roster_suite_policy": "refuse, explicitly review its source/schema before accepting",
@@ -500,3 +500,27 @@ A docs/plans/original-completion-2026-10-02/execution/p08-03/runtime-preview-pro
A docs/plans/original-completion-2026-10-02/execution/p08-03/runtime-preview-proposal/runtime_preview.patch A docs/plans/original-completion-2026-10-02/execution/p08-03/runtime-preview-proposal/runtime_preview.patch
``` ```
Exact validation commands and initial/retry results are recorded in implementation.json repair17.validation. No Godot/native/full execution or recollection was performed. Exact validation commands and initial/retry results are recorded in implementation.json repair17.validation. No Godot/native/full execution or recollection was performed.
## Repair18 — submitted, no acceptance
7297a4c050f0293df331c2c51bb32def248bcb6b
['4775001', '127f22f']
dbf5cb8 / native source 12f04d6; target/stairs/construction/pointer passes; 8 unchanged state pairs; Opus pass; final-validation pass (historical support trace, no current applicability claimed)
Every path in diff(M..worktree) must be inside allowed_paths; every path outside allowed_paths must equal its blob at 127f22f. Inherited Runtime is read-only.
runtime_preview.patch was superseded by the integrated helper and was not applied; retained as history.
to be bound by Runner
(1) pending Root native/OS; (2)-(5) remain pending
not claimed
['Root freeze then normal OS and native recollection including changed preview and demolition UI', 'exactly one final all-suite regression on roster derived by Root', 'independent Opus review of all 5 conditions with raw-proof access', 'integration, current final verification and ledger', 'Windows same-distribution/device open; no Mac substitution']
R09 history and pins remain unrewritten. Low findings (empty cell, audio, idempotency, English Runtime messages) remain deferred.
Repair18 local validation: python3 -B tools/test_p08_normal_placement_evidence.py ran 131 tests in 88.476s, exit 1: one existing R09 raw-proof read PermissionError; no weakening. py_compile initially failed on default cache permissions, then passed with PYTHONPYCACHEPREFIX inside allowed fixture scratch. JSON load and git diff --check passed. Scope adapter passed including boundary equality. Exact commands and source SHA256 are in implementation.json repair18. Root/Runner raw-proof access remains required.
@@ -79,7 +79,7 @@
"actual_native_OS": "invalidated for changed demolition/support display; recollect after combined product fixes", "actual_native_OS": "invalidated for changed demolition/support display; recollect after combined product fixes",
"independent_review": "pending", "independent_review": "pending",
"accepted": false, "accepted": false,
"state": "blocked pending Root reconciliation and application of runtime_preview.patch; Main display premise gap recorded" "state": "pending Root native/OS"
}, },
"invalid_position_reason": { "invalid_position_reason": {
"implementation": "prepared", "implementation": "prepared",
@@ -1898,5 +1898,61 @@
"18 focused tests: passed with one Root-positive skip before actual bytes were used", "18 focused tests: passed with one Root-positive skip before actual bytes were used",
"18 focused tests: passed without skips after actual original hashes were used" "18 focused tests: passed without skips after actual original hashes were used"
] ]
},
"repair18": {
"input_M": "7297a4c050f0293df331c2c51bb32def248bcb6b",
"parents": [
"4775001",
"127f22f"
],
"support_trace": "dbf5cb8 / native source 12f04d6; target/stairs/construction/pointer passes; 8 unchanged state pairs; Opus pass; final-validation pass (historical support trace, no current applicability claimed)",
"adapter": "Every path in diff(M..worktree) must be inside allowed_paths; every path outside allowed_paths must equal its blob at 127f22f. Inherited Runtime is read-only.",
"proposal": "runtime_preview.patch was superseded by the integrated helper and was not applied; retained as history.",
"HEAD": "to be bound by Runner",
"conditions": "(1) pending Root native/OS; (2)-(5) remain pending",
"acceptance": "not claimed",
"pending": [
"Root freeze then normal OS and native recollection including changed preview and demolition UI",
"exactly one final all-suite regression on roster derived by Root",
"independent Opus review of all 5 conditions with raw-proof access",
"integration, current final verification and ledger",
"Windows same-distribution/device open; no Mac substitution"
],
"local_source_sha256": {
"scenes/main/main.gd": "92677e4ac57ed8d418a4a716bf0874cbbaea79cff893e5163c82ad1f75e7140a",
"scenes/main/world_preview.gd": "07e9a36baaa5d0904693f968164101a3938248cfd2cb65b600b64d10eca961d8",
"tools/check_p08_normal_placement_evidence.py": "926418042d4ae8c2afba2bd91799f5d32eac62c673ae83c332f3b2d12fcff90d",
"tools/test_p08_normal_placement_evidence.py": "b83f3c7d917d47d53007b25cb31f11011e2184f53ace3423f38a3007762b5675",
"tests/fixtures/p08-03/checker_fixture.py": "a9d376f0af35814b948347383edf11d299f2e78c3347161a49ae60e5a654ec24",
"tests/fixtures/p08-03/placement.gd": "c59f08fd232f73d0e06398cd3577aba8f5a5840d18c61ce5c88db9e7dba51923",
"docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json": "560516247535ee55f3eebe22dec8f3c50ed02ad180b12a8a1f49169543a04562"
},
"validation": [
{
"command": "python3 -B tools/test_p08_normal_placement_evidence.py",
"result": "exit 1: 131 tests in 88.476s; one error in existing R09ImageTests.test_baseline_progressive_and_collection_positive: PermissionError reading /Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p08-03-native-os-r09/index.json; no test weakened or evidence substituted."
},
{
"command": "python3 -m py_compile tools/check_p08_normal_placement_evidence.py tools/test_p08_normal_placement_evidence.py tests/fixtures/p08-03/checker_fixture.py",
"result": "exit 1: PermissionError at default Python cache path"
},
{
"command": "PYTHONPYCACHEPREFIX=tests/fixtures/p08-03/.scratch/repair18-pycache python3 -m py_compile tools/check_p08_normal_placement_evidence.py tools/test_p08_normal_placement_evidence.py tests/fixtures/p08-03/checker_fixture.py",
"result": "exit 0"
},
{
"command": "python3 -c 'import json; from pathlib import Path; base=Path(\"docs/plans/original-completion-2026-10-02/execution\"); json.load((base/\"p08-03/implementation.json\").open()); json.load((base/\"completion_marker_contracts.json\").open()); print(\"JSON load OK\")'",
"result": "exit 0: JSON load OK"
},
{
"command": "git diff --check",
"result": "exit 0"
},
{
"command": "python3 -c 'import subprocess,fnmatch\nallowed=['\\''scenes/main/main.gd'\\'','\\''scenes/main/world_preview.gd'\\'','\\''tests/ui/test_p08_normal_placement_ops.gd'\\'','\\''tests/ui/test_p08_normal_placement_ops.gd.uid'\\'','\\''tests/fixtures/p08-03/**'\\'','\\''tools/run_p08_normal_placement.py'\\'','\\''tools/check_p08_normal_placement_evidence.py'\\'','\\''tools/test_p08_normal_placement_evidence.py'\\'','\\''tools/regression/suite-policy.json'\\'','\\''docs/plans/original-completion-2026-10-02/execution/p08-03/**'\\'','\\''docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json'\\'']\npaths=subprocess.check_output(['\\''git'\\'','\\''diff'\\'','\\''--name-only'\\'','\\''7297a4c050f0293df331c2c51bb32def248bcb6b'\\''],text=True).splitlines()+subprocess.check_output(['\\''git'\\'','\\''ls-files'\\'','\\''--others'\\'','\\''--exclude-standard'\\''],text=True).splitlines()\nbad=[p for p in paths if not any(fnmatch.fnmatchcase(p,a) for a in allowed)]\nprint('\\''Scope checked paths:'\\'',len(paths));print('\\''Outside allowed_paths:'\\'',bad);assert not bad\nboundary=subprocess.check_output(['\\''git'\\'','\\''diff'\\'','\\''--name-only'\\'','\\''127f22f'\\''],text=True).splitlines()\nbad_boundary=[p for p in boundary if not any(fnmatch.fnmatchcase(p,a) for a in allowed)]\nprint('\\''Outside paths differing from boundary:'\\'',bad_boundary);assert not bad_boundary\n'",
"result": "exit 0: no outside allowed_paths; no outside paths differing from 127f22f"
}
],
"context_gap": "R09 original raw-proof read access denied in this environment; Root/Runner must execute with required raw-proof access."
} }
} }
+13 -1
View File
@@ -2124,7 +2124,19 @@ func _preview_failure_message(report: Dictionary) -> String:
var codes_value: Variant = report.get(&"error_codes", []) var codes_value: Variant = report.get(&"error_codes", [])
if codes_value is Array and not codes_value.is_empty(): if codes_value is Array and not codes_value.is_empty():
var code := String(codes_value[0]) var code := String(codes_value[0])
return "配置不可: %s" % PREVIEW_REASON_NAMES.get(code, code) if PREVIEW_REASON_NAMES.has(code):
return "配置不可: %s" % PREVIEW_REASON_NAMES[code]
var code_reasons: Variant = report.get(&"reasons", [])
if code_reasons is Array and not code_reasons.is_empty():
var reason: Variant = code_reasons[0]
for candidate: Variant in code_reasons:
if candidate is Dictionary and String(candidate.get(&"code", "")) == code:
reason = candidate
break
if reason is Dictionary:
var message := String(reason.get(&"message", ""))
if not message.is_empty():
return "配置不可: %s" % message
var reasons_value: Variant = report.get(&"reasons", []) var reasons_value: Variant = report.get(&"reasons", [])
if reasons_value is Array and not reasons_value.is_empty() and reasons_value[0] is Dictionary: if reasons_value is Array and not reasons_value.is_empty() and reasons_value[0] is Dictionary:
return "配置不可: %s" % String(reasons_value[0].get(&"message", "条件を満たしていません")) return "配置不可: %s" % String(reasons_value[0].get(&"message", "条件を満たしていません"))
+6
View File
@@ -202,6 +202,12 @@ func get_build_preview_report(cell: Variant = null) -> Dictionary:
var target: Variant = _hovered_cell if cell == null else cell var target: Variant = _hovered_cell if cell == null else cell
if build_definition == null or not target is Vector3i: if build_definition == null or not target is Vector3i:
return INVALID_PREVIEW.duplicate(true) return INVALID_PREVIEW.duplicate(true)
if _runtime != null and _runtime.has_method(&"preview_build_facility"):
var result: Dictionary = _runtime.call(&"preview_build_facility", build_definition.id, Vector3i(target.x, target.y, layer_mode), build_rotation)
result[&"connection"] = _build_connection_report(
_cells_from_serialized(result.get(&"cells", [])),
Vector3i(target.x, target.y, layer_mode))
return result
var construction: Variant = _service(&"construction_service") var construction: Variant = _service(&"construction_service")
if construction != null and construction.has_method(&"validate_placement"): if construction != null and construction.has_method(&"validate_placement"):
var report: Variant = construction.call( var report: Variant = construction.call(
+1 -1
View File
@@ -240,7 +240,7 @@ def make(base, source, checker):
elif label == 'missing_connection': elif label == 'missing_connection':
row['expected'] = 'STAIR_LANDING_MISSING' row['expected'] = 'STAIR_LANDING_MISSING'
report.update(can_build=False, error_codes=[row['expected']], reasons=[{'code':row['expected'], 'message':'先に階段の一つ下の階に通路を作ってください。', 'cells':[], 'details':{'reason':row['expected']}}]) report.update(can_build=False, error_codes=[row['expected']], reasons=[{'code':row['expected'], 'message':'先に階段の一つ下の階に通路を作ってください。', 'cells':[], 'details':{'reason':row['expected']}}])
row['status'] = report['reasons'][0]['message'] row['status'] = '配置不可: '+report['reasons'][0]['message']
elif label == 'cancel_demolition': elif label == 'cancel_demolition':
row.update(kind='cancel', status='撤去を取り消しました。') row.update(kind='cancel', status='撤去を取り消しました。')
elif label in ('demolition', 'refused_demolition'): elif label in ('demolition', 'refused_demolition'):
+1
View File
@@ -123,6 +123,7 @@ func build(id: StringName, cell: Vector3i, label: String, expected: String = "")
expect(not results.is_empty() and results.back().code == "OK", label + " accepted") expect(not results.is_empty() and results.back().code == "OK", label + " accepted")
elif expected == "STAIR_LANDING_MISSING": elif expected == "STAIR_LANDING_MISSING":
expect(not report.can_build and expected in report.error_codes and results.is_empty(), label + " preview refusal") expect(not report.can_build and expected in report.error_codes and results.is_empty(), label + " preview refusal")
expect(status == "配置不可: " + String(report.reasons[0].message), label + " preview refusal status")
else: else:
expect(expected in report.error_codes and results.is_empty(), label + " preview refusal") expect(expected in report.error_codes and results.is_empty(), label + " preview refusal")
var row := {"id": label, "kind": "build", "definition": String(id), "expected": expected, var row := {"id": label, "kind": "build", "definition": String(id), "expected": expected,
+5 -5
View File
@@ -568,10 +568,13 @@ def action_gate(row, typed, root=HERE.parent):
require(row['report']['error_codes'][0] == code and row['status'] == message, 'reason mismatch') require(row['report']['error_codes'][0] == code and row['status'] == message, 'reason mismatch')
require(not row['report']['can_build'] and not results, 'preview rejection bypassed') require(not row['report']['can_build'] and not results, 'preview rejection bypassed')
preserve(before, after, results) preserve(before, after, results)
elif label == 'missing_connection' and not results: elif label == 'missing_connection':
require(not results, 'preview rejection bypassed')
require(not row['report']['can_build'], 'preview/command disagreement') require(not row['report']['can_build'], 'preview/command disagreement')
require('STAIR_LANDING_MISSING' in row['report']['error_codes'], 'preview reason differs from Runtime reason') require('STAIR_LANDING_MISSING' in row['report']['error_codes'], 'preview reason differs from Runtime reason')
require(row['report']['reasons'] and row['status'] == row['report']['reasons'][0]['message'], 'connection reason mismatch') reason = next((entry for entry in row['report'].get('reasons', []) if isinstance(entry, dict) and entry.get('code') == 'STAIR_LANDING_MISSING'), {})
message = reason.get('message')
require(isinstance(message, str) and bool(message) and row['status'] == '配置不可: '+message and 'STAIR_LANDING_MISSING' not in row['status'], 'connection reason mismatch')
preserve(before, after, []) preserve(before, after, [])
elif kind == 'cancel': elif kind == 'cancel':
require(not results and row['status'] == '撤去を取り消しました。', 'cancel submitted command') require(not results and row['status'] == '撤去を取り消しました。', 'cancel submitted command')
@@ -622,9 +625,6 @@ def action_gate(row, typed, root=HERE.parent):
require(set(resources_a) == set(resources_b) and all(resources_b[k] == v - cost.get(k, 0) for k, v in resources_a.items()), 'build cost mismatch') require(set(resources_a) == set(resources_b) and all(resources_b[k] == v - cost.get(k, 0) for k, v in resources_a.items()), 'build cost mismatch')
if label == 'supported_connected': if label == 'supported_connected':
require(row['report']['connection']['status'] == 'CONNECTED', 'positive connection absent') require(row['report']['connection']['status'] == 'CONNECTED', 'positive connection absent')
if label == 'missing_connection':
require(row['status'] == '操作に失敗しました: '+result['message'], 'connection reason mismatch')
require(not row['report']['can_build'], 'preview/command disagreement')
def check(project_root, evidence_dir): def check(project_root, evidence_dir):
root, evidence = Path(project_root).resolve(), Path(evidence_dir).resolve() root, evidence = Path(project_root).resolve(), Path(evidence_dir).resolve()
+20 -1
View File
@@ -116,9 +116,28 @@ class EvidenceTests(unittest.TestCase):
def test_agreeing_preview_refusal(self): def test_agreeing_preview_refusal(self):
row = self.action_row('missing_connection') row = self.action_row('missing_connection')
self.assertEqual(row['status'], row['report']['reasons'][0]['message']) self.assertEqual(row['status'], '配置不可: '+row['report']['reasons'][0]['message'])
self.action_check(row) self.action_check(row)
def test_connection_status_corruptions(self):
for status in ('先に階段の一つ下の階に通路を作ってください。', '配置不可: STAIR_LANDING_MISSING', '配置不可: wrong message'):
with self.subTest(status=status):
row = self.action_row('missing_connection')
row['status'] = status
self.action_check(row, 'connection reason mismatch')
def test_connection_reason_missing_or_empty(self):
for reasons in ([], [{'code':'OTHER', 'message':'other'}], [{'code':'STAIR_LANDING_MISSING', 'message':''}]):
with self.subTest(reasons=reasons):
row = self.action_row('missing_connection')
row['report']['reasons'] = reasons
self.action_check(row, 'connection reason mismatch')
def test_connection_preview_rejection_bypassed(self):
row = self.action_row('missing_connection')
row['results'] = [{'code':'VALIDATION_FAILED', 'details':{'reason':'STAIR_LANDING_MISSING'}, 'message':row['status']}]
self.action_check(row, 'preview rejection bypassed')
def test_preview_command_disagreement(self): def test_preview_command_disagreement(self):
row = self.action_row('missing_connection') row = self.action_row('missing_connection')
row['report']['can_build'] = True row['report']['can_build'] = True