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

This commit is contained in:
Agent Control Runner
2026-10-14 04:21:06 +09:00
parent c2b59f04f9
commit 8e3d79d5f2
12 changed files with 543 additions and 54 deletions
@@ -11,7 +11,7 @@ This is one existing unit in 30 packages / 88 units, with no partial credit.
| invalid_position_reason | Reception footprint crosses the boundary from an in-bounds origin; existing reception overlap. Exact displayed Japanese reason and report codes; whole typed before/after preservation. | Source and semantic corruption controls only; actual refusals pending. | | invalid_position_reason | Reception footprint crosses the boundary from an in-bounds origin; existing reception overlap. Exact displayed Japanese reason and report codes; whole typed before/after preservation. | Source and semantic corruption controls only; actual refusals pending. |
| demolish_rebuild | Palette demolition toggle, cell selection, confirmation/cancel, Runtime `demolish_facility`, synchronous/queued results. Lower landing refusal, cancellation, reception removal, same-cell reconstruction. Occupancy, operational/construction instances, costs/refund checked. | Implemented; Godot parsing, actual UI, audio ownership, Runtime results pending. | | demolish_rebuild | Palette demolition toggle, cell selection, confirmation/cancel, Runtime `demolish_facility`, synchronous/queued results. Lower landing refusal, cancellation, reception removal, same-cell reconstruction. Occupancy, operational/construction instances, costs/refund checked. | Implemented; Godot parsing, actual UI, audio ownership, Runtime results pending. |
| pan_zoom_world_identity | Move mode + non-Ctrl wheel; normal wheel zoom, 0.5/4.0 clamps; recomputed local/viewport/screen coordinates and actual selected cell. OS recorder requests CUA only. | Mock bridge/checker controls only; actual OS wheel/screenshot provenance pending. | | pan_zoom_world_identity | Move mode + non-Ctrl wheel; normal wheel zoom, 0.5/4.0 clamps; recomputed local/viewport/screen coordinates and actual selected cell. OS recorder requests CUA only. | Mock bridge/checker controls only; actual OS wheel/screenshot provenance pending. |
| separate_pid_resume | Formal four-member save; separate native writer/reader; normal load menu; whole typed/raw checkpoint identity, instance ID, cell and overlap judgement. | Collector/source prepared; real private profiles, actual distinct PIDs and formal saves pending. | | separate_pid_resume | Formal four-member save; separate native writer/reader; normal load menu; whole typed/raw presave/formal identity and source-defined post-operation reconstruction, instance ID, cell and overlap judgement. | R02 diagnosed notification difference; repaired candidate requires fresh native/OS collection. |
## Original-matched diagnostic choices ## Original-matched diagnostic choices
@@ -31,7 +31,7 @@ and a budget report explicitly tagged diagnostic. No production costs,
services, balance values or definitions are changed. New art is unnecessary; services, balance values or definitions are changed. New art is unnecessary;
the demolition button reuses Main's existing styling and audio. the demolition button reuses Main's existing styling and audio.
## r01 fixture repair (current candidate) ## r01 fixture repair (retained earlier submission)
The only failure authorities used are `NATIVE_R01_FAILURE_AND_SCOPE.json` The only failure authorities used are `NATIVE_R01_FAILURE_AND_SCOPE.json`
and `native-r01-writer-stderr.log`. The r01 writer failed 13 expectations; and `native-r01-writer-stderr.log`. The r01 writer failed 13 expectations;
@@ -51,12 +51,55 @@ explicitly recorded. Every observed Runtime save/load result and direct
SaveService diagnostic return is recorded as typed JSON and SHA-pinned raw SaveService diagnostic return is recorded as typed JSON and SHA-pinned raw
Variant, with PID, requested/absolute paths and user-data directory. Variant, with PID, requested/absolute paths and user-data directory.
The 35 local checker tests remain synthetic controls. Their negative cases The earlier 35 local checker tests remain synthetic controls. Their negative cases
also cover missing backup members, slot metadata SHA, failed actual returns, also cover missing backup members, slot metadata SHA, failed actual returns,
and saves outside private profiles. The mock collector supplies a controlled and saves outside private profiles. The mock collector supplies a controlled
disk-space input; the production disk-admission gate is unchanged. disk-space input; the production disk-admission gate is unchanged.
Current native/OS/full regression/review/integration remain pending. Current native/OS/full regression/review/integration remain pending.
## r02 whole-model operation repair (current candidate)
The retained submitted HEAD is `c2b59f04f94dda0a61991ec3bb7f0e946196f995`.
`NATIVE_R02_READER_OPERATION_DELTA.json` and `native-r02-reader-stderr.log`
are supplied historical evidence: reader PID 58430 failed the old whole
postsave/postload equality assertion. Their observed 161/162 counts are
diagnostic observations, never constants in the checker or scenario.
The fixture captures the complete writer model immediately before the normal
save, leaves `writer-checkpoint.var` as the complete postsave snapshot, and
uses `SaveService.inspect_snapshot` to decode the formal world read-only.
Both writer and reader retain inspection returned Dictionaries and complete
typed/raw formal models. The presave and formal models must match in full;
the reader formal model must match the writer formal model in full.
For each role, the expected complete model starts from that formal model.
Only one `source_defined_notification` is constructed using Runtime lines
4680–4720, `StableIdGenerator.next_id`, `SimulationDomainEvent.to_dictionary`
and `DomainEventStream.publish/create_snapshot`. The payload is recomputed
from the actual returned Dictionary, including optional selected/manifest
fields and all timing fields. Event envelope, next ID, counter increment,
sorted published IDs, existing history and the source's recent-event limit
are derived independently. No fields or Runtime effects are removed,
normalized away or copied from the observed notification as an expectation.
The complete typed expectation and raw expected Variant must match the
complete postsave/postload checkpoint. Read-only inspection must also leave
the whole live model unchanged.
Each role exports `*-expected-operation`, `*-operation-actual` and
`*-operation-comparison.json` alongside unchanged-format raw checkpoints,
formal models and returned-result records. On unknown drift, these complete
raw inputs remain and the checker reports the precise typed difference paths.
No new native difference was observed by this worker; fresh native execution
is pending, so the supplied R02 trace is not a PASS for these changed sources.
The current Python suite retains all 35 earlier tests and adds 14 semantic
tests, including every notification payload/envelope field, type drift,
counter/published-ID errors, extra or rewritten history, unrelated/unknown
model drift, read-only formal mismatch and a non-trace-derived count with
the source's history-limit behavior. These are synthetic controls only.
Exact executed source/input SHAs and the current 49-test result are recorded
in `local-validation.json` and `implementation.json`.
## Evidence boundary ## Evidence boundary
`placement.gd` contains the shared finite scenario and handler endpoints. `placement.gd` contains the shared finite scenario and handler endpoints.
@@ -0,0 +1,102 @@
{
"unit": "P08-03",
"submitted": "c2b59f04f94dda0a61991ec3bb7f0e946196f995",
"native_phases": {
"import": {
"pid": 58251,
"passed": true
},
"parse": {
"pid": 58353,
"passed": true
},
"writer": {
"pid": 58388,
"passed": true
},
"reader": {
"pid": 58430,
"passed": false,
"failure": "whole formal resume model",
"failures": 1
}
},
"all_owned_ended": true,
"cleanup_errors": [],
"no_OS_started": true,
"no_full_regression": true,
"whole_typed_writer_reader_diff": [
[
"systems",
"runtime",
"domain_events",
"recent_events",
"161",
"event_type"
],
[
"systems",
"runtime",
"domain_events",
"recent_events",
"161",
"payload"
]
],
"writer_last_event": "SAVE_CHECKPOINT_SUCCEEDED",
"reader_last_event": "SAVE_RECOVERY_LOADED",
"formal_event_count": 161,
"post_operation_event_count": 162,
"formal_to_post_differences": {
"formal_to_post_operation_differences": {
"writer": [
[
"systems",
"runtime",
"id_generator",
"counters",
"domain_event"
],
[
"systems",
"runtime",
"domain_events",
"published_ids"
],
[
"systems",
"runtime",
"domain_events",
"recent_events"
]
],
"reader": [
[
"systems",
"runtime",
"id_generator",
"counters",
"domain_event"
],
[
"systems",
"runtime",
"domain_events",
"published_ids"
],
[
"systems",
"runtime",
"domain_events",
"recent_events"
]
]
}
},
"existing_source_contract": "Runtime._complete_save_operation/_save_event_payload publishes the corresponding operation notification before save_operation_completed signal",
"diagnosis": "Whole post-save vs post-load raw-equality oracle contradicts distinct existing save/load notifications; all other typed fields match. Formal saved state must be compared fully, and exact operation effects must be verified without deleting/masking events. Not yet an acceptance or a product-defect conclusion.",
"required_fix_judgment": "Actual Opus must choose faithful saved-model + complete post-operation delta evidence; preserve all raw states/events/counters, no Runtime edits or criterion relaxation",
"registered_history": "old r01 preserved as historical-native-r01-7dd450c6; original published artifacts unchanged",
"acceptance_credit": 0,
"updated_utc": "2026-10-13T19:05:00.421862+00:00"
}
@@ -1,5 +1,20 @@
# Root collection protocol (not executed by worker) # Root collection protocol (not executed by worker)
R02 repair requires a fresh exact-source collection. The supplied R02 reader
failure is historical evidence, not a PASS for the repaired candidate.
Keep every raw postsave/postload snapshot and returned Dictionary. In each
handler and OS pair the shared fixture now also emits writer-presave,
writer/reader-formal-model, expected-operation, operation-actual and
operation-comparison artifacts. Formal models come from the read-only normal
`SaveService.inspect_snapshot` API. `writer-checkpoint.var` remains the raw
postsave state; never replace it with the presave/formal model.
The checker reconstructs one source-defined notification from each actual
returned Dictionary and compares the complete expected typed/raw model.
Counts come from the saved model, not the historical 161/162 trace. Preserve
all artifacts on an unknown difference and record the checker difference
paths with the corresponding actual/expected files before another repair.
The collector and Runtime/SaveService/UI remain unchanged in this repair.
Read the fixed spec and condition matrix first. Freeze/commit through the Read the fixed spec and condition matrix first. Freeze/commit through the
authorized Root workflow. The worker has not committed or run this procedure. authorized Root workflow. The worker has not committed or run this procedure.
Use a clean checkout at the exact submitted HEAD, an installed native Godot, Use a clean checkout at the exact submitted HEAD, an installed native Godot,
@@ -5,7 +5,7 @@
"P08-03" "P08-03"
], ],
"base_commit": "12430ba89970bda349781e51157e72e48a4ac06f", "base_commit": "12430ba89970bda349781e51157e72e48a4ac06f",
"actual_worktree_HEAD": "7dd450c64c897fc8e9e6bbd9fa242f28f42cb1af", "actual_worktree_HEAD": "c2b59f04f94dda0a61991ec3bb7f0e946196f995",
"worker_committed": false, "worker_committed": false,
"final_commit": null, "final_commit": null,
"final_commit_note": "Root must freeze and record the submitted candidate; worker git writes are prohibited.", "final_commit_note": "Root must freeze and record the submitted candidate; worker git writes are prohibited.",
@@ -18,29 +18,31 @@
}, },
"final_local_files_sha256": { "final_local_files_sha256": {
"docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json": "f59f01f65c795057117fda753706a6200928d4954af901fc965cf1a7c09232b5", "docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json": "f59f01f65c795057117fda753706a6200928d4954af901fc965cf1a7c09232b5",
"docs/plans/original-completion-2026-10-02/execution/p08-03/CONDITION_MATRIX.md": "3cbdc2bd8eccb8cf1f060fb237072dc5b935e8cd147c74884c8c1abb2c810c81", "docs/plans/original-completion-2026-10-02/execution/p08-03/CONDITION_MATRIX.md": "e62103e73c9703f92c9361506aa7f7546a01c95b18eef25f827d249e33fb3a86",
"docs/plans/original-completion-2026-10-02/execution/p08-03/ROOT_COLLECTION.md": "ed1d3978bba78a9ffe68808ec1f1f1990ae180630b5eace9587b8dd1719b43ef", "docs/plans/original-completion-2026-10-02/execution/p08-03/ROOT_COLLECTION.md": "79944e586fab86096de003fb0f6ad99b2af7f5d6c3043668edc0f9b39addc218",
"docs/plans/original-completion-2026-10-02/execution/p08-03/passive_os_placement_recorder.gd": "21855e3b32263eb98127202467b770cb1334fa70c6b357825a122db385ffa8d6", "docs/plans/original-completion-2026-10-02/execution/p08-03/passive_os_placement_recorder.gd": "21855e3b32263eb98127202467b770cb1334fa70c6b357825a122db385ffa8d6",
"docs/plans/original-completion-2026-10-02/execution/p08-03/passive_os_placement_recorder.gd.uid": "d7f97560c8bbdfdaed173b2b48261e65b92847bd0493dd1cc2ebb309d0622314", "docs/plans/original-completion-2026-10-02/execution/p08-03/passive_os_placement_recorder.gd.uid": "d7f97560c8bbdfdaed173b2b48261e65b92847bd0493dd1cc2ebb309d0622314",
"scenes/main/main.gd": "e015e18fee8c114ddce39e46a04be139e694a4e3569855065576c0c909b94fc6", "scenes/main/main.gd": "e015e18fee8c114ddce39e46a04be139e694a4e3569855065576c0c909b94fc6",
"scenes/main/world_preview.gd": "2f0e417ae2ff1745062bdbdcec74b3b058cfcffbbd9001ca3e6a62cd23efb47d", "scenes/main/world_preview.gd": "2f0e417ae2ff1745062bdbdcec74b3b058cfcffbbd9001ca3e6a62cd23efb47d",
"tests/fixtures/p08-03/checker_fixture.py": "c6dd2471f408a790f174e5ab73bbdbd899838f51a0dcde91bd0655da63238c46", "tests/fixtures/p08-03/checker_fixture.py": "0d12e09e5342a016bc9e61c056b07357e761c29f9d82fbf941ce524f51ff131e",
"tests/fixtures/p08-03/passive_placement.gd": "c17a1874fa585ec1ea892b51860f0997176df084b08751ea41e27ea5bc1189e8", "tests/fixtures/p08-03/passive_placement.gd": "c17a1874fa585ec1ea892b51860f0997176df084b08751ea41e27ea5bc1189e8",
"tests/fixtures/p08-03/passive_placement.gd.uid": "f75e3f2d9555c30cee3a1de882f32225fc624565a78a7ef3f5c604325c578ce3", "tests/fixtures/p08-03/passive_placement.gd.uid": "f75e3f2d9555c30cee3a1de882f32225fc624565a78a7ef3f5c604325c578ce3",
"tests/fixtures/p08-03/placement.gd": "84296f340e26daff3e7cff50ae6e1d5ef8f717a39d9dfa9ff1b590059fa00a81", "tests/fixtures/p08-03/placement.gd": "d47b54189be76b9c8e3a6f99c9ecdcbeb75b790cf4fb03d180be9654baa2548f",
"tests/fixtures/p08-03/placement.gd.uid": "5caca08c1e627e0ca07e2ab7b13bd82b24a22f77513fe87e30b79023568e1987", "tests/fixtures/p08-03/placement.gd.uid": "5caca08c1e627e0ca07e2ab7b13bd82b24a22f77513fe87e30b79023568e1987",
"tests/ui/test_p08_normal_placement_ops.gd": "734f4f85d0b0fc45dc31fc23502425b911635667dabc0341ba0d88b0107f4405", "tests/ui/test_p08_normal_placement_ops.gd": "734f4f85d0b0fc45dc31fc23502425b911635667dabc0341ba0d88b0107f4405",
"tests/ui/test_p08_normal_placement_ops.gd.uid": "7d168ac50ca82ae1979da31c467f9a5020ca5ec7616b5bf99294413d85b3b299", "tests/ui/test_p08_normal_placement_ops.gd.uid": "7d168ac50ca82ae1979da31c467f9a5020ca5ec7616b5bf99294413d85b3b299",
"tools/check_p08_normal_placement_evidence.py": "9805c595b95172926f515e5600aaa14e19c83f0e9a2f88406b17656076b0e82c", "tools/check_p08_normal_placement_evidence.py": "5baea283760e04f23abefcf576f9a3e8230b96ed30d82569528dcd211a6032cf",
"tools/regression/suite-policy.json": "e875e6b05bbac8dfb98c2af007221436fa558ddb8806b6214689830295b32e8d", "tools/regression/suite-policy.json": "e875e6b05bbac8dfb98c2af007221436fa558ddb8806b6214689830295b32e8d",
"tools/run_p08_normal_placement.py": "191831b4f684bdd7e99b3be6038c15a6830f544d17c941869b1c2a8fbe4a2aa3", "tools/run_p08_normal_placement.py": "191831b4f684bdd7e99b3be6038c15a6830f544d17c941869b1c2a8fbe4a2aa3",
"tools/test_p08_normal_placement_evidence.py": "2c493349de1d72d1eae383356f8bf3904096c810550f9de373816a25d5ae54c8" "tools/test_p08_normal_placement_evidence.py": "ebf6c0ef2c32dc8fac61d570a7fea99c1ac2499b92399db32afec26147b9d84c",
"docs/plans/original-completion-2026-10-02/execution/p08-03/NATIVE_R02_READER_OPERATION_DELTA.json": "60463e735d4f473bdad7b0925c37b9ca71ee34d6f0eb74ea6c07b508b5153e41",
"docs/plans/original-completion-2026-10-02/execution/p08-03/native-r02-reader-stderr.log": "8079b3dc2ba7b9a9c37fcd9a5a8df0971354027c60def4b95fb15c7310832040"
}, },
"executed_source_manifest": { "executed_source_manifest": {
"path": "local-source-manifest.json", "path": "local-source-manifest.json",
"sha256": "f3b5d4edb152c685bd10efa4ccbdb87fda5e3bdbb26a60c66bc12c1780a41003", "sha256": "25b7492044046b5c8498f968b648392b592d42e266b2ff6fdb4773e52ba3e451",
"source_file_count": 1326, "source_file_count": 1326,
"contains": "Complete local execution source map and immutable/fixture input map; no material-size omission" "contains": "Complete final local execution source and immutable/fixture/evidence input map; no material-size omission"
}, },
"final_input_sha256": { "final_input_sha256": {
"AGENTS.md": "8d8dc40211390275f858f322743309a0663c4f2d859780a8b8a08cea1450e8b3", "AGENTS.md": "8d8dc40211390275f858f322743309a0663c4f2d859780a8b8a08cea1450e8b3",
@@ -50,21 +52,23 @@
"docs/plans/original-completion-2026-10-02/execution/p08-03/passive_os_placement_recorder.gd.uid": "d7f97560c8bbdfdaed173b2b48261e65b92847bd0493dd1cc2ebb309d0622314", "docs/plans/original-completion-2026-10-02/execution/p08-03/passive_os_placement_recorder.gd.uid": "d7f97560c8bbdfdaed173b2b48261e65b92847bd0493dd1cc2ebb309d0622314",
"docs/spec/acceptance/P08-03.json": "37a6a581b5a9bbe03c07a3a266b03df1f740ff488da33ecd9bd60f5b32f1efe1", "docs/spec/acceptance/P08-03.json": "37a6a581b5a9bbe03c07a3a266b03df1f740ff488da33ecd9bd60f5b32f1efe1",
"docs/traceability/original/BASELINE.txt": "75e4bdbdeb379c9269553f0c776bdb9cc96e01f2d6aa5ca650d132c63a8aeb1a", "docs/traceability/original/BASELINE.txt": "75e4bdbdeb379c9269553f0c776bdb9cc96e01f2d6aa5ca650d132c63a8aeb1a",
"tests/fixtures/p08-03/checker_fixture.py": "c6dd2471f408a790f174e5ab73bbdbd899838f51a0dcde91bd0655da63238c46", "tests/fixtures/p08-03/checker_fixture.py": "0d12e09e5342a016bc9e61c056b07357e761c29f9d82fbf941ce524f51ff131e",
"tests/fixtures/p08-03/passive_placement.gd": "c17a1874fa585ec1ea892b51860f0997176df084b08751ea41e27ea5bc1189e8", "tests/fixtures/p08-03/passive_placement.gd": "c17a1874fa585ec1ea892b51860f0997176df084b08751ea41e27ea5bc1189e8",
"tests/fixtures/p08-03/passive_placement.gd.uid": "f75e3f2d9555c30cee3a1de882f32225fc624565a78a7ef3f5c604325c578ce3", "tests/fixtures/p08-03/passive_placement.gd.uid": "f75e3f2d9555c30cee3a1de882f32225fc624565a78a7ef3f5c604325c578ce3",
"tests/fixtures/p08-03/placement.gd": "84296f340e26daff3e7cff50ae6e1d5ef8f717a39d9dfa9ff1b590059fa00a81", "tests/fixtures/p08-03/placement.gd": "d47b54189be76b9c8e3a6f99c9ecdcbeb75b790cf4fb03d180be9654baa2548f",
"tests/fixtures/p08-03/placement.gd.uid": "5caca08c1e627e0ca07e2ab7b13bd82b24a22f77513fe87e30b79023568e1987", "tests/fixtures/p08-03/placement.gd.uid": "5caca08c1e627e0ca07e2ab7b13bd82b24a22f77513fe87e30b79023568e1987",
"docs/plans/original-completion-2026-10-02/execution/p08-03/NATIVE_R02_READER_OPERATION_DELTA.json": "60463e735d4f473bdad7b0925c37b9ca71ee34d6f0eb74ea6c07b508b5153e41",
"docs/plans/original-completion-2026-10-02/execution/p08-03/native-r02-reader-stderr.log": "8079b3dc2ba7b9a9c37fcd9a5a8df0971354027c60def4b95fb15c7310832040",
"docs/plans/original-completion-2026-10-02/execution/p08-03/NATIVE_R01_FAILURE_AND_SCOPE.json": "afc8dada6451e7e61d6d57abb0d7d040f5abcdb93432c8431c1c920dc4984515", "docs/plans/original-completion-2026-10-02/execution/p08-03/NATIVE_R01_FAILURE_AND_SCOPE.json": "afc8dada6451e7e61d6d57abb0d7d040f5abcdb93432c8431c1c920dc4984515",
"docs/plans/original-completion-2026-10-02/execution/p08-03/native-r01-writer-stderr.log": "07fe6cccad7305b2ec88cb91fa79fa22ec9269d7c99d62a1f69971f7c5d22686" "docs/plans/original-completion-2026-10-02/execution/p08-03/native-r01-writer-stderr.log": "07fe6cccad7305b2ec88cb91fa79fa22ec9269d7c99d62a1f69971f7c5d22686"
}, },
"actual_local_validation": { "actual_local_validation": {
"path": "local-validation.json", "path": "local-validation.json",
"sha256": "531616ae3151b7a506978e43b754e1d6a372508515510da65c0fccffbbfff1c0", "sha256": "ce90b9a8dc96b85736555df29d57ebddf3767df7c33043b2e9d39018c38e8729",
"python_syntax": "pass: four final Python sources compiled in memory", "python_syntax": "pass: four final Python sources compiled in memory",
"checker_mock_tests": 35, "checker_mock_tests": 49,
"checker_mock_exit_code": 0, "checker_mock_exit_code": 0,
"log_sha256": "0779ddd3305f3a2c6c719240e036bb1d7f0d10f97ce21854a0f946feefcace93", "log_sha256": "0cb0e9deddcd9d90883a9416ecfed346bf1f4337a51247d08a39d49160313129",
"git_diff_check": "pass" "git_diff_check": "pass"
}, },
"conditions": { "conditions": {
@@ -142,18 +146,23 @@
"P08_01_all30_claimed": false, "P08_01_all30_claimed": false,
"formal_acceptance": false, "formal_acceptance": false,
"repair": { "repair": {
"prior_submitted_commit": "7dd450c64c897fc8e9e6bbd9fa242f28f42cb1af", "prior_submitted_commit": "c2b59f04f94dda0a61991ec3bb7f0e946196f995",
"failure_authorities": [ "failure_authorities": [
"NATIVE_R01_FAILURE_AND_SCOPE.json", "NATIVE_R02_READER_OPERATION_DELTA.json",
"native-r01-writer-stderr.log" "native-r02-reader-stderr.log"
], ],
"changes": [ "changes": [
"fixture toggled signal and idempotent choose", "Complete presave/read-only formal/post-operation models retained",
"private user:// formal and initial saves; SHA exports of members/backup/slot metadata", "Source-defined single notification rebuilt independently from returned Dictionary with exact typed/raw complete-state comparison",
"actual returned Dictionaries and paths captured prospectively" "Historical events and all other fields retained; unknown drift reports exact paths and complete raw artifacts"
], ],
"production_changes": false, "production_changes": false,
"historical_r01_dicts": "absent; not reconstructed or appended", "collector_changes": false,
"worker_git_writes": false "worker_git_writes": false,
"trace_counts_hardcoded": false,
"new_unknown_native_differences_observed": false,
"native_repair_validation": "pending",
"context_gaps": [],
"historical_R02": "Writer passed and reader failed on c2b59f04; no acceptance or changed-source PASS inherited"
} }
} }
@@ -3678,8 +3678,8 @@
"sha256": "8f150654767de0c66329d7d9e84d306c4fa5a65c918e04fb520b626b39f68334" "sha256": "8f150654767de0c66329d7d9e84d306c4fa5a65c918e04fb520b626b39f68334"
}, },
"tests/fixtures/p08-03/checker_fixture.py": { "tests/fixtures/p08-03/checker_fixture.py": {
"bytes": 22311, "bytes": 24737,
"sha256": "c6dd2471f408a790f174e5ab73bbdbd899838f51a0dcde91bd0655da63238c46" "sha256": "0d12e09e5342a016bc9e61c056b07357e761c29f9d82fbf941ce524f51ff131e"
}, },
"tests/fixtures/p08-03/passive_placement.gd": { "tests/fixtures/p08-03/passive_placement.gd": {
"bytes": 8799, "bytes": 8799,
@@ -3690,8 +3690,8 @@
"sha256": "f75e3f2d9555c30cee3a1de882f32225fc624565a78a7ef3f5c604325c578ce3" "sha256": "f75e3f2d9555c30cee3a1de882f32225fc624565a78a7ef3f5c604325c578ce3"
}, },
"tests/fixtures/p08-03/placement.gd": { "tests/fixtures/p08-03/placement.gd": {
"bytes": 17684, "bytes": 21854,
"sha256": "84296f340e26daff3e7cff50ae6e1d5ef8f717a39d9dfa9ff1b590059fa00a81" "sha256": "d47b54189be76b9c8e3a6f99c9ecdcbeb75b790cf4fb03d180be9654baa2548f"
}, },
"tests/fixtures/p08-03/placement.gd.uid": { "tests/fixtures/p08-03/placement.gd.uid": {
"bytes": 20, "bytes": 20,
@@ -5218,8 +5218,8 @@
"sha256": "928353233bf85436a5aaf8c4f77f3f82472d94b8ad2646cb1b030baf92b3b5dc" "sha256": "928353233bf85436a5aaf8c4f77f3f82472d94b8ad2646cb1b030baf92b3b5dc"
}, },
"tools/check_p08_normal_placement_evidence.py": { "tools/check_p08_normal_placement_evidence.py": {
"bytes": 34820, "bytes": 41464,
"sha256": "9805c595b95172926f515e5600aaa14e19c83f0e9a2f88406b17656076b0e82c" "sha256": "5baea283760e04f23abefcf576f9a3e8230b96ed30d82569528dcd211a6032cf"
}, },
"tools/check_p24_selection_evidence.py": { "tools/check_p24_selection_evidence.py": {
"bytes": 14622, "bytes": 14622,
@@ -5298,8 +5298,8 @@
"sha256": "fd9ef7ec63f53dfd5e04e261606c9303d01a2e6590e22a35fef2e35df1429280" "sha256": "fd9ef7ec63f53dfd5e04e261606c9303d01a2e6590e22a35fef2e35df1429280"
}, },
"tools/test_p08_normal_placement_evidence.py": { "tools/test_p08_normal_placement_evidence.py": {
"bytes": 13526, "bytes": 20263,
"sha256": "2c493349de1d72d1eae383356f8bf3904096c810550f9de373816a25d5ae54c8" "sha256": "ebf6c0ef2c32dc8fac61d570a7fea99c1ac2499b92399db32afec26147b9d84c"
}, },
"tools/verify_exported_opening.py": { "tools/verify_exported_opening.py": {
"bytes": 1519, "bytes": 1519,
@@ -5314,11 +5314,13 @@
"docs/plans/original-completion-2026-10-02/execution/p08-03/passive_os_placement_recorder.gd.uid": "d7f97560c8bbdfdaed173b2b48261e65b92847bd0493dd1cc2ebb309d0622314", "docs/plans/original-completion-2026-10-02/execution/p08-03/passive_os_placement_recorder.gd.uid": "d7f97560c8bbdfdaed173b2b48261e65b92847bd0493dd1cc2ebb309d0622314",
"docs/spec/acceptance/P08-03.json": "37a6a581b5a9bbe03c07a3a266b03df1f740ff488da33ecd9bd60f5b32f1efe1", "docs/spec/acceptance/P08-03.json": "37a6a581b5a9bbe03c07a3a266b03df1f740ff488da33ecd9bd60f5b32f1efe1",
"docs/traceability/original/BASELINE.txt": "75e4bdbdeb379c9269553f0c776bdb9cc96e01f2d6aa5ca650d132c63a8aeb1a", "docs/traceability/original/BASELINE.txt": "75e4bdbdeb379c9269553f0c776bdb9cc96e01f2d6aa5ca650d132c63a8aeb1a",
"tests/fixtures/p08-03/checker_fixture.py": "c6dd2471f408a790f174e5ab73bbdbd899838f51a0dcde91bd0655da63238c46", "tests/fixtures/p08-03/checker_fixture.py": "0d12e09e5342a016bc9e61c056b07357e761c29f9d82fbf941ce524f51ff131e",
"tests/fixtures/p08-03/passive_placement.gd": "c17a1874fa585ec1ea892b51860f0997176df084b08751ea41e27ea5bc1189e8", "tests/fixtures/p08-03/passive_placement.gd": "c17a1874fa585ec1ea892b51860f0997176df084b08751ea41e27ea5bc1189e8",
"tests/fixtures/p08-03/passive_placement.gd.uid": "f75e3f2d9555c30cee3a1de882f32225fc624565a78a7ef3f5c604325c578ce3", "tests/fixtures/p08-03/passive_placement.gd.uid": "f75e3f2d9555c30cee3a1de882f32225fc624565a78a7ef3f5c604325c578ce3",
"tests/fixtures/p08-03/placement.gd": "84296f340e26daff3e7cff50ae6e1d5ef8f717a39d9dfa9ff1b590059fa00a81", "tests/fixtures/p08-03/placement.gd": "d47b54189be76b9c8e3a6f99c9ecdcbeb75b790cf4fb03d180be9654baa2548f",
"tests/fixtures/p08-03/placement.gd.uid": "5caca08c1e627e0ca07e2ab7b13bd82b24a22f77513fe87e30b79023568e1987", "tests/fixtures/p08-03/placement.gd.uid": "5caca08c1e627e0ca07e2ab7b13bd82b24a22f77513fe87e30b79023568e1987",
"docs/plans/original-completion-2026-10-02/execution/p08-03/NATIVE_R02_READER_OPERATION_DELTA.json": "60463e735d4f473bdad7b0925c37b9ca71ee34d6f0eb74ea6c07b508b5153e41",
"docs/plans/original-completion-2026-10-02/execution/p08-03/native-r02-reader-stderr.log": "8079b3dc2ba7b9a9c37fcd9a5a8df0971354027c60def4b95fb15c7310832040",
"docs/plans/original-completion-2026-10-02/execution/p08-03/NATIVE_R01_FAILURE_AND_SCOPE.json": "afc8dada6451e7e61d6d57abb0d7d040f5abcdb93432c8431c1c920dc4984515", "docs/plans/original-completion-2026-10-02/execution/p08-03/NATIVE_R01_FAILURE_AND_SCOPE.json": "afc8dada6451e7e61d6d57abb0d7d040f5abcdb93432c8431c1c920dc4984515",
"docs/plans/original-completion-2026-10-02/execution/p08-03/native-r01-writer-stderr.log": "07fe6cccad7305b2ec88cb91fa79fa22ec9269d7c99d62a1f69971f7c5d22686" "docs/plans/original-completion-2026-10-02/execution/p08-03/native-r01-writer-stderr.log": "07fe6cccad7305b2ec88cb91fa79fa22ec9269d7c99d62a1f69971f7c5d22686"
} }
@@ -1,14 +1,14 @@
{ {
"task_id": "P08-03", "task_id": "P08-03",
"started_at": "2026-10-13T18:51:54.296622+00:00", "started_at": "2026-10-13T19:19:51.362993+00:00",
"ended_at": "2026-10-13T18:52:13.521432+00:00", "ended_at": "2026-10-13T19:20:39.117228+00:00",
"syntax": { "syntax": {
"method": "compile(source, filename, exec); no pyc", "method": "compile(source, filename, exec); no pyc",
"files": { "files": {
"tools/run_p08_normal_placement.py": "191831b4f684bdd7e99b3be6038c15a6830f544d17c941869b1c2a8fbe4a2aa3", "tools/run_p08_normal_placement.py": "191831b4f684bdd7e99b3be6038c15a6830f544d17c941869b1c2a8fbe4a2aa3",
"tools/check_p08_normal_placement_evidence.py": "9805c595b95172926f515e5600aaa14e19c83f0e9a2f88406b17656076b0e82c", "tools/check_p08_normal_placement_evidence.py": "5baea283760e04f23abefcf576f9a3e8230b96ed30d82569528dcd211a6032cf",
"tools/test_p08_normal_placement_evidence.py": "2c493349de1d72d1eae383356f8bf3904096c810550f9de373816a25d5ae54c8", "tools/test_p08_normal_placement_evidence.py": "ebf6c0ef2c32dc8fac61d570a7fea99c1ac2499b92399db32afec26147b9d84c",
"tests/fixtures/p08-03/checker_fixture.py": "c6dd2471f408a790f174e5ab73bbdbd899838f51a0dcde91bd0655da63238c46" "tests/fixtures/p08-03/checker_fixture.py": "0d12e09e5342a016bc9e61c056b07357e761c29f9d82fbf941ce524f51ff131e"
}, },
"passed": true "passed": true
}, },
@@ -19,14 +19,17 @@
"tools/test_p08_normal_placement_evidence.py" "tools/test_p08_normal_placement_evidence.py"
], ],
"exit_code": 0, "exit_code": 0,
"log": "repair-local-checker-tests.log", "log": "repair-r02-local-checker-tests.log",
"log_sha256": "0779ddd3305f3a2c6c719240e036bb1d7f0d10f97ce21854a0f946feefcace93", "log_sha256": "0cb0e9deddcd9d90883a9416ecfed346bf1f4337a51247d08a39d49160313129",
"synthetic_only": true, "synthetic_only": true,
"test_count": 35 "test_count": 49,
"retained_previous_tests": 35,
"new_semantic_tests": 14
}, },
"local_source_manifest": "local-source-manifest.json", "local_source_manifest": "local-source-manifest.json",
"local_source_manifest_sha256": "f3b5d4edb152c685bd10efa4ccbdb87fda5e3bdbb26a60c66bc12c1780a41003", "local_source_manifest_sha256": "25b7492044046b5c8498f968b648392b592d42e266b2ff6fdb4773e52ba3e451",
"source_and_inputs_unchanged_during_tests": true, "source_and_inputs_unchanged_during_tests": true,
"git_diff_check": "pass",
"native_engines_started": 0, "native_engines_started": 0,
"OS_operations": 0, "OS_operations": 0,
"full_regression_executed": false, "full_regression_executed": false,
@@ -0,0 +1,6 @@
ERROR: P08 placement: whole formal resume model
at: push_error (core/variant/variant_utility.cpp:1023)
GDScript backtrace (most recent call first):
[0] expect (res://tests/fixtures/p08-03/placement.gd:36)
[1] reader (res://tests/fixtures/p08-03/placement.gd:249)
[2] frames (res://tests/fixtures/p08-03/placement.gd:68)
@@ -0,0 +1,5 @@
.................................................
----------------------------------------------------------------------
Ran 49 tests in 44.792s
OK
+36 -5
View File
@@ -117,10 +117,17 @@ def make(base, source, checker):
folder.mkdir() folder.mkdir()
runtime = {k: {} for k in sorted(checker.RUNTIME_KEYS)} runtime = {k: {} for k in sorted(checker.RUNTIME_KEYS)}
runtime.update(world={'occupancies': [], 'bounds':{'min_x':-10,'max_y':10}}, construction={'facilities': [], 'jobs': []}, runtime.update(world={'occupancies': [], 'bounds':{'min_x':-10,'max_y':10}}, construction={'facilities': [], 'jobs': []},
id_generator={'version':1,'counters':{'domain_event':7}},
domain_events={'version':1,'published_ids':[],'recent_events':[]},
facilities={'instances': []}, next_command_id=1, facilities={'instances': []}, next_command_id=1,
command_queue={'processed': [], 'pending': [], 'in_flight': [], 'accepted': [], 'idempotency': {}}) command_queue={'processed': [], 'pending': [], 'in_flight': [], 'accepted': [], 'idempotency': {}})
model = {'schema_version': 1, 'clock': {'absolute_minute': 360, 'paused': True}, model = {'schema_version': 1, 'clock': {'absolute_minute': 360, 'paused': True},
'game_state': {'resources': {'gold': 5000, 'materials': 50}}, 'systems': {'runtime': runtime}} 'game_state': {'resources': {'gold': 5000, 'materials': 50}}, 'systems': {'runtime': runtime}}
runtime['domain_events']['published_ids'] = ['domain_event.%012d' % n for n in range(1,8)]
runtime['domain_events']['recent_events'] = [
{'event_id':'domain_event.%012d' % n, 'event_type':'SYNTHETIC_HISTORY',
'absolute_minute':360, 'source_service':'synthetic', 'entity_ids':[],
'payload':{'ordinal':n}, 'causation_command_id':0} for n in range(1,8)]
target = [-8, 0, 0] target = [-8, 0, 0]
cost = {'reception': {'gold': 500, 'materials': 8}, 'guest_stairs': {'gold': 40, 'materials': 0}, 'standard_room':{'gold':350,'materials':5}} cost = {'reception': {'gold': 500, 'materials': 8}, 'guest_stairs': {'gold': 40, 'materials': 0}, 'standard_room':{'gold':350,'materials':5}}
rooms = 0 rooms = 0
@@ -236,8 +243,10 @@ def make(base, source, checker):
budget = {'setup': 'diagnostic', 'costs': [{'definition': d, 'cost': cost[d]} for d in ('reception', 'reception', 'guest_stairs', 'guest_stairs')], budget = {'setup': 'diagnostic', 'costs': [{'definition': d, 'cost': cost[d]} for d in ('reception', 'reception', 'guest_stairs', 'guest_stairs')],
'needed': {'gold': 1080, 'materials': 16}, 'initial': {'gold': 5000, 'materials': 50}, 'deltas': {}} 'needed': {'gold': 1080, 'materials': 16}, 'initial': {'gold': 5000, 'materials': 50}, 'deltas': {}}
put(folder/'writer-actions.json', {'setup': 'diagnostic', 'actions': actions, 'budget': budget}) put(folder/'writer-actions.json', {'setup': 'diagnostic', 'actions': actions, 'budget': budget})
snap('writer-checkpoint') snap('writer-presave')
snap('reader-checkpoint') snap('writer-formal-model')
snap('reader-formal-model')
formal_model = typed(model)
formal = folder/'formal' formal = folder/'formal'
formal.mkdir() formal.mkdir()
for member in checker.MEMBERS: (formal/member).write_text('synthetic '+member+'\n') for member in checker.MEMBERS: (formal/member).write_text('synthetic '+member+'\n')
@@ -251,14 +260,36 @@ def make(base, source, checker):
'members':{n:{'sha256':sha(folder/name/n),'bytes':(folder/name/n).stat().st_size} for n in checker.MEMBERS}} 'members':{n:{'sha256':sha(folder/name/n),'bytes':(folder/name/n).stat().st_size} for n in checker.MEMBERS}}
for name,suffix in [('formal',''),('formal.bak','.bak')]}, for name,suffix in [('formal',''),('formal.bak','.bak')]},
'slot_metadata':{'file':'formal-slot-metadata.json','sha256':sha(formal/'manifest.json')}}) 'slot_metadata':{'file':'formal-slot-metadata.json','sha256':sha(formal/'manifest.json')}})
for role,pid,op in [('writer',pids[0],'save'),('reader',pids[1],'load')]: for role,pid,op in [('writer',pids[0],'save'),('reader',pids[1],'recovery')]:
returned = {'ok':True,'operation':op,'save_path':'user://saves/manual_ui',
'code':'OK','message':'synthetic result','duration_us':13,'runtime_duration_us':17,
'manifest':{'save_id':'synthetic-save','absolute_minute':360}}
if role == 'reader':
returned['selected'] = {'id':'formal','kind':'formal','generation':0}
expected = checker.expected_operation(formal_model,returned,
'SAVE_CHECKPOINT_SUCCEEDED' if role == 'writer' else 'SAVE_RECOVERY_LOADED')
for label in ('checkpoint','expected-operation','operation-actual'):
put(folder/(role+'-'+label+'.json'),expected)
(folder/(role+'-'+label+'.var')).write_bytes(json.dumps(expected,sort_keys=True).encode())
count = len(model['systems']['runtime']['domain_events']['recent_events'])
put(folder/(role+'-operation-comparison.json'), {'pid':pid,'delta_kind':'source_defined_notification',
'formal_model':role+'-formal-model','actual_model':role+'-checkpoint',
'expected_model':role+'-expected-operation','returned_result':typed(returned),
'saved_recent_count':count,'expected_recent_count':count+1,'actual_recent_count':count+1,
'whole_raw_equal':True})
stem = role+'-save-result-0001' stem = role+'-save-result-0001'
(folder/(stem+'.var')).write_bytes(b'synthetic raw Dictionary stand-in') (folder/(stem+'.var')).write_bytes(b'synthetic raw Dictionary stand-in')
put(folder/(stem+'.json'), {'pid':pid,'operation':'runtime_operation', put(folder/(stem+'.json'), {'pid':pid,'operation':'runtime_operation',
'requested_path':'user://saves/manual_ui','absolute_path':absolute,'user_data_dir':profile, 'requested_path':'user://saves/manual_ui','absolute_path':absolute,'user_data_dir':profile,
'result':typed({'ok':True,'operation':op,'save_path':'user://saves/manual_ui'}), 'result':typed(returned),
'raw_sha256':sha(folder/(stem+'.var'))}) 'raw_sha256':sha(folder/(stem+'.var'))})
put(folder/(role+'-save-results.json'), {'pid':pid,'records':[stem]}) inspect_stem = role+'-save-result-0002'
(folder/(inspect_stem+'.var')).write_bytes(b'synthetic inspection Dictionary stand-in')
put(folder/(inspect_stem+'.json'), {'pid':pid,'operation':'formal_inspect',
'requested_path':'user://saves/manual_ui','absolute_path':absolute,'user_data_dir':profile,
'result':typed({'ok':True,'read_only':True,'snapshot':copy.deepcopy(model)}),
'raw_sha256':sha(folder/(inspect_stem+'.var'))})
put(folder/(role+'-save-results.json'), {'pid':pid,'records':[stem,inspect_stem]})
put(folder/'save.json', {'members': {n: sha(formal/n) for n in checker.MEMBERS}, put(folder/'save.json', {'members': {n: sha(formal/n) for n in checker.MEMBERS},
'pid': pids[0], 'cell': target, 'instance_id': 'f4'}) 'pid': pids[0], 'cell': target, 'instance_id': 'f4'})
row = {'id': 'resume_overlap', 'kind': 'build', 'position': position(target), row = {'id': 'resume_overlap', 'kind': 'build', 'position': position(target),
+74 -3
View File
@@ -20,6 +20,7 @@ var input_method := "handler"
var observed_cell: Variant = null var observed_cell: Variant = null
var save_serial := 0 var save_serial := 0
var save_records: Array = [] var save_records: Array = []
var operation_results: Array[Dictionary] = []
const INITIAL_SAVE := "user://saves/p08_initial" const INITIAL_SAVE := "user://saves/p08_initial"
func annotate(row: Dictionary) -> void: func annotate(row: Dictionary) -> void:
@@ -43,8 +44,10 @@ func json_file(name: String, data: Variant) -> void:
file.close() file.close()
func capture(label: String) -> String: func capture(label: String) -> String:
return capture_model(label, runtime.create_snapshot())
func capture_model(label: String, snapshot: Dictionary) -> String:
var name := role + "-" + label var name := role + "-" + label
var snapshot: Dictionary = runtime.create_snapshot()
json_file(name + ".json", Codec.new().typed(snapshot)) json_file(name + ".json", Codec.new().typed(snapshot))
var file := FileAccess.open(directory.path_join(name + ".var"), FileAccess.WRITE) var file := FileAccess.open(directory.path_join(name + ".var"), FileAccess.WRITE)
if file != null: if file != null:
@@ -226,10 +229,15 @@ func writer() -> void:
await demolish(target, "demolition") await demolish(target, "demolition")
await build(&"reception", target, "rebuild") await build(&"reception", target, "rebuild")
await camera() await camera()
capture("presave")
await button(main.find_child("ManualSaveButton", true, false)) await button(main.find_child("ManualSaveButton", true, false))
expect(main.build_status_label.text == "手動セーブを保存しました。", "normal save") expect(main.build_status_label.text == "手動セーブを保存しました。", "normal save")
capture("checkpoint") capture("checkpoint")
await archive_formal() await archive_formal()
var formal := inspect_formal()
var presave: Dictionary = bytes_to_var(FileAccess.get_file_as_bytes(directory.path_join("writer-presave.var")))
expect(var_to_bytes(presave) == var_to_bytes(formal), "whole presave formal model")
verify_operation(formal, operation_results.back(), &"SAVE_CHECKPOINT_SUCCEEDED")
var members := {} var members := {}
for member: String in MEMBERS: for member: String in MEMBERS:
members[member] = FileAccess.get_sha256(directory.path_join("formal/" + member)) members[member] = FileAccess.get_sha256(directory.path_join("formal/" + member))
@@ -245,8 +253,10 @@ func reader(distinct: bool) -> void:
await frames() await frames()
expect(main.build_status_label.text == "手動セーブを読み込みました。", "normal resume") expect(main.build_status_label.text == "手動セーブを読み込みました。", "normal resume")
capture("checkpoint") capture("checkpoint")
var original := FileAccess.get_file_as_bytes(directory.path_join("writer-checkpoint.var")) var formal := inspect_formal()
expect(original == var_to_bytes(runtime.create_snapshot()), "whole formal resume model") var original := FileAccess.get_file_as_bytes(directory.path_join("writer-formal-model.var"))
expect(original == var_to_bytes(formal), "whole formal resume model")
verify_operation(formal, operation_results.back(), &"SAVE_RECOVERY_LOADED")
expect(String(runtime.world_service.get_occupant(target)) == saved.instance_id, "same resumed instance/cell") expect(String(runtime.world_service.get_occupant(target)) == saved.instance_id, "same resumed instance/cell")
for member: String in MEMBERS: for member: String in MEMBERS:
expect(FileAccess.get_sha256(directory.path_join("formal/" + member)) == saved.members[member], "reader preserves formal member " + member) expect(FileAccess.get_sha256(directory.path_join("formal/" + member)) == saved.members[member], "reader preserves formal member " + member)
@@ -325,7 +335,68 @@ func save_initial_fixture() -> void:
func archive_formal() -> void: func archive_formal() -> void:
export_package(String(main.manual_save_path), "formal") export_package(String(main.manual_save_path), "formal")
func inspect_formal() -> Dictionary:
var stored := SaveService.inspect_snapshot(String(main.manual_save_path))
record_save_result("formal_inspect", String(main.manual_save_path), stored)
expect(bool(stored.get(&"ok", false)), "read-only formal inspection")
var formal: Dictionary = stored.get(&"snapshot", {})
capture_model("formal-model", formal)
return formal
func verify_operation(formal: Dictionary, result: Dictionary, event_type: StringName) -> void:
var expected := formal.duplicate(true)
var model: Dictionary = expected[&"systems"][&"runtime"]
var counters: Dictionary = model[&"id_generator"]["counters"]
var next: int = int(counters.get("domain_event", 0)) + 1
counters["domain_event"] = next
var event_id := "domain_event.%012d" % next
var payload := {
&"ok": bool(result.get(&"ok", false)),
&"code": StringName(result.get(&"code", &"UNKNOWN")),
&"message": str(result.get(&"message", "")),
&"operation": StringName(result.get(&"operation", &"unknown")),
&"save_path": str(result.get(&"save_path", result.get(&"path", ""))),
&"reason": StringName(result.get(&"reason", &"")),
&"request_id": int(result.get(&"request_id", 0)),
&"checkpoint_absolute_minute": int(result.get(&"checkpoint_absolute_minute", formal[&"clock"][&"absolute_minute"])),
&"duration_us": int(result.get(&"duration_us", 0)),
&"runtime_duration_us": int(result.get(&"runtime_duration_us", 0)),
}
if result.get(&"selected") is Dictionary:
var selected: Dictionary = result[&"selected"]
payload[&"selected_candidate_id"] = StringName(selected.get(&"id", &""))
payload[&"selected_candidate_kind"] = StringName(selected.get(&"kind", &""))
payload[&"selected_generation"] = int(selected.get(&"generation", -1))
if result.get(&"manifest") is Dictionary:
var manifest: Dictionary = result[&"manifest"]
payload[&"save_id"] = str(manifest.get("save_id", ""))
payload[&"saved_absolute_minute"] = int(manifest.get("absolute_minute", -1))
var stream: Dictionary = model[&"domain_events"]
stream["published_ids"].append(event_id)
stream["published_ids"].sort()
var notification := SimulationDomainEvent.new(StringName(event_id), event_type,
int(formal[&"clock"][&"absolute_minute"]), &"save_service", [], payload, 0)
stream["recent_events"].append(notification.to_dictionary())
if stream["recent_events"].size() > DomainEventStream.RECENT_EVENT_LIMIT:
stream["recent_events"].pop_front()
capture_model("expected-operation", expected)
capture("operation-actual")
var actual: Dictionary = bytes_to_var(FileAccess.get_file_as_bytes(directory.path_join(role + "-checkpoint.var")))
json_file(role + "-operation-comparison.json", {
"pid": OS.get_process_id(), "delta_kind": "source_defined_notification",
"formal_model": role + "-formal-model", "actual_model": role + "-checkpoint",
"expected_model": role + "-expected-operation", "returned_result": Codec.new().typed(result),
"saved_recent_count": formal[&"systems"][&"runtime"][&"domain_events"]["recent_events"].size(),
"expected_recent_count": stream["recent_events"].size(),
"actual_recent_count": actual[&"systems"][&"runtime"][&"domain_events"]["recent_events"].size(),
"whole_raw_equal": var_to_bytes(expected) == var_to_bytes(actual),
})
# Complete raw actual/expected snapshots remain available on unknown drift.
expect(var_to_bytes(expected) == var_to_bytes(actual), "whole source-defined save operation model; see " + role + "-operation-comparison.json")
expect(var_to_bytes(actual) == var_to_bytes(runtime.create_snapshot()), "read-only inspection preserves live model")
func record_save_operation(result: Dictionary) -> void: func record_save_operation(result: Dictionary) -> void:
operation_results.append(result.duplicate(true))
record_save_result("runtime_operation", String(main.manual_save_path), result) record_save_result("runtime_operation", String(main.manual_save_path), result)
func record_save_result(operation: String, path: String, result: Dictionary) -> void: func record_save_result(operation: String, path: String, result: Dictionary) -> void:
+95 -2
View File
@@ -12,6 +12,7 @@ import sys
import struct import struct
import zlib import zlib
import subprocess import subprocess
import copy
sys.dont_write_bytecode = True sys.dont_write_bytecode = True
HERE = Path(__file__).resolve().parent HERE = Path(__file__).resolve().parent
@@ -53,6 +54,68 @@ def load(path): return json.loads(Path(path).read_bytes())
def require(ok, message): def require(ok, message):
if not ok: raise ValueError(message) if not ok: raise ValueError(message)
def scalar(value, kind=None):
kind = kind or ('bool' if isinstance(value, bool) else 'int' if isinstance(value, int) else 'String')
return {'type':kind, 'text':('&' if kind == 'StringName' else '')+json.dumps(value, ensure_ascii=False)}
def dictionary(entries, key_type='String'):
return {'type':'Dictionary', 'entries':[[scalar(k, key_type), v] for k,v in entries.items()]}
def typed_member(node, *path):
for key in path:
matches = [v for k,v in node['entries'] if decode(k) == key]
require(len(matches) == 1, 'missing/duplicate typed model member '+repr(path))
node = matches[0]
return node
def expected_operation(formal, result, event_type):
"""Rebuild the full typed state from source rules, never from the actual delta.
Runtime 4680-4720, StableIdGenerator.next_id, SimulationDomainEvent.to_dictionary
and DomainEventStream.publish/create_snapshot define this single notification.
"""
saved = decode(formal)
minute = saved['clock']['absolute_minute']
values = {'ok':bool(result.get('ok',False)), 'code':str(result.get('code','UNKNOWN')),
'message':str(result.get('message','')), 'operation':str(result.get('operation','unknown')),
'save_path':str(result.get('save_path',result.get('path',''))), 'reason':str(result.get('reason','')),
'request_id':int(result.get('request_id',0)),
'checkpoint_absolute_minute':int(result.get('checkpoint_absolute_minute',minute)),
'duration_us':int(result.get('duration_us',0)), 'runtime_duration_us':int(result.get('runtime_duration_us',0))}
names = {'code','operation','reason','selected_candidate_id','selected_candidate_kind'}
if isinstance(result.get('selected'),dict):
selected = result['selected']
values.update(selected_candidate_id=str(selected.get('id','')),
selected_candidate_kind=str(selected.get('kind','')), selected_generation=int(selected.get('generation',-1)))
if isinstance(result.get('manifest'),dict):
manifest = result['manifest']
values.update(save_id=str(manifest.get('save_id','')), saved_absolute_minute=int(manifest.get('absolute_minute',-1)))
expected = copy.deepcopy(formal)
runtime = typed_member(expected,'systems','runtime')
counters = typed_member(runtime,'id_generator','counters')
count = decode(counters).get('domain_event',0)+1
require(0 < count <= 999999999999, 'invalid domain event counter')
for key,value in counters['entries']:
if decode(key) == 'domain_event':
value.update(scalar(count)); break
else:
counters['entries'].append([scalar('domain_event'),scalar(count)])
event_id = 'domain_event.%012d' % count
stream = typed_member(runtime,'domain_events')
published = typed_member(stream,'published_ids')
require(event_id not in decode(published), 'duplicate operation notification')
published['items'].append(scalar(event_id))
published['items'].sort(key=decode)
payload = dictionary({k:scalar(v,'StringName' if k in names else None) for k,v in values.items()},'StringName')
event = dictionary({'event_id':scalar(event_id), 'event_type':scalar(event_type),
'absolute_minute':scalar(minute), 'source_service':scalar('save_service'),
'entity_ids':{'type':'Array','element_type':4,'element_class':'','items':[]},
'payload':payload, 'causation_command_id':scalar(0)})
recent = typed_member(stream,'recent_events')
recent['items'].append(event)
recent['items'] = recent['items'][-256:]
return expected
def png(data): def png(data):
require(data.startswith(b'\x89PNG\r\n\x1a\n'), 'missing OS screenshot') require(data.startswith(b'\x89PNG\r\n\x1a\n'), 'missing OS screenshot')
offset = 8; chunks = []; payload = b'' offset = 8; chunks = []; payload = b''
@@ -312,6 +375,8 @@ def check(project_root, evidence_dir):
artifact(directory+'/'+name+'.var') artifact(directory+'/'+name+'.var')
return document(directory+'/'+name+'.json') return document(directory+'/'+name+'.json')
rows = [] rows = []
normal_results = {}
inspections = {}
for role, pid in zip(('writer', 'reader'), expected_pids): for role, pid in zip(('writer', 'reader'), expected_pids):
process = document(directory+'/'+role+'-process.json') process = document(directory+'/'+role+'-process.json')
require(process['pid'] == pid and process['failures'] == 0 and process['checks'] > 0, 'process attribution') require(process['pid'] == pid and process['failures'] == 0 and process['checks'] > 0, 'process attribution')
@@ -329,6 +394,10 @@ def check(project_root, evidence_dir):
require(isinstance(result, dict) and result.get('ok') is True, 'actual save/load failed') require(isinstance(result, dict) and result.get('ok') is True, 'actual save/load failed')
if record['operation'] == 'runtime_operation': if record['operation'] == 'runtime_operation':
operations.append(result.get('operation')) operations.append(result.get('operation'))
normal_results[role] = result
if record['operation'] == 'formal_inspect':
require(result.get('read_only') is True and 'snapshot' in result, 'missing read-only formal model')
inspections[role] = typed_member(record['result'],'snapshot')
require(('save' in operations) if role == 'writer' else any(op in ('load','recovery','recovery_candidate') for op in operations), 'missing actual normal save/load result') require(('save' in operations) if role == 'writer' else any(op in ('load','recovery','recovery_candidate') for op in operations), 'missing actual normal save/load result')
doc = document(directory+'/'+role+'-actions.json') doc = document(directory+'/'+role+'-actions.json')
require(doc['setup'] == 'diagnostic', 'unlabelled diagnostic') require(doc['setup'] == 'diagnostic', 'unlabelled diagnostic')
@@ -368,9 +437,33 @@ def check(project_root, evidence_dir):
require(len(matches) == 1 and {tuple(c) for c in matches[0]['occupied_cells']} == cells, 'production footprint mismatch') require(len(matches) == 1 and {tuple(c) for c in matches[0]['occupied_cells']} == cells, 'production footprint mismatch')
rows += doc['actions'] rows += doc['actions']
checkpoint = typed('writer-checkpoint') checkpoint = typed('writer-checkpoint')
require(checkpoint == typed('reader-checkpoint'), 'wrong resume model')
state(checkpoint) state(checkpoint)
require(sha(artifact(directory+'/writer-checkpoint.var')) == sha(artifact(directory+'/reader-checkpoint.var')), 'wrong raw resume') formal = typed('writer-formal-model')
require(not typed_differences(typed('writer-presave'),formal), 'wrong presave formal model')
require(sha(artifact(directory+'/writer-presave.var')) == sha(artifact(directory+'/writer-formal-model.var')), 'wrong raw presave formal model')
require(not typed_differences(formal,typed('reader-formal-model')), 'wrong formal resume model')
require(sha(artifact(directory+'/writer-formal-model.var')) == sha(artifact(directory+'/reader-formal-model.var')), 'wrong raw formal resume model')
for role in ('writer','reader'):
require(role in inspections and not typed_differences(formal,inspections[role]), 'wrong read-only formal model')
result = normal_results[role]
require(result['operation'] == ('save' if role == 'writer' else 'recovery'), 'wrong operation notification kind')
event_type = 'SAVE_CHECKPOINT_SUCCEEDED' if role == 'writer' else 'SAVE_RECOVERY_LOADED'
expected = expected_operation(formal,result,event_type)
differences = typed_differences(expected,typed(role+'-checkpoint'))
require(not differences, 'wrong resume model: source-defined operation drift '+role+' '+repr(differences))
require(not typed_differences(expected,typed(role+'-expected-operation')), 'wrong reconstructed operation model')
require(sha(artifact(directory+'/'+role+'-expected-operation.var')) == sha(artifact(directory+'/'+role+'-checkpoint.var')), 'wrong raw operation model')
require(not typed_differences(typed(role+'-checkpoint'),typed(role+'-operation-actual')), 'inspection changed live model')
require(sha(artifact(directory+'/'+role+'-operation-actual.var')) == sha(artifact(directory+'/'+role+'-checkpoint.var')), 'wrong raw inspection preservation')
comparison = document(directory+'/'+role+'-operation-comparison.json')
require(comparison == {'pid':expected_pids[0 if role == 'writer' else 1],
'delta_kind':'source_defined_notification','formal_model':role+'-formal-model',
'actual_model':role+'-checkpoint','expected_model':role+'-expected-operation',
'returned_result':comparison['returned_result'],
'saved_recent_count':len(decode(formal)['systems']['runtime']['domain_events']['recent_events']),
'expected_recent_count':len(decode(expected)['systems']['runtime']['domain_events']['recent_events']),
'actual_recent_count':len(decode(typed(role+'-checkpoint'))['systems']['runtime']['domain_events']['recent_events']),
'whole_raw_equal':True} and decode(comparison['returned_result']) == result, 'wrong operation comparison attribution')
saved = document(directory+'/save.json') saved = document(directory+'/save.json')
require(set(saved['members']) == set(MEMBERS) and saved['pid'] == expected_pids[0], 'missing formal members/writer') require(set(saved['members']) == set(MEMBERS) and saved['pid'] == expected_pids[0], 'missing formal members/writer')
for member in MEMBERS: for member in MEMBERS:
+109
View File
@@ -135,6 +135,115 @@ class EvidenceTests(unittest.TestCase):
self.edit('pair/reader-save-result-0001.json', lambda d: d.update(result=F.typed({'ok':True,'operation':'load'}), requested_path='/outside/formal')) self.edit('pair/reader-save-result-0001.json', lambda d: d.update(result=F.typed({'ok':True,'operation':'load'}), requested_path='/outside/formal'))
self.reject('save outside private user profile') self.reject('save outside private user profile')
def test_notification_payload_is_recomputed_from_returned_dict(self):
path = 'pair/reader-checkpoint.json'
original = (self.out/path).read_bytes()
fields = ('ok','code','message','operation','save_path','reason','request_id',
'checkpoint_absolute_minute','duration_us','runtime_duration_us',
'selected_candidate_id','selected_candidate_kind','selected_generation',
'save_id','saved_absolute_minute')
for field in fields:
with self.subTest(field=field):
data = json.loads(original)
event = C.typed_member(data,'systems','runtime','domain_events','recent_events')['items'][-1]
value = C.typed_member(event,'payload',field)
decoded = C.decode(value)
changed = not decoded if isinstance(decoded,bool) else decoded+1 if isinstance(decoded,int) else decoded+'-drift'
value.update(C.scalar(changed,value['type']))
F.put(self.out/path,data); F.repin(self.out)
self.reject('source-defined operation drift')
(self.out/path).write_bytes(original); F.repin(self.out)
def test_notification_envelope_cannot_drift(self):
path = 'pair/writer-checkpoint.json'
original = (self.out/path).read_bytes()
for field in ('event_id','event_type','source_service','absolute_minute','causation_command_id','entity_ids'):
with self.subTest(field=field):
data = json.loads(original)
event = C.typed_member(data,'systems','runtime','domain_events','recent_events')['items'][-1]
value = C.typed_member(event,field)
if field == 'entity_ids': value['items'].append(C.scalar('unexpected-entity'))
else:
old = C.decode(value)
value.update(C.scalar(old+1 if isinstance(old,int) else old+'-drift',value['type']))
F.put(self.out/path,data); F.repin(self.out)
self.reject('source-defined operation drift')
(self.out/path).write_bytes(original); F.repin(self.out)
def test_notification_counter_must_increment_exactly_once(self):
def corrupt(data):
C.typed_member(data,'systems','runtime','id_generator','counters','domain_event')['text'] = '9'
self.edit('pair/reader-checkpoint.json',corrupt)
self.reject('source-defined operation drift')
def test_notification_published_ids_cannot_be_omitted(self):
self.edit('pair/writer-checkpoint.json',lambda d: C.typed_member(d,'systems','runtime','domain_events','published_ids')['items'].clear())
self.reject('source-defined operation drift')
def test_extra_notification_is_rejected(self):
def corrupt(data):
events = C.typed_member(data,'systems','runtime','domain_events','recent_events')['items']
events.append(copy.deepcopy(events[-1]))
self.edit('pair/reader-checkpoint.json',corrupt)
self.reject('source-defined operation drift')
def test_existing_event_history_cannot_be_rewritten(self):
def corrupt(data):
event = C.typed_member(data,'systems','runtime','domain_events','recent_events')['items'][0]
C.typed_member(event,'event_type').update(text='"REWRITTEN_HISTORY"')
self.edit('pair/reader-checkpoint.json',corrupt)
self.reject('source-defined operation drift')
def test_postsave_unrelated_resource_drift_is_rejected(self):
self.edit('pair/writer-checkpoint.json',lambda d: C.typed_member(d,'game_state','resources','gold').update(text='9999'))
self.reject('source-defined operation drift')
def test_postload_unknown_field_drift_is_rejected(self):
self.edit('pair/reader-checkpoint.json',lambda d: C.typed_member(d,'systems','runtime','inventory')['entries'].append([C.scalar('unknown'),C.scalar(1)]))
self.reject('source-defined operation drift')
def test_presave_must_match_read_only_formal_model(self):
self.edit('pair/writer-presave.json',lambda d: C.typed_member(d,'clock','absolute_minute').update(text='361'))
self.reject('wrong presave formal model')
def test_read_only_inspection_must_match_complete_formal_model(self):
self.edit('pair/reader-save-result-0002.json',lambda d: C.typed_member(d['result'],'snapshot','clock','absolute_minute').update(text='361'))
self.reject('wrong read-only formal model')
def test_inspection_cannot_lose_read_only_attribution(self):
self.edit('pair/writer-save-result-0002.json',lambda d: C.typed_member(d['result'],'read_only').update(text='false'))
self.reject('missing read-only formal model')
def test_returned_result_payload_tampering_is_rejected(self):
self.edit('pair/reader-save-result-0001.json',lambda d: C.typed_member(d['result'],'duration_us').update(text='999'))
self.reject('source-defined operation drift')
def test_notification_payload_type_drift_is_rejected(self):
def corrupt(data):
event = C.typed_member(data,'systems','runtime','domain_events','recent_events')['items'][-1]
C.typed_member(event,'payload','operation')['type'] = 'String'
C.typed_member(event,'payload','operation')['text'] = '"recovery"'
self.edit('pair/reader-checkpoint.json',corrupt)
self.reject('source-defined operation drift')
def test_operation_count_is_source_derived_with_recent_limit(self):
model = C.decode(json.loads((self.out/'pair/writer-formal-model.json').read_text()))
runtime = model['systems']['runtime']
runtime['id_generator']['counters']['domain_event'] = 900
stream = runtime['domain_events']
stream['published_ids'] = ['domain_event.%012d' % n for n in range(1,901)]
stream['recent_events'] = [{'ordinal':n} for n in range(645,901)]
expected = C.decode(C.expected_operation(F.typed(model),{'ok':True},'SAVE_CHECKPOINT_SUCCEEDED'))
new = expected['systems']['runtime']
self.assertEqual(901,new['id_generator']['counters']['domain_event'])
self.assertEqual(901,len(new['domain_events']['published_ids']))
self.assertEqual(256,len(new['domain_events']['recent_events']))
self.assertEqual(stream['recent_events'][1:],new['domain_events']['recent_events'][:-1])
self.assertEqual('domain_event.000000000901',new['domain_events']['recent_events'][-1]['event_id'])
del runtime['id_generator']['counters']
with self.assertRaisesRegex(ValueError,'missing/duplicate typed model member'):
C.expected_operation(F.typed(model),{'ok':True},'SAVE_CHECKPOINT_SUCCEEDED')
def test_missing_formal_member(self): def test_missing_formal_member(self):
for name in ('formal','formal.bak'): for name in ('formal','formal.bak'):
with self.subTest(package=name): with self.subTest(package=name):