P08-03: 配置の通常操作受入
This commit is contained in:
@@ -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.
|
||||||
|
|||||||
+102
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-10
@@ -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,
|
||||||
|
|||||||
+6
@@ -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)
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
.................................................
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
Ran 49 tests in 44.792s
|
||||||
|
|
||||||
|
OK
|
||||||
+36
-5
@@ -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),
|
||||||
|
|||||||
Vendored
+74
-3
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user