P08-03: 配置の通常操作受入
This commit is contained in:
@@ -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
@@ -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", "条件を満たしていません"))
|
||||||
|
|||||||
@@ -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
@@ -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'):
|
||||||
|
|||||||
Vendored
+1
@@ -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,
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user