Compare commits
20
Commits
@@ -0,0 +1,26 @@
|
||||
# Current checker protocol — repair29
|
||||
|
||||
Worker **SOURCE-READY / NOTRUN**. [CURRENT_STATUS.md](CURRENT_STATUS.md) is canonical for execution facts; creation-time NOT RUN in REPAIR22–28 and runbooks is historical. [ORDER_AUDIT.md](ORDER_AUDIT.md) and order-audit.json give the eleven-phase/seven-domain audit. [D05_REMAINING_ORDER_CANDIDATE.md](D05_REMAINING_ORDER_CANDIDATE.md) is approval material only.
|
||||
|
||||
B = `/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build`.
|
||||
The checker remains a61 (`a61c5b496e656f60c66898e42500bc9a2d45f947690ce8f5f19a03984ebbc850`), with its registry unchanged. Fifteen conditions + EV, seven domains, three safety outcomes and30/88 denominator remain fixed. Original credit13/88 unchanged; P04 remains incomplete.
|
||||
|
||||
| Evidence | Root-measured current fact |
|
||||
| --- | --- |
|
||||
| B/p04-s4-native-r28 | 4/4 PASS; PIDs49940,50044,50078,50105 all ended; guards0; cleanup0. |
|
||||
| B/audit-support/p04-root-fb8b-s4-r01 | S4 controls and native receipts PASS. |
|
||||
| B/audit-support/p04-root-fb8b-local-r01 | Local8 PASS and registered validation fb8b PASS28.095s. |
|
||||
| B/audit-support/p04-raw-collect-r22 | Current collection, preserving raw and exact relocation/series SHA256 bindings. |
|
||||
| B/p04-evidence/index.json | Schema2; missing_conditions=[]; all15 conditions + EV mechanically verified. |
|
||||
|
||||
Root-measured native product5f6bc11d/main9b1a18c1. Current product is B/audit-support/p04-r17-full-inputs-r01/product, not deb11300. Static current hashes and handler/function lines, plus exact proof pins, are in REPAIR29_STATIC_SOURCE.json. The index preserves source/config/input/environment applicability and native ownership joins; mechanical PASS does not grant D05 approval or original completion.
|
||||
|
||||
C NATIVE_RUNS/native_ownership/verify_native remains unchanged. C shift proof joins actual owned start/end PIDs, serviceID, original priceSnapshot, service.payment key and receipt entries; no fixed PID or ledger number. S1 typed raw joins pre-travel ACTIVE reservation/task/staff/cell to boarded trip; JSON summaries do not replace typed evidence.
|
||||
|
||||
Only S4 supplies isolated power_stop. It preserves native ownership, full product1338 plus sole QA override, Writer/Reader packages, exact Runtime typed restoration and replay, actual NO_POWER, operating dependency, powered/ACTIVE kitchen, positive global mana and open operator. Raw graph/revision, source-derived hold, intervening held minutes, zero stopped charge, original identity and one recovery sale are joined without canonicalization or dropped fields. S4 causes NO_POWER by directly calling disconnect_nodes; it is not global-depletion proof. Global depletion appears only in B1 combined diagnostic; B1 has RELATED_FACILITY_INACTIVE and supplies neither isolated power_stop nor priority approval.
|
||||
|
||||
The low authority finding remains explicit: destruction_stop is bound to BASELINE:183, a mana-depletion line, not destruction authority. An explicit D05 destruction rule is required; pin/registry correction only after approval through normal repair. Observation and mechanical verification do not resolve this gap. A01 midnight ownership upkeep, dependency hold and18:00 are approved and are not re-asked. Other mandatory priorities remain pending under D05(a–d).
|
||||
|
||||
Full178 used checker f5 (`f5b44827958f2ae352fe5c4bc3a1ffd4211ec7a41fc4c0759c8b954891662324`),21615 checks; exact receipt preserved at B/audit-support/p18-regression-support-final-r01/native-full178/runner-ended.json (runner-input.json selected_source_map preserves all1339 selected inputs; source-start.json records initial product sources). **No full1339 run against a61 exists.** Suites/parse not executing Python support checker-independent product applicability only. Static full178 selected-map comparison found one test-source difference (tests/business/test_required_kitchen.gd: full3a9c92f8…, current5dcc86d6…); no game-source difference. Runner must preserve that test applicability binding; no blanket1338 identity is claimed. Do not relabel the old full proof as a61 validation.
|
||||
|
||||
Remaining Runner work: owner D05 decisions after review, any subsequent normal repair, support53cb normal registered rebind, final full run and final applicability binding, formal independent review, integration P04 first then support on main9b1a18c1, and individually applicable nativeWindows obligations. Missing original authority or unknown mandatory rules remain gaps regardless of mechanical PASS. No probe or test is requested or run by this docs-only repair.
|
||||
@@ -0,0 +1,31 @@
|
||||
# P04-01 current status — repair29
|
||||
|
||||
Worker checks: SOURCE-READY / NOTRUN. No probes or tests were run by this worker.
|
||||
|
||||
This canonical table overrides creation-time NOT RUN / pending-execution entries in REPAIR22–28, their status/validation records, and the runbooks (including S4_ROOT_RUN.md). Those files are historical and remain untouched. It does not relabel an old run as execution of repair29.
|
||||
|
||||
`B` = `/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build`.
|
||||
|
||||
| Item | Canonical status | Exact-version evidence |
|
||||
| --- | --- | --- |
|
||||
| Current native product / main | Root-measured `5f6bc11d` / `9b1a18c1` | Product1338 at B/audit-support/p04-r17-full-inputs-r01/product; current SHA256 inventory in REPAIR29_STATIC_SOURCE.json. Old deb11300 is not current. |
|
||||
| S4 r28 | Root-measured 4/4 PASS; import/parse/Writer/Reader PIDs49940,50044,50078,50105 all ended; guards0; cleanup0 | B/p04-s4-native-r28; B/audit-support/p04-root-fb8b-s4-r01/native.result.json. Full controls in that same support directory. |
|
||||
| Local8 | Root-measured PASS | B/audit-support/p04-root-fb8b-local-r01: bulk, negative, applicability, native, series, encoder, join, roles result/stdio records. |
|
||||
| Collection | Root-measured r22 collected, no native re-run claimed | B/audit-support/p04-raw-collect-r22/MANIFEST.json and s1/s2a/s2b/s3/s4.json. |
|
||||
| Index | Root-measured schema2, missing_conditions=[], all15 conditions + EV mechanically verified | B/p04-evidence/index.json and generation-invocation.json; local-r01 index/checker output. This is not D05 approval. |
|
||||
| Registered validation on fb8b7919 | Root-measured PASS,28.095s | B/audit-support/p04-root-fb8b-local-r01/registered-validation.result.json and stdout log. Does not establish final repair29 validation or acceptance. |
|
||||
| Full178 | Root-measured178 suites /21615 checks PASS used checker f5, **not a61** | B/audit-support/p18-regression-support-final-r01/native-full178/runner-ended.json and source-start.json; exact receipt remains preserved. Checker f5 SHA256 `f5b44827958f2ae352fe5c4bc3a1ffd4211ec7a41fc4c0759c8b954891662324`. |
|
||||
| Full1339 against a61 | **No such full run exists** | a61 SHA256 `a61c5b496e656f60c66898e42500bc9a2d45f947690ce8f5f19a03984ebbc850`. Local8 and registered validation are separate evidence. |
|
||||
| Worker repair29 | SOURCE-READY / NOTRUN | Static source/line/registration/SHA256 inspection only. No tests, probes, Git, engine, Runner or agents. |
|
||||
| Owner D05 | Pending a–d, candidate only | D05_REMAINING_ORDER_CANDIDATE.md/json; A01, dependency hold and18:00 remain approved. |
|
||||
| Original unit | **Incomplete;13/88 unchanged** | Mechanical PASS does not approve unknown mandatory priorities, replace independent review, or integrate the unit. |
|
||||
|
||||
Proof hashes for the exact collection/index/full receipt/validation and current source are in [REPAIR29_STATIC_SOURCE.json](REPAIR29_STATIC_SOURCE.json). Applicable source/config/input/environment joins stay in the immutable schema2 index and raw collection; no raw is normalized, spliced, regenerated or relabelled here.
|
||||
|
||||
Product reuse requires exact applicability: the178 suites do not execute the Python checker and the316-file GDScript parse roster excludes it. Static comparison with the preserved full178 selected_source_map found one test-source difference, tests/business/test_required_kitchen.gd (full178 3a9c92f8…, current product5dcc86d6…), and no game-source difference. Preserve the Runner applicability binding for this test; do not claim all1338 selected product hashes match that full178 receipt or byte-identical full1339 validation against a61. Preserve the f5 full178 receipt and all historical partial/full outputs separately.
|
||||
|
||||
Remaining Runner steps: independent review of these documents, then Root obtains owner decisions (or explicit non-mandatory rulings) for D05 a–d. Unknown mandatory rules stay pending. Any destruction authority pin/registry or other approved implementation change follows a normal repair. The support53cb normal registered rebind, final full run on the final source/checker, final source/config/input/environment binding, formal independent review and integration remain Runner work. Integration order is **P04 first, then support on main9b (`9b1a18c1`)**. NativeWindows obligations remain individually pending if required. No criterion is waived and no extra original unit is counted.
|
||||
|
||||
Known authority/scope gaps: destruction_stop binds BASELINE:183 (mana depletion, not destruction); explicit D05 destruction rule required before normal pin repair. S4 NO_POWER was induced by direct disconnect_nodes; global depletion appears only in B1 combined diagnostic. Preserve these distinctions even though the mechanical index passes.
|
||||
|
||||
Changed files in repair29 (all under this directory): ORDER_AUDIT.md; order-audit.json; D05_REMAINING_ORDER_CANDIDATE.md; D05_REMAINING_ORDER_CANDIDATE.json; CHECKER_PROTOCOL.md; EVIDENCE_COLLECTION.md; P04_ORDER_EVIDENCE_PROTOCOL.json; CURRENT_STATUS.md; REPAIR29_STATIC_SOURCE.json. Product/checker/registry, native footprint, staged inputs, raw and historical runbooks are unchanged.
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"plan_id": "P04-01",
|
||||
"decision_scope": "D05 dependency closure and 18:00 transaction order only",
|
||||
"recorded_at": "2026-10-15T13:51:09.167952+00:00",
|
||||
"origin": "direct_user_reply",
|
||||
"question_item_ids": [
|
||||
"[\"request_user_input_async\",\"call_cea3c55446014be4bde664de0ab8c381\",0]",
|
||||
"[\"request_user_input_async\",\"call_cea3c55446014be4bde664de0ab8c381\",1]"
|
||||
],
|
||||
"approved_choices": [
|
||||
{
|
||||
"id": "D05-P04-DEPENDENCY",
|
||||
"user_answer": "依存先の休業中は進捗・課金を停止し、予約を保持して復帰後に1回だけ完了する(Opus推奨・現状維持)",
|
||||
"contract": "依存先の休業中は開始済みサービスの進捗と課金を停止し、予約とサービスIDを保持する。復帰後に残りを処理し、完了・課金は1回だけ。停止上限は新設しない。直接の休業操作はD-034を維持する。"
|
||||
},
|
||||
{
|
||||
"id": "D05-P04-SHIFT-1800",
|
||||
"user_answer": "17:59→18:00の昼番作業を先に確定し、完了・課金後に交代する(Opus推奨・現状維持)",
|
||||
"contract": "[17:59,18:00)の昼番作業を先に確定し、必要時間に達したサービスを18:00に1回だけ完了・課金してから交代する。夜番は[18:00,18:01)から反映する。"
|
||||
}
|
||||
],
|
||||
"proposal_sha256": "082c6a9e7d8d0648be261ae30e52983e10ed677eaf90ab872b7c9e19793f773c",
|
||||
"required_validation": [
|
||||
"停止中課金0",
|
||||
"完了課金1回",
|
||||
"予約の孤立0",
|
||||
"同分再送",
|
||||
"日跨ぎ",
|
||||
"正式保存・別PID復元",
|
||||
"夜番不在",
|
||||
"既存残業タスク",
|
||||
"7領域の現版への証拠適用性",
|
||||
"必要検証・独立レビュー・通常統合"
|
||||
],
|
||||
"formal_acceptance": false,
|
||||
"original_unit_complete": false,
|
||||
"original_denominator": {
|
||||
"packages": 30,
|
||||
"units": 88
|
||||
},
|
||||
"remaining": "仕様選択は確定。条件ごとの既存証拠適用性確認と不足境界の検証、独立レビュー、通常統合は未完了。D05の他領域を一括解決したとは扱わない。"
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
{
|
||||
"task_id": "P04-01",
|
||||
"revision": "repair29",
|
||||
"status": "approval candidate only; SOURCE-READY / NOTRUN",
|
||||
"approval": null,
|
||||
"apply_to_product_or_checker": false,
|
||||
"review_flow": "Root asks owner after review. This document neither requests approval from worker nor records it.",
|
||||
"proof_legend": {
|
||||
"R": "B/audit-support/p04-raw-collect-r22",
|
||||
"I": "B/p04-evidence/index.json schema2",
|
||||
"pins": "REPAIR29_STATIC_SOURCE.json; R/MANIFEST.json and I raw_references/versions"
|
||||
},
|
||||
"choices": [
|
||||
{
|
||||
"id": "a",
|
||||
"topic": "Power vs closure/dependency",
|
||||
"status": "pending D05",
|
||||
"observation": {
|
||||
"status": "observed-current",
|
||||
"behavior": "RELATED_FACILITY_INACTIVE selected before NO_POWER; both pause service with no charge.",
|
||||
"source": "FacilityService _prepare_service_for_tick:853–874, phase FACILITY_COMPLETION after MANA projection",
|
||||
"proof": "Root-measured R/s4.json isolated NO_POWER; R/s3.json B1 combined RELATED diagnostic; REPAIR29_STATIC_SOURCE.json source pins"
|
||||
},
|
||||
"inputs": "Same service and minute, related facility inactive and power unavailable; include direct closure separately under D-034.",
|
||||
"A": {
|
||||
"recommended": true,
|
||||
"rule": "Keep current RELATED-first reason selection.",
|
||||
"outcome": "Service held, progress/payment paused, reservation and serviceID retained.",
|
||||
"side_effects": "Reason reports dependency while both causes apply; no new hold limit.",
|
||||
"dependencies": "Approved dependency hold, D-034 direct closure, existing MANA-before-completion order."
|
||||
},
|
||||
"B": {
|
||||
"rule": "NO_POWER wins reason selection when both conditions fail.",
|
||||
"outcome": "Same no-charge hold safety; reason reports power.",
|
||||
"side_effects": "Reason/event and recovery-precedence semantics would change; concrete implementation and exact-version proof require normal repair.",
|
||||
"dependencies": "Must retain no-charge while either legitimate hold persists; preserve approved dependency reservation/identity behavior."
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "b",
|
||||
"topic": "Destruction",
|
||||
"status": "pending D05",
|
||||
"observation": {
|
||||
"status": "observed-current",
|
||||
"behavior": "Command destruction in APPLY precedes completion. Incident destruction in INCIDENTS follows it; B4a shows25G charge then destruction in same tick.",
|
||||
"source": "Runtime _process_tick_commands:5743, _advance_facilities:8482, _advance_defense:8577 / _apply_defense_outcome_boundary:9804",
|
||||
"proof": "Root-measured R/s3.json B4a/B4b; REPAIR29_STATIC_SOURCE.json"
|
||||
},
|
||||
"inputs": "A service due for completion in a minute with command or incident destruction of its facility.",
|
||||
"A": {
|
||||
"recommended": true,
|
||||
"rule": "Keep command-before-completion and incident-after-completion behavior.",
|
||||
"outcome": "Command destruction prevents completion; incident may follow a valid completion/charge that minute; next-tick broken failure/release remains.",
|
||||
"side_effects": "Completion and destruction can coexist in one tick as B4a; not a universal no-sale-on-destruction rule.",
|
||||
"dependencies": "P04 stopped-charge and orphan-reservation safety; explicit D05 destruction authority required."
|
||||
},
|
||||
"B": {
|
||||
"rule": "An incident in the same minute suppresses that minute completion (or voids it if already posted).",
|
||||
"outcome": "No retained completion/sale for the incident-destruction minute.",
|
||||
"side_effects": "Would require preemption or atomic reversal of completion, inventory, payment and rewards; no unapproved numbers or refund amounts supplied.",
|
||||
"dependencies": "Owner must specify the suppress/void transaction semantics; preserve idempotency, no orphan reservations and approved shift/hold rules; normal implementation and new proof required."
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "c",
|
||||
"topic": "Exit",
|
||||
"status": "pending D05",
|
||||
"observation": {
|
||||
"status": "observed-current",
|
||||
"behavior": "Command exit APPLY before completion/charging; AI PLANNED_EXIT after them; LEAVING despawns later.",
|
||||
"source": "Runtime _execute_leave_facility_queue:7553; _advance_guest_ai_decisions:8738–8762",
|
||||
"proof": "Root-measured R/s3.json B2a/B2b/B3 and B1 departure history; source pins REPAIR29_STATIC_SOURCE.json"
|
||||
},
|
||||
"inputs": "Exit command or AI exit due at a minute with a completion-ready service. Current AI PLANNED_EXIT applies to DECIDING guests; do not assume it preempts an active service.",
|
||||
"A": {
|
||||
"recommended": true,
|
||||
"rule": "Keep current command/AI distinction and state predicates.",
|
||||
"outcome": "Command leave/release happens first; eligible AI planned exit follows completion/charging and queue processing.",
|
||||
"side_effects": "AI due time alone does not universally cancel current-minute completion; later despawn remains separate.",
|
||||
"dependencies": "D-034, reservation ownership/release, phase6 completion before phase10 AI."
|
||||
},
|
||||
"B": {
|
||||
"rule": "An exit due this minute preempts completion.",
|
||||
"outcome": "Applicable due exits release/cancel before completion and no completion sale occurs for that preempted service.",
|
||||
"side_effects": "Changes eligibility for exit and service cancellation; would change simultaneous completion/payment observations.",
|
||||
"dependencies": "Owner must define applicable due-exit states; preserve no orphan reservation, no duplicate charge and approved dependency/shift behavior; normal repair and exact proof required."
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "d",
|
||||
"topic": "Cost time/day attribution beyond A01 and18:00",
|
||||
"status": "pending D05",
|
||||
"observation": {
|
||||
"status": "observed-current",
|
||||
"behavior": "Sale dated at post-increment minute. At00:00 TIME_BOUNDARIES settlement/upkeep precede completions;00:00 sales go to new day because daily-close period_end is exclusive.",
|
||||
"source": "Kernel advance_one_minute:122; Runtime _apply_service_transaction:11785 /11826–11838; coordinator _process_daily_close:1125 /1136",
|
||||
"proof": "Root-measured R/s2a.json paid midnight/replay; I C; REPAIR29_STATIC_SOURCE.json"
|
||||
},
|
||||
"inputs": "Service completes on calendar boundary; account period ends at boundary minute. A01 ownership upkeep and18:00 shift interval remain fixed.",
|
||||
"A": {
|
||||
"recommended": true,
|
||||
"rule": "Keep post-increment sale time and new-day attribution for00:00 sales.",
|
||||
"outcome": "Closing day excludes boundary-minute completion; new day receives sale.",
|
||||
"side_effects": "Closing summary is finalized before those sales; upkeep ownership rule unchanged.",
|
||||
"dependencies": "Exclusive daily-close interval, sale timestamp, idempotent close/replay; P04 mandatory timing/attribution separate from D04/P05 numeric cost math."
|
||||
},
|
||||
"B": {
|
||||
"rule": "Boundary-minute sales belong to closing day.",
|
||||
"outcome": "Closing day includes those sales.",
|
||||
"side_effects": "Would require a distinct accounting attribution or revised boundary transaction sequencing; affects daily summaries/reconciliation/replay. No numeric cost policy is invented.",
|
||||
"dependencies": "Owner must settle attribution mechanics and same-boundary replay; preserve A01 and18:00, all P04 safety criteria; normal repair and exact-version proof required."
|
||||
}
|
||||
}
|
||||
],
|
||||
"bundle": {
|
||||
"label": "accept current-behavior A for a-d",
|
||||
"status": "pending D05",
|
||||
"recommended": true,
|
||||
"coupling_notes": [
|
||||
"a preserves the approved hold and no-charge safety independent of reason priority.",
|
||||
"b and c use the same completion boundary: commands before, incidents and AI after. Changing either requires separate applicability and replay checks.",
|
||||
"d relies on TIME_BOUNDARIES before completion; changing attribution can affect the transaction history used by b/c and settlement idempotency.",
|
||||
"A01 ownership upkeep, dependency hold and18:00 interval are already approved and are excluded from renewed approval.",
|
||||
"Owner may explicitly rule some remaining items non-mandatory; no such ruling exists here. Unknowns stay pending; mandatory acceptance is never relaxed."
|
||||
]
|
||||
},
|
||||
"authority_gap": "destruction_stop currently binds BASELINE:183, which is mana depletion, not destruction authority. Explicit D05 destruction rule required. Pin/registry repair only after approval through normal repair.",
|
||||
"power_scope": "S4 NO_POWER uses direct disconnect_nodes; not proof of global depletion, which exists only in B1 combined diagnostic.",
|
||||
"original_unit_complete": false
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
# D05 remaining order candidate — repair29
|
||||
|
||||
**Approval candidate only. SOURCE-READY / NOTRUN.** Root asks the owner after review. Nothing is applied to product, checker or registry; observation is not approval. A01 midnight ownership upkeep, dependency hold and18:00 are already approved and are not re-asked.
|
||||
|
||||
Structured choices: [D05_REMAINING_ORDER_CANDIDATE.json](D05_REMAINING_ORDER_CANDIDATE.json). Source path/line/SHA256 pins: [REPAIR29_STATIC_SOURCE.json](REPAIR29_STATIC_SOURCE.json); phase map: [ORDER_AUDIT.md](ORDER_AUDIT.md). R = B/audit-support/p04-raw-collect-r22; I = B/p04-evidence/index.json. These are Root-measured exact-version proof references, with raw/provenance pins in R/MANIFEST.json, I and the static manifest.
|
||||
|
||||
## (a) Power vs closure/dependency — pending D05
|
||||
|
||||
**Observed-current:** RELATED_FACILITY_INACTIVE selected before NO_POWER; both pause service with no charge. Source: FacilityService _prepare_service_for_tick:853–874, phase FACILITY_COMPLETION after MANA projection. Proof: Root-measured R/s4.json isolated NO_POWER; R/s3.json B1 combined RELATED diagnostic; REPAIR29_STATIC_SOURCE.json source pins.
|
||||
|
||||
**Inputs:** Same service and minute, related facility inactive and power unavailable; include direct closure separately under D-034.
|
||||
|
||||
**Option A (recommended; keep current behavior):** Keep current RELATED-first reason selection.
|
||||
|
||||
- Outcomes: Service held, progress/payment paused, reservation and serviceID retained.
|
||||
- Side effects: Reason reports dependency while both causes apply; no new hold limit.
|
||||
- Dependencies: Approved dependency hold, D-034 direct closure, existing MANA-before-completion order.
|
||||
|
||||
**Option B:** NO_POWER wins reason selection when both conditions fail.
|
||||
|
||||
- Outcomes: Same no-charge hold safety; reason reports power.
|
||||
- Side effects: Reason/event and recovery-precedence semantics would change; concrete implementation and exact-version proof require normal repair.
|
||||
- Dependencies: Must retain no-charge while either legitimate hold persists; preserve approved dependency reservation/identity behavior.
|
||||
|
||||
## (b) Destruction — pending D05
|
||||
|
||||
**Observed-current:** Command destruction in APPLY precedes completion. Incident destruction in INCIDENTS follows it; B4a shows25G charge then destruction in same tick. Source: Runtime _process_tick_commands:5743, _advance_facilities:8482, _advance_defense:8577 / _apply_defense_outcome_boundary:9804. Proof: Root-measured R/s3.json B4a/B4b; REPAIR29_STATIC_SOURCE.json.
|
||||
|
||||
**Inputs:** A service due for completion in a minute with command or incident destruction of its facility.
|
||||
|
||||
**Option A (recommended; keep current behavior):** Keep command-before-completion and incident-after-completion behavior.
|
||||
|
||||
- Outcomes: Command destruction prevents completion; incident may follow a valid completion/charge that minute; next-tick broken failure/release remains.
|
||||
- Side effects: Completion and destruction can coexist in one tick as B4a; not a universal no-sale-on-destruction rule.
|
||||
- Dependencies: P04 stopped-charge and orphan-reservation safety; explicit D05 destruction authority required.
|
||||
|
||||
**Option B:** An incident in the same minute suppresses that minute completion (or voids it if already posted).
|
||||
|
||||
- Outcomes: No retained completion/sale for the incident-destruction minute.
|
||||
- Side effects: Would require preemption or atomic reversal of completion, inventory, payment and rewards; no unapproved numbers or refund amounts supplied.
|
||||
- Dependencies: Owner must specify the suppress/void transaction semantics; preserve idempotency, no orphan reservations and approved shift/hold rules; normal implementation and new proof required.
|
||||
|
||||
## (c) Exit — pending D05
|
||||
|
||||
**Observed-current:** Command exit APPLY before completion/charging; AI PLANNED_EXIT after them; LEAVING despawns later. Source: Runtime _execute_leave_facility_queue:7553; _advance_guest_ai_decisions:8738–8762. Proof: Root-measured R/s3.json B2a/B2b/B3 and B1 departure history; source pins REPAIR29_STATIC_SOURCE.json.
|
||||
|
||||
**Inputs:** Exit command or AI exit due at a minute with a completion-ready service. Current AI PLANNED_EXIT applies to DECIDING guests; do not assume it preempts an active service.
|
||||
|
||||
**Option A (recommended; keep current behavior):** Keep current command/AI distinction and state predicates.
|
||||
|
||||
- Outcomes: Command leave/release happens first; eligible AI planned exit follows completion/charging and queue processing.
|
||||
- Side effects: AI due time alone does not universally cancel current-minute completion; later despawn remains separate.
|
||||
- Dependencies: D-034, reservation ownership/release, phase6 completion before phase10 AI.
|
||||
|
||||
**Option B:** An exit due this minute preempts completion.
|
||||
|
||||
- Outcomes: Applicable due exits release/cancel before completion and no completion sale occurs for that preempted service.
|
||||
- Side effects: Changes eligibility for exit and service cancellation; would change simultaneous completion/payment observations.
|
||||
- Dependencies: Owner must define applicable due-exit states; preserve no orphan reservation, no duplicate charge and approved dependency/shift behavior; normal repair and exact proof required.
|
||||
|
||||
## (d) Cost time/day attribution beyond A01 and18:00 — pending D05
|
||||
|
||||
**Observed-current:** Sale dated at post-increment minute. At00:00 TIME_BOUNDARIES settlement/upkeep precede completions;00:00 sales go to new day because daily-close period_end is exclusive. Source: Kernel advance_one_minute:122; Runtime _apply_service_transaction:11785 /11826–11838; coordinator _process_daily_close:1125 /1136. Proof: Root-measured R/s2a.json paid midnight/replay; I C; REPAIR29_STATIC_SOURCE.json.
|
||||
|
||||
**Inputs:** Service completes on calendar boundary; account period ends at boundary minute. A01 ownership upkeep and18:00 shift interval remain fixed.
|
||||
|
||||
**Option A (recommended; keep current behavior):** Keep post-increment sale time and new-day attribution for00:00 sales.
|
||||
|
||||
- Outcomes: Closing day excludes boundary-minute completion; new day receives sale.
|
||||
- Side effects: Closing summary is finalized before those sales; upkeep ownership rule unchanged.
|
||||
- Dependencies: Exclusive daily-close interval, sale timestamp, idempotent close/replay; P04 mandatory timing/attribution separate from D04/P05 numeric cost math.
|
||||
|
||||
**Option B:** Boundary-minute sales belong to closing day.
|
||||
|
||||
- Outcomes: Closing day includes those sales.
|
||||
- Side effects: Would require a distinct accounting attribution or revised boundary transaction sequencing; affects daily summaries/reconciliation/replay. No numeric cost policy is invented.
|
||||
- Dependencies: Owner must settle attribution mechanics and same-boundary replay; preserve A01 and18:00, all P04 safety criteria; normal repair and exact-version proof required.
|
||||
|
||||
## Bundle: accept current-behavior A for a-d
|
||||
|
||||
Recommended candidate, not approved.
|
||||
|
||||
- a preserves the approved hold and no-charge safety independent of reason priority.
|
||||
- b and c use the same completion boundary: commands before, incidents and AI after. Changing either requires separate applicability and replay checks.
|
||||
- d relies on TIME_BOUNDARIES before completion; changing attribution can affect the transaction history used by b/c and settlement idempotency.
|
||||
- A01 ownership upkeep, dependency hold and18:00 interval are already approved and are excluded from renewed approval.
|
||||
- Owner may explicitly rule some remaining items non-mandatory; no such ruling exists here. Unknowns stay pending; mandatory acceptance is never relaxed.
|
||||
|
||||
destruction_stop currently binds BASELINE:183, which is mana depletion, not destruction authority. Explicit D05 destruction rule required. Pin/registry repair only after approval through normal repair.
|
||||
|
||||
S4 NO_POWER uses direct disconnect_nodes; not proof of global depletion, which exists only in B1 combined diagnostic.
|
||||
|
||||
P04 timing/attribution remains mandatory; D04/P05 numeric cost math is separate. No acceptance criterion is relaxed. P04 remains incomplete; original13/88 unchanged.
|
||||
@@ -0,0 +1,23 @@
|
||||
# Evidence collection status — repair29
|
||||
|
||||
Worker **SOURCE-READY / NOTRUN** (static Read/SHA256 only). No probes, tests, engine or Runner execution in this repair. [CURRENT_STATUS.md](CURRENT_STATUS.md) overrides historical creation-time NOT RUN entries in REPAIR22–28 and runbooks, which remain untouched. Do not repeat their completed execution steps merely because their original prose says pending.
|
||||
|
||||
B = `/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build`.
|
||||
|
||||
| Completed material | Current fact (Root-measured) |
|
||||
| --- | --- |
|
||||
| B/p04-s4-native-r28 | Four phases PASS; PIDs49940,50044,50078,50105 all ended, guards0, cleanup0. |
|
||||
| B/audit-support/p04-root-fb8b-s4-r01 | S4 control/native receipts preserved. |
|
||||
| B/audit-support/p04-root-fb8b-local-r01 | Local8 PASS; registered validation on fb8b PASS28.095s. |
|
||||
| B/audit-support/p04-raw-collect-r22 | Completed collection of preserved C/series applicability; no new C/S1/S2a/S2b/S3 native runs. |
|
||||
| B/p04-evidence | Current schema2 index/generation invocation, missing_conditions=[], all15 conditions + EV mechanically verified. |
|
||||
|
||||
Native product5f6bc11d and main9b1a18c1 remain the Root-measured versions. REPAIR29_STATIC_SOURCE.json hashes current B/audit-support/p04-r17-full-inputs-r01/product (1338 files), not old deb11300, and pins current collection/index. Per-series raw/source/config/input/environment joins remain in the immutable collection and schema2 index; this worker neither regenerates nor normalizes raw.
|
||||
|
||||
S4 is isolated NO_POWER from direct disconnect_nodes, not global mana depletion. Global depletion is observed only in B1 combined diagnostic. B1 supplies no isolated power_stop or priority approval. Destruction authority BASELINE:183 is a known wrong category (mana depletion); explicit owner D05 destruction rule is required before normal registry/pin repair. Product/checker/registry remain unchanged here.
|
||||
|
||||
Full178 used checker **f5**,21615 checks; exact receipt preserved at B/audit-support/p18-regression-support-final-r01/native-full178/runner-ended.json and exact selected inputs at runner-input.json selected_source_map (source-start.json records initial product sources). f5 SHA256 is f5b44827958f2ae352fe5c4bc3a1ffd4211ec7a41fc4c0759c8b954891662324. Product suites/316-file GDScript parse do not execute Python; this does not establish an exact1339 proof against a61. Static comparison with the full178 selected_source_map found one test-source difference (tests/business/test_required_kitchen.gd: full3a9c92f8…, current5dcc86d6…) and no game-source difference. Runner must retain explicit applicability for that test; no blanket product1338 identity with that full178 receipt is claimed. **No full1339 run exists against a61** (a61c5b496e656f60c66898e42500bc9a2d45f947690ce8f5f19a03984ebbc850). Keep the old full and partial receipts distinct and intact.
|
||||
|
||||
Remaining steps belong to Runner: review this repair; Root obtains owner D05(a–d) decisions or explicit non-mandatory rulings; unknown mandatory rules remain pending. Any approved checker/registry/product change uses a normal repair. Perform support53cb normal registered rebind, final full run against the final source/checker and final source/config/input/environment applicability binding, independent review and integration (P04 first, then support on main9b1a18c1). NativeWindows obligations stay individually pending if required. This document does not launch or authorize duplicate probes and does not replace Runner scheduling.
|
||||
|
||||
A01 midnight ownership upkeep, dependency hold and18:00 are already approved. P04 timing/attribution remains mandatory, separate from D04/P05 numeric cost math. Original13/88 credit,15 conditions + EV, seven domains, three safety outcomes and30/88 denominator remain unchanged. Original unit is incomplete.
|
||||
@@ -0,0 +1,61 @@
|
||||
# P04-01 order audit — repair29
|
||||
|
||||
Worker **SOURCE-READY / NOTRUN**: static Read and SHA256 only. No original completion. [CURRENT_STATUS.md](CURRENT_STATUS.md) is canonical for execution status. [order-audit.json](order-audit.json) is the matching structured audit.
|
||||
|
||||
B = `/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build`.
|
||||
P = `B/audit-support/p04-r17-full-inputs-r01/product`. Source references below are relative to P, not old deb11300. All1338 product file SHA256 values, function lines and16 handler registrations are recomputed in [REPAIR29_STATIC_SOURCE.json](REPAIR29_STATIC_SOURCE.json).
|
||||
Kernel `scripts/core/simulation_kernel.gd` SHA256: `341c0c878c4894adbac628d0db6362ff69fc3347bdd9e61e50936e15dc2d1edb`.
|
||||
Runtime `autoload/simulation_runtime.gd` SHA256: `7f01b2dcdf28bc965c7588173c2dc70054633faf7204b1ac38ba0f4369331b47`.
|
||||
Root-measured native product `5f6bc11d`; main `9b1a18c1`.
|
||||
|
||||
## Actual eleven phases — observed-current
|
||||
|
||||
Kernel advance_one_minute:122 defines the order; _run_phase:236 string-sorts handler IDs. Registrations below are Runtime lines366–435 plus operations:1766. Every literal ID begins `simulation_runtime.`. Observations do not approve D05.
|
||||
|
||||
1. **APPLY_COMMANDS** — simulation_runtime.commands → _process_tick_commands (registration:369). Previous minute: commands → _process_tick_commands:5743. Operations gate requests current tick abort and returns before Clock changes; otherwise commands apply for minute+1.
|
||||
|
||||
2. **TIME_BOUNDARIES** — simulation_runtime.0_weather → _advance_weather (registration:366); simulation_runtime.a_upkeep → _advance_facility_upkeep (registration:368); simulation_runtime.operations → _advance_operations_time (registration:1766). After APPLY: absolute_minute += 1, _sync_calendar_fields, _handle_time_boundaries:254 emits settlement_requested(completed_day) then day_started at00:00, before TIME_BOUNDARIES handlers. Handler order: weather, upkeep, operations. Runtime upkeep:1855; operations:1898 → coordinator _process_minute_boundaries:882: 00:00 upkeep, daily close, 06:00 morning, demand, arrivals (conditional boundaries and early returns).
|
||||
|
||||
3. **MANA** — simulation_runtime.mana → _advance_mana (registration:379). Runtime _advance_mana:10694: proxy preflight → mana_network advance → mana_power_projection.apply → MANA_STATE_CHANGED events.
|
||||
|
||||
4. **CONSTRUCTION** — simulation_runtime.construction → _advance_construction (registration:384). Construction:8064 follows power and precedes logistics.
|
||||
|
||||
5. **LOGISTICS** — simulation_runtime.logistics → _advance_inventory_and_logistics (registration:389). Logistics:8472 precedes completion.
|
||||
|
||||
6. **FACILITY_COMPLETION** — simulation_runtime.facilities → _advance_facilities (registration:394). Runtime _advance_facilities:8482: facility_service.advance_one_minute then kitchen. FacilityService _prepare_service_for_tick:853 checks BROKEN, related_facility_operating (RELATED_FACILITY_INACTIVE), power_available (NO_POWER), staff grace. Only ACTIVE services advance (:569). _complete_service:895 → Runtime _complete_service_transaction:11769 → _apply_service_transaction:11785 posts SERVICE_SALE / SERVICE_CASH_SWEEP at the post-increment minute (:11826–11838).
|
||||
|
||||
7. **VITALS_AND_ENVIRONMENT** — simulation_runtime.guest_vitals → _advance_guest_vitals (registration:399). Guest vitals:8488 follows completion.
|
||||
|
||||
8. **INCIDENTS_AND_EVENTS** — simulation_runtime.authority_incidents → _advance_authority_incidents (registration:409); simulation_runtime.defense → _advance_defense (registration:414); simulation_runtime.relationship_events → _advance_relationship_events (registration:419). String-sorted authority_incidents:8551 → defense:8577 → relationship_events:8615; after completion/charging.
|
||||
|
||||
9. **FACILITY_QUEUES** — simulation_runtime.guest_queues → _advance_guest_queues (registration:404). _advance_guest_queues:8502 releases closed queues first (_release_closed_business_queues:12624); closed queues spend no patience (:8516–8517).
|
||||
|
||||
10. **AI_DECISIONS** — simulation_runtime.staff_ai → _advance_staff_ai (registration:424). _advance_staff_ai:8658: prune elevator waits, elevators, rescue, staff_service, prune waits, staff_commute (shift handover), staffing sync, cleaning, fatigue, rescue and staffing sync again, then _advance_guest_ai_decisions:8738. DECIDING PLANNED_EXIT → LEAVING; despawn occurs only on later minute (:8760–8762).
|
||||
|
||||
11. **PUBLISH_SNAPSHOT** — simulation_runtime.snapshot → _publish_snapshot (registration:374); simulation_runtime.zz_save_checkpoints → _process_save_checkpoints (registration:431). snapshot:12476 then zz_save_checkpoints:4514; persistence follows publication.
|
||||
|
||||
## Seven domains
|
||||
|
||||
Proofs below are **Root-measured**. R = B/audit-support/p04-raw-collect-r22; I = B/p04-evidence/index.json (schema2). I native_bundle/raw_references/versions/source_map/environment preserve exact C and series bindings. R/MANIFEST.json pins series bytes/SHA256 and relocation; each series retains raw provenance. REPAIR29_STATIC_SOURCE.json also pins R files and I. Source function paths/lines/hashes are in that static manifest.
|
||||
|
||||
| Domain | Status / authority | Phase and source function+line | Relative priority | Exact-version proof | Pending |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 閉店 | approved; D-034, D05-P04-DEPENDENCY | APPLY_COMMANDS / FACILITY_COMPLETION / FACILITY_QUEUES: Runtime _process_tick_commands:5743; FacilityService _prepare_service_for_tick:853; Runtime _release_closed_business_queues:12624 | Command closure before completion; dependency hold during preparation; closed queue release after completion. | I native_bundle C; R/s2a.json; R/s3.json B1 | D05(a) simultaneous reason selection |
|
||||
| 停電 | observed-current; BASELINE:183, P04-01 safety acceptance | MANA → FACILITY_COMPLETION: Runtime _advance_mana:10694; FacilityService _prepare_service_for_tick:853 | Projection before completion; RELATED chosen before NO_POWER. | R/s4.json; B/p04-s4-native-r28; R/s3.json B1 combined diagnostic | D05(a) |
|
||||
| 破壊 | pending D05; P04-01 safety acceptance (original); explicit destruction rule pending D05 | APPLY_COMMANDS vs INCIDENTS_AND_EVENTS: Runtime _process_tick_commands:5743; _advance_defense:8577; _apply_defense_outcome_boundary:9804; FacilityService _prepare_service_for_tick:853 | Command destruction before completion; incident destruction after completion: B4a charges25G then destroys in same tick; B4b next-tick failure/release. | R/s3.json B4a/B4b | D05(b); BASELINE:183 is mana depletion, not destruction authority; destruction_stop registry correction only after approval |
|
||||
| 退出 | pending D05; D-034, P04-01 safety acceptance | APPLY_COMMANDS vs AI_DECISIONS: Runtime _execute_leave_facility_queue:7553; _advance_guest_ai_decisions:8738 | Command exit before completion/charging; AI PLANNED_EXIT after them; despawn later. | R/s3.json B2a/B2b/B3 and B1 departure history | D05(c) |
|
||||
| 課金 | pending D05; P04-01 safety acceptance, D05-P04-DEPENDENCY, D05-P04-SHIFT-1800 | TIME_BOUNDARIES → FACILITY_COMPLETION: Runtime _advance_facility_upkeep:1855; _complete_service_transaction:11769; _apply_service_transaction:11785; coordinator _process_daily_close:1125 | Upkeep/daily close before sale; post-increment sale timestamp, exclusive close period_end;00:00 sale belongs to new day. | I native_bundle C; R/s2a.json; R/s4.json | D05(d), beyond approved A01 and18:00; timing/attribution mandatory P04, numeric math D04/P05 separate |
|
||||
| シフト交代 | approved; BASELINE:64, BASELINE:67, D05-P04-SHIFT-1800 | FACILITY_COMPLETION → AI_DECISIONS: Runtime _advance_facilities:8482; _advance_staff_ai:8658; StaffService advance_one_minute:700 | 18:00 completion/payment before shift handover; day staff [17:59,18:00), night [18:00,18:01). | I C first1080 sale joins; R/s2b.json no-night1081/overtime | Already approved; do not re-ask |
|
||||
| 00:00決算 | approved; BASELINE:71, BASELINE:72, A01, D05-P04-DEPENDENCY | pre-TIME_BOUNDARIES signals → TIME_BOUNDARIES → FACILITY_COMPLETION: Kernel _handle_time_boundaries:254; Runtime _advance_operations_time:1898; coordinator _process_minute_boundaries:882; _process_daily_close:1125 | Midnight signals, then upkeep/close, then completion. | I C paused/formal; R/s2a.json nonzero payroll/upkeep, replay and typed restore | A01 approved ownership-based midnight upkeep not reopened; extra attribution D05(d) |
|
||||
|
||||
S4 proves isolated NO_POWER caused by the probe calling disconnect_nodes directly, not global mana depletion. Global depletion appears only in B1 combined diagnostic; B1 does not prove isolated power or approve priority. BASELINE:183 is mana depletion, not destruction authority. Current destruction_stop binding is therefore a known authority gap. Checker a61 and registry stay unchanged until approval and normal repair.
|
||||
|
||||
## Cross-cutting safety
|
||||
|
||||
- **No charge while paused NO_POWER or RELATED** — original; FACILITY_COMPLETION; FacilityService _prepare_service_for_tick:853, ACTIVE-only gate:569, _complete_service:895. Preparation before advance/completion/payment. Root-measured proof: R/s4.json isolated power; I C and R/s2a.json dependency hold.
|
||||
- **Double-settlement idempotency** — original; TIME_BOUNDARIES; coordinator _process_daily_close:1125; _day_summaries replay:1127–1130; daily-close:N:1137. Before facility completion. Root-measured proof: I C; R/s2a.json same-boundary replay and separate-PID restore, unchanged accounting.
|
||||
- **Orphan reservation release** — original; APPLY_COMMANDS / FACILITY_QUEUES; Runtime _execute_leave_facility_queue:7553 (release keys:7565–7569); _leave_guest_queue_boundary:9178; _release_closed_business_queues:12624; _release_closed_business_request:12642. Command release before completion; closed-queue release afterward; closed patience not spent. Root-measured proof: R/s3.json B2a/B2b/B3/B4b; I C; R/s2a.json reservation/service identity and restore.
|
||||
|
||||
A01 midnight ownership upkeep, dependency hold and18:00 are already approved; do not re-ask. Authority pins are in I/authority; D05_OWNER_APPROVAL.json approves only the two named D05 choices. Remaining candidate choices and their side effects are in [D05_REMAINING_ORDER_CANDIDATE.md](D05_REMAINING_ORDER_CANDIDATE.md). P04 timing/attribution remains mandatory and separate from D04/P05 numeric cost math. Unknown mandatory rules remain pending.
|
||||
|
||||
Original credit **13/88 unchanged**,15 conditions + EV, seven domains, three safety outcomes and30/88 denominator unchanged. P04 remains incomplete. Root execution history and remaining Runner steps are in CURRENT_STATUS.md.
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"scope": "Internal evidence-format design within P04-01 checker work, not a new game rule or acceptance waiver. Missing or unsupported semantic proof must fail.",
|
||||
"input_path": "BUILD_OUTPUT_DIR/p04-evidence/index.json",
|
||||
"required_index_fields": {
|
||||
"schema_version": 1,
|
||||
"task_id": "P04-01",
|
||||
"candidate_commit": "exact candidate Git SHA",
|
||||
"source_map": "repository-relative file to raw SHA256; all observed runtime/kernel/affected service sources required",
|
||||
"environment": "engine path/hash, OS, configuration and inputs raw pins",
|
||||
"artifacts": "unique artifact id to build-relative path/bytes/sha256/type; resolve safely within evidence dir; reject missing, duplicate, symlink, traversal",
|
||||
"domains": "exact7 required domain objects",
|
||||
"authority": "original file raw pin + applicable approved decision refs; pending mandatory rules are errors",
|
||||
"review_status": "pending_review or approved; pending review may pass mechanical stage only if every original evidence predicate passed",
|
||||
"formal_acceptance": false
|
||||
},
|
||||
"required_domains": [
|
||||
"閉店",
|
||||
"停電",
|
||||
"破壊",
|
||||
"退出",
|
||||
"課金",
|
||||
"シフト交代",
|
||||
"00:00決算"
|
||||
],
|
||||
"domain_fields": {
|
||||
"source_refs": "actual current functions/phase order source pins",
|
||||
"authority_refs": "original/approved refs; implementation observations alone do not authorize a missing rule",
|
||||
"cases": "finite scenario IDs and artifact IDs binding input, before state, delivered operation/clock boundary, after state, producer/consumer PID and unchanged raw save package where applicable",
|
||||
"invariants": "supported predicate descriptors on actual raw JSON observation/state data; not boolean pass claims or test counts",
|
||||
"applicability": "exact source/config/input/environment bindings; old mismatch cannot auto-pass"
|
||||
},
|
||||
"native_receipt_predicates": [
|
||||
"actual owned PID>0; start/end PID identical; exit0 and engine_ended; receipt bound to pinned rawstdout/native logs",
|
||||
"source pins in observation and phase receipt match index and checkout; config/input pins are verified",
|
||||
"where cross-process evidence is claimed, PIDs differ and exact formal save members match producer and consumer pins"
|
||||
],
|
||||
"invariant_predicate_format": {
|
||||
"artifact_id": "existing pinned raw observation or state artifact",
|
||||
"json_pointer": "RFC6901 JSON pointer into artifact JSON",
|
||||
"operator": "one of eq, nonnegative, distinct, same_literal, count_eq; reject unknown",
|
||||
"expected": "only applicable original/approved rule values or independently computed actual state relationships, never unapproved numeric policy",
|
||||
"paired_artifact_id": "when comparing before/after or restart, required paired pinned raw artifact",
|
||||
"paired_json_pointer": "comparison source if needed",
|
||||
"authority_ref": "original/approved authority for this condition",
|
||||
"raw_origin": "actual engine-produced source or explicit root derivation with each raw input hash and documented formula; unsupported derivation rejected"
|
||||
},
|
||||
"original_invariant_mapping": [
|
||||
"停止が有効な区間のactual service/payment/accounting before-after差が許可されない売上を含まない。停止が未確定D05ならfail。",
|
||||
"同じ00:00境界の精算receiptが1回、同境界再送と再開で資金/台帳が不変。A01時点は承認済み。",
|
||||
"閉店/破壊/退出でlive reservationsとactive targetの対応が一致し、除去/復元後の孤立が0。",
|
||||
"課金の再送/再開で同じservice/payment identityが重複反映されない。",
|
||||
"閉店後serviceとシフト最終区間等の未承認解釈を実装観測だけでpassしない。"
|
||||
],
|
||||
"fail_closed": [
|
||||
"missing index or mandatory fields",
|
||||
"duplicate keys/IDs, invalid field types or paths",
|
||||
"unknown predicate or unsupported proof source",
|
||||
"raw file hash/size, source/config/input/environment mismatch",
|
||||
"mandatory D05 rule pending or authority missing",
|
||||
"unexecuted proof, incomplete7domain or required boundary coverage",
|
||||
"selfreported pass/test count as substitute for actual state invariants"
|
||||
],
|
||||
"implementation_note": "Implement this finite mechanical protocol and document exact supported predicates. If evidence is absent, output actual missing IDs and exit nonzero. No requirement to create fake pass evidence. Do not change source/Clock/game numbers; no engine/test run by worker.",
|
||||
"revision": "repair29",
|
||||
"status": "Worker SOURCE-READY / NOTRUN; Root-measured S4r28/local8/r22/schema2 index/registered validation PASS on fb8b; no original acceptance",
|
||||
"accepted_index_schema_versions": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"schema2_note": "Current candidate-role/index binding uses the existing schema2 implementation. Legacy field descriptions below are not a replacement schema.",
|
||||
"current_order_table": "order-audit.json",
|
||||
"series_collection": "BUILD_OUTPUT_DIR/audit-support/p04-raw-collect-r22",
|
||||
"power_stop_binding": {
|
||||
"only_series": "S4",
|
||||
"status": "Root-measured S4r28 4/4 PASS; all four owned PIDs ended; guards0 cleanup0",
|
||||
"required_reason": "NO_POWER",
|
||||
"related_facility_operating": true,
|
||||
"kitchen_power_available": true,
|
||||
"global_mana": ">0",
|
||||
"operator_open": true,
|
||||
"formal_points": [
|
||||
"boundary",
|
||||
"held",
|
||||
"recovered"
|
||||
],
|
||||
"canonicalization": false,
|
||||
"dropped_fields": false,
|
||||
"native_path": "B/p04-s4-native-r28",
|
||||
"controls_path": "B/audit-support/p04-root-fb8b-s4-r01",
|
||||
"PIDs": [
|
||||
49940,
|
||||
50044,
|
||||
50078,
|
||||
50105
|
||||
],
|
||||
"cause": "probe calls disconnect_nodes directly; isolated NO_POWER, not proof of global depletion",
|
||||
"global_depletion_scope": "Only B1 combined diagnostic observes global depletion; B1 supplies no isolated power_stop or priority approval"
|
||||
},
|
||||
"B1": {
|
||||
"scope": "combined dependency/closure diagnostic only",
|
||||
"power_stop_evidence": false,
|
||||
"priority_approval": false
|
||||
},
|
||||
"D05_power_vs_closure": "pending D05(a); observation RELATED before NO_POWER is not approval; choices in D05_REMAINING_ORDER_CANDIDATE.json",
|
||||
"history": "Creation-time NOT RUN in REPAIR22–28 and runbooks is historical; CURRENT_STATUS.md canonical table overrides execution status. Historical files untouched.",
|
||||
"version_policy": "Full178 used checker f5,21615 checks, exact receipt preserved. No full1339 against a61 exists. Product-only reuse is distinct from current-checker validation. support53cb normal rebind, final full run, independent review and P04-then-support integration on main9b remain Runner steps.",
|
||||
"root_measurements": {
|
||||
"label": "Root-measured",
|
||||
"local8": "B/audit-support/p04-root-fb8b-local-r01 PASS",
|
||||
"collection": "B/audit-support/p04-raw-collect-r22",
|
||||
"index": "B/p04-evidence/index.json schema2; missing_conditions=[];15 conditions + EV verified",
|
||||
"registered_validation": "fb8b PASS28.095s; local-r01/registered-validation.result.json",
|
||||
"native_product": "5f6bc11d",
|
||||
"main": "9b1a18c1",
|
||||
"canonical_status": "CURRENT_STATUS.md",
|
||||
"exact_sha256_pins": "REPAIR29_STATIC_SOURCE.json"
|
||||
},
|
||||
"destruction_stop_binding": {
|
||||
"status": "pending D05",
|
||||
"current_registry_authority": "BASELINE:183",
|
||||
"gap": "mana depletion, not destruction authority",
|
||||
"action": "No checker or registry edits before owner approval; explicit destruction rule and subsequent normal repair required"
|
||||
},
|
||||
"remaining_order_choices": "D05_REMAINING_ORDER_CANDIDATE.json; pending, not applied",
|
||||
"full178_applicability_context": {
|
||||
"selected_map": "B/audit-support/p18-regression-support-final-r01/native-full178/runner-input.json",
|
||||
"difference": [
|
||||
{
|
||||
"path": "tests/business/test_required_kitchen.gd",
|
||||
"current_sha256": "5dcc86d66bdc47003472d6c30d6889eb514e016003265965bdd8496d123d5ca5",
|
||||
"full178_sha256": "3a9c92f80c24064ef0858fba10bd7caab63203932942837f7e142349578f6b2f"
|
||||
}
|
||||
],
|
||||
"status": "SOURCE-READY / NOTRUN; test-source applicability retained as Runner responsibility; no blanket1338 identity claim"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
# Runner実行担当からの第1修正入力
|
||||
|
||||
これは原ゲーム仕様の変更ではありません。旧Runtimeと差分診断を追加試験入力として供給します。既存監査を再実装せず、技術的な証拠index形式とfailclosed checkerを作成してください。原7領域とD05 pendingを維持します。原条件の証拠欠損は検証失敗です。rootは検証器のsourceを変更していません。
|
||||
|
||||
読取専用入力(Runner allocation.build_dir内):
|
||||
- /Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build/audit-support/support.json
|
||||
- /Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build/audit-support/historical-runtime.txt
|
||||
|
||||
旧Runtime SHA256: 2d7e2cea5424585e9aa4302ab66d1fc4dbaefe743ce4328fccc1efa2f19b82e4。入力を検証してからdiffしてください。
|
||||
|
||||
内部indexの形式設計はspecの検証器作成指示に含まれる技術判断であり、原ゲーム規則の補完ではありません。欠損/古いソース/不正hash/未実行/未定義必須規則は必ず非0にしてください。全証拠が揃わないまま元成果を完了扱いにしないでください。エンジン・全回帰・製品source編集は0。
|
||||
|
||||
実際のClaude commanderの記録済み判断:
|
||||
|
||||
{
|
||||
"action": "request-repair",
|
||||
"task_id": "P04-01",
|
||||
"reason": "初回Codex blockedの原因は、検証器tools/check_p04_order_evidence.pyが未作成だったこと(missing gate schemaで仕様補完を避けて停止)。これは内部証拠formatの実装判断で、原ゲーム仕様の不足ではない。既存specの検証器作成指示が、元要件を守るfailclosed形式の設計を許可している。repairs=0で、第1repairは残2/2以内。入力は再起動に十分: input.json等のlocal copy、current source pins、14差分(production7とmanaged14を区別、追加7はroot対managedでCRLF→LFのみbyte検証済み、rawSHA差は隠さない)、正確な旧Runtime(SHA 2d7e2cea完全一致)をbuild_dir/audit-support/historical-runtime.txtへraw保存済み。support.jsonとこのtxtを読取専用の追加試験入力として使う(Git変更scopeなし)。repair範囲: (1) 既存のorder-audit.jsonとORDER_AUDIT.mdは保持し、重複作成しない。必要なら最小の追記・訂正のみ。(2) --verify-onlyのcheckerを作成する。BUILD_OUTPUT_DIR/p04-evidence/index.jsonを必須とし、7領域、不変のsource pin、raw proof hash、適用性を検証する。欠損、D05 pending、stale source、未実行proof、自己申告pass、test count単独のpassは非0で終了する。raw証拠が全て不足していてもcheckerは作成する。生証拠を作ったことにはしない。D05は未承認のまま、その不足がある受入をpassにしない。(3) 全回帰・engine実行、ゲームsource変更、Clock・値の変更は0。原7領域/元30/88/部分点なしの受入分母は維持し、仕様変更・分母変更・予算拡大・main統合は含めない。allowed_pathsは検証器と実行ディレクトリ配下に限る。"
|
||||
}
|
||||
|
||||
追加技術入力: 同じaudit-support内のruntime-diff.patchは旧SHA確認済みRuntimeと現Runtimeのdiff -u出力です。原ソース2件と突合して利用できます。
|
||||
|
||||
前回shellのhere-documentは/tmp作成制限で失敗しています。sandbox/権限を変更せず、既存のpython -c等の一時ファイル不要の方式、または許可scopeへのapply_patchを使ってください。前回未読だったこの文書とP04_ORDER_EVIDENCE_PROTOCOL.jsonを最初に読んでください。index形式は技術設計であり、未知ゲーム規則の確定ではありません。D05や証拠不足は非0を維持し、検証器と設計書をsubmittedとして提出することは元成果acceptedではありません。
|
||||
@@ -0,0 +1,92 @@
|
||||
# REPAIR17 — blocked on required raw access
|
||||
|
||||
This is not accepted or completed. Stage R is not implemented. No new condition
|
||||
is claimed as proven. This document records an observed input-access blocker,
|
||||
not a replacement implementation or a proposal to trust Root summaries.
|
||||
|
||||
## Checkout and scope
|
||||
|
||||
- Observed HEAD: `accd07c91d4e34d9b7c7fc20e389df2424b167e1` (the requested reused checkout).
|
||||
- Registered contract base: `deb1bcb2374eb378b840ca345db952bd8572cb6e`.
|
||||
- Only this status file was created. The checker, `verify_native`, C checks,
|
||||
existing fixtures, probes, originals, and logs were not modified.
|
||||
- No git writes, Runner operations, engine runs, or other agents were used.
|
||||
|
||||
## Reproduced blocker
|
||||
|
||||
Let `B=/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build`.
|
||||
The four `B/<series>/raw/{writer,reader}.json` files are readable. Their literal
|
||||
`slots` mappings locate the formal packages. Attempting `Path.read_bytes()` on
|
||||
every `journal.log`, `manifest.json`, `thumbnail.png`, and `world.bin` in those
|
||||
slots returned `PermissionError: [Errno 1] Operation not permitted`:
|
||||
|
||||
| Series | Slots per role | Unreadable original + copy members |
|
||||
| --- | ---: | ---: |
|
||||
| `p04-s1-native-r02` | 14 | 112 |
|
||||
| `p04-s2a-native-r03` | 1 | 8 |
|
||||
| `p04-s2b-native-r02` | 6 | 48 |
|
||||
| `p04-s3-native-r01` | 22 | 176 |
|
||||
|
||||
For example, this exact required S3 original cannot be read:
|
||||
|
||||
```text
|
||||
/Users/korizatou114/Library/Application Support/TheTree-PCK-tests-078167a92c6e4888a6b4ee4a005fd858-standard-runtime-writer/saves/p04_s3/B1-after/world.bin
|
||||
```
|
||||
|
||||
Its Reader counterpart is also inaccessible:
|
||||
|
||||
```text
|
||||
/Users/korizatou114/Library/Application Support/TheTree-PCK-tests-b1fd1956bd28493cbac1e00f3a775232-standard-runtime-reader/saves/p04_s3/B1-after/world.bin
|
||||
```
|
||||
|
||||
Searching each series directory for `world.bin` found only the unrelated
|
||||
`stage/tests/fixtures/required_kitchen_legacy14/formal-package/world.bin`.
|
||||
There is no `.review-material` directory in this checkout. Root result SHA
|
||||
fields cannot replace re-hashing the actual original and Reader member bytes.
|
||||
|
||||
Current-game source access also fails: reading the first source_before entry,
|
||||
`/Users/korizatou114/Projects/とりにく工房ゲーム制作部/AGENTS.md`, returned the same error.
|
||||
Each run's fixed_input_pins contains three inaccessible entries. The remaining
|
||||
entries re-hashed successfully (S1: 13, S2a: 14, S2b: 10, S3: 15).
|
||||
Each staged source tree has 1337 hashes matching source_before and one differing
|
||||
test script (`tests/s3/test_runtime_staff_assignment.gd`); this is not a proof
|
||||
that the current game's full 1338-file source set still matches.
|
||||
|
||||
Required unblock: make these declared original inputs readable to the worker,
|
||||
or supply an authorized, provenance-bound relocation of their full bytes,
|
||||
including both formal roles and the current-game source/fixed inputs. No new
|
||||
engine run is requested. Permission denial must not be interpreted as missing
|
||||
files, matching hashes, or failed product behavior.
|
||||
|
||||
## Evidence and remaining conditions
|
||||
|
||||
Readable raw locations were inspected under all four series, including
|
||||
runner-input/source_before, lifecycle receipts, raw inventory, and raw Writer
|
||||
and Reader records. D05 supplement bytes were read. ROOT result files were
|
||||
used to inspect locators, never as proof of passing conditions.
|
||||
|
||||
- S1: EV remains unverified in this run; no paid-service power claim.
|
||||
- S2a: midnight_settlement, midnight_replay, save_restore remain unverified.
|
||||
- S2b: night_staff_absent, overtime_task and same-minute rejection remain unverified.
|
||||
- S3: closure_release, exit_release, patience_release and B4 release-once remain unverified.
|
||||
- power_stop, destruction_stop, shift_completion, same_minute_replay remain unverified.
|
||||
- Existing C verification was neither changed nor rerun; no fresh C result is claimed.
|
||||
- Required native-Windows conditions remain pending individually; this run supplies no Windows proof.
|
||||
|
||||
Stage A remains pending: actual HEAD + filesystem candidate binding, the known
|
||||
`MAIN_ANCHOR` `9694...`, candidate_tree trust, and `3139` metadata holes are
|
||||
untouched. No final index or composition was generated.
|
||||
|
||||
## Footprint and verification
|
||||
|
||||
Read-only `df -h` reported about 3.1 GiB available. No evidence payload was
|
||||
copied. `B/audit-support/p04-raw-collect-r17/` did not exist at the access audit
|
||||
and was not created. A trustworthy collection footprint cannot be finalized
|
||||
without access to the required member bytes; no collector was run.
|
||||
|
||||
Only Python read/hash/access diagnostics were executed. Fixture suites were
|
||||
not run and are not reported as passing. `p04_native_series.py`,
|
||||
`collect_p04_native.py`, and `checker_series_fixtures.py` were not created, so
|
||||
there are no valid Root collector/new-fixture commands to advertise yet.
|
||||
Implementation and its required positive/adversarial checks remain outstanding
|
||||
after the required inputs become readable.
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
{"estimated_payload_bytes": 2970454, "files": 5, "available_disk_bytes": 4596576256, "large_payloads_copied": 0}
|
||||
{"collection": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build/audit-support/p04-raw-collect-r17", "manifest_sha256": "953a798831043abdf7d54909e8e041cb3fbe6d901ca8affa661ea28e62ca5975", "actual_payload_bytes": 2970454, "formal_acceptance": false}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"C_provenance_sha256": "51d746879b4197445a09cd44a88dd918c365fa98025d9cd652a740cee09a9040", "C_source_manifest_sha256": "dbaab3c293c191eb5f3aff34854945e2d564f824df50a92802b3a7eda55321a8", "error": "candidate binding stage A pending; uncollected original boundaries: power_stop, save_restore, shift_completion", "formal_acceptance": false, "machine_verified": false, "missing_conditions": ["power_stop", "save_restore", "shift_completion"], "nativeWindows": {"EV": "pending", "closure_release": "pending", "dependency_stop": "pending", "destruction_stop": "pending", "exit_release": "pending", "midnight_replay": "pending", "midnight_settlement": "pending", "night_staff_absent": "pending", "overtime_task": "pending", "patience_release": "pending", "paused_midnight": "pending", "power_stop": "pending", "recovery_completion": "pending", "same_minute_replay": "pending", "save_restore": "pending", "shift_completion": "pending"}, "relocation_manifest_sha256": "a15f29fa12537f6d8aaf7cabea390922a9f16e3eb9c10981e5742aafc8c4c8a8", "series_bindings": {"s1": "9792569af9c8ecef177ffdeb880b814fb2878231f23e77c3749e18c228ff0ceb", "s2a": "746b58cf1aff95b082c35f6cc2c29556f146a7c56cce0475f4f2c091168605a7", "s2b": "e6d5412c14809825cf368af1d93972bc9c4e41f4c09afa414732b007c9cc7589", "s3": "a9b4b80d54c18ae2a1dc82514052e22d6174ff79999c76fa622282709759b433"}, "series_manifest_sha256": "953a798831043abdf7d54909e8e041cb3fbe6d901ca8affa661ea28e62ca5975", "stage_A": "candidate binding stage A pending", "task_id": "P04-01", "verified_conditions": ["EV", "closure_release", "dependency_stop", "destruction_stop", "exit_release", "midnight_replay", "midnight_settlement", "night_staff_absent", "overtime_task", "patience_release", "paused_midnight", "recovery_completion", "same_minute_replay"]}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
{
|
||||
"s1": {
|
||||
"facts": {
|
||||
"paid_service_power_claim": false
|
||||
},
|
||||
"formal_acceptance": false,
|
||||
"owned_pids": [
|
||||
64745,
|
||||
64788,
|
||||
64818,
|
||||
64850
|
||||
],
|
||||
"phase_pairs": 0,
|
||||
"product_content": "5f6bc11d6bb6e92d22d787989b219fef3a435bc1",
|
||||
"raw_reference_count": 97,
|
||||
"relocated_reference_count": 1464,
|
||||
"relocation_manifest_sha256": "a15f29fa12537f6d8aaf7cabea390922a9f16e3eb9c10981e5742aafc8c4c8a8",
|
||||
"series": "s1",
|
||||
"source_files": 1338,
|
||||
"verified_conditions": [
|
||||
"EV"
|
||||
],
|
||||
"verifier_sha256": "61f6647be4d2ec95db0599b94a3c6acb5f91ca43501256548f5ae161ab2f0e4f"
|
||||
},
|
||||
"s2a": {
|
||||
"facts": {
|
||||
"paid_originals": {
|
||||
"completed_service_id": "service_00000027",
|
||||
"same_minute_replay": "request resend, not advance_to settlement replay",
|
||||
"shift_completion": "missing: zero SERVICE_SALE rows at 1080",
|
||||
"transaction_id": "ledger_00000085"
|
||||
},
|
||||
"payroll_gold": 255,
|
||||
"raw_getter_bytes_equal": false,
|
||||
"sales_gold": 25,
|
||||
"save_restore_gap": "Raw getter also changes typed arrays to untyped arrays; authorized normalization permits only StringName/String and ordering",
|
||||
"settlement_replay_is_request_resend": false,
|
||||
"upkeep_gold": 5
|
||||
},
|
||||
"formal_acceptance": false,
|
||||
"owned_pids": [
|
||||
85229,
|
||||
85263,
|
||||
85303,
|
||||
85425
|
||||
],
|
||||
"phase_pairs": 0,
|
||||
"product_content": "5f6bc11d6bb6e92d22d787989b219fef3a435bc1",
|
||||
"raw_reference_count": 486,
|
||||
"relocated_reference_count": 1362,
|
||||
"relocation_manifest_sha256": "a15f29fa12537f6d8aaf7cabea390922a9f16e3eb9c10981e5742aafc8c4c8a8",
|
||||
"series": "s2a",
|
||||
"source_files": 1338,
|
||||
"verified_conditions": [
|
||||
"midnight_replay",
|
||||
"midnight_settlement",
|
||||
"same_minute_replay"
|
||||
],
|
||||
"verifier_sha256": "61f6647be4d2ec95db0599b94a3c6acb5f91ca43501256548f5ae161ab2f0e4f"
|
||||
},
|
||||
"s2b": {
|
||||
"facts": {
|
||||
"same_minute_rejection": "MINUTE_ALREADY_APPLIED"
|
||||
},
|
||||
"formal_acceptance": false,
|
||||
"owned_pids": [
|
||||
98696,
|
||||
98744,
|
||||
98770,
|
||||
98810
|
||||
],
|
||||
"phase_pairs": 12,
|
||||
"product_content": "5f6bc11d6bb6e92d22d787989b219fef3a435bc1",
|
||||
"raw_reference_count": 163,
|
||||
"relocated_reference_count": 1397,
|
||||
"relocation_manifest_sha256": "a15f29fa12537f6d8aaf7cabea390922a9f16e3eb9c10981e5742aafc8c4c8a8",
|
||||
"series": "s2b",
|
||||
"source_files": 1338,
|
||||
"verified_conditions": [
|
||||
"night_staff_absent",
|
||||
"overtime_task"
|
||||
],
|
||||
"verifier_sha256": "61f6647be4d2ec95db0599b94a3c6acb5f91ca43501256548f5ae161ab2f0e4f"
|
||||
},
|
||||
"s3": {
|
||||
"facts": {
|
||||
"B1": {
|
||||
"amount": 0,
|
||||
"reservation_states": [
|
||||
"ACTIVE",
|
||||
"RELEASED"
|
||||
],
|
||||
"sales": 0
|
||||
},
|
||||
"B2a": {
|
||||
"amount": 25,
|
||||
"reservation_states": [
|
||||
"CONSUMED"
|
||||
],
|
||||
"sales": 1
|
||||
},
|
||||
"B2b": {
|
||||
"amount": 0,
|
||||
"reservation_states": [
|
||||
"RELEASED"
|
||||
],
|
||||
"sales": 0
|
||||
},
|
||||
"B3": {
|
||||
"amount": 0,
|
||||
"reservation_states": [
|
||||
"RELEASED"
|
||||
],
|
||||
"sales": 0
|
||||
},
|
||||
"B4a": {
|
||||
"amount": 25,
|
||||
"reservation_states": [
|
||||
"CONSUMED"
|
||||
],
|
||||
"sales": 1
|
||||
},
|
||||
"B4b": {
|
||||
"amount": 0,
|
||||
"reservation_states": [
|
||||
"RELEASED"
|
||||
],
|
||||
"sales": 0
|
||||
}
|
||||
},
|
||||
"formal_acceptance": false,
|
||||
"owned_pids": [
|
||||
10448,
|
||||
10493,
|
||||
10523,
|
||||
10578
|
||||
],
|
||||
"phase_pairs": 352,
|
||||
"product_content": "5f6bc11d6bb6e92d22d787989b219fef3a435bc1",
|
||||
"raw_reference_count": 891,
|
||||
"relocated_reference_count": 1530,
|
||||
"relocation_manifest_sha256": "a15f29fa12537f6d8aaf7cabea390922a9f16e3eb9c10981e5742aafc8c4c8a8",
|
||||
"series": "s3",
|
||||
"source_files": 1338,
|
||||
"verified_conditions": [
|
||||
"closure_release",
|
||||
"destruction_stop",
|
||||
"exit_release",
|
||||
"patience_release"
|
||||
],
|
||||
"verifier_sha256": "61f6647be4d2ec95db0599b94a3c6acb5f91ca43501256548f5ae161ab2f0e4f"
|
||||
}
|
||||
}
|
||||
+367
@@ -0,0 +1,367 @@
|
||||
{
|
||||
"formal_acceptance": false,
|
||||
"results": [
|
||||
{
|
||||
"class": "small_synthetic_positive",
|
||||
"result": "pass",
|
||||
"series": "shared"
|
||||
},
|
||||
{
|
||||
"class": "full_body_positive",
|
||||
"result": "pass",
|
||||
"series": "s1"
|
||||
},
|
||||
{
|
||||
"class": "wrong_source_pin",
|
||||
"error": "SHA drift: native /native/body.gd",
|
||||
"result": "rejected",
|
||||
"series": "s1"
|
||||
},
|
||||
{
|
||||
"class": "signature_only_pin",
|
||||
"error": "full-function-body pin mismatch",
|
||||
"result": "rejected",
|
||||
"series": "s1"
|
||||
},
|
||||
{
|
||||
"class": "symlink",
|
||||
"error": "symlink: /Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/P04-01-implement/docs/plans/original-completion-2026-10-02/execution/p04-01/.series-fixtures-ici55vnl/symlink.gd",
|
||||
"result": "rejected",
|
||||
"series": "s1"
|
||||
},
|
||||
{
|
||||
"class": "traversal",
|
||||
"error": "raw traversal",
|
||||
"result": "rejected",
|
||||
"series": "s1"
|
||||
},
|
||||
{
|
||||
"class": "unmapped_original_path",
|
||||
"error": "unmapped original path: /native/unknown.gd",
|
||||
"result": "rejected",
|
||||
"series": "s1"
|
||||
},
|
||||
{
|
||||
"class": "relocated_manifest_native_mismatch",
|
||||
"error": "byte length drift: native /native/body.gd",
|
||||
"result": "rejected",
|
||||
"series": "s1"
|
||||
},
|
||||
{
|
||||
"class": "relocated_byte_manifest_mismatch",
|
||||
"error": "SHA drift: relocation /native/body.gd",
|
||||
"result": "rejected",
|
||||
"series": "s1"
|
||||
},
|
||||
{
|
||||
"class": "JSON_only_getter_substitute",
|
||||
"error": "unsupported Variant flags 8827:25959 at 0",
|
||||
"result": "rejected",
|
||||
"series": "s1"
|
||||
},
|
||||
{
|
||||
"class": "forged_scalar",
|
||||
"error": "SHA drift: p04-s1-native-r02/raw/writer.json",
|
||||
"result": "rejected",
|
||||
"series": "s1"
|
||||
},
|
||||
{
|
||||
"class": "changed_raw_byte",
|
||||
"error": "SHA drift: p04-s1-native-r02/raw/writer.json",
|
||||
"result": "rejected",
|
||||
"series": "s1"
|
||||
},
|
||||
{
|
||||
"class": "formal_member_SHA_drift",
|
||||
"error": "SHA drift: p04-s1-native-r02/execution.json",
|
||||
"result": "rejected",
|
||||
"series": "s1"
|
||||
},
|
||||
{
|
||||
"class": "unowned_missing_PID_end",
|
||||
"error": "missing PID end",
|
||||
"result": "rejected",
|
||||
"series": "s1"
|
||||
},
|
||||
{
|
||||
"class": "altered_phase_pair",
|
||||
"error": "SHA drift: p04-s1-native-r02/raw/reader-landing-held.var",
|
||||
"result": "rejected",
|
||||
"series": "s1"
|
||||
},
|
||||
{
|
||||
"class": "relabeled_interruption_reason",
|
||||
"error": "SHA drift: p04-s1-native-r02/raw/writer-landing-depleted.json",
|
||||
"result": "rejected",
|
||||
"series": "s1"
|
||||
},
|
||||
{
|
||||
"class": "full_body_positive",
|
||||
"result": "pass",
|
||||
"series": "s2a"
|
||||
},
|
||||
{
|
||||
"class": "wrong_source_pin",
|
||||
"error": "SHA drift: native /native/body.gd",
|
||||
"result": "rejected",
|
||||
"series": "s2a"
|
||||
},
|
||||
{
|
||||
"class": "signature_only_pin",
|
||||
"error": "full-function-body pin mismatch",
|
||||
"result": "rejected",
|
||||
"series": "s2a"
|
||||
},
|
||||
{
|
||||
"class": "symlink",
|
||||
"error": "symlink: /Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/P04-01-implement/docs/plans/original-completion-2026-10-02/execution/p04-01/.series-fixtures-ici55vnl/symlink.gd",
|
||||
"result": "rejected",
|
||||
"series": "s2a"
|
||||
},
|
||||
{
|
||||
"class": "traversal",
|
||||
"error": "raw traversal",
|
||||
"result": "rejected",
|
||||
"series": "s2a"
|
||||
},
|
||||
{
|
||||
"class": "unmapped_original_path",
|
||||
"error": "unmapped original path: /native/unknown.gd",
|
||||
"result": "rejected",
|
||||
"series": "s2a"
|
||||
},
|
||||
{
|
||||
"class": "relocated_manifest_native_mismatch",
|
||||
"error": "byte length drift: native /native/body.gd",
|
||||
"result": "rejected",
|
||||
"series": "s2a"
|
||||
},
|
||||
{
|
||||
"class": "relocated_byte_manifest_mismatch",
|
||||
"error": "SHA drift: relocation /native/body.gd",
|
||||
"result": "rejected",
|
||||
"series": "s2a"
|
||||
},
|
||||
{
|
||||
"class": "JSON_only_getter_substitute",
|
||||
"error": "unsupported Variant flags 8827:25959 at 0",
|
||||
"result": "rejected",
|
||||
"series": "s2a"
|
||||
},
|
||||
{
|
||||
"class": "forged_scalar",
|
||||
"error": "SHA drift: p04-s2a-native-r03/raw/writer.json",
|
||||
"result": "rejected",
|
||||
"series": "s2a"
|
||||
},
|
||||
{
|
||||
"class": "changed_raw_byte",
|
||||
"error": "SHA drift: p04-s2a-native-r03/raw/writer.json",
|
||||
"result": "rejected",
|
||||
"series": "s2a"
|
||||
},
|
||||
{
|
||||
"class": "formal_member_SHA_drift",
|
||||
"error": "SHA drift: p04-s2a-native-r03/execution.json",
|
||||
"result": "rejected",
|
||||
"series": "s2a"
|
||||
},
|
||||
{
|
||||
"class": "unowned_missing_PID_end",
|
||||
"error": "missing PID end",
|
||||
"result": "rejected",
|
||||
"series": "s2a"
|
||||
},
|
||||
{
|
||||
"class": "altered_phase_pair",
|
||||
"error": "SHA drift: p04-s2a-native-r03/raw/reader-C3-recall.var",
|
||||
"result": "rejected",
|
||||
"series": "s2a"
|
||||
},
|
||||
{
|
||||
"class": "relabeled_interruption_reason",
|
||||
"error": "SHA drift: p04-s2a-native-r03/raw/writer-C1-midnight.json",
|
||||
"result": "rejected",
|
||||
"series": "s2a"
|
||||
},
|
||||
{
|
||||
"class": "full_body_positive",
|
||||
"result": "pass",
|
||||
"series": "s2b"
|
||||
},
|
||||
{
|
||||
"class": "wrong_source_pin",
|
||||
"error": "SHA drift: native /native/body.gd",
|
||||
"result": "rejected",
|
||||
"series": "s2b"
|
||||
},
|
||||
{
|
||||
"class": "signature_only_pin",
|
||||
"error": "full-function-body pin mismatch",
|
||||
"result": "rejected",
|
||||
"series": "s2b"
|
||||
},
|
||||
{
|
||||
"class": "symlink",
|
||||
"error": "symlink: /Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/P04-01-implement/docs/plans/original-completion-2026-10-02/execution/p04-01/.series-fixtures-ici55vnl/symlink.gd",
|
||||
"result": "rejected",
|
||||
"series": "s2b"
|
||||
},
|
||||
{
|
||||
"class": "traversal",
|
||||
"error": "raw traversal",
|
||||
"result": "rejected",
|
||||
"series": "s2b"
|
||||
},
|
||||
{
|
||||
"class": "unmapped_original_path",
|
||||
"error": "unmapped original path: /native/unknown.gd",
|
||||
"result": "rejected",
|
||||
"series": "s2b"
|
||||
},
|
||||
{
|
||||
"class": "relocated_manifest_native_mismatch",
|
||||
"error": "byte length drift: native /native/body.gd",
|
||||
"result": "rejected",
|
||||
"series": "s2b"
|
||||
},
|
||||
{
|
||||
"class": "relocated_byte_manifest_mismatch",
|
||||
"error": "SHA drift: relocation /native/body.gd",
|
||||
"result": "rejected",
|
||||
"series": "s2b"
|
||||
},
|
||||
{
|
||||
"class": "JSON_only_getter_substitute",
|
||||
"error": "unsupported Variant flags 8827:25959 at 0",
|
||||
"result": "rejected",
|
||||
"series": "s2b"
|
||||
},
|
||||
{
|
||||
"class": "forged_scalar",
|
||||
"error": "SHA drift: p04-s2b-native-r02/raw/writer.json",
|
||||
"result": "rejected",
|
||||
"series": "s2b"
|
||||
},
|
||||
{
|
||||
"class": "changed_raw_byte",
|
||||
"error": "SHA drift: p04-s2b-native-r02/raw/writer.json",
|
||||
"result": "rejected",
|
||||
"series": "s2b"
|
||||
},
|
||||
{
|
||||
"class": "formal_member_SHA_drift",
|
||||
"error": "SHA drift: p04-s2b-native-r02/execution.json",
|
||||
"result": "rejected",
|
||||
"series": "s2b"
|
||||
},
|
||||
{
|
||||
"class": "unowned_missing_PID_end",
|
||||
"error": "missing PID end",
|
||||
"result": "rejected",
|
||||
"series": "s2b"
|
||||
},
|
||||
{
|
||||
"class": "altered_phase_pair",
|
||||
"error": "SHA drift: p04-s2b-native-r02/raw/reader-night_absent-1080-before-facility.var",
|
||||
"result": "rejected",
|
||||
"series": "s2b"
|
||||
},
|
||||
{
|
||||
"class": "relabeled_interruption_reason",
|
||||
"error": "SHA drift: p04-s2b-native-r02/raw/writer-night_absent-1081.json",
|
||||
"result": "rejected",
|
||||
"series": "s2b"
|
||||
},
|
||||
{
|
||||
"class": "full_body_positive",
|
||||
"result": "pass",
|
||||
"series": "s3"
|
||||
},
|
||||
{
|
||||
"class": "wrong_source_pin",
|
||||
"error": "SHA drift: native /native/body.gd",
|
||||
"result": "rejected",
|
||||
"series": "s3"
|
||||
},
|
||||
{
|
||||
"class": "signature_only_pin",
|
||||
"error": "full-function-body pin mismatch",
|
||||
"result": "rejected",
|
||||
"series": "s3"
|
||||
},
|
||||
{
|
||||
"class": "symlink",
|
||||
"error": "symlink: /Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/P04-01-implement/docs/plans/original-completion-2026-10-02/execution/p04-01/.series-fixtures-ici55vnl/symlink.gd",
|
||||
"result": "rejected",
|
||||
"series": "s3"
|
||||
},
|
||||
{
|
||||
"class": "traversal",
|
||||
"error": "raw traversal",
|
||||
"result": "rejected",
|
||||
"series": "s3"
|
||||
},
|
||||
{
|
||||
"class": "unmapped_original_path",
|
||||
"error": "unmapped original path: /native/unknown.gd",
|
||||
"result": "rejected",
|
||||
"series": "s3"
|
||||
},
|
||||
{
|
||||
"class": "relocated_manifest_native_mismatch",
|
||||
"error": "byte length drift: native /native/body.gd",
|
||||
"result": "rejected",
|
||||
"series": "s3"
|
||||
},
|
||||
{
|
||||
"class": "relocated_byte_manifest_mismatch",
|
||||
"error": "SHA drift: relocation /native/body.gd",
|
||||
"result": "rejected",
|
||||
"series": "s3"
|
||||
},
|
||||
{
|
||||
"class": "JSON_only_getter_substitute",
|
||||
"error": "unsupported Variant flags 8827:25959 at 0",
|
||||
"result": "rejected",
|
||||
"series": "s3"
|
||||
},
|
||||
{
|
||||
"class": "forged_scalar",
|
||||
"error": "SHA drift: p04-s3-native-r01/raw/writer.json",
|
||||
"result": "rejected",
|
||||
"series": "s3"
|
||||
},
|
||||
{
|
||||
"class": "changed_raw_byte",
|
||||
"error": "SHA drift: p04-s3-native-r01/raw/writer.json",
|
||||
"result": "rejected",
|
||||
"series": "s3"
|
||||
},
|
||||
{
|
||||
"class": "formal_member_SHA_drift",
|
||||
"error": "SHA drift: p04-s3-native-r01/execution.json",
|
||||
"result": "rejected",
|
||||
"series": "s3"
|
||||
},
|
||||
{
|
||||
"class": "unowned_missing_PID_end",
|
||||
"error": "missing PID end",
|
||||
"result": "rejected",
|
||||
"series": "s3"
|
||||
},
|
||||
{
|
||||
"class": "altered_phase_pair",
|
||||
"error": "SHA drift: p04-s3-native-r01/raw/reader-B1-phase-960-TIME_BOUNDARIES.var",
|
||||
"result": "rejected",
|
||||
"series": "s3"
|
||||
},
|
||||
{
|
||||
"class": "relabeled_interruption_reason",
|
||||
"error": "SHA drift: p04-s3-native-r01/raw/writer-B1-after.json",
|
||||
"result": "rejected",
|
||||
"series": "s3"
|
||||
}
|
||||
],
|
||||
"scope": "file-only Stage R controls; immutable descriptor and primitive negatives"
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
# REPAIR18 — executable Stage R, blocked on remaining conditions
|
||||
|
||||
This is **not accepted or completed**. Executable R artifacts and a reference-only
|
||||
collection were produced and the file-only checks were run. The whole gate
|
||||
fails. Original completion credit remains **13/88**, with no additional credit.
|
||||
The repair counter and `max_repair_attempts: null` were not changed.
|
||||
|
||||
## Scope and preservation
|
||||
|
||||
- Checkout: `accd07c91d4e34d9b7c7fc20e389df2424b167e1`; registered base:
|
||||
`deb1bcb2374eb378b840ca345db952bd8572cb6e`.
|
||||
- Changed `tools/check_p04_order_evidence.py` only to add R composition and its
|
||||
separate read-only CLI, call composition from the whole missing-check, and
|
||||
retain an unconditional Stage A failure.
|
||||
- Created `p04_native_series.py`, `collect_p04_native.py`,
|
||||
`checker_series_fixtures.py`, this status, and the four output files listed below.
|
||||
- All existing checker functions except `verify` and `main` were compared with
|
||||
HEAD and are byte-unchanged, including `verify_native` and all C helpers.
|
||||
All three existing fixture files are byte-unchanged. Their failures were not
|
||||
hidden by editing assertions or Stage A code.
|
||||
- `REPAIR17_STATUS.md` was never written. Its SHA256 is
|
||||
`6497c335739f266c9026aa6499cf5c49ae80d590d6aa2d0f90d32adc08018061`.
|
||||
- No git writes, new worktree/branch, agents, Runner operations, engine runs,
|
||||
full regression, permission requests, production edits, input edits, or final
|
||||
`B/p04-evidence/index.json` writes occurred. This did not revalidate accd.
|
||||
|
||||
## Inputs and actual collection
|
||||
|
||||
Here `B=/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build`,
|
||||
`R=B/audit-support/p04-r17-full-inputs-r01`, and
|
||||
`E=docs/plans/original-completion-2026-10-02/execution/p04-01`.
|
||||
|
||||
The first input identity check verified `R/MANIFEST.json`: **1,244,135 bytes**,
|
||||
SHA256 `a15f29fa12537f6d8aaf7cabea390922a9f16e3eb9c10981e5742aafc8c4c8a8`.
|
||||
All **1,721** relocated members were used and rehashed across the series,
|
||||
covering 71,488,772 logical bytes. All four complete native `source_before`
|
||||
sets equal the supplied 1,338-file product set at content anchor
|
||||
`5f6bc11d6bb6e92d22d787989b219fef3a435bc1`. The one native QA override is checked
|
||||
separately against its supplied probe bytes; it is not represented as product
|
||||
content. Full-function-body pins include trailing comments and declarations.
|
||||
|
||||
External original paths remain provenance strings. The resolver uses exact
|
||||
`original_to_relocated` keys, and checks each used file against both native
|
||||
descriptors and relocation descriptors. There is no prefix substitution or
|
||||
external-path fallback. Formal slot membership, directory membership, separate
|
||||
Writer/Reader paths and inodes, symlinks, traversal and duplicates are checked.
|
||||
All **344** formal members in **86** original/Reader slots were rehashed.
|
||||
|
||||
The collector created only `B/audit-support/p04-raw-collect-r17/`. It did not
|
||||
exist before creation. It contains four series reference manifests and one
|
||||
collection manifest; no large evidence snapshots were copied.
|
||||
|
||||
Actual collector output (exit 0):
|
||||
|
||||
```json
|
||||
{"estimated_payload_bytes":2970454,"files":5,"available_disk_bytes":4596576256,"large_payloads_copied":0}
|
||||
{"actual_payload_bytes":2970454,"manifest_sha256":"953a798831043abdf7d54909e8e041cb3fbe6d901ca8affa661ea28e62ca5975","formal_acceptance":false}
|
||||
```
|
||||
|
||||
The estimate was printed before directory creation or output writes. The
|
||||
collection is deterministic and refuses an existing destination on rerun.
|
||||
Per-series manifests bind native raw references, relocation references, and
|
||||
verifier bytes by SHA. Composition reruns C and all series verifiers and
|
||||
compares the recomputed results and references with those manifests; editing a
|
||||
`verified_conditions` list cannot add credit.
|
||||
|
||||
## Conditions derived from literal raw
|
||||
|
||||
| Verifier | Native raw | Relocated formal members | Conditions proved |
|
||||
| --- | --- | --- | --- |
|
||||
| S1 | `B/p04-s1-native-r02/raw/` | `R/formal/p04-s1-native-r02/{writer,reader}/` | `EV` |
|
||||
| S2a | `B/p04-s2a-native-r03/raw/` | `R/formal/p04-s2a-native-r03/{writer,reader}/C3/` | `midnight_settlement`, `midnight_replay` |
|
||||
| S2b | `B/p04-s2b-native-r02/raw/` | `R/formal/p04-s2b-native-r02/{writer,reader}/` | `night_staff_absent`, `overtime_task` |
|
||||
| S3 | `B/p04-s3-native-r01/raw/` | `R/formal/p04-s3-native-r01/{writer,reader}/` | `closure_release`, `exit_release`, `patience_release`, `destruction_stop` (B4b) |
|
||||
| Paid/resend supplement, collected with S2a | `B/audit-support/p04-current-native-series-r01/p04-idempotency-paid-midnight-native-r01/order-evidence/` | Manifest pin resolved through `R/MANIFEST.json`, full raw already inside B | `same_minute_replay` (request resend) |
|
||||
| Unchanged C | `B/audit-support/native-first-current/` | Existing C bundle, unchanged | `dependency_stop`, `recovery_completion`, `paused_midnight` |
|
||||
|
||||
All product reads resolve to `R/product/` through manifest keys. Fixed-input
|
||||
relocations and every original formal provenance string are retained in the
|
||||
four generated series manifests; they are not replaced with guessed paths.
|
||||
|
||||
Owned import/parse/Writer/Reader PIDs checked against starts, ends, owners,
|
||||
commands, raw stream hashes, exit gates and cleanup/guard records:
|
||||
|
||||
| Series | PIDs | Formal slots per role | Exact raw phase pairs |
|
||||
| --- | --- | ---: | ---: |
|
||||
| S1 | 64745, 64788, 64818, 64850 | 14 | 0 (14 exact typed snapshot restore points instead) |
|
||||
| S2a | 85229, 85263, 85303, 85425 | 1 | 0 (typed settlement/replay and formal Runtime restore comparisons) |
|
||||
| S2b | 98696, 98744, 98770, 98810 | 6 | 12 |
|
||||
| S3 | 10448, 10493, 10523, 10578 | 22 | 352 JSON/typed phase files |
|
||||
|
||||
- S1 checks task/trip/resource identity, safe landing, no-safe-landing hold,
|
||||
preserved reservation identity and release. It makes no paid-service power claim.
|
||||
- S2a derives a 25G sale/one transaction, 255G payroll/one transaction
|
||||
(Akane 120, Bardo 135 joined to actual staff and shifts), and 5G upkeep in
|
||||
three rows/one transaction. `advance_to(1440)` has processed minutes 0 and
|
||||
daily closes 0, with entire typed Runtime bytes unchanged. Formal Reader
|
||||
Runtime restore and recall bytes match exactly.
|
||||
- The supplemental request-resend raw preserves typed economy, facilities,
|
||||
inventory, guests and staff at the same minute, and joins the one completed
|
||||
service `service_00000027` to sale transaction `ledger_00000085`. This is not
|
||||
settlement-replay proof; S2a supplies that distinct proof.
|
||||
- S2b derives `STAFF_UNAVAILABLE`/`WAITING_FOR_STAFF`, the absent night workers,
|
||||
overtime task/staff/shift joins and one duty minute. Its same-minute call is
|
||||
rejected as `MINUTE_ALREADY_APPLIED`, with entire typed Runtime unchanged.
|
||||
- S3 retains `RELATED_FACILITY_INACTIVE` in B1, never relabeling it `NO_POWER`.
|
||||
The directly closed facility retains its started service while the dependent
|
||||
facility interruption pauses it; the queued reservation is released.
|
||||
B2a completes/consumes once; B2b and B3 release without a sale.
|
||||
B4b's queued event completes, durability becomes 0/`BROKEN`, then the service
|
||||
fails with `BROKEN` at 961, releases its reservation and has zero sale rows.
|
||||
|
||||
## Unresolved raw/specification gaps
|
||||
|
||||
1. **`power_stop` missing.** EV evidence is not a paid-service power proof;
|
||||
B1 is a dependency interruption. No relabeling or invented D05 rule was used.
|
||||
2. **`shift_completion` missing.** The actual supplied paid-shift originals at
|
||||
`B/audit-support/p04-current-native-series-r01/p04-paid-shift-midnight-native-r01/order-evidence/`
|
||||
have zero `SERVICE_SALE` rows in all three 17:59/completion-phase/18:00
|
||||
snapshots. The `paid` filenames do not establish a charged completion at 1080.
|
||||
3. **`save_restore` withheld under the fixed normalization restriction.** The
|
||||
entire formal Runtime does restore exactly. However, the getter comparison
|
||||
changes not only StringName/String and dictionary order but typed arrays to
|
||||
untyped arrays. Examples are `/anomalies`, `/anomalies/0/allocations`,
|
||||
`/anomalies/0/unallocated_by_staff`, `/anomalies/0/facility_ids`,
|
||||
`/settlement/input_payments` and `/settlement/rows`. Native array headers
|
||||
include `1c000100...` versus `1c000000...`. The raw getter byte inequality is
|
||||
preserved and parsed from `.var`; JSON is not substituted. Normalization was
|
||||
not broadened beyond the instruction, so this condition remains missing.
|
||||
4. **B4a zero-charge assertion is unsupported.**
|
||||
`B/p04-s3-native-r01/raw/writer-B4a-next.{json,var}` contains one 25G sale at
|
||||
960 and a completed service with consumed reservation. B4b has zero sales.
|
||||
The implementation records both facts, rather than declaring B4a charge 0.
|
||||
Destruction-stop credit above is specifically the B4b failed-service branch.
|
||||
|
||||
D05 approval and supplement full bytes in `p04-whole-closure-r01` were read.
|
||||
They approve dependency closure and the 18:00 ordering choice, not an invented
|
||||
blanket policy for all D05 domains. The supplied raw discrepancies require
|
||||
owner-level resolution or appropriate evidence; this worker did not amend scope.
|
||||
|
||||
Each of these native-Windows conditions remains individually **pending**:
|
||||
`EV`, `power_stop`, `destruction_stop`, `dependency_stop`, `shift_completion`,
|
||||
`recovery_completion`, `same_minute_replay`, `closure_release`, `exit_release`,
|
||||
`patience_release`, `paused_midnight`, `midnight_settlement`, `midnight_replay`,
|
||||
`save_restore`, `night_staff_absent`, `overtime_task`. The machine output also
|
||||
contains an explicit per-condition pending map.
|
||||
|
||||
## Actual validation outputs
|
||||
|
||||
- Static Python compilation: **4 files passed** (in-memory compile; no engine).
|
||||
- Existing C fixture: **exit 0**, 5 positive controls passed and 25 negatives
|
||||
rejected. `verify_native` reports 10 owned processes and its original three
|
||||
conditions. No C condition names were edited.
|
||||
- Existing negative fixture: **exit 1**, at `synthetic_scalar_only_rejected`:
|
||||
`AssertionError: ('synthetic_scalar_only_rejected', {'task_id': 'P04-01', 'machine_verified': False, 'formal_acceptance': False, 'error': "'product_binding'"})`.
|
||||
- Existing applicability fixture: **exit 1**:
|
||||
`AssertionError: negative fixture accepted: wrong_tested_revision`.
|
||||
These two suites therefore could not be confirmed to pass/reject as requested.
|
||||
Their files and the functions involved were unchanged; they were not repaired
|
||||
by touching the deferred A holes, and no baseline rerun is claimed.
|
||||
- New series fixtures: **exit 0**, **5 small positive controls and 56 rejected
|
||||
negatives**, 14 negative classes per series. Controls cover immutable raw and
|
||||
descriptor overlays, source/body pins, PID endings, paths, relocation byte
|
||||
drift and typed-vs-JSON rejection. Immutable descriptor hashes are deliberately
|
||||
not refreshed; the tests do not claim to be new engine evidence.
|
||||
- Real series verifiers ran successfully during collection and were rerun by
|
||||
composition. Outputs are recorded in `REPAIR18_REAL_SERIES_OUTPUT.json`.
|
||||
- Separate composition CLI: **exit 1**, with 13 raw-derived conditions and:
|
||||
|
||||
```text
|
||||
candidate binding stage A pending; uncollected original boundaries: power_stop, save_restore, shift_completion
|
||||
```
|
||||
|
||||
Full new outputs: `REPAIR18_COLLECTOR_OUTPUT.jsonl`,
|
||||
`REPAIR18_SERIES_FIXTURE_OUTPUT.json`, `REPAIR18_REAL_SERIES_OUTPUT.json`,
|
||||
`REPAIR18_COMBINED_OUTPUT.json`. The 13 verified boundary conditions are not
|
||||
13 newly accepted units and do not change the original 13/88 credit.
|
||||
|
||||
## Exact Root commands
|
||||
|
||||
Run from this existing checkout. These commands perform file-only work:
|
||||
|
||||
```sh
|
||||
B=/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build
|
||||
E=docs/plans/original-completion-2026-10-02/execution/p04-01
|
||||
python3 -B "$E/collect_p04_native.py" --build "$B"
|
||||
python3 -B "$E/checker_series_fixtures.py" --build "$B"
|
||||
python3 -B "$E/p04_native_series.py" --build "$B"
|
||||
python3 -B "$E/checker_negative_fixtures.py"
|
||||
python3 -B "$E/checker_applicability_fixtures.py" --native-bundle "$B/audit-support/native-first-current"
|
||||
python3 -B "$E/checker_native_fixtures.py" "$B/audit-support/native-first-current"
|
||||
BUILD_OUTPUT_DIR="$B" python3 -B tools/check_p04_order_evidence.py --verify-series-only
|
||||
```
|
||||
|
||||
The collector command above was already executed successfully; another execution
|
||||
must refuse the now-existing destination. Review the existing collection rather
|
||||
than overwriting it. The final command is intentionally failing; it does not
|
||||
need or generate a final index and never performs candidate composition.
|
||||
|
||||
## Stage A and subsequent dependencies
|
||||
|
||||
Stage A remains pending: actual HEAD/filesystem candidate binding,
|
||||
`MAIN_ANCHOR` `9694...`, `candidate_tree` trust and the `3139` metadata holes
|
||||
are untouched. `--verify-only` still requires the actual final evidence index;
|
||||
its missing-check unions recomputed C/R results and still fails for any missing
|
||||
condition, and it cannot succeed while Stage A is pending.
|
||||
|
||||
Required sequence remains **R → A → final index → mandatory whole validation →
|
||||
independent review → ordinary integration**. Outstanding raw/specification gaps
|
||||
and the two existing fixture failures prevent representing this dispatch as a
|
||||
completed or accepted implementation.
|
||||
@@ -0,0 +1,58 @@
|
||||
# Repair19 persistence explanation corrigendum
|
||||
|
||||
This corrects the old Root explanation that the live day-summary comparison
|
||||
differed only in StringName/String and dictionary order. That explanation missed
|
||||
generic Array headers. It also corrects the superseded proposal to explain this
|
||||
path using `_to_snapshot_variant`.
|
||||
|
||||
The exact relocated source is
|
||||
`B/audit-support/p04-r17-full-inputs-r01/product/scripts/operations/operations_coordinator.gd`.
|
||||
Its 130891 bytes have SHA256
|
||||
`871a248ee101b6df543fd5cf0440b5d4b0f02c21fa145a99f0687888c1bc6f90`.
|
||||
Full bodies, including bytes through the next named function, were read directly
|
||||
and compared with `T/ROOT_ACTUAL_PERSISTENCE_CALL_CHAIN.json`, where
|
||||
`T=B/audit-support/p04-repair19-close-r01`. All five bodies, lengths and hashes
|
||||
match that supplied file:
|
||||
|
||||
| Function | Line | Bytes | SHA256 |
|
||||
| --- | ---: | ---: | --- |
|
||||
| `get_day_summary` | 577 | 153 | `449eef926054cf76c39d6b4c016da3d775d41f2b343b4f8ad57b62c594cb32bb` |
|
||||
| `create_snapshot` | 620 | 3988 | `8c6e4f000e6df6f0f996445a57c4c17223cae4e1df57a9c2e3a605cf4d9f5309` |
|
||||
| `_snapshot_root_report` | 2943 | 2062 | `96084d76beeac11d165753a82160f789cd47b4dec4b6bb8cd6599a4f5ed08efe` |
|
||||
| `_visit_snapshot_tree` | 2992 | 3045 | `70792369c05882b813632a3c96f934928e9a089d420700cfc0d4c8fa971972a0` |
|
||||
| `_to_snapshot_variant` | 3083 | 550 | `f7db3cf2d495bb117fc7f6795633daa272c03d8eb7f99977e462e0a9df20cd8a` |
|
||||
|
||||
`get_day_summary` returns `_day_summaries[day].duplicate(true)` directly. It
|
||||
exposes an internal API representation; the product getter need not change.
|
||||
`create_snapshot` first validates with capture=true. In that pass,
|
||||
`day_summaries` is not among the reusable encoded fields. It then assembles
|
||||
`saved_days` and calls `_snapshot_root_report(raw_snapshot, field_cache, false)`.
|
||||
That report invokes `_visit_snapshot_tree` with encoding enabled. The visitor
|
||||
creates untyped arrays, converts StringName to String, stringifies permitted
|
||||
dictionary keys and sorts the resulting String keys with native sorting.
|
||||
|
||||
The separate `_to_snapshot_variant` helper is not on this call chain. Its
|
||||
`sort_custom(_variant_string_less)`, lack of finite-float/budget checks and
|
||||
pass-through of unsupported types prevent using it as a general equivalent.
|
||||
|
||||
The supplied `T/ROOT_S2A_TYPED_ARRAY_HEADER_AUDIT.json` lists the following six
|
||||
changes. These are explicitly attributed to that diagnostic; they have not been
|
||||
independently rerun by a repair19 fixture in this dispatch.
|
||||
|
||||
| Path below `day_summary` | Writer header | Writer element kind | Reader header | Length |
|
||||
| --- | --- | --- | --- | ---: |
|
||||
| `/anomalies` | `1c000100` | Dictionary (27) | `1c000000` | 1 |
|
||||
| `/anomalies/0/allocations` | `1c000100` | Dictionary (27) | `1c000000` | 1 |
|
||||
| `/anomalies/0/facility_ids` | `1c000100` | String (4) | `1c000000` | 3 |
|
||||
| `/anomalies/0/unallocated_by_staff` | `1c000100` | Dictionary (27) | `1c000000` | 0 |
|
||||
| `/settlement/input_payments` | `1c000100` | Dictionary (27) | `1c000000` | 5 |
|
||||
| `/settlement/rows` | `1c000100` | Dictionary (27) | `1c000000` | 5 |
|
||||
|
||||
Every listed Reader array is untyped. Raw getter byte equality remains **false**.
|
||||
This diagnostic is separate from the source-defined persisted snapshot.
|
||||
Neither this explanation nor an optional projection may weaken exact native
|
||||
expected/restored typed-byte comparison, entire Runtime equality, formal 4+4
|
||||
member equality, settlement counts, duplicate-payment checks or replay checks.
|
||||
No projection implementation or new save_restore verification is claimed here.
|
||||
The executable R18 normalization gate is still unchanged, so save_restore is
|
||||
still open pending the exact encoder port and its required validation.
|
||||
@@ -0,0 +1,78 @@
|
||||
# Repair19 — incomplete; execution constraint clarification pending
|
||||
|
||||
This is not a ready submission and not formal acceptance. Original credit stays
|
||||
13/88. All 15 conditions plus EV retain their existing scope; nativeWindows
|
||||
remains pending for each. No product, acceptance, domain or outcome was changed.
|
||||
|
||||
## Blocking instruction conflict
|
||||
|
||||
The recorded repair19 direction requires local fixture reruns, isolated temp-Git
|
||||
negative and positive controls, and removing the Stage-A refusal only after
|
||||
those tests pass. The final worker instructions prohibit Git writes and say
|
||||
tests are formally executed by Runner. This dispatch has requested clarification
|
||||
on whether that prohibition exempts the specifically requested file-only and
|
||||
isolated temp-Git fixtures. There is no answer at the time of this report.
|
||||
No exemption has been assumed. This is an execution-constraint blocker, not a
|
||||
claim that pending formal acceptance itself blocks implementation.
|
||||
|
||||
## Changes actually made in this dispatch
|
||||
|
||||
- `p04_native_series.py`: retain both JSON and Variant Writer/Reader phase-byte
|
||||
comparisons, but increment the S3 pair metric only for `.var`. The requested
|
||||
interpretation is 352 checked file pairs representing 176 observations. A
|
||||
new execution output confirming that count has not been generated.
|
||||
- `p04_native_series.py`: add the B1 `power_stop` join for the five observations
|
||||
at 959–963, positive finite original reserve, actual public mana and power
|
||||
flags, direct closure, same service identity/progress/reservations, queued B
|
||||
release/stock/patience, recovery payment and unchanged next ledger. Bind the
|
||||
saved 961 recovery command and the APPLY_COMMANDS phase. Preserve the actual
|
||||
`RELATED_FACILITY_INACTIVE` reason. This code is untested; its addition to the
|
||||
verifier's returned conditions is conditional on every assertion succeeding
|
||||
when Runner executes it. No successful power_stop result is claimed here.
|
||||
- `REPAIR19_CORRIGENDUM.md`: correct the StringName/order-only explanation,
|
||||
distinguish raw getter from public persistence, list all six supplied typed
|
||||
Array header changes, and record the actual source call-chain full-body pins.
|
||||
|
||||
## Static observations and preservation
|
||||
|
||||
- In-memory Python `compile` of the edited series module succeeded. No fixture,
|
||||
native, Godot, full-regression or Runner run occurred.
|
||||
- The exact relocated operations source was read. All five complete function
|
||||
bodies, lengths and hashes equal
|
||||
`B/audit-support/p04-repair19-close-r01/ROOT_ACTUAL_PERSISTENCE_CALL_CHAIN.json`.
|
||||
The hash table and exact call-chain explanation are in the corrigendum.
|
||||
- The checker region from `NATIVE_RUNS` through `verify_native`, inclusive of
|
||||
the C constants and helpers, equals its HEAD bytes. Region SHA256:
|
||||
`5d778b3cfab7c139ca03812489508ce9aae90c1d17f8c2249c340f30d1496b3b`.
|
||||
- No R17/R18 output or status file was edited. The existing uncommitted R18
|
||||
checker changes remain. No Git write, agent, integration or Runner operation
|
||||
occurred. No native evidence was created or modified.
|
||||
- No collector was run. Both the existing r17 collection and the required fresh
|
||||
r19 destination remain untouched by this dispatch. Since the series verifier
|
||||
bytes changed, the old r17 collection cannot validate these new verifier bytes;
|
||||
it must never be overwritten or represented as a repair19 collection.
|
||||
|
||||
## Remaining implementation and validation
|
||||
|
||||
Repair19 is incomplete. R2's C-backed paid shift join, R3's exact encoder port
|
||||
and fixtures, Stage A's actual Git-role verifier, new index generator, fixture
|
||||
protocol repairs and all requested fixture executions remain to be completed.
|
||||
The old Stage-A unconditional refusal remains in place. The old
|
||||
`verify_product_target` has not been edited or replaced in this dispatch.
|
||||
The 9b1a18c anchor resolves to
|
||||
`9b1a18c1eb25ca1300dcd59da78423c0dda2f498`; the old checker constant has not yet
|
||||
been changed as part of a complete Stage-A repair.
|
||||
|
||||
No new output JSON is reported, and no old fixture success is reused as success
|
||||
for the edited module. B4a/B4b semantics, frozen save_restore measurements and
|
||||
its current open result remain unchanged. The 25 C negatives and 56 series
|
||||
negatives have not been rerun.
|
||||
|
||||
There are no valid repair19 index-generation or submitted/merged-candidate Root
|
||||
commands yet: the required generator and role verifier are not implemented.
|
||||
Providing plausible commands for nonexistent functionality would be misleading.
|
||||
The completed follow-up must supply those exact commands after implementing and
|
||||
validating the protocol, retain all existing outputs, and collect exclusively
|
||||
into the fresh `B/audit-support/p04-raw-collect-r19` destination.
|
||||
|
||||
Here `B=/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build`.
|
||||
@@ -0,0 +1,40 @@
|
||||
# Repair20 persistence corrigendum
|
||||
|
||||
The old Root explanation, “StringName/order only”, omitted six generic Array
|
||||
header changes. `get_day_summary` returns the internal day's `duplicate(true)`;
|
||||
it does not return the canonical persisted representation. Its raw Writer and
|
||||
Reader bytes remain unequal. All original raw files and values are retained.
|
||||
No product getter change is proposed.
|
||||
|
||||
| Path under day_summary | Writer header | Element kind | Reader header | Length |
|
||||
| --- | --- | --- | --- | ---: |
|
||||
| anomalies | 1c000100 | Dictionary (27) | 1c000000 | 1 |
|
||||
| anomalies/0/allocations | 1c000100 | Dictionary (27) | 1c000000 | 1 |
|
||||
| anomalies/0/facility_ids | 1c000100 | String (4) | 1c000000 | 3 |
|
||||
| anomalies/0/unallocated_by_staff | 1c000100 | Dictionary (27) | 1c000000 | 0 |
|
||||
| settlement/input_payments | 1c000100 | Dictionary (27) | 1c000000 | 5 |
|
||||
| settlement/rows | 1c000100 | Dictionary (27) | 1c000000 | 5 |
|
||||
|
||||
These are the six entries in `T/ROOT_S2A_TYPED_ARRAY_HEADER_AUDIT.json`.
|
||||
The new code derives them again from typed nodes and compares the list to that
|
||||
diagnostic. This execution is **NOT RUN** in repair20.
|
||||
|
||||
The persistence chain is `create_snapshot` (620) →
|
||||
`_snapshot_root_report(raw_snapshot, field_cache, false)` (2943) →
|
||||
`_visit_snapshot_tree(..., encode=true)` (2992). The earlier capture=true pass
|
||||
does not cache encoded day_summaries. `_to_snapshot_variant` is not on this path.
|
||||
|
||||
The projection is confined to raw getter → persisted day1. It retains all
|
||||
scalar Variant bytes, including numeric type, rejects nonfinite floats and
|
||||
unsupported values, stringifies permitted keys, rejects canonical collisions,
|
||||
sorts String keys, rebuilds untyped Arrays, and enforces collection, depth, node,
|
||||
single-string and aggregate-string limits. Unsupported dictionary-key types
|
||||
are rejected, never silently normalized. The optional admitted subset does not
|
||||
include composite-key Godot stringification. No projection is allowed on the
|
||||
native persisted expected/restored comparison.
|
||||
|
||||
Full Runtime Writer/Reader/restored equality, formal 4+4 member equality,
|
||||
settlement/duplicate-payment checks and the actual advance_to replay checks
|
||||
remain independent strict requirements. If getter projection still differs
|
||||
from the native canonical_day1 bytes, `save_restore` remains open and reports
|
||||
that exact mismatch. No new successful equality is claimed without Root's run.
|
||||
@@ -0,0 +1,183 @@
|
||||
# Repair20 — source submission; tests NOT RUN
|
||||
|
||||
Source-only repair. No Git command, fixture, collector, series, Godot/native,
|
||||
Runner, agent, commit, branch or integration operation was executed. The only
|
||||
code validation performed was Python `compile(..., 'exec')` in memory, without
|
||||
imports/execution of the edited modules. No acceptance is claimed. Original
|
||||
credit remains 13/88; all 15 conditions + EV, seven domains and three safety
|
||||
outcomes retain their scope. nativeWindows remains pending for every condition.
|
||||
The next submission SHA is assigned by Runner and is not predeclared here.
|
||||
|
||||
Changed files:
|
||||
|
||||
- `tools/check_p04_order_evidence.py`: real checkout/role binding, isolated
|
||||
object stores under B, first-1080 R2 join, schema2 recomputation and r20 collection.
|
||||
- `p04_native_series.py`: B1 departure/despawn/full-state joins, explicit 352/176
|
||||
S3 checks, confined getter projection, persistence pins and six-header diagnostic.
|
||||
- `collect_p04_native.py`: exclusive new collection at
|
||||
`B/audit-support/p04-raw-collect-r20`.
|
||||
- `checker_negative_fixtures.py`: retain existing content-case IDs and meanings;
|
||||
isolate their content layer explicitly in the test harness, while the separate
|
||||
real-Git fixture exercises production role binding without a bypass.
|
||||
- New `build_p04_index.py`, `checker_role_fixtures.py`,
|
||||
`checker_encoder_fixtures.py`, `checker_join_fixtures.py`.
|
||||
- New `REPAIR20_STATUS.md` and `REPAIR20_CORRIGENDUM.md`.
|
||||
|
||||
In-memory compilation: all eleven Python files (checker plus ten modules in E)
|
||||
compile. This is syntax validation only; every test below is **NOT RUN**.
|
||||
The retained C region, `NATIVE_RUNS` through `verify_native`, is byte-for-byte
|
||||
equal to T's retained source, SHA256
|
||||
`5d778b3cfab7c139ca03812489508ce9aae90c1d17f8c2249c340f30d1496b3b`.
|
||||
`verify_product_target` itself is also byte-for-byte retained and no longer called.
|
||||
Existing repair17/18/19 status, corrigendum, raw outputs and T's retained archives
|
||||
were not modified. Existing C 25 negatives and series 56 negatives remain required.
|
||||
|
||||
## R3 source pins
|
||||
|
||||
Product source: `scripts/operations/operations_coordinator.gd`, 130891 bytes,
|
||||
SHA256 `871a248ee101b6df543fd5cf0440b5d4b0f02c21fa145a99f0687888c1bc6f90`.
|
||||
Runtime verification checks the complete bodies and cross-checks the actual
|
||||
body bytes, lengths and hashes against `T/ROOT_ACTUAL_PERSISTENCE_CALL_CHAIN.json`.
|
||||
|
||||
| Function | SHA256 |
|
||||
| --- | --- |
|
||||
| get_day_summary | 449eef926054cf76c39d6b4c016da3d775d41f2b343b4f8ad57b62c594cb32bb |
|
||||
| create_snapshot | 8c6e4f000e6df6f0f996445a57c4c17223cae4e1df57a9c2e3a605cf4d9f5309 |
|
||||
| _snapshot_root_report | 96084d76beeac11d165753a82160f789cd47b4dec4b6bb8cd6599a4f5ed08efe |
|
||||
| _visit_snapshot_tree | 70792369c05882b813632a3c96f934928e9a089d420700cfc0d4c8fa971972a0 |
|
||||
|
||||
The corrigendum lists all six Array header changes. Native persisted comparisons
|
||||
are direct typed-byte comparisons. Raw getter inequality remains diagnostic.
|
||||
The optional projection admits only the supported persistence subset; unsupported
|
||||
composite dictionary keys fail closed. Its negative controls include field loss,
|
||||
extra fields, both numeric-type directions, value changes, nonfinite floats,
|
||||
invalid keys, collisions, unsaveable values, wrong call chain and use on native
|
||||
persisted comparisons. The positive changes only Array headers and StringName.
|
||||
|
||||
## Condition disposition on this untested source
|
||||
|
||||
No condition is newly proven by this dispatch. All tests are **NOT RUN**.
|
||||
|
||||
| Condition | State and reason pending Root execution |
|
||||
| --- | --- |
|
||||
| power_stop | Open: new B1 959–963 join; real B at 959/960, normal recorded departure then absence, unchanged one-time release, stock 1, wallet 50, no B charge; recovery 25 at 962 and no extra 963 charge. |
|
||||
| destruction_stop | Pending: retained S3 B4b BROKEN failure and zero-sale/release predicates. |
|
||||
| dependency_stop | Pending: unchanged C paused service identity/progress/reservation proof. |
|
||||
| shift_completion | Open: new C first-1080 join; PIDs 77462/77504/77536 ended, 1338-file content 5f, 44→45, ledger_00000006/25G, DAY2 present before handover and zero after, no extra at 1081. |
|
||||
| recovery_completion | Pending: unchanged C recovery transaction/idempotency join. |
|
||||
| same_minute_replay | Pending: retained real paid request-resend join; never substituted for settlement replay. |
|
||||
| closure_release | Pending: retained S3 closure/release plus despawn-aware B1 checks. |
|
||||
| exit_release | Pending: retained S3 B2 release/completion ordering. |
|
||||
| patience_release | Pending: retained S3 B3 patience expiration and release. |
|
||||
| paused_midnight | Pending: unchanged C paused-midnight series. |
|
||||
| midnight_settlement | Pending: retained S2a nonzero sales/payroll/upkeep/duty joins. |
|
||||
| midnight_replay | Pending: actual advance_to replay, zero processed minutes and zero daily closes, exact Runtime bytes. |
|
||||
| save_restore | Open: direct full typed Runtime and formal 4+4 equality remain mandatory; optional getter projection must equal actual persisted day1. A remaining mismatch is explicitly reported, never projected out of native restore. |
|
||||
| night_staff_absent | Pending: retained S2b WAITING_FOR_STAFF at 1081. |
|
||||
| overtime_task | Pending: retained S2b interrupt-level-zero task/staff/shift and once-only duty joins. |
|
||||
| EV | Pending: retained S1 reachable reserved staff power-stop proof and pinned static chain. |
|
||||
|
||||
Stage A is implemented but **NOT RUN**. Submission is inferred from actual Git
|
||||
ancestry and scoped diff; the composed product is labelled prospective, not a
|
||||
tested worker game. Integration/final requires an actual two-parent merge and
|
||||
exact recomputed merge tree, native product map/modes and submitted checker/doc
|
||||
patch. Every tracked file and executing module must match HEAD bytes/mode;
|
||||
untracked/ignored files and symlinks fail. Unknown/deleted sources and candidate,
|
||||
code-only or phase declarations cannot authorize a role. Metadata membership
|
||||
comes from the real 9b1a18c tree, without the old count assumptions.
|
||||
|
||||
Schema2 retains native source/config/input/environment, real lifecycle PIDs,
|
||||
formal proofs, complete raw references, authority and original-audit uncertainty,
|
||||
domain and reachability mappings. Its entire payload is recomputed from raw by
|
||||
the same checker. It contains no candidate==HEAD pin or Root-passed flag.
|
||||
`generation-invocation.json` separately records the actual invocation/HEAD.
|
||||
|
||||
## Root execution order (all commands below NOT RUN)
|
||||
|
||||
Run from the clean, actual auto-submitted checkout. Root supplies actual clean
|
||||
merged/final checkout paths later; do not substitute invented SHAs. Preserve all
|
||||
prior output directories and use exclusive fresh output destinations.
|
||||
|
||||
```sh
|
||||
export B=/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build
|
||||
export E=docs/plans/original-completion-2026-10-02/execution/p04-01
|
||||
export BUILD_OUTPUT_DIR="$B"
|
||||
export PYTHONDONTWRITEBYTECODE=1
|
||||
```
|
||||
|
||||
1. Local file-only and fixture controls on the actual new SHA:
|
||||
|
||||
```sh
|
||||
python3 "$E/checker_negative_fixtures.py"
|
||||
python3 "$E/checker_applicability_fixtures.py" --native-bundle "$B/audit-support/native-first-current"
|
||||
python3 "$E/checker_native_fixtures.py" "$B/audit-support/native-first-current"
|
||||
python3 "$E/checker_series_fixtures.py" --build "$B"
|
||||
python3 "$E/checker_encoder_fixtures.py"
|
||||
python3 "$E/checker_join_fixtures.py" --build "$B"
|
||||
```
|
||||
|
||||
2. Real series against B, then the exclusive r20 collection:
|
||||
|
||||
```sh
|
||||
python3 "$E/p04_native_series.py" --build "$B"
|
||||
python3 "$E/collect_p04_native.py" --build "$B"
|
||||
```
|
||||
|
||||
3. After auto-submit, generate the fresh normal Runner index. B/p04-evidence was
|
||||
absent at static inspection. If it is now occupied, retain it and choose an
|
||||
explicit fresh directory with P04_EVIDENCE_DIR for manual checks; do not overwrite.
|
||||
The normal Runner uses B/p04-evidence and must receive that actual generated index.
|
||||
|
||||
```sh
|
||||
python3 "$E/build_p04_index.py" --build "$B" --output "$B/p04-evidence" \
|
||||
--invocation validate \
|
||||
--registered-ref deb1bcb2374eb378b840ca345db952bd8572cb6e \
|
||||
--tested-ref 5f6bc11d6bb6e92d22d787989b219fef3a435bc1
|
||||
```
|
||||
|
||||
4. Checker on submitted, merged and final actual checkouts. Real temp-Git fixtures
|
||||
need the generated index, so their execution follows step 3, not a fabricated
|
||||
pre-submission index. They create throwaway repositories/objects only under B.
|
||||
|
||||
```sh
|
||||
python3 "$E/checker_role_fixtures.py" --build "$B" --evidence "$B/p04-evidence"
|
||||
python3 tools/check_p04_order_evidence.py --verify-only
|
||||
python3 "$P04_MERGED_CHECKOUT/tools/check_p04_order_evidence.py" --verify-only
|
||||
python3 "$P04_FINAL_CHECKOUT/tools/check_p04_order_evidence.py" --verify-only
|
||||
```
|
||||
|
||||
The same index works in all three roles. For separate invocation records, the
|
||||
generator also accepts `--invocation prepare-integration` and `--invocation final`
|
||||
with explicit fresh output paths while executing from those actual checkouts.
|
||||
These labels are receipt metadata and grant no role. Root must respect the normal
|
||||
Runner state machine: its prepare_integration requires prior formal review.
|
||||
An isolated local prospective-merge control does not replace that review or move
|
||||
Runner state. `runner.validate("P04-01")`, `runner.prepare_integration("P04-01")`
|
||||
and `runner.integrate("P04-01")` remain Root operations only.
|
||||
|
||||
5. Mandatory whole validation and full 178-game regression once on exact final
|
||||
sources. The supplied Runner core exposes
|
||||
`runner.verify("P04-01", final_checkout, actual_final_sha, "final")`; retain every
|
||||
configured mandatory validation, then run the existing guarded full-game runner:
|
||||
|
||||
```sh
|
||||
python3 "$P04_FINAL_CHECKOUT/tools/run_all_tests.py" \
|
||||
--project-root "$P04_FINAL_CHECKOUT" --output "$B/p04-final-product-full-r01"
|
||||
```
|
||||
|
||||
Do not change timeout, marker contracts or suite policy, splice phases, or reuse
|
||||
historical 177-suite proof. Confirm the final selected source map and 178-suite
|
||||
roster, and preserve source/default-profile invariance. This command is the one
|
||||
full run, not permission for an additional `make test` native run.
|
||||
|
||||
6. Independent Opus review using Root's existing reviewer route, with exact
|
||||
source/validation materials mirrored into the frozen checkout's .review-material.
|
||||
The supplied core's formal method is `runner.review("P04-01")`; that method reviews
|
||||
the actual submitted revision and requires its validation evidence. Any additional
|
||||
final-composition Opus review must explicitly receive the actual final revision.
|
||||
The external commander/Opus CLI entry point and installed Runner configuration
|
||||
were not supplied in this checkout, so no guessed shell command is asserted for
|
||||
them. Root retains its existing formal validation/review/integration workflow.
|
||||
|
||||
The absent external entry-point/configuration details are handoff context gaps,
|
||||
not evidence of a pass, and not a blocker solely because Root testing is pending.
|
||||
@@ -0,0 +1,95 @@
|
||||
# Repair21 — source submission; tests NOTRUN
|
||||
|
||||
P04-01 source-only performance repair. Changes are restricted to the checker,
|
||||
the new `bulk_reader_equivalence.py` fixture, and this status file. No Git
|
||||
command, test, fixture, collector, Runner, native/Godot run, agent, commit,
|
||||
branch or integration operation was executed by the worker.
|
||||
|
||||
`read_blobs` validates full lowercase 40-hex OIDs before starting a process,
|
||||
deduplicates/sorts them, and uses one `git cat-file --batch` process per nonempty
|
||||
request. Empty requests start no process. `check_output` inherits stderr and
|
||||
raises for nonzero exit status just like `git_bytes`. The separately injectable
|
||||
byte parser verifies ordered OIDs, blob type, decimal size, exact framing and
|
||||
complete output consumption. There is deliberately no blob cache, so no object
|
||||
can be reused across `_GIT_ENV` namespaces. The two bulk call sites preserve
|
||||
selected-path sorted manifests and every actual-file byte/mode comparison.
|
||||
|
||||
No existing require message or assertion was removed. The `NATIVE_RUNS` through
|
||||
`verify_native` region, commit-type checks, ancestry, merge-tree isolation,
|
||||
status/untracked/symlink/module checks, schema/encoder, six existing suites and
|
||||
role case IDs were not edited. Checker self-blob reads remain unchanged.
|
||||
|
||||
The fixture source covers binary NUL/CR/LF/trailing LF, empty and large blobs,
|
||||
unicode `ls-tree -z` path resolution, duplicates, empty input, malformed/short
|
||||
OIDs, missing objects, tree/commit objects, invalid/truncated/oversized/garbage
|
||||
responses, process failure, one-process sorted input, and private object
|
||||
visibility inside versus outside and across isolated namespaces. It has not run.
|
||||
|
||||
Static validation: `ast.parse` and in-memory `compile(..., 'exec')` completed for
|
||||
the checker and new fixture. Neither module was imported or executed. This is
|
||||
syntax validation only. Tests: **NOTRUN**. Runtime equivalence and completion
|
||||
within the registered 180 seconds remain for Root to measure.
|
||||
|
||||
The existing schema2 index at B/p04-evidence/index.json was read as data, along
|
||||
with the existing builder and role fixture. Its registered/execution/tested
|
||||
roles remain deb1bcb/9767b0a/5f6bc11 and original_credit remains 13/88.
|
||||
The existing index and r20 collection are untouched. No new generation is
|
||||
needed if Root confirms the recomputed payload is identical. If generation is
|
||||
needed, use a new explicit destination, never overwrite existing evidence.
|
||||
Original P04-01 remains incomplete; criteria and nativeWindows pending status
|
||||
are unchanged. No original credit or acceptance is claimed.
|
||||
|
||||
## Root execution order — all NOTRUN
|
||||
|
||||
Use the clean new auto-submitted checkout, whose SHA is assigned after this
|
||||
source submission. Do not use the registered base SHA as the new submission.
|
||||
|
||||
```sh
|
||||
export B=/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build
|
||||
export E=docs/plans/original-completion-2026-10-02/execution/p04-01
|
||||
export BUILD_OUTPUT_DIR="$B"
|
||||
export PYTHONDONTWRITEBYTECODE=1
|
||||
python3 "$E/bulk_reader_equivalence.py"
|
||||
python3 "$E/checker_negative_fixtures.py"
|
||||
python3 "$E/checker_applicability_fixtures.py" --native-bundle "$B/audit-support/native-first-current"
|
||||
python3 "$E/checker_native_fixtures.py" "$B/audit-support/native-first-current"
|
||||
python3 "$E/checker_series_fixtures.py" --build "$B"
|
||||
python3 "$E/checker_encoder_fixtures.py"
|
||||
python3 "$E/checker_join_fixtures.py" --build "$B"
|
||||
python3 "$E/checker_role_fixtures.py" --build "$B" --evidence "$B/p04-evidence"
|
||||
```
|
||||
|
||||
Next run the registered validation on the **new auto-submitted SHA**, retaining
|
||||
its existing 180-second timeout and actual timed receipt. The supplied formal
|
||||
API call is exactly:
|
||||
|
||||
```python
|
||||
runner.validate("P04-01")
|
||||
```
|
||||
|
||||
The external Runner launcher/configuration is not present in the supplied task
|
||||
context; use Root's already registered invocation, without substituting an
|
||||
invented CLI, changing timeout, or treating direct checker timing as registered
|
||||
validation. Record actual SHA, elapsed time and exit status from that invocation.
|
||||
|
||||
Then run direct real-candidate controls from actual clean submitted, merged and
|
||||
final checkouts, preserving the formal review/integration state machine:
|
||||
|
||||
```sh
|
||||
python3 tools/check_p04_order_evidence.py --verify-only
|
||||
python3 "$P04_MERGED_CHECKOUT/tools/check_p04_order_evidence.py" --verify-only
|
||||
python3 "$P04_FINAL_CHECKOUT/tools/check_p04_order_evidence.py" --verify-only
|
||||
```
|
||||
|
||||
After required formal validation/review and exact final-source binding, run the
|
||||
full178 product regression once, using a fresh output destination:
|
||||
|
||||
```sh
|
||||
python3 "$P04_FINAL_CHECKOUT/tools/run_all_tests.py" \
|
||||
--project-root "$P04_FINAL_CHECKOUT" --output "$B/p04-final-product-full-r21"
|
||||
```
|
||||
|
||||
Do not regenerate raw collections, overwrite index/r20 evidence, alter existing
|
||||
case semantics, or run an additional native/full suite to compensate for a
|
||||
timeout. Any unrelated improvement is deferred. Pending Root execution is not
|
||||
a worker specification gap and does not change this submission's scope.
|
||||
+348
File diff suppressed because one or more lines are too long
+73
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"scope": "AST/static source checks and in-memory compile ONLY; no module imports or execution of task code",
|
||||
"python": [
|
||||
{
|
||||
"path": "tools/check_p04_order_evidence.py",
|
||||
"bytes": 78399,
|
||||
"sha256": "a61c5b496e656f60c66898e42500bc9a2d45f947690ce8f5f19a03984ebbc850",
|
||||
"AST": "parsed",
|
||||
"in_memory_compile": "compiled; not executed"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p04-01/p04_native_series.py",
|
||||
"bytes": 72591,
|
||||
"sha256": "667c95e10c451239292b08589706bfef978de730b321151ae7845dd10a06c411",
|
||||
"AST": "parsed",
|
||||
"in_memory_compile": "compiled; not executed"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p04-01/checker_join_fixtures.py",
|
||||
"bytes": 7888,
|
||||
"sha256": "f89ee6b835a8b0dc47f804686f7e338e7178c24a5a30a8a25bcd8bf59dfbea0c",
|
||||
"AST": "parsed",
|
||||
"in_memory_compile": "compiled; not executed"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p04-01/checker_series_fixtures.py",
|
||||
"bytes": 6779,
|
||||
"sha256": "f91b2d7003fd5c8cd196f849ad392d37479317e7d109eb584ccde5e5073e5bee",
|
||||
"AST": "parsed",
|
||||
"in_memory_compile": "compiled; not executed"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p04-01/collect_p04_native.py",
|
||||
"bytes": 2996,
|
||||
"sha256": "d727230de0af94dfc7e68bec40e84ecaa86a5b98d5e5bc16c2a35fe9c67fa12d",
|
||||
"AST": "parsed",
|
||||
"in_memory_compile": "compiled; not executed"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p04-01/build_p04_index.py",
|
||||
"bytes": 9246,
|
||||
"sha256": "47ae05b7bec617063e5d4fca2d8687f25e2eee0f809b5676ec91cf21c3223773",
|
||||
"AST": "parsed",
|
||||
"in_memory_compile": "compiled; not executed"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p04-01/s4/run_s4_native.py",
|
||||
"bytes": 18623,
|
||||
"sha256": "03a0aaab7e824d1b516ee4c6b456913439515c03daa687b5822cdfefb4d54c46",
|
||||
"AST": "parsed",
|
||||
"in_memory_compile": "compiled; not executed"
|
||||
}
|
||||
],
|
||||
"gd_validation": "static text/declaration review only; engine parse NOT RUN",
|
||||
"gd_sha256": "ceb592d853fd50063f20496c7b6df05257528dbe1c0d0d80bc6a8ccb1f9dd6d7",
|
||||
"C_frozen": {
|
||||
"exact_equal_to_immutable_full_stage": true,
|
||||
"bytes": 20918,
|
||||
"sha256": "5d778b3cfab7c139ca03812489508ce9aae90c1d17f8c2249c340f30d1496b3b",
|
||||
"body_with_one_final_LF_sha256": "33ae2af6afb98b48919ed679ab8519b7da7ddfda68c00f71c557d5cc9aad4a6c"
|
||||
},
|
||||
"required_registry_unchanged": true,
|
||||
"required_conditions": 15,
|
||||
"EV": "additional existing obligation, unchanged",
|
||||
"domains": 7,
|
||||
"product_input_files": 1338,
|
||||
"parse_gd_files": 316,
|
||||
"native_runs": 0,
|
||||
"fixture_runs": 0,
|
||||
"tests": "NOT RUN",
|
||||
"S4_raw": "pending Root execution",
|
||||
"formal_acceptance": false
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
# Repair22 — source-ready submission; NOT RUN
|
||||
|
||||
This batch changes only `tools/check_p04_order_evidence.py` and this P04-01
|
||||
documentation subtree. No Git command, test/fixture, engine, Runner operation,
|
||||
agent, manual commit or integration was performed. The normal autosubmit owns
|
||||
the submitted revision; the contract base SHA is not a new evidence version.
|
||||
|
||||
S3 B1 no longer supplies `power_stop`. Its full diagnostic checks remain under
|
||||
`dependency_closure_diagnostic`, including the actual dependency reason,
|
||||
closure/release, departure history and recovery sale. It approves no priority.
|
||||
Strict power-vs-closure order remains D05 pending/unknown. The existing approvals
|
||||
cover dependency hold and the18:00 shift interval only; A01 is not reopened.
|
||||
|
||||
S4 is a new minimal single-case QA probe using the existing ActorHardening,
|
||||
physical-kitchen and guarded Writer/Reader pattern. It cuts the actual sole
|
||||
cafeteria edge at959, waits for normal evaluation/projection at960, retains the
|
||||
held state at961, reconnects that same edge, and waits normally through the
|
||||
source-derived hold until970. Source constants were re-read as10/10. No forced
|
||||
evaluation, hold bypass, direct power/progress/patience/capacity writes or
|
||||
product edits are introduced. Kitchen Facility.OPEN / network ACTIVE, positive
|
||||
mana, operating dependency and open operator distinguish it from B1.
|
||||
|
||||
Three formal Writer points and separate-process Reader restores retain every
|
||||
typed Runtime field and all four SaveService members. Reconnect resend at970
|
||||
must leave the full Runtime unchanged; normal971 and Reader replay add no
|
||||
extra sale/completion. Full intermediate phase raw is preserved. All graph
|
||||
nodes, cut/restored edge sets, revisions, allocation and derived dependency
|
||||
restoration are checked. Normal post-completion guest redecision/despawn is
|
||||
retained, not forced or concealed; no exit accompanies the outage/hold.
|
||||
|
||||
R now derives shift PID and sale IDs from the existing full raw joins. S1 joins
|
||||
the existing pre-travel reservation/task/staff cell to the boarded task/trip,
|
||||
and checks actual mana0 and elevator loss of power. The existing join fixtures
|
||||
are retained and extended with the requested semantic negatives and consistent
|
||||
dynamic-ID positive controls. Existing C25 and all other fixture sources remain
|
||||
in place. No fixture result is claimed: S4 positives await actual Root raw.
|
||||
|
||||
ORDER_AUDIT.md and order-audit.json are one current seven-domain table with
|
||||
normative rules, source, C/S1/S2a/S2b/S3 evidence, pending S4, approved authority
|
||||
and pending D05. REPAIR17–21 remain untouched historical documents and outputs;
|
||||
their current-status claims are superseded by repair22. The current protocol,
|
||||
collection plan and this status agree: no final acceptance.
|
||||
|
||||
Static validation is recorded in REPAIR22_STATIC_VALIDATION.json:
|
||||
|
||||
- Seven Python files parsed with AST and compiled in memory; none imported or
|
||||
executed. GDScript received static text/source review only; engine parse NOT RUN.
|
||||
- The entire frozen C region is byte-identical to the preserved full-stage
|
||||
checker. Its one-final-LF body hash is
|
||||
`33ae2af6afb98b48919ed679ab8519b7da7ddfda68c00f71c557d5cc9aad4a6c`, matching
|
||||
the supplied repair21 C_FULL_BLOCK_EXACT receipt. Existing C checks are intact.
|
||||
- The complete15-condition registry matches that immutable source; EV, seven
|
||||
domains and three safety outcomes are unchanged. Product1338 inputs and the
|
||||
316-file `.gd` parse roster were counted from the actual relocation manifest.
|
||||
- The five supplied public power bodies were compared to their actual relocated
|
||||
source. Full bodies, hashes, timing and restore analysis are retained in
|
||||
REPAIR22_SOURCE_FACTS.json.
|
||||
|
||||
The exact finite Root plan is `s4/S4_ROOT_RUN.md`: one fresh own-B four-phase
|
||||
collection, with explicit arguments and full input/QA/source footprints. All
|
||||
old C/S1/S2a/S2b/S3 raw and all R17 sources remain accessible and are not rerun.
|
||||
S4 remains **missing/pending Root execution**. Future native/index/cross-gate
|
||||
work does not block this source submission. The updated collector/index
|
||||
references use a fresh r22 destination; no r17/r20 evidence is overwritten.
|
||||
|
||||
Preserve the21615-check proof. Static full-map comparison found exactly the
|
||||
unchanged product1338 plus the old checker in its1339 map. The new checker hash
|
||||
differs; never claim1339 byte identity. The178 product suites do not invoke the
|
||||
checker and `.py` is excluded from the316 parse roster, so reuse requires the
|
||||
explicit QA-only source/config/input/environment applicability argument in
|
||||
EVIDENCE_COLLECTION.md. New-checker validation and S4 are separate, pending.
|
||||
|
||||
Root must rebind support53cb through its normal registered task once the new
|
||||
source is known, without metadata bypass. Only if an exact final1339 full proof
|
||||
is required, perform one run after all fixes, S4 raw, local checks, eight suites
|
||||
and rebind are bundled; do not splice versions or perform a blanket full now.
|
||||
|
||||
No required source context is missing for this preparation. Remaining D05
|
||||
choices are explicitly pending and not silently implemented. Formal validation,
|
||||
independent review, exact final-source applicability and normal integration
|
||||
remain pending. CurrentAlphaScope,30 packages/88 deliveries, original13/88 and
|
||||
P04 main9b are unchanged. P04 receives no credit; nothing is integrated.
|
||||
@@ -0,0 +1,34 @@
|
||||
# Repair 23 — submitted, NOT RUN
|
||||
|
||||
S4 source preparation only. The wrapper now defaults to fresh import without
|
||||
accessing cache receipts or cache directories. It checks that private `.godot`
|
||||
is absent immediately before import and requires the complete 316-script parse
|
||||
roster. The complete 1338-file source check and sole QA override remain intact.
|
||||
Optional cache mode retains the original registered-path, passed-run, source
|
||||
identity, engine identity and original-cache preservation guards.
|
||||
|
||||
Engine identity is independently pinned to
|
||||
`c7cccbf8fb143e34e02fd6521e09be2c2b974f0d5db080b19071c9c570718ccf`.
|
||||
Read-only SHA256 inspection found identical bytes in the installed executable
|
||||
and the preserved r22 staged executable; neither was launched.
|
||||
|
||||
Current wrapper SHA256:
|
||||
`6d5f4e05e87607413bfb5498dc9c9e17c2bfd96bf751c9e645050ca6f8e69d5c`.
|
||||
The current runbook binds this SHA. The series verifier already binds the wrapper
|
||||
to current checkout bytes dynamically, so no series metadata change is needed.
|
||||
Historical REPAIR22_STATIC_VALIDATION.json is preserved as historical evidence,
|
||||
not rewritten to claim validation of the new wrapper.
|
||||
|
||||
Root must use fresh staging `audit-support/p04-s4-source-r23` and fresh plan
|
||||
`p04-s4-plan-r23.json`. Output remains `p04-s4-native-r22`. Old staging and plan
|
||||
are preserved. All R22 fixes and other suite wrappers/checks are retained.
|
||||
|
||||
Checker SHA256 remains
|
||||
`a61c5b496e656f60c66898e42500bc9a2d45f947690ce8f5f19a03984ebbc850`;
|
||||
S4 probe SHA256 remains
|
||||
`ceb592d853fd50063f20496c7b6df05257528dbe1c0d0d80bc6a8ccb1f9dd6d7`.
|
||||
No game input file was edited. Review was limited to source reading and hashes.
|
||||
No Git, tests, fixtures, check-only execution, engine, Runner, agents or manual
|
||||
commit were run. Native S4 evidence, root native/data/index work, independent
|
||||
review and formal acceptance remain pending. The expected validate MANIFEST
|
||||
failure is unchanged. Original P04-01 completion is not claimed.
|
||||
@@ -0,0 +1,77 @@
|
||||
# Repair24 — source-ready submission
|
||||
|
||||
Bounded continuation of P04-01 request-repair24; tests NOT RUN.
|
||||
Existing wrapper edits and the untracked identity fixture were read first and
|
||||
preserved/corrected. Only this execution subtree was written. No Git, engine,
|
||||
Runner, agent, commit or integration operation was performed.
|
||||
|
||||
Changed source/runbook SHA256 (exact bytes):
|
||||
|
||||
| File | SHA256 |
|
||||
| --- | --- |
|
||||
| s4/run_s4_native.py | dda495d33566eae14c8a707e77f8ec9dc6c7bd043076a091e90dd53f6a7490db |
|
||||
| p04_native_series.py | 348197759c169c2094db3b1114ca3abe5525b8d4bc9aca611d4579871a130b40 |
|
||||
| s4/engine_identity_negative_fixtures.py | f9148ef79cb62ec6631e7e3f23e1d3b062434f7152977aab3b9d18d82672255e |
|
||||
| s4/S4_ROOT_RUN.md | c1a00e3dad8d91d28df1a7f868f6e4e905f1df721baac964865e341c84356135 |
|
||||
|
||||
This new status document records the hashes above; its own hash must be taken
|
||||
from the submitted checkout, outside its own contents.
|
||||
|
||||
Wrapper pins changed: the own-B bare Godot copy is replaced with exactly
|
||||
`/Applications/Godot.app/Contents/MacOS/Godot`, rejecting symlinks and any
|
||||
different resolved path. Its constant SHA remains
|
||||
`c7cccbf8fb143e34e02fd6521e09be2c2b974f0d5db080b19071c9c570718ccf`.
|
||||
Engine identity no longer depends on an old cache runner-input. Fresh NO-CACHE
|
||||
is the default at plan and native collection. Optional explicit cache mode
|
||||
rejects failed receipts before reading their inputs or cache contents.
|
||||
No default cache or cache PASS is required. No engine is copied, re-signed or
|
||||
modified, and no codesign/xattr/security operation is added.
|
||||
|
||||
Before-launch, during-collection and explicit after-cleanup re-pins retain the
|
||||
full runtime descriptor (path/resolved path, bytes, SHA, device, inode, mode,
|
||||
mtime and ctime). Runner-input and execution retain equal before/during/after
|
||||
identity records and execution retains labeled observations. fixed_guard still
|
||||
re-pins the engine. The verifier permits only this exact installed engine
|
||||
outside B, reads/hashes it exactly and checks the recorded identities.
|
||||
Fixed inputs and QA remain own-B only; actual command[0] must equal the pin.
|
||||
|
||||
Defaults and Root staging are now:
|
||||
`p04-s4-native-r24`, `p04-s4-plan-r24.json`,
|
||||
`audit-support/p04-s4-source-r24`. Only RUNS S4 changes to r24.
|
||||
C/S1-S3 bindings, other series and the frozen C block (20916) are unchanged.
|
||||
Source1338, parse316, sole QA override, default-player protection, owned
|
||||
four-distinct-PID capture, live-engine refusal, disk admission, fresh output,
|
||||
cleanup-before-fixed-guard, formal Writer/Reader, NO_POWER, typed declarations,
|
||||
forbidden mutations/bypasses and mana constants guards remain.
|
||||
|
||||
Untouched checker: SHA256
|
||||
`a61c5b496e656f60c66898e42500bc9a2d45f947690ce8f5f19a03984ebbc850`,
|
||||
78399 B. Untouched probe: SHA256
|
||||
`ceb592d853fd50063f20496c7b6df05257528dbe1c0d0d80bc6a8ccb1f9dd6d7`.
|
||||
All 1338 game files are unchanged; no game source was written. No new claim of
|
||||
executed 1338-file verification is made.
|
||||
|
||||
The formal reviewer's five findings are carried forward unchanged, with no
|
||||
closure claimed. Their individual review text was not supplied in this dispatch
|
||||
or found in the local execution records searched; it is not reconstructed here.
|
||||
Main 9b remains frozen at original 13/88. Support 53cb remains review pending.
|
||||
D05 remains pending. No acceptance denominator, specification, main integration
|
||||
or original completion state changes.
|
||||
|
||||
r22 is an immutable failed proof and remains ineligible. Diagnostics:
|
||||
`audit-support/p04-root-d393-s4-r01/ENGINE_STAGING_DIAGNOSTICS.json`.
|
||||
Its pre-banner staged-copy failure is an environment/staging fault; no product
|
||||
logic or probe/checker repair is claimed. Root uses the installed executable
|
||||
directly and preserves r22. Do not rerun S1-S3, C or full178.
|
||||
|
||||
Validation: static AST parsing and in-memory compile completed for the wrapper,
|
||||
series verifier and identity fixture; no module was imported or executed.
|
||||
Read-only SHA/byte inspection completed for the four listed files, checker and
|
||||
probe. These are source checks only. Tests NOT RUN: identity fixtures, all other
|
||||
fixtures/suites, check-only plan generation, native S4, full178, registered
|
||||
validation and independent review. No unexecuted result is reported as passed.
|
||||
|
||||
Registered validation stays exit 2 until Root generates the current
|
||||
MANIFEST.json collection; the gate is not changed. This is the carried-forward
|
||||
expected gate state, not a newly executed validation result. Root execution,
|
||||
current exact-version evidence and formal independent review remain pending.
|
||||
+1802
File diff suppressed because it is too large
Load Diff
+173
@@ -0,0 +1,173 @@
|
||||
# repair7 作業状態
|
||||
|
||||
状態: blocked(実装未完了)。whole合格、登録validate成功、実測成功を意味しない。
|
||||
提出547のGit objectと既存C実装は保持。今回の変更は未コミット。
|
||||
engine・全回帰・checker試験・登録validate・Runner操作は未実行。
|
||||
原15条件+EV、7領域、3安全性結果、30/88、部分点0を維持する。
|
||||
|
||||
## 実装した部分と未検証部分
|
||||
|
||||
checkerに名前付きtargetの照合を追加した。main_anchorは
|
||||
9694f50ee63750192376ca69bc7dda3f884784c5、submittedは提出後に確定する実commit。
|
||||
composed_treeはRoot例の904bbbを固定せず、毎回merge-treeから再計算する。
|
||||
実行時の一時object directoryとcanonical objectsのread-only alternateを使い、
|
||||
canonical refs/HEAD/indexへの書込み命令を使わない。この実装はまだ実行していない。
|
||||
|
||||
全tree entryと選択sourceを区別し、1338件のpath/bytes/SHA256をGit blobと
|
||||
runner-input.source_beforeで両方向比較する。source選択規則はmain_anchorの
|
||||
tools/run_all_tests.pyのSHAに固定。追加選択pathはcheckerのみ。
|
||||
3139件の変更されていない既存metadataを明示分類し、追加・変更・削除pathは
|
||||
登録scope内のみ許す。submitted差分もregisteredから独立にscope検査する。
|
||||
|
||||
ORDER_FUNCTIONS、REACH_REFS、applicability_deltaはcomposed_treeのbytesを読む。
|
||||
candidate_treeは実候補のtree OIDとし、composed_treeの全entryとの一致を要求する。
|
||||
checker metadataだけのcommit SHA差はengine再実行の理由にしない。
|
||||
checker source bytesはsubmitted/composed/checker commitと別々に比較する。
|
||||
自己blob一致は提出前なので**未実施**。Rootが提出後に検証する。
|
||||
|
||||
## 新しい入力形式(未試験)
|
||||
|
||||
既存indexの条件・authority・domain・rawを省略する形式ではない。
|
||||
|
||||
- `candidate_commit`: 実際にcheckerを実行するcheckoutのHEAD。
|
||||
- `candidate_tree`: 実際に検証する候補tree。登録時にも指定し、比較を省略しない。
|
||||
- `product_binding.product_target`: main_anchor/submitted/composed_treeの完全OID。
|
||||
- `product_binding.tested_product`: 実測製品commit。製品manifestをGitから再計算する。
|
||||
- `product_binding.runner_inputs`: 実runner-inputファイルのdescriptor配列。
|
||||
descriptorはpath/bytes/sha256に加えてmaterialsとwrapperを持つ。
|
||||
materialsはnative固定input/QAの原pathから供給済み実ファイルdescriptorへのmap。
|
||||
原pathは由来のみで、そこからファイルを開かない。wrapperも実ファイルdescriptor。
|
||||
Cの3 runのrunner-inputを全て含める。各selected_source_mapの差分は宣言済みQA
|
||||
override 1件だけに限定する。
|
||||
- `versions`: registered、execution_origin、checker、testedの4役割。
|
||||
checkerはcommit/sha256。testedはproduct_target/manifest_sha256。
|
||||
manifest digestはpath→{bytes,sha256}をsort_keys=True、separators=(',', ':')の
|
||||
JSON UTF-8で符号化したSHA256。checkerのSHAは製品digestに代用しない。
|
||||
- `native_bundle`: 従来のmanifestキーからpath/bytes/sha256 descriptorへ変更。
|
||||
OWNERSHIP_RAW_PROVENANCE.jsonの実ファイルを指定する。
|
||||
|
||||
descriptorの相対pathはp04-evidence配下。絶対pathはbuild_dir/audit-support配下の
|
||||
p04-whole-closure-r01、native-first-current、p04-current-native-series-r01の3系統のみ。
|
||||
ファイルを追加コピーせずその場で読む。symlink・欠落・SHA/bytes不一致は失敗する。
|
||||
|
||||
## 残作業(Rootのnative実行へ進める状態ではない)
|
||||
|
||||
1. S1/S2/S3のprobeと、import/parse各1回→実測→正式Writer→別PID Readerを
|
||||
結合するwrapperは未実装。既存templateを読んだだけで完成とは扱わない。
|
||||
2. 全条件のactor/service/予約/帳簿/同minute/正式保存rawを結合するcollectorは
|
||||
未実装。既存verify_nativeの有限3条件を全条件collectorに代用できない。
|
||||
verified_conditionsの全条件への動的導出も未実装。
|
||||
3. 新しいtarget側のconfig/input/environment実体照合は未完了。
|
||||
現在は固定input/QA/wrapperと既存environment pin検査があり、これだけで
|
||||
engine環境を全runnerの実起動に結合したとは主張しない。
|
||||
4. 新入力形式へのfixture更新・Runner正式試験・提出後自己blob検査は未実施。
|
||||
既存repair6の試験結果を今回の変更の合格として転記しない。
|
||||
|
||||
実装残作業を仕様不足として偽装しない。原本の未確定D05部分は従来どおりpending。
|
||||
新しい金額、費用帰属、EV停止規則を追加して残作業を埋めない。
|
||||
|
||||
## B/Cの確定事項と実体確認
|
||||
|
||||
main969のautoload/simulation_runtime.gdのrepay_loan_principal本体と
|
||||
tests/mvp/test_original_midnight_settlement.gdの返済テストをGit objectから読んだ。
|
||||
公開返済APIとledger分類は存在する。自動返済・全UI完成は主張しない。
|
||||
|
||||
duty_paymentsはstaff_id/dayで勤務recordに結合できるpayroll settlement取引数で、
|
||||
期待値1のまま。実コードではWAGE行のsource_entity_idがpayroll settlement_id、
|
||||
settlement.rowsがstaff_id/payment、ShiftInstance snapshotがstaff_id/start_dayと
|
||||
worked_minutes/overtime_minutesを保持する。給与行の数や勤務分数をそのまま
|
||||
duty_paymentsに代入してはならない。day基準と勤務の実測差分も照合対象。
|
||||
|
||||
具体的に読んだ追加rawは
|
||||
`p04-current-native-series-r01/p04-paid-shift-midnight-native-r01/order-evidence/midnight-midnight.json`
|
||||
のstaff/payroll/economy部分と
|
||||
`p04-current-native-series-r01/p04-paid-shift-midnight-formal-native-r01/runner-input.json`
|
||||
のsource_beforeサンプル・fixed_input_pins。これを39raw読了・採用とはしない。
|
||||
加えてnative-first-currentのactual-native、dependency-midnight-native、
|
||||
dependency-formal-native各runner-input.jsonのキーとQA/QA_overridesを読んだ。
|
||||
前2件にはfixed_input_pinsがなく、formalのみ保存入力のpinを持つ。
|
||||
|
||||
midnight_replayは実際の同minute決算API再呼出しまでpending。
|
||||
paid資料の1442完了は1440売上境界を証明せず、request再送は決算API再呼出しを
|
||||
証明しない。shift_completionはROOT_APPROVED_SHIFT_CURRENT_RESULTと
|
||||
approved-shift-current-r01.gdの1080系列を結合する必要がある。
|
||||
|
||||
順序はworker実装完了→Root資源確認→S1→S2→S3→collector→登録validate→
|
||||
独立whole review→prepare-integration/final。全case収集前のwhole合格はない。
|
||||
|
||||
S1準備に移行、A未検証。repair8はs1/の3ファイルによるコード準備のみ。未実測・原P04 whole未達。
|
||||
|
||||
## repair9 — S1 code-ready / submitted
|
||||
|
||||
今回の限定修正は完了。上記repair7の未完一覧は履歴であり、今回のS1実装をblockedとはしない。
|
||||
原P04 whole未達・修正版native未実測。git書込み・Runner操作・engine起動なし。
|
||||
|
||||
原因はfixtureのint(String)=0の読み違い。InventoryReservation.create_snapshot()は
|
||||
stateをACTIVE/RELEASED等の文字列名で保存するが、旧probe:150はint()で比較し、
|
||||
RELEASEDもACTIVE=0として双方向照合していた。productは不変。
|
||||
s1/p04_s1_ev_power_probe.gdのsafety():144にstates_knownを追加し、旧150
|
||||
(修正後151–155)をString化・enum名存在確認・ACTIVE名比較へ変更した。
|
||||
修正後165の既存孤立outcomeにstates_knownをANDし、不明状態なら明示的に失敗する。
|
||||
既知状態のcheck数は各case 3のまま。不明状態も6 checksの既存枠内で失敗する。
|
||||
staff状態の119、purpose==staff_task、8境界、双方向照合、孤立0、ledger・到着・
|
||||
remaining_minutesは維持。非ACTIVE履歴もsnapshot/rawから削除・除外しない。
|
||||
wrapperに予約state判定の説明はなくコード変更不要。S1_ROOT_RUN.mdに型の説明と
|
||||
Root用fresh r02 plan/outputを反映した。
|
||||
|
||||
### 直接読んだ根拠(絶対path)
|
||||
|
||||
- /Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build/audit-support/p04-whole-closure-r01/ROOT_S1_NATIVE_R01_RESULT.json
|
||||
- /Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build/p04-s1-native-r01/raw/writer-landing-observations.json
|
||||
- /Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build/p04-s1-native-r01/raw/writer-landing-observations.var
|
||||
- /Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build/p04-s1-native-r01/raw/writer-no-safe-landing-observations.json
|
||||
- /Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build/p04-s1-native-r01/raw/writer-no-safe-landing-observations.var
|
||||
- /Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build/p04-s1-native-r01/raw/writer.json
|
||||
- /Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build/p04-s1-native-r01/writer/stderr.log
|
||||
- /Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build/p04-s1-native-r01/execution.json
|
||||
|
||||
指定の /Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build/p04-s1-native-r01/raw/writer/stderr.log と
|
||||
/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build/p04-s1-native-r01/raw/writer/execution.json はFileNotFoundError。
|
||||
/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build/p04-s1-native-r01/writer/execution.json も存在しない。
|
||||
上記実在stderrとrun全体executionを代わりに直接確認した。欠落pathを読了とはしない。
|
||||
.varは全bytes読取りのみで、engineによるtyped復号・JSONとの型付き一致検証は未実施。
|
||||
|
||||
### rawからの手照合(オーナー提示値と区別)
|
||||
|
||||
以下は提示値の転記ではなく、閲覧用JSONの予約・task行を直接読み、両方向を照合した値。
|
||||
全行のreservation_idはreservation_00000001、requester/task_idはs1_rescue_with_tool。
|
||||
ACTIVE行ではtask.reservation_idsに同IDがあり、RELEASED行ではidsが空なので、
|
||||
有効予約→taskとtask.ids→有効予約の欠落数はいずれも0。
|
||||
|
||||
|case|境界 minute|予約state / task state|孤立(両方向合計)|
|
||||
|---|---|---|---|
|
||||
|landing|boarded 779|ACTIVE / MOVING|0|
|
||||
|landing|stopped 781|RELEASED / CANCELLED|0|
|
||||
|landing|held_after 801|RELEASED / CANCELLED|0|
|
||||
|landing|final 802|RELEASED / CANCELLED|0|
|
||||
|no-safe-landing|boarded 779|ACTIVE / MOVING|0|
|
||||
|no-safe-landing|stopped 781|ACTIVE / MOVING|0|
|
||||
|no-safe-landing|held_after 801|ACTIVE / MOVING|0|
|
||||
|no-safe-landing|final 847|RELEASED / CANCELLED|0|
|
||||
|
||||
両caseで予約時761はACTIVE、搭乗779もACTIVE。landingのterminal_atは780、
|
||||
no-safe-landingは815。landingという観測キー自体はminute801であり、780は解放時点。
|
||||
ledger配列長のheld_after−reserve_exhausted_before_tickは各0。
|
||||
到着はboardedのrequest_id末尾000002(medic)/000003(patient)とshaft stopsで絞ると各1回。
|
||||
medicには先行request末尾000001の到着もあるが対象requestではない。
|
||||
remaining_minutesはlanding 0→0、no-safe-landing 4→4。
|
||||
Writer receiptからPID59355、6 checks / 2 failures、旧orphansはlanding 3、no-safe-landing 1。
|
||||
これら直接確認値はオーナー提示値に一致する。未知の予約stateは観測JSONにない。
|
||||
修正後の8境界孤立0は既存rawの再解釈であり、修正版native成功を意味しない。
|
||||
|
||||
### 静的検証
|
||||
|
||||
run_s1_native.pyのpy_compileはexit0(pycacheはtask build/s1-repair9-python-cache)。
|
||||
既定製品rootのcheck-onlyはtools/run_all_tests.pyのPermissionErrorでexit2。
|
||||
checkoutを--project-root指定したcheck-onlyはexit0、engine0、source1300、fixed pins16。
|
||||
cache receiptはPermissionErrorをcache_accessへ記録。製品1338件やnative開始条件の証明ではない。
|
||||
最終説明更新後のplanはtask build/p04-s1-repair9-worker-final-plan.json、
|
||||
output指定はtask build/p04-s1-repair9-worker-unused(native未使用)。
|
||||
Rootは既定製品rootで独自fresh r02 plan/outputを用いnativeを1回実行し、
|
||||
Writer6 checks全PASSとdistinct Readerの14正式slot復元を確認する。worker planは転用しない。
|
||||
同版missing-index whole validateは再実行しない。S2/S3/collector/Aは今回扱わない。
|
||||
D05の採用済み2承認は維持し、それ以外の未確定事項と混同しない。
|
||||
@@ -0,0 +1,19 @@
|
||||
# Runner実行担当からの第1修正入力
|
||||
|
||||
これは原ゲーム仕様の変更ではありません。旧Runtimeと差分診断を追加試験入力として供給します。既存監査を再実装せず、技術的な証拠index形式とfailclosed checkerを作成してください。原7領域とD05 pendingを維持します。原条件の証拠欠損は検証失敗です。rootは検証器のsourceを変更していません。
|
||||
|
||||
読取専用入力(Runner allocation.build_dir内):
|
||||
- /Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build/audit-support/support.json
|
||||
- /Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build/audit-support/historical-runtime.txt
|
||||
|
||||
旧Runtime SHA256: 2d7e2cea5424585e9aa4302ab66d1fc4dbaefe743ce4328fccc1efa2f19b82e4。入力を検証してからdiffしてください。
|
||||
|
||||
内部indexの形式設計はspecの検証器作成指示に含まれる技術判断であり、原ゲーム規則の補完ではありません。欠損/古いソース/不正hash/未実行/未定義必須規則は必ず非0にしてください。全証拠が揃わないまま元成果を完了扱いにしないでください。エンジン・全回帰・製品source編集は0。
|
||||
|
||||
実際のClaude commanderの記録済み判断:
|
||||
|
||||
{
|
||||
"action": "request-repair",
|
||||
"task_id": "P04-01",
|
||||
"reason": "初回Codex blockedの原因は、検証器tools/check_p04_order_evidence.pyが未作成だったこと(missing gate schemaで仕様補完を避けて停止)。これは内部証拠formatの実装判断で、原ゲーム仕様の不足ではない。既存specの検証器作成指示が、元要件を守るfailclosed形式の設計を許可している。repairs=0で、第1repairは残2/2以内。入力は再起動に十分: input.json等のlocal copy、current source pins、14差分(production7とmanaged14を区別、追加7はroot対managedでCRLF→LFのみbyte検証済み、rawSHA差は隠さない)、正確な旧Runtime(SHA 2d7e2cea完全一致)をbuild_dir/audit-support/historical-runtime.txtへraw保存済み。support.jsonとこのtxtを読取専用の追加試験入力として使う(Git変更scopeなし)。repair範囲: (1) 既存のorder-audit.jsonとORDER_AUDIT.mdは保持し、重複作成しない。必要なら最小の追記・訂正のみ。(2) --verify-onlyのcheckerを作成する。BUILD_OUTPUT_DIR/p04-evidence/index.jsonを必須とし、7領域、不変のsource pin、raw proof hash、適用性を検証する。欠損、D05 pending、stale source、未実行proof、自己申告pass、test count単独のpassは非0で終了する。raw証拠が全て不足していてもcheckerは作成する。生証拠を作ったことにはしない。D05は未承認のまま、その不足がある受入をpassにしない。(3) 全回帰・engine実行、ゲームsource変更、Clock・値の変更は0。原7領域/元30/88/部分点なしの受入分母は維持し、仕様変更・分母変更・予算拡大・main統合は含めない。allowed_pathsは検証器と実行ディレクトリ配下に限る。"
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build a fresh evidence index from supplied native bytes after auto-submit.
|
||||
|
||||
The index has no candidate identity or Root-passed flag. Invocations share the
|
||||
same payload; checkout roles are established afresh by the checker.
|
||||
"""
|
||||
import argparse
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
ROOT = Path(__file__).resolve().parents[5]
|
||||
|
||||
|
||||
def checker():
|
||||
spec = importlib.util.spec_from_file_location('p04_index_checker', ROOT / 'tools/check_p04_order_evidence.py')
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[spec.name] = module
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def descriptor(path):
|
||||
data = path.read_bytes()
|
||||
return {'path': str(path.absolute()), 'bytes': len(data), 'sha256': hashlib.sha256(data).hexdigest()}
|
||||
|
||||
|
||||
def payload(build, gate=None):
|
||||
gate = gate or checker()
|
||||
build = Path(build).resolve(strict=True)
|
||||
native = build / 'audit-support/native-first-current'
|
||||
c = gate.verify_native(native)
|
||||
raw = gate.combine_native_series(build, c)
|
||||
source = gate.product_manifest(gate.tree_entries(gate.TESTED_PRODUCT))
|
||||
gate.require(len(source) == 1338, 'complete native source required')
|
||||
source_map = {p: v['sha256'] for p, v in source.items()}
|
||||
# Resolve content from supplied roots only; never open original provenance
|
||||
# paths, never omit required source/config/input/environment material.
|
||||
supplied = {}
|
||||
roots = [build / 'audit-support' / name for name in
|
||||
('native-first-current', 'p04-whole-closure-r01', 'p04-current-native-series-r01',
|
||||
'p04-r17-full-inputs-r01')]
|
||||
for root in roots:
|
||||
if not root.exists():
|
||||
continue
|
||||
for path in sorted(root.rglob('*')):
|
||||
if path.is_file():
|
||||
gate.safe(root, path.relative_to(root).as_posix())
|
||||
pin = descriptor(path)
|
||||
supplied.setdefault(pin['sha256'], pin)
|
||||
runners = []
|
||||
for run in gate.NATIVE_RUNS:
|
||||
path = native / run / 'runner-input.json'
|
||||
inputs = gate.load(path)
|
||||
row = descriptor(path)
|
||||
row['materials'] = {}
|
||||
for pin in inputs.get('fixed_input_pins', []) + [inputs['QA']]:
|
||||
gate.require(pin['sha256'] in supplied, 'missing supplied runner material: ' + pin['path'])
|
||||
actual = supplied[pin['sha256']]
|
||||
gate.require(actual['bytes'] == pin['bytes'], 'runner material length')
|
||||
row['materials'][pin['path']] = actual
|
||||
gate.require(inputs['wrapper_sha256'] in supplied, 'missing supplied native wrapper')
|
||||
row['wrapper'] = supplied[inputs['wrapper_sha256']]
|
||||
runners.append(row)
|
||||
# C references retain every lifecycle, raw phase, source, environment and
|
||||
# formal member mapping; Stage R retains its complete original->relocated map.
|
||||
references = {}
|
||||
for row in gate.load(native / 'SOURCE_MANIFEST.json')['files']:
|
||||
references['C/' + row['copy']] = descriptor(gate.safe(native, row['copy']))
|
||||
for row in gate.load(native / 'OWNERSHIP_RAW_PROVENANCE.json')['copied']:
|
||||
references['C/' + row['target']] = descriptor(gate.safe(native, row['target']))
|
||||
series = {}
|
||||
collection = build / 'audit-support/p04-raw-collect-r22'
|
||||
for name in ('s1', 's2a', 's2b', 's3', 's4'):
|
||||
series[name] = gate.load(collection / (name + '.json'))
|
||||
relocation = gate.load(build / 'audit-support/p04-r17-full-inputs-r01/MANIFEST.json')
|
||||
authorities = {}
|
||||
for ref in sorted({r for _, refs, _ in gate.REQUIRED.values() for r in refs}):
|
||||
relative = (gate.DOC_SCOPE + 'D05_OWNER_APPROVAL.json' if ref.startswith('D05-P04-') else
|
||||
'docs/traceability/original/BASELINE.txt' if ref.startswith('BASELINE:') else
|
||||
'docs/DESIGN_DECISIONS.md' if ref == 'D-034' else
|
||||
'docs/plans/original-completion-2026-10-02/execution/p04-01-input-r01/ORDER_CONTRACT_DRAFT.md')
|
||||
path = gate.safe(gate.ROOT, relative)
|
||||
if ref.startswith('D05-P04-'):
|
||||
gate.require(gate.sha(path) == gate.OWNER_HASH, 'owner approval hash')
|
||||
authorities[ref] = {'source': relative, 'sha256': gate.sha(path),
|
||||
'status': 'approved' if not ref.startswith('BASELINE:') else 'original'}
|
||||
conditions = {cid: {'case_id': cid, 'authority_refs': list(refs), 'nativeWindows': 'pending',
|
||||
'raw_verified': cid in raw['verified_conditions'],
|
||||
'required_measurements': metrics}
|
||||
for cid, (_, refs, metrics) in gate.REQUIRED.items()}
|
||||
domains = {domain: {'conditions': [cid for cid, (d, _, _) in gate.REQUIRED.items() if d == domain],
|
||||
'source_refs': [{'path': p, 'sha256': source_map[p], 'functions': list(fs)}
|
||||
for p, fs in gate.ORDER_FUNCTIONS.items()],
|
||||
'authority_refs': sorted({r for _, (d, refs, _) in gate.REQUIRED.items()
|
||||
if d == domain for r in refs})}
|
||||
for domain in sorted(gate.DOMAINS)}
|
||||
reachability = {}
|
||||
for name, rows in gate.REACH_REFS.items():
|
||||
refs = []
|
||||
for path, function, digest, lines in rows:
|
||||
gate.resolve_anchor(gate.git_bytes('show', gate.TESTED_PRODUCT + ':' + path), function, digest, lines)
|
||||
refs.append({'path': path, 'sha256': source_map[path], 'function': function,
|
||||
'anchor_sha256': digest, 'line_count': lines})
|
||||
reachability[name] = {'reach': name == 'EV', 'case_id': 'EV' if name == 'EV' else None, 'source_refs': refs,
|
||||
'raw_verified': 'EV' in raw['verified_conditions'] if name == 'EV' else None}
|
||||
delta = gate.source_delta(gate.REGISTERED_COMMIT, gate.TESTED_PRODUCT)
|
||||
index = {'schema_version': 2, 'task_id': 'P04-01', 'formal_acceptance': False,
|
||||
'versions': {'registered': gate.REGISTERED_COMMIT, 'execution_origin': gate.EXECUTION_ORIGIN,
|
||||
'tested': gate.TESTED_PRODUCT},
|
||||
'product_binding': {'tested_product': gate.TESTED_PRODUCT, 'runner_inputs': runners},
|
||||
'source_map': source_map, 'registered_pins': gate.load(gate.SPEC)['current_source_pins'],
|
||||
'applicability_delta': delta,
|
||||
'delta_cases': {r['path'] + ':' + r['function']: {cid: cid for cid in
|
||||
gate.boundary_conditions(r['path'], r['function'])} for r in delta},
|
||||
'domains': domains, 'authority': authorities, 'conditions': conditions,
|
||||
'reachability': reachability, 'raw_conditions': raw, 'raw_references': references,
|
||||
'series': series, 'source_config_input_environment_and_formal': relocation,
|
||||
'native_bundle': descriptor(native / 'OWNERSHIP_RAW_PROVENANCE.json'),
|
||||
'original_audit': {'sha256': gate.sha(gate.ROOT / gate.DOC_SCOPE / 'order-audit.json'),
|
||||
'content': gate.load(gate.ROOT / gate.DOC_SCOPE / 'order-audit.json')},
|
||||
'review_status': 'pending_review', 'original_credit': '13/88',
|
||||
'safety_outcomes': ['no charging while legitimately stopped',
|
||||
'no double midnight settlement', 'no orphan reservations']}
|
||||
gate.applicability(index, gate.load(gate.SPEC), source_map, gate.TESTED_PRODUCT)
|
||||
return index
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--build', required=True, type=Path)
|
||||
parser.add_argument('--output', required=True, type=Path, help='fresh directory; exclusive creation')
|
||||
parser.add_argument('--invocation', required=True, choices=('validate', 'prepare-integration', 'final'))
|
||||
parser.add_argument('--registered-ref', required=True)
|
||||
parser.add_argument('--tested-ref', required=True)
|
||||
args = parser.parse_args()
|
||||
gate = checker()
|
||||
os.environ['BUILD_OUTPUT_DIR'] = str(args.build.resolve(strict=True))
|
||||
gate.require(args.registered_ref == gate.REGISTERED_COMMIT and args.tested_ref == gate.TESTED_PRODUCT,
|
||||
'source refs differ from immutable evidence roles')
|
||||
output = args.output.absolute()
|
||||
gate.require(not output.exists() and not output.is_symlink(), 'output must be fresh')
|
||||
for path in output.parents:
|
||||
gate.require(not path.is_symlink(), 'output parent symlink')
|
||||
index = payload(args.build, gate)
|
||||
with gate.isolated_objects():
|
||||
_, role = gate.verify_roles(index, args.build.resolve())
|
||||
# The historical invocation is separate from the reusable evidence index.
|
||||
receipt = {'invocation': args.invocation, 'actual_head': role['actual_head'],
|
||||
'actual_tree': role['actual_tree'], 'role': role['role'], 'formal_acceptance': False}
|
||||
output.mkdir(parents=False)
|
||||
for name, value in (('index.json', index), ('generation-invocation.json', receipt)):
|
||||
with (output / name).open('x', encoding='utf-8') as stream:
|
||||
json.dump(value, stream, ensure_ascii=False, sort_keys=True, indent=2)
|
||||
stream.write('\n')
|
||||
print(json.dumps({'evidence_directory': str(output), **receipt}))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Root-only disposable repository controls; never original acceptance evidence."""
|
||||
import importlib.util
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from unittest.mock import patch
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
ROOT = Path(__file__).resolve().parents[5]
|
||||
|
||||
|
||||
def rejected(operation):
|
||||
try:
|
||||
operation()
|
||||
except (ValueError, subprocess.CalledProcessError):
|
||||
return
|
||||
raise AssertionError('invalid input/response accepted')
|
||||
|
||||
|
||||
def run():
|
||||
spec = importlib.util.spec_from_file_location('bulk_gate', ROOT / 'tools/check_p04_order_evidence.py')
|
||||
gate = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(gate)
|
||||
with tempfile.TemporaryDirectory(prefix='p04-bulk-fixture-') as temporary:
|
||||
base = Path(temporary)
|
||||
repo = base / 'repo'
|
||||
repo.mkdir()
|
||||
gate.ROOT = repo
|
||||
env = gate.git_environment()
|
||||
env.update(GIT_AUTHOR_NAME='fixture', GIT_AUTHOR_EMAIL='fixture@invalid',
|
||||
GIT_COMMITTER_NAME='fixture', GIT_COMMITTER_EMAIL='fixture@invalid')
|
||||
|
||||
def git(*args, data=None):
|
||||
return subprocess.check_output(['git', '-c', 'core.hooksPath=/dev/null', *args],
|
||||
input=data, cwd=repo, env=env)
|
||||
|
||||
git('init', '--quiet')
|
||||
payloads = [b'\x00binary\r\nline\n', b'', bytes(range(256)) * 32768]
|
||||
oids = [git('hash-object', '-w', '--stdin', data=data).decode().strip() for data in payloads]
|
||||
name = '日本語の木.dat'
|
||||
(repo / name).write_bytes(payloads[0])
|
||||
git('add', '--', name)
|
||||
git('commit', '--quiet', '-m', 'fixture')
|
||||
entries = gate.tree_entries('HEAD') # ls-tree -z resolves the unicode path.
|
||||
assert entries[name][1] == oids[0]
|
||||
expected = {oid: git('cat-file', 'blob', oid) for oid in oids}
|
||||
original_output = subprocess.check_output
|
||||
with patch.object(gate.subprocess, 'check_output', wraps=original_output) as process:
|
||||
assert gate.read_blobs(reversed(oids + [oids[0]])) == expected
|
||||
assert process.call_count == 1
|
||||
assert process.call_args.kwargs['input'] == ('\n'.join(sorted(set(oids))) + '\n').encode()
|
||||
with patch.object(gate.subprocess, 'check_output', side_effect=AssertionError('process started')):
|
||||
assert gate.read_blobs([]) == {}
|
||||
for invalid in ('abc', 'g' * 40, 'a' * 39, 'a' * 41, 'HEAD', oids[0] + '\n', None):
|
||||
rejected(lambda: gate.read_blobs([invalid]))
|
||||
for oid in ('0' * 40, git('rev-parse', 'HEAD').decode().strip(),
|
||||
git('rev-parse', 'HEAD^{tree}').decode().strip()):
|
||||
rejected(lambda: gate.read_blobs([oid]))
|
||||
|
||||
oid = oids[0]
|
||||
header = oid.encode() + b' blob '
|
||||
valid = header + b'3\na\x00\n\n'
|
||||
assert gate.parse_blob_batch(valid, [oid]) == {oid: b'a\x00\n'}
|
||||
bad = [b'', header + b'3', header + b'3\na', valid[:-1],
|
||||
header + b'999\na\n', header + b'1\nabc\n', valid + b'garbage',
|
||||
oid.encode() + b' missing\n', oid.encode() + b' ambiguous\n',
|
||||
header + b'-1\n', header + b'x\n', header + b'1.0\n',
|
||||
b'f' * 40 + b' blob 0\n\n', oid.encode() + b' tree 0\n\n']
|
||||
for response in bad:
|
||||
rejected(lambda: gate.parse_blob_batch(response, [oid]))
|
||||
with patch.object(gate.subprocess, 'check_output', return_value=response):
|
||||
rejected(lambda: gate.read_blobs([oid]))
|
||||
rejected(lambda: gate.parse_blob_batch(valid + valid, [oid]))
|
||||
rejected(lambda: gate.parse_blob_batch(valid, [oid, oids[1]]))
|
||||
with patch.object(gate.subprocess, 'check_output',
|
||||
side_effect=subprocess.CalledProcessError(7, ['git'])):
|
||||
rejected(lambda: gate.read_blobs([oid]))
|
||||
|
||||
with patch.dict(os.environ, BUILD_OUTPUT_DIR=str(base)):
|
||||
with gate.isolated_objects():
|
||||
private_data = b'isolated-only unique payload\x00\n'
|
||||
private = subprocess.check_output(
|
||||
['git', 'hash-object', '-w', '--stdin'], input=private_data,
|
||||
cwd=repo, env=gate._GIT_ENV).decode().strip()
|
||||
assert gate.read_blobs([private]) == {private: private_data}
|
||||
rejected(lambda: gate.read_blobs([private]))
|
||||
with gate.isolated_objects():
|
||||
rejected(lambda: gate.read_blobs([private]))
|
||||
assert gate.read_blobs(oids) == expected
|
||||
assert gate._GIT_ENV is None
|
||||
print('bulk reader equivalence controls completed')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
run()
|
||||
@@ -0,0 +1,576 @@
|
||||
{
|
||||
"scope": "C Python controls on supplied raw; no formal acceptance",
|
||||
"formal_original_credit": 0,
|
||||
"engine_runs": 0,
|
||||
"results": [
|
||||
{
|
||||
"control": "native_ownership_supplied_raw",
|
||||
"result": "pass"
|
||||
},
|
||||
{
|
||||
"control": "native_saves_supplied_raw",
|
||||
"result": "pass"
|
||||
},
|
||||
{
|
||||
"control": "native_ledger_supplied_raw",
|
||||
"result": "pass"
|
||||
},
|
||||
{
|
||||
"control": "supplied_applicability",
|
||||
"result": "pass"
|
||||
},
|
||||
{
|
||||
"control": "verify_native_supplied_raw",
|
||||
"result": "pass",
|
||||
"details": {
|
||||
"service_id": "service_00000001",
|
||||
"transaction_id": "ledger_00000081",
|
||||
"receipt_rows": 8,
|
||||
"formal_original_credit": 0,
|
||||
"production_commit": "5f6bc11d6bb6e92d22d787989b219fef3a435bc1",
|
||||
"owned_processes": 10,
|
||||
"verified_conditions": [
|
||||
"dependency_stop",
|
||||
"recovery_completion",
|
||||
"paused_midnight"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"control": "signature_line_sha",
|
||||
"result": "rejected",
|
||||
"error": "native ordering bytes",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "trailing_comment_excluded_sha",
|
||||
"result": "rejected",
|
||||
"error": "native ordering bytes",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "one_byte_raw",
|
||||
"result": "rejected",
|
||||
"error": "native SHA: actual-native/repro/stdout.log",
|
||||
"manifest_hashes_refreshed": false
|
||||
},
|
||||
{
|
||||
"control": "missing_member",
|
||||
"result": "rejected",
|
||||
"error": "missing/outside file: dependency-formal-native/formal-packages/reader/paused/world.bin",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "writer_reader_same_pid",
|
||||
"result": "rejected",
|
||||
"error": "native writer reader PID binding",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "writer_reader_same_profile",
|
||||
"result": "rejected",
|
||||
"error": "native writer reader profile reused",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "abnormal_ended",
|
||||
"result": "rejected",
|
||||
"error": "native process bindings",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "process_pid_changed",
|
||||
"result": "rejected",
|
||||
"error": "native PID chain",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "cleanup_missing",
|
||||
"result": "rejected",
|
||||
"error": "native owned cleanup binding",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "execution_owned_pid_deleted",
|
||||
"result": "rejected",
|
||||
"error": "native execution cleanup membership",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "reader_receipt_row_duplicated",
|
||||
"result": "rejected",
|
||||
"error": "native duplicate receipt row",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "reader_receipt_row_deleted",
|
||||
"result": "rejected",
|
||||
"error": "native reader receipt delta",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "observation_receipt_deleted",
|
||||
"result": "rejected",
|
||||
"error": "native ledger receipt delta",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "paused_sale_injected",
|
||||
"result": "rejected",
|
||||
"error": "native ledger receipt delta",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "paused_sale_joined_ledger_wallet",
|
||||
"result": "rejected",
|
||||
"error": "native PAUSED sale/progress/reason",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "reservation_orphaned",
|
||||
"result": "rejected",
|
||||
"error": "native reservation snapshot binding",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "reservation_orphaned_both_snapshots",
|
||||
"result": "rejected",
|
||||
"error": "native orphan reservation relation",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "wallet_wrong",
|
||||
"result": "rejected",
|
||||
"error": "native wallet delta",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "idempotency_wrong_transaction",
|
||||
"result": "rejected",
|
||||
"error": "native sale service join",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "production_commit_mismatch",
|
||||
"result": "rejected",
|
||||
"error": "native production commit mismatch",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "current_source_pin_mismatch",
|
||||
"result": "rejected",
|
||||
"error": "native current_source_pins mismatch",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "input_source_pin_mismatch",
|
||||
"result": "rejected",
|
||||
"error": "native input source pin mismatch",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "original_member_changed",
|
||||
"result": "rejected",
|
||||
"error": "native original members changed",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "typed_var_changed",
|
||||
"result": "rejected",
|
||||
"error": "native typed SHA mismatch",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "self_reported_scalar_true",
|
||||
"result": "rejected",
|
||||
"error": "native reader receipt delta",
|
||||
"manifest_hashes_refreshed": true
|
||||
}
|
||||
],
|
||||
"previous_validation": {
|
||||
"scope": "C Python controls only; synthetic corrected applicability is not native proof",
|
||||
"formal_original_credit": 0,
|
||||
"engine_runs": 0,
|
||||
"results": [
|
||||
{
|
||||
"control": "native_ownership_supplied_raw",
|
||||
"result": "pass"
|
||||
},
|
||||
{
|
||||
"control": "native_saves_supplied_raw",
|
||||
"result": "pass"
|
||||
},
|
||||
{
|
||||
"control": "native_ledger_supplied_raw",
|
||||
"result": "pass"
|
||||
},
|
||||
{
|
||||
"control": "supplied_applicability",
|
||||
"result": "rejected",
|
||||
"error": "native ordering bytes"
|
||||
},
|
||||
{
|
||||
"control": "synthetic_corrected_applicability_composition",
|
||||
"result": "pass",
|
||||
"native_acceptance": false
|
||||
},
|
||||
{
|
||||
"control": "one_byte_raw",
|
||||
"result": "rejected",
|
||||
"error": "native SHA: actual-native/repro/stdout.log",
|
||||
"manifest_hashes_refreshed": false
|
||||
},
|
||||
{
|
||||
"control": "missing_member",
|
||||
"result": "rejected",
|
||||
"error": "missing/outside file: dependency-formal-native/formal-packages/reader/paused/world.bin",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "writer_reader_same_pid",
|
||||
"result": "rejected",
|
||||
"error": "native writer reader PID binding",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "writer_reader_same_profile",
|
||||
"result": "rejected",
|
||||
"error": "native writer reader profile reused",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "abnormal_ended",
|
||||
"result": "rejected",
|
||||
"error": "native process bindings",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "process_pid_changed",
|
||||
"result": "rejected",
|
||||
"error": "native PID chain",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "cleanup_missing",
|
||||
"result": "rejected",
|
||||
"error": "native owned cleanup binding",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "execution_owned_pid_deleted",
|
||||
"result": "rejected",
|
||||
"error": "native execution cleanup membership",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "reader_receipt_row_duplicated",
|
||||
"result": "rejected",
|
||||
"error": "native duplicate receipt row",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "reader_receipt_row_deleted",
|
||||
"result": "rejected",
|
||||
"error": "native reader receipt delta",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "observation_receipt_deleted",
|
||||
"result": "rejected",
|
||||
"error": "native ledger receipt delta",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "paused_sale_injected",
|
||||
"result": "rejected",
|
||||
"error": "native ledger receipt delta",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "paused_sale_joined_ledger_wallet",
|
||||
"result": "rejected",
|
||||
"error": "native PAUSED sale/progress/reason",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "reservation_orphaned",
|
||||
"result": "rejected",
|
||||
"error": "native reservation snapshot binding",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "reservation_orphaned_both_snapshots",
|
||||
"result": "rejected",
|
||||
"error": "native orphan reservation relation",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "wallet_wrong",
|
||||
"result": "rejected",
|
||||
"error": "native wallet delta",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "idempotency_wrong_transaction",
|
||||
"result": "rejected",
|
||||
"error": "native sale service join",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "production_commit_mismatch",
|
||||
"result": "rejected",
|
||||
"error": "native production commit mismatch",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "current_source_pin_mismatch",
|
||||
"result": "rejected",
|
||||
"error": "native current_source_pins mismatch",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "input_source_pin_mismatch",
|
||||
"result": "rejected",
|
||||
"error": "native input source pin mismatch",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "original_member_changed",
|
||||
"result": "rejected",
|
||||
"error": "native original members changed",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "typed_var_changed",
|
||||
"result": "rejected",
|
||||
"error": "native typed SHA mismatch",
|
||||
"manifest_hashes_refreshed": true
|
||||
},
|
||||
{
|
||||
"control": "self_reported_scalar_true",
|
||||
"result": "rejected",
|
||||
"error": "native reader receipt delta",
|
||||
"manifest_hashes_refreshed": true
|
||||
}
|
||||
],
|
||||
"validated_source_sha256": {
|
||||
"tools/check_p04_order_evidence.py": "98b2692c604055d7b970e6ef9e3ad1795f35823c3c19d1acad5b80ad2ec7a378",
|
||||
"docs/plans/original-completion-2026-10-02/execution/p04-01/checker_native_fixtures.py": "26bbdcee483816b8bc60c250bbc63bf0a72a194dce2ba9b690abace9b6b17bbc"
|
||||
},
|
||||
"supplied_bundle_pins": {
|
||||
"SOURCE_MANIFEST.json": "381b439fb1d4ee5f30e21243b03ad755c3ab654a9e414af5648004054d67b5f4",
|
||||
"OWNERSHIP_RAW_PROVENANCE.json": "51d746879b4197445a09cd44a88dd918c365fa98025d9cd652a740cee09a9040",
|
||||
"POST_P18_CURRENT_SOURCE_APPLICABILITY.json": "ecb92215b7bfb4368ddeb6d44dcd467d13e97d677ca2f29df3ab9684f6c757c7"
|
||||
},
|
||||
"whole_native_status": "blocked: supplied applicability body hashes exclude four function bodies",
|
||||
"whole_gate_executed": false
|
||||
},
|
||||
"whole_native_status": "pass: corrected supplied bundle; strict full raw SHA",
|
||||
"whole_gate_executed": false,
|
||||
"validation_invocations": {
|
||||
"native": 1,
|
||||
"existing_fixtures": 1,
|
||||
"applicability_ab": 1
|
||||
},
|
||||
"existing_fixtures": {
|
||||
"scope": "A/B and optional C applicability controls; no native acceptance",
|
||||
"results": [
|
||||
{
|
||||
"control": "registered_equals_tested",
|
||||
"revision": "deb1bcb2374eb378b840ca345db952bd8572cb6e",
|
||||
"changed_units": 0,
|
||||
"result": "pass"
|
||||
},
|
||||
{
|
||||
"control": "line_shift_resolves",
|
||||
"revision": "deb1bcb2374eb378b840ca345db952bd8572cb6e",
|
||||
"result": "pass"
|
||||
},
|
||||
{
|
||||
"control": "legal_delta",
|
||||
"revision": "5f6bc11d6bb6e92d22d787989b219fef3a435bc1",
|
||||
"changed_units": 25,
|
||||
"result": "pass"
|
||||
},
|
||||
{
|
||||
"control": "line_shift_resolves",
|
||||
"revision": "5f6bc11d6bb6e92d22d787989b219fef3a435bc1",
|
||||
"result": "pass"
|
||||
},
|
||||
{
|
||||
"control": "undeclared_delta",
|
||||
"result": "rejected",
|
||||
"error": "git diff/applicability_delta mismatch"
|
||||
},
|
||||
{
|
||||
"control": "missing_boundary_case",
|
||||
"result": "rejected",
|
||||
"error": "boundary delta coverage: autoload/simulation_runtime.gd:_build_facility_precheck"
|
||||
},
|
||||
{
|
||||
"control": "wrong_tested_revision",
|
||||
"result": "rejected",
|
||||
"error": "version roles"
|
||||
},
|
||||
{
|
||||
"control": "native_applicability",
|
||||
"result": "pass"
|
||||
}
|
||||
]
|
||||
},
|
||||
"applicability_ab": {
|
||||
"scope": "Synthetic checker checks only; no engine evidence or formal acceptance",
|
||||
"py_compile": true,
|
||||
"results": [
|
||||
{
|
||||
"fixture": "synthetic_scalar_only_rejected",
|
||||
"exit_code": 1,
|
||||
"result": {
|
||||
"task_id": "P04-01",
|
||||
"machine_verified": false,
|
||||
"formal_acceptance": false,
|
||||
"error": "native bundle required; self-reported scalar evidence is insufficient"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture": "forged_registered_pin",
|
||||
"exit_code": 1,
|
||||
"result": {
|
||||
"task_id": "P04-01",
|
||||
"machine_verified": false,
|
||||
"formal_acceptance": false,
|
||||
"error": "registered pin declaration"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture": "false_delta",
|
||||
"exit_code": 1,
|
||||
"result": {
|
||||
"task_id": "P04-01",
|
||||
"machine_verified": false,
|
||||
"formal_acceptance": false,
|
||||
"error": "git diff/applicability_delta mismatch"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture": "wrong_reach_anchor",
|
||||
"exit_code": 1,
|
||||
"result": {
|
||||
"task_id": "P04-01",
|
||||
"machine_verified": false,
|
||||
"formal_acceptance": false,
|
||||
"error": "unknown reachability anchor"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture": "missing_index",
|
||||
"exit_code": 1,
|
||||
"result": {
|
||||
"task_id": "P04-01",
|
||||
"machine_verified": false,
|
||||
"formal_acceptance": false,
|
||||
"error": "missing/outside file: index.json"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture": "duplicate_key",
|
||||
"exit_code": 1,
|
||||
"result": {
|
||||
"task_id": "P04-01",
|
||||
"machine_verified": false,
|
||||
"formal_acceptance": false,
|
||||
"error": "duplicate JSON key: schema_version"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture": "symlink",
|
||||
"exit_code": 1,
|
||||
"result": {
|
||||
"task_id": "P04-01",
|
||||
"machine_verified": false,
|
||||
"formal_acceptance": false,
|
||||
"error": "symlink: stdout.json"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture": "hash_mismatch",
|
||||
"exit_code": 1,
|
||||
"result": {
|
||||
"task_id": "P04-01",
|
||||
"machine_verified": false,
|
||||
"formal_acceptance": false,
|
||||
"error": "artifact hash: stdout"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture": "stale_pin",
|
||||
"exit_code": 1,
|
||||
"result": {
|
||||
"task_id": "P04-01",
|
||||
"machine_verified": false,
|
||||
"formal_acceptance": false,
|
||||
"error": "stale pin: autoload/simulation_runtime.gd"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture": "unexecuted_receipt",
|
||||
"exit_code": 1,
|
||||
"result": {
|
||||
"task_id": "P04-01",
|
||||
"machine_verified": false,
|
||||
"formal_acceptance": false,
|
||||
"error": "unexecuted/incomplete receipt"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture": "pending_authority",
|
||||
"exit_code": 1,
|
||||
"result": {
|
||||
"task_id": "P04-01",
|
||||
"machine_verified": false,
|
||||
"formal_acceptance": false,
|
||||
"error": "pending/missing authority"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture": "missing_condition",
|
||||
"exit_code": 1,
|
||||
"result": {
|
||||
"task_id": "P04-01",
|
||||
"machine_verified": false,
|
||||
"formal_acceptance": false,
|
||||
"error": "mandatory conditions missing/unknown"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture": "unsupported_reach_false",
|
||||
"exit_code": 1,
|
||||
"result": {
|
||||
"task_id": "P04-01",
|
||||
"machine_verified": false,
|
||||
"formal_acceptance": false,
|
||||
"error": "unsupported reach=false: transport"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixture": "owner_hash_mismatch",
|
||||
"exit_code": 1,
|
||||
"result": {
|
||||
"task_id": "P04-01",
|
||||
"machine_verified": false,
|
||||
"formal_acceptance": false,
|
||||
"error": "owner approval hash mismatch"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"validated_source_sha256": {
|
||||
"tools/check_p04_order_evidence.py": "afe8c7cc3d3b511bacdaa1965fb06a0eefd1b204b7e9b930eb29767c4d320824",
|
||||
"docs/plans/original-completion-2026-10-02/execution/p04-01/checker_native_fixtures.py": "634b5e3a3491cdcdab549ed5d812935f73c2958d8cca008ce35c80b6ebb298e4"
|
||||
},
|
||||
"supplied_bundle_pins": {
|
||||
"SOURCE_MANIFEST.json": "dbaab3c293c191eb5f3aff34854945e2d564f824df50a92802b3a7eda55321a8",
|
||||
"OWNERSHIP_RAW_PROVENANCE.json": "51d746879b4197445a09cd44a88dd918c365fa98025d9cd652a740cee09a9040",
|
||||
"POST_P18_CURRENT_SOURCE_APPLICABILITY.json": "46e0cc35cb95496d5f26d3cab7d3a31cb29faacaa16714daab3aefdf65b420a3"
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Python-only A/B controls. Real Git blobs, synthetic case IDs; no engine proof."""
|
||||
import copy
|
||||
import argparse
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
ROOT = Path(__file__).resolve().parents[5]
|
||||
spec = importlib.util.spec_from_file_location('p04_gate', ROOT / 'tools/check_p04_order_evidence.py')
|
||||
gate = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(gate)
|
||||
PRODUCTION = '5f6bc11d6bb6e92d22d787989b219fef3a435bc1'
|
||||
|
||||
|
||||
def run(native_bundle=None):
|
||||
results = []
|
||||
registered_spec = gate.load(gate.SPEC)
|
||||
# Bind Git reads to the actual repository while testing exported raw blobs.
|
||||
# No branch/index/checkout operation and no synthetic Git command output.
|
||||
gate.git_bytes = lambda *args: subprocess.check_output(['git', *args], cwd=ROOT)
|
||||
with tempfile.TemporaryDirectory(prefix='.applicability-fixtures-', dir=Path(__file__).parent) as tmp:
|
||||
for revision in (gate.REGISTERED_COMMIT, PRODUCTION):
|
||||
base = Path(tmp) / revision
|
||||
source = {}
|
||||
for name in gate.git_bytes('ls-tree', '-r', '--name-only', '-z', revision,
|
||||
'--', 'autoload/', 'scripts/').split(b'\0'):
|
||||
if not name.endswith(b'.gd'):
|
||||
continue
|
||||
path = name.decode()
|
||||
target = base / path
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
target.write_bytes(gate.git_bytes('show', revision + ':' + path))
|
||||
source[path] = gate.sha(target)
|
||||
gate.ROOT = base
|
||||
delta = gate.source_delta(gate.REGISTERED_COMMIT, revision)
|
||||
index = dict(registered_pins=registered_spec['current_source_pins'],
|
||||
versions=dict(execution_origin=gate.EXECUTION_ORIGIN, registered=gate.REGISTERED_COMMIT,
|
||||
tested=revision, integration=None), applicability_delta=delta,
|
||||
conditions={cid: {'case_id': cid} for cid in gate.REQUIRED},
|
||||
reachability={'EV': {'case_id': 'EV'}}, delta_cases={})
|
||||
index['delta_cases'] = {r['path'] + ':' + r['function']: {
|
||||
cid: cid for cid in gate.boundary_conditions(r['path'], r['function'])} for r in delta}
|
||||
gate.applicability(index, registered_spec, source, revision)
|
||||
results.append(dict(control='registered_equals_tested' if not delta else 'legal_delta',
|
||||
revision=revision, changed_units=len(delta), result='pass'))
|
||||
for name, refs in gate.REACH_REFS.items():
|
||||
for path, function, digest, lines in refs:
|
||||
data = (base / path).read_bytes()
|
||||
gate.resolve_anchor(data, function, digest, lines)
|
||||
# Seven lines added before the function must not break reachability.
|
||||
gate.resolve_anchor(b'\n' * 7 + data, function, digest, lines)
|
||||
results.append(dict(control='line_shift_resolves', revision=revision, result='pass'))
|
||||
if delta:
|
||||
for label, mutate in (
|
||||
('undeclared_delta', lambda i: i['applicability_delta'].pop()),
|
||||
('missing_boundary_case', lambda i: i['delta_cases'][next(iter(i['delta_cases']))].clear()),
|
||||
('wrong_tested_revision', lambda i: i['versions'].update(tested=gate.REGISTERED_COMMIT)),
|
||||
):
|
||||
altered = copy.deepcopy(index)
|
||||
mutate(altered)
|
||||
try:
|
||||
gate.applicability(altered, registered_spec, source, revision)
|
||||
except ValueError as exc:
|
||||
results.append(dict(control=label, result='rejected', error=str(exc)))
|
||||
else:
|
||||
raise AssertionError('negative fixture accepted: ' + label)
|
||||
gate.ROOT = ROOT
|
||||
if native_bundle is not None:
|
||||
try:
|
||||
gate.native_applicability(native_bundle)
|
||||
except ValueError as exc:
|
||||
# A/B Git equality must not turn an inconsistent native report into
|
||||
# applicable evidence. The C mutation suite tests a corrected copy.
|
||||
assert str(exc) == 'native ordering bytes', str(exc)
|
||||
results.append(dict(control='native_report_body_hash_gap', result='rejected', error=str(exc)))
|
||||
else:
|
||||
results.append(dict(control='native_applicability', result='pass'))
|
||||
return {'scope': 'A/B and optional C applicability controls; no native acceptance', 'results': results}
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--native-bundle', type=Path)
|
||||
print(json.dumps(run(parser.parse_args().native_bundle), ensure_ascii=False, indent=2))
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Optional getter projection controls; these are never native proof."""
|
||||
import json
|
||||
import struct
|
||||
import sys
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
import p04_native_series as s
|
||||
|
||||
|
||||
def string(value, kind=4):
|
||||
data = value.encode()
|
||||
return struct.pack('<II', kind, len(data)) + data + b'\0' * ((-len(data)) % 4)
|
||||
|
||||
|
||||
def integer(value):
|
||||
return struct.pack('<Ii', 2, value)
|
||||
|
||||
|
||||
def dictionary(rows):
|
||||
return struct.pack('<II', 27, len(rows)) + b''.join(k + v for k, v in rows)
|
||||
|
||||
|
||||
def array(values, typed=None):
|
||||
return (struct.pack('<I', 28 if typed is None else 28 | (1 << 16)) +
|
||||
(b'' if typed is None else struct.pack('<I', typed)) +
|
||||
struct.pack('<I', len(values)) + b''.join(values))
|
||||
|
||||
|
||||
def run():
|
||||
results = []
|
||||
canonical = dictionary([(string('a'), array([string('item')])), (string('n'), integer(7))])
|
||||
getter = dictionary([(string('n', 21), integer(7)),
|
||||
(string('a', 21), array([string('item', 21)], typed=21))])
|
||||
assert s.persisted_day_projection(s.variant(getter)) == canonical
|
||||
results.append({'case': 'typed_array_headers_and_StringName_only', 'result': 'accepted'})
|
||||
|
||||
def reject(name, action, error=None):
|
||||
try:
|
||||
action()
|
||||
except ValueError as exc:
|
||||
assert error is None or error in str(exc), (name, str(exc))
|
||||
results.append({'case': name, 'result': 'rejected', 'error': str(exc)})
|
||||
else:
|
||||
raise AssertionError('negative accepted: ' + name)
|
||||
|
||||
def compare(data):
|
||||
s.require(s.persisted_day_projection(s.variant(data)) == canonical, 'persisted value mismatch')
|
||||
|
||||
reject('dropped_field', lambda: compare(dictionary([(string('n'), integer(7))])))
|
||||
reject('extra_field', lambda: compare(dictionary([(string('a'), array([string('item')])),
|
||||
(string('n'), integer(7)), (string('x'), integer(0))])))
|
||||
reject('int_to_float', lambda: compare(dictionary([(string('a'), array([string('item')])),
|
||||
(string('n'), struct.pack('<If', 3, 7.0))])))
|
||||
floating = dictionary([(string('n'), struct.pack('<If', 3, 7.0))])
|
||||
reject('float_to_int', lambda: s.require(s.persisted_day_projection(s.variant(floating)) ==
|
||||
dictionary([(string('n'), integer(7))]), 'numeric type changed'))
|
||||
reject('value_change', lambda: compare(dictionary([(string('a'), array([string('item')])),
|
||||
(string('n'), integer(8))])))
|
||||
reject('nonfinite_float', lambda: s.persisted_day_projection(s.variant(dictionary([
|
||||
(string('n'), struct.pack('<If', 3, float('inf')))]))), 'NON_FINITE_NUMBER')
|
||||
reject('invalid_key_type', lambda: s.persisted_day_projection(s.variant(dictionary([
|
||||
(struct.pack('<II', 1, 1), integer(1))]))), 'INVALID_DICTIONARY_KEY')
|
||||
reject('key_collision', lambda: s.persisted_day_projection(s.variant(dictionary([
|
||||
(integer(1), integer(1)), (string('1'), integer(2))]))), 'DUPLICATE_CANONICAL_KEY')
|
||||
reject('unsaveable_variant', lambda: s.persisted_day_projection(s.variant(dictionary([
|
||||
(string('x'), struct.pack('<Iff', 5, 1.0, 2.0))]))), 'UNSAVEABLE_VARIANT')
|
||||
reject('wrong_to_snapshot_variant_chain', lambda: s.persisted_day_projection(s.variant(getter),
|
||||
chain='_to_snapshot_variant'), 'projection call chain')
|
||||
reject('projection_on_native_persisted_comparison', lambda: s.persisted_day_projection(s.variant(getter),
|
||||
comparison='native_expected_restored'), 'projection forbidden')
|
||||
reject('single_string_budget', lambda: s.persisted_day_projection(s.variant(dictionary([
|
||||
(string('x'), string('x' * 4097))]))), 'STRING_TOO_LONG')
|
||||
reject('collection_budget', lambda: s.persisted_day_projection(s.variant(dictionary([
|
||||
(string('x'), array([integer(0)] * 20001))]))), 'COLLECTION_TOO_LARGE')
|
||||
nested = integer(1)
|
||||
for _ in range(33):
|
||||
nested = array([nested])
|
||||
reject('depth_budget', lambda: s.persisted_day_projection(s.variant(dictionary([
|
||||
(string('x'), nested)]))), 'MAX_VARIANT_DEPTH')
|
||||
# The collision prepass must win over invalid-key and child errors.
|
||||
reject('collision_error_precedence', lambda: s.persisted_day_projection(s.variant(dictionary([
|
||||
(struct.pack('<II', 1, 1), struct.pack('<If', 3, float('inf'))),
|
||||
(string('true'), integer(0))]))), 'DUPLICATE_CANONICAL_KEY')
|
||||
return {'scope': 'synthetic projection controls only', 'results': results}
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(json.dumps(run(), indent=2))
|
||||
@@ -0,0 +1,155 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Real B1/R2 positive controls and semantic mutation negatives for Root."""
|
||||
import argparse
|
||||
import copy
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from unittest.mock import patch
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
import p04_native_series as series
|
||||
from build_p04_index import checker
|
||||
|
||||
|
||||
def run(build):
|
||||
gate = checker()
|
||||
base = build / 'audit-support/native-first-current'
|
||||
results = []
|
||||
|
||||
def reject(name, action):
|
||||
try:
|
||||
action()
|
||||
except (ValueError, KeyError, TypeError) as exc:
|
||||
results.append({'case': name, 'result': 'rejected', 'error': str(exc)})
|
||||
else:
|
||||
raise AssertionError('negative accepted: ' + name)
|
||||
|
||||
context = series.Series(build, 's3')
|
||||
series.dependency_closure_diagnostic(context)
|
||||
reject('B1_cannot_supply_power_stop', lambda: series.power_stop(context))
|
||||
results.append({'case': 'B1_real_despawn_positive', 'result': 'accepted'})
|
||||
original_value = context.value
|
||||
for name, label, mutate in (
|
||||
('dependency_reason_relabelled_as_NO_POWER', 'after', lambda r: r['service'].update(interruption_reason='NO_POWER')),
|
||||
('B1_missing_at_closure', 'after', lambda r: r['guests'].update({'p04.s3.guest.b': {}})),
|
||||
('B1_synthetic_later_guest', 'next', lambda r: r['guests'].update({'p04.s3.guest.b': {'patience_remaining': 45}})),
|
||||
('B1_wrong_release_minute', 'next', lambda r: r['reservations'][1].update(terminal_at=961)),
|
||||
('B1_changed_wallet', 'recovered', lambda r: r['wallets'].update({'p04.s3.guest.b': 25})),
|
||||
('B1_missing_departure_history', 'next', lambda r: r['snapshot']['systems']['runtime']['guests'].update(persistent_records=[])),
|
||||
):
|
||||
def altered(key, label=label, mutate=mutate):
|
||||
value = copy.deepcopy(original_value(key))
|
||||
if key == 'writer-B1-' + label:
|
||||
mutate(value)
|
||||
return value
|
||||
with patch.object(context, 'value', altered):
|
||||
reject(name, lambda: series.dependency_closure_diagnostic(context))
|
||||
gate.verify_shift_completion(base)
|
||||
results.append({'case': 'R2_real_first_1080_positive', 'result': 'accepted'})
|
||||
original_load = gate.load
|
||||
observation_path = base / 'actual-native/order-evidence/observations.json'
|
||||
for name, mutate in (
|
||||
('R2_paid_midnight_substitution', lambda o: o['observations'][2]['service'].update(completed_at=1442)),
|
||||
('R2_wrong_transaction', lambda o: o['observations'][2]['receipts'][0].update(id='ledger_00000099')),
|
||||
('R2_missing_DAY2', lambda o: o['observations'][2].update(active_staff=[])),
|
||||
('R2_after_staff_still_present', lambda o: o['observations'][3].update(active_staff=['p04.order.server.a'])),
|
||||
('R2_next_extra_receipt', lambda o: o['observations'][5]['receipts'].append(copy.deepcopy(o['first_receipts'][0]))),
|
||||
):
|
||||
def altered(path, mutate=mutate):
|
||||
value = original_load(path)
|
||||
if path == observation_path:
|
||||
mutate(value)
|
||||
return value
|
||||
with patch.object(gate, 'load', altered):
|
||||
reject(name, lambda: gate.verify_shift_completion(base))
|
||||
# Dynamic IDs are legitimate when every raw join agrees. These are semantic
|
||||
# overlays of already verified raw, not replacement native evidence.
|
||||
shift = gate.verify_shift_completion(base)
|
||||
old_pids = gate.native_ownership(base)
|
||||
replacements = {pid: pid + 1000000 for pid in old_pids.values()}
|
||||
replacements[shift['transaction_id']] = 'dynamic-sale-repair22'
|
||||
|
||||
def remap(value):
|
||||
if isinstance(value, dict): return {k: remap(v) for k, v in value.items()}
|
||||
if isinstance(value, list): return [remap(v) for v in value]
|
||||
if isinstance(value, (str, int)) and not isinstance(value, bool):
|
||||
return replacements.get(value, value)
|
||||
return value
|
||||
|
||||
with patch.object(gate, 'load', lambda path: remap(original_load(path))):
|
||||
dynamic = gate.verify_shift_completion(base)
|
||||
assert dynamic['transaction_id'] == 'dynamic-sale-repair22'
|
||||
results.append({'case': 'R2_dynamic_PID_and_transaction_positive', 'result': 'accepted'})
|
||||
wrong_end = base / 'actual-native' / gate.NATIVE_RUNS['actual-native'][0] / 'ended.json'
|
||||
|
||||
def wrong_pid(path):
|
||||
value = original_load(path)
|
||||
if path == wrong_end: value['actual_pid'] += 1
|
||||
return value
|
||||
|
||||
with patch.object(gate, 'load', wrong_pid):
|
||||
reject('R2_wrong_PID_join', lambda: gate.verify_shift_completion(base))
|
||||
|
||||
s1 = series.Series(build, 's1')
|
||||
series.s1_semantics(s1)
|
||||
s1_value = s1.value
|
||||
for name, mutate in (
|
||||
('S1_pre_travel_mismatch', lambda r: r['reserved_before_travel'].update(staff_cell=(6, 0, 1))),
|
||||
('S1_no_actual_outage', lambda r: r['reserve_exhausted_before_tick']['snapshot']['game_state']['resources'].update(mana=1)),
|
||||
):
|
||||
def altered(key, mutate=mutate):
|
||||
value = copy.deepcopy(s1_value(key))
|
||||
if key == 'writer-landing-observations': mutate(value)
|
||||
return value
|
||||
with patch.object(s1, 'value', altered):
|
||||
reject(name, lambda: series.s1_semantics(s1))
|
||||
|
||||
# S4 is deliberately unavailable until the single Root collection exists.
|
||||
# Never substitute B1 or manufacture a positive raw fixture for it.
|
||||
s4 = series.S4Series(build)
|
||||
series.power_stop(s4)
|
||||
results.append({'case': 'S4_real_NO_POWER_positive', 'result': 'accepted'})
|
||||
s4_value = s4.value
|
||||
|
||||
def facility_change(row, field, value, kitchen=False):
|
||||
target = row['kitchen' if kitchen else 'facility']
|
||||
target[field] = value
|
||||
for f in series.runtime(row)['facilities']['instances']:
|
||||
if f['instance_id'] == target['instance_id']: f[field] = value
|
||||
|
||||
for name, mutate in (
|
||||
('S4_related_facility_operating_false', lambda r: r.update(related_facility_operating=False)),
|
||||
('S4_kitchen_unpowered', lambda r: facility_change(r, 'power_available', False, kitchen=True)),
|
||||
('S4_closure_confound', lambda r: facility_change(r, 'operator_open', False)),
|
||||
('S4_no_actual_outage', lambda r: facility_change(r, 'power_available', True)),
|
||||
('S4_wrong_transaction_join', lambda r: series.runtime(r)['economy']['idempotency'].clear()),
|
||||
):
|
||||
def altered(key, mutate=mutate, name=name):
|
||||
value = copy.deepcopy(s4_value(key))
|
||||
label = 'recovered' if name == 'S4_wrong_transaction_join' else 'boundary'
|
||||
if key == 'writer-S4-' + label: mutate(value)
|
||||
return value
|
||||
with patch.object(s4, 'value', altered):
|
||||
reject(name, lambda: series.power_stop(s4))
|
||||
# Rename every occurrence, including service.payment.<serviceID> keys.
|
||||
original = s4_value('writer-S4-before')['service']
|
||||
paid = series.sales(s4_value('writer-S4-recovered'))[0]
|
||||
id_map = {original['service_id']: 'dynamic-service-22', paid['transaction_id']: 'dynamic-sale-22',
|
||||
'service.payment.' + original['service_id']: 'service.payment.dynamic-service-22'}
|
||||
|
||||
def dynamic_ids(value):
|
||||
if isinstance(value, dict): return {id_map.get(k, k): dynamic_ids(v) for k, v in value.items()}
|
||||
if isinstance(value, list): return [dynamic_ids(v) for v in value]
|
||||
return id_map.get(value, value) if isinstance(value, str) else value
|
||||
|
||||
with patch.object(s4, 'value', lambda key: dynamic_ids(s4_value(key))):
|
||||
assert series.power_stop(s4)['transaction_id'] == 'dynamic-sale-22'
|
||||
results.append({'case': 'S4_dynamic_service_and_sale_positive', 'result': 'accepted'})
|
||||
return {'scope': 'semantic controls only; no new native execution', 'results': results}
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--build', type=Path, required=True)
|
||||
print(json.dumps(run(parser.parse_args().build), indent=2))
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
#!/usr/bin/env python3
|
||||
"""C component controls and raw mutations. No engine, Runner or acceptance."""
|
||||
import argparse
|
||||
import copy
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
HERE = Path(__file__).resolve().parent
|
||||
ROOT = HERE.parents[4]
|
||||
spec = importlib.util.spec_from_file_location('p04_native_gate', ROOT / 'tools/check_p04_order_evidence.py')
|
||||
gate = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(gate)
|
||||
|
||||
|
||||
def run(source):
|
||||
results = []
|
||||
for function in (gate.native_ownership, gate.native_saves, gate.native_ledger):
|
||||
function(source)
|
||||
results.append({'control': function.__name__ + '_supplied_raw', 'result': 'pass'})
|
||||
gate.native_applicability(source)
|
||||
results.append({'control': 'supplied_applicability', 'result': 'pass'})
|
||||
native = gate.verify_native(source)
|
||||
results.append({'control': 'verify_native_supplied_raw', 'result': 'pass', 'details': native})
|
||||
with tempfile.TemporaryDirectory(prefix='.native-fixtures-', dir=HERE) as tmp:
|
||||
base = Path(tmp) / 'raw'
|
||||
shutil.copytree(source, base)
|
||||
app_path = 'POST_P18_CURRENT_SOURCE_APPLICABILITY.json'
|
||||
originals = {p.relative_to(base).as_posix(): p.read_bytes() for p in base.rglob('*') if p.is_file()}
|
||||
changed = set()
|
||||
|
||||
def edit(name, mutation):
|
||||
path = base / name
|
||||
value = gate.load(path)
|
||||
mutation(value)
|
||||
path.write_text(json.dumps(value))
|
||||
changed.add(name)
|
||||
|
||||
def refresh():
|
||||
for manifest, key, field in (('OWNERSHIP_RAW_PROVENANCE.json', 'copied', 'target'),
|
||||
('SOURCE_MANIFEST.json', 'files', 'copy')):
|
||||
def update(value):
|
||||
for row in value[key]:
|
||||
path = base / row[field]
|
||||
if path.is_file():
|
||||
row.update(bytes=path.stat().st_size, sha256=gate.sha(path))
|
||||
edit(manifest, update)
|
||||
|
||||
def check(name, function, mutation, error, rehash=True):
|
||||
for path in changed:
|
||||
(base / path).write_bytes(originals[path])
|
||||
changed.clear()
|
||||
mutation()
|
||||
if rehash:
|
||||
refresh()
|
||||
try:
|
||||
function(base)
|
||||
except (ValueError, KeyError, TypeError) as exc:
|
||||
assert error in str(exc), (name, str(exc))
|
||||
results.append({'control': name, 'result': 'rejected', 'error': str(exc),
|
||||
'manifest_hashes_refreshed': rehash})
|
||||
else:
|
||||
raise AssertionError('negative fixture accepted: ' + name)
|
||||
|
||||
def truncated_hash(kind):
|
||||
def mutate(app):
|
||||
for row in app['ordering_functions']:
|
||||
body = gate.function_bytes(gate.git_bytes(
|
||||
'show', app['production_commit'] + ':' + row['path']))[row['function']]
|
||||
if kind == 'signature':
|
||||
if row['function'] != '_complete_service_transaction':
|
||||
continue
|
||||
truncated = body.splitlines(keepends=True)[0]
|
||||
else:
|
||||
offsets = [m.start() for m in re.finditer(rb'(?m)^#', body)]
|
||||
offsets = [i for i in offsets if all(
|
||||
not line.strip() or line.startswith(b'#') for line in body[i:].splitlines())]
|
||||
if not offsets:
|
||||
continue
|
||||
truncated = body[:offsets[0]]
|
||||
assert truncated != body
|
||||
digest = hashlib.sha256(truncated).hexdigest()
|
||||
row.update(registered_body_sha256=digest, production_body_sha256=digest)
|
||||
return
|
||||
raise AssertionError('no truncation target: ' + kind)
|
||||
edit(app_path, mutate)
|
||||
check('signature_line_sha', gate.native_applicability,
|
||||
lambda: truncated_hash('signature'), 'native ordering bytes')
|
||||
check('trailing_comment_excluded_sha', gate.native_applicability,
|
||||
lambda: truncated_hash('comment'), 'native ordering bytes')
|
||||
|
||||
formal = 'dependency-formal-native/'
|
||||
obs = 'dependency-midnight-native/order-evidence/observations.json'
|
||||
reader = formal + 'restart-evidence/reader.json'
|
||||
def corrupt():
|
||||
name = 'actual-native/repro/stdout.log'
|
||||
data = bytearray((base / name).read_bytes())
|
||||
data[0] ^= 1
|
||||
(base / name).write_bytes(data)
|
||||
changed.add(name)
|
||||
check('one_byte_raw', gate.native_ownership, corrupt, 'native SHA', False)
|
||||
def remove_member():
|
||||
name = formal + 'formal-packages/reader/paused/world.bin'
|
||||
(base / name).unlink()
|
||||
changed.add(name)
|
||||
check('missing_member', gate.native_saves, remove_member, 'missing/outside')
|
||||
check('writer_reader_same_pid', gate.native_ownership,
|
||||
lambda: edit(reader, lambda d: d.update(pid=gate.load(base / (formal + 'restart-evidence/writer.json'))['pid'])),
|
||||
'PID binding')
|
||||
check('writer_reader_same_profile', gate.native_ownership,
|
||||
lambda: edit(reader, lambda d: d.update(profile=gate.load(base / (formal + 'restart-evidence/writer.json'))['profile'])),
|
||||
'profile reused')
|
||||
check('abnormal_ended', gate.native_ownership,
|
||||
lambda: edit(formal + 'reader/ended.json', lambda d: d.update(exit_code=1)), 'process bindings')
|
||||
check('process_pid_changed', gate.native_ownership,
|
||||
lambda: edit(formal + 'reader/process.json', lambda d: d.update(pid=d['pid'] + 1)), 'PID chain')
|
||||
check('cleanup_missing', gate.native_ownership,
|
||||
lambda: edit(formal + 'reader/outer-owned-finally.json', lambda d: d.update(cleanup=[])), 'cleanup binding')
|
||||
check('execution_owned_pid_deleted', gate.native_ownership,
|
||||
lambda: edit(formal + 'execution.json', lambda d: d['actual_captured_PIDs'].pop()), 'cleanup membership')
|
||||
check('reader_receipt_row_duplicated', gate.native_ledger,
|
||||
lambda: edit(reader, lambda d: d['results'][0]['receipts'][-1]['rows'].append(copy.deepcopy(d['results'][0]['receipts'][-1]['rows'][0]))),
|
||||
'duplicate receipt row')
|
||||
check('reader_receipt_row_deleted', gate.native_ledger,
|
||||
lambda: edit(reader, lambda d: d['results'][0]['receipts'][-1]['rows'].pop()), 'reader receipt delta')
|
||||
check('observation_receipt_deleted', gate.native_ledger,
|
||||
lambda: edit(obs, lambda d: d['observations'][3]['receipts'].pop()), 'ledger receipt delta')
|
||||
check('paused_sale_injected', gate.native_ledger,
|
||||
lambda: edit(obs, lambda d: d['observations'][1]['receipts'].append(copy.deepcopy(d['observations'][3]['receipts'][-1]))),
|
||||
'ledger receipt delta')
|
||||
def joined_paused_sale():
|
||||
observation = gate.load(base / obs)
|
||||
receipt = copy.deepcopy(observation['observations'][3]['receipts'][-1])
|
||||
sale = next(r for r in receipt['rows'] if r['category'] == 'SERVICE_SALE')
|
||||
def inject(d):
|
||||
paused = d['observations'][1]
|
||||
paused['receipts'].append(receipt)
|
||||
paused['wallet'] -= sale['amount']
|
||||
paused['guest']['wallet_gold'] = paused['wallet']
|
||||
edit(obs, inject)
|
||||
edit('dependency-midnight-native/order-evidence/midnight-midnight.json',
|
||||
lambda d: d['systems']['runtime']['economy']['entries'].extend(receipt['rows']))
|
||||
check('paused_sale_joined_ledger_wallet', gate.native_ledger, joined_paused_sale, 'PAUSED sale/progress/reason')
|
||||
check('reservation_orphaned', gate.native_ledger,
|
||||
lambda: edit(obs, lambda d: d['observations'][1]['reservations']['reservations'][0].update(requester_id='orphan')),
|
||||
'reservation snapshot binding')
|
||||
def joined_orphan():
|
||||
edit(obs, lambda d: d['observations'][1]['reservations']['reservations'][0].update(requester_id='orphan'))
|
||||
edit('dependency-midnight-native/order-evidence/midnight-midnight.json',
|
||||
lambda d: d['systems']['runtime']['inventory']['reservations'][0].update(requester_id='orphan'))
|
||||
check('reservation_orphaned_both_snapshots', gate.native_ledger, joined_orphan, 'orphan reservation relation')
|
||||
check('wallet_wrong', gate.native_ledger,
|
||||
lambda: edit(obs, lambda d: d['observations'][3].update(wallet=d['observations'][3]['wallet'] + 1)), 'wallet delta')
|
||||
state = 'dependency-midnight-native/order-evidence/midnight-after.json'
|
||||
check('idempotency_wrong_transaction', gate.native_ledger,
|
||||
lambda: edit(state, lambda d: next(r for r in d['systems']['runtime']['economy']['idempotency'] if r['key'].startswith('service.payment.')).update(transaction_id='wrong')),
|
||||
'sale service join')
|
||||
check('production_commit_mismatch', gate.native_applicability,
|
||||
lambda: edit('ROOT_APPROVED_DEPENDENCY_FORMAL_RESULT.json', lambda d: d.update(production_commit=gate.REGISTERED_COMMIT)),
|
||||
'production commit mismatch')
|
||||
check('current_source_pin_mismatch', gate.native_applicability,
|
||||
lambda: edit(app_path, lambda d: d['registered_source_pins'][0].update(production_sha256='0' * 64)),
|
||||
'current_source_pins mismatch')
|
||||
check('input_source_pin_mismatch', gate.native_applicability,
|
||||
lambda: edit(formal + 'runner-input.json', lambda d: d['selected_source_map'].update({'autoload/simulation_runtime.gd': '0' * 64})),
|
||||
'input source pin mismatch')
|
||||
check('original_member_changed', gate.native_saves,
|
||||
lambda: edit(formal + 'execution.json', lambda d: d['formal_after']['before']['world.bin'].update(sha256='0' * 64)),
|
||||
'original members changed')
|
||||
def typed_corrupt():
|
||||
name = formal + 'restart-evidence/reader-before-loaded.var'
|
||||
(base / name).write_bytes(b'forged typed bytes')
|
||||
changed.add(name)
|
||||
check('typed_var_changed', gate.native_saves, typed_corrupt, 'typed SHA mismatch')
|
||||
check('self_reported_scalar_true', gate.native_ledger,
|
||||
lambda: edit(reader, lambda d: d.update(results=[{'receipts': [], 'sale_count': 1}],
|
||||
passed=True, formal_save_restored=True)), 'reader receipt delta')
|
||||
return {'scope': 'C Python controls on supplied raw; no formal acceptance',
|
||||
'formal_original_credit': 0, 'engine_runs': 0, 'results': results}
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('native_bundle', type=Path)
|
||||
print(json.dumps(run(parser.parse_args().native_bundle), ensure_ascii=False, indent=2))
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Synthetic checker fixtures only; never engine/acceptance evidence."""
|
||||
import copy
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from unittest.mock import patch
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[5]
|
||||
CHECKER = ROOT / 'tools/check_p04_order_evidence.py'
|
||||
spec = importlib.util.spec_from_file_location('p04_checker', CHECKER)
|
||||
gate = importlib.util.module_from_spec(spec)
|
||||
sys.dont_write_bytecode = True
|
||||
spec.loader.exec_module(gate)
|
||||
|
||||
|
||||
def run():
|
||||
results = []
|
||||
with tempfile.TemporaryDirectory(prefix='.checker-fixtures-', dir=Path(__file__).parent) as temp:
|
||||
base = Path(temp)
|
||||
# Keep py_compile output inside allowed_paths; delete with the fixture.
|
||||
env = dict(os.environ, PYTHONPYCACHEPREFIX=str(base / 'pycache'))
|
||||
subprocess.run(['python3', '-m', 'py_compile', str(CHECKER)], env=env, check=True)
|
||||
evidence = base / 'p04-evidence'
|
||||
evidence.mkdir()
|
||||
artifacts = {}
|
||||
def add(aid, value, kind='observation'):
|
||||
data = value if isinstance(value, bytes) else json.dumps(value).encode()
|
||||
path = evidence / (aid + '.json')
|
||||
path.write_bytes(data)
|
||||
artifacts[aid] = dict(path=path.name, bytes=len(data),
|
||||
sha256=hashlib.sha256(data).hexdigest(), type=kind)
|
||||
return aid
|
||||
oldmap = gate.load(ROOT / 'docs/plans/original-completion-2026-10-02/execution/p04-01-input-r01/historical-source-map.json')
|
||||
source = {p: gate.sha(ROOT / p) for p in oldmap if p.endswith('.gd') and p.startswith(('autoload/', 'scripts/'))}
|
||||
for folder in ('autoload', 'scripts'):
|
||||
source.update({p.relative_to(ROOT).as_posix(): gate.sha(p) for p in (ROOT / folder).rglob('*.gd')})
|
||||
add('fixture_environment', b'SYNTHETIC CHECKER FIXTURE - NOT AN ENGINE')
|
||||
environment = dict(os='synthetic-checker-fixture', **{
|
||||
k: {'fixture_environment.json': artifacts['fixture_environment']['sha256']}
|
||||
for k in ('engine', 'configuration', 'inputs')})
|
||||
add('baseline', (ROOT / 'docs/traceability/original/BASELINE.txt').read_bytes(), 'authority')
|
||||
add('decisions', (ROOT / 'docs/DESIGN_DECISIONS.md').read_bytes(), 'authority')
|
||||
add('a01', (ROOT / 'docs/plans/original-completion-2026-10-02/execution/p04-01-input-r01/ORDER_CONTRACT_DRAFT.md').read_bytes(), 'authority')
|
||||
add('owner', (ROOT / 'docs/plans/original-completion-2026-10-02/execution/p04-01/D05_OWNER_APPROVAL.json').read_bytes(), 'authority')
|
||||
authority = {ref: dict(status='approved', artifact_id=(
|
||||
'owner' if ref.startswith('D05-P04-') else 'baseline' if ref.startswith('BASELINE:')
|
||||
else 'decisions' if ref == 'D-034' else 'a01'))
|
||||
for _, refs, _ in gate.REQUIRED.values() for ref in refs}
|
||||
add('stdout', b'SYNTHETIC fixture log', 'stdout')
|
||||
add('native_log', b'SYNTHETIC fixture log', 'native_log')
|
||||
domains = {d: dict(source_refs=[dict(path='autoload/simulation_runtime.gd', function='_advance_facilities',
|
||||
sha256=source['autoload/simulation_runtime.gd'])], authority_refs=[],
|
||||
cases=[], invariants=[], applicability=dict(source_map=source, environment=environment))
|
||||
for d in gate.DOMAINS}
|
||||
conditions = {}
|
||||
for cid, (d, refs, metrics) in gate.REQUIRED.items():
|
||||
domain = domains[d]
|
||||
domain['authority_refs'] = sorted(set(domain['authority_refs']) | set(refs))
|
||||
case = dict(id=cid, raw_origin='engine')
|
||||
for field in ('input', 'before', 'operation', 'after'):
|
||||
case[field] = add(cid + '_' + field, {'conditions': {cid: metrics}} if field == 'after' else {'fixture': True})
|
||||
receipt = dict(start_pid=101, end_pid=101, exit_code=0, engine_ended=True,
|
||||
case_id=cid, source_map=source, environment=environment,
|
||||
stdout='stdout', native_log='native_log',
|
||||
artifact_hashes={f: artifacts[case[f]]['sha256'] for f in ('input', 'before', 'operation', 'after')})
|
||||
condition = dict(case_id=cid, authority_refs=list(refs), nativeWindows='pending')
|
||||
if cid == 'save_restore':
|
||||
condition['save_artifact'] = add('save', b'SYNTHETIC save', 'save')
|
||||
receipt['saved_sha256'] = artifacts['save']['sha256']
|
||||
reader = dict(receipt, start_pid=102, end_pid=102, restored_sha256=artifacts['save']['sha256'])
|
||||
condition['restore_receipt'] = add('reader', reader, 'receipt')
|
||||
case['receipt'] = add(cid + '_receipt', receipt, 'receipt')
|
||||
domain['cases'].append(case)
|
||||
for metric, value in metrics.items():
|
||||
domain['invariants'].append(dict(case_id=cid, artifact_id=case['after'],
|
||||
json_pointer='/conditions/' + cid + '/' + metric, operator='eq', expected=value,
|
||||
authority_ref=refs[0], raw_origin='engine'))
|
||||
conditions[cid] = condition
|
||||
# EV case has a separate, reachable reserved-staff scenario.
|
||||
ev_case = copy.deepcopy(domains['停電']['cases'][0])
|
||||
ev_case['id'] = 'ev_stop'
|
||||
ev_case['after'] = add('ev_after', {'ev': dict(orphan_reservations=0, reserved_before_travel=True, power_stopped=True)})
|
||||
receipt = gate.load(evidence / artifacts[ev_case['receipt']]['path'])
|
||||
receipt['case_id'] = ev_case['id']
|
||||
receipt['artifact_hashes']['after'] = artifacts['ev_after']['sha256']
|
||||
ev_case['receipt'] = add('ev_receipt', receipt, 'receipt')
|
||||
domains['停電']['cases'].append(ev_case)
|
||||
domains['停電']['invariants'].append(dict(case_id='ev_stop', artifact_id='ev_after',
|
||||
json_pointer='/ev/orphan_reservations', operator='eq', expected=0,
|
||||
authority_ref='BASELINE:183', raw_origin='engine'))
|
||||
reachability = {}
|
||||
for name, refs in gate.REACH_REFS.items():
|
||||
reachability[name] = dict(reach=name == 'EV', reason='Synthetic static-chain fixture',
|
||||
source_refs=[dict(path=p, sha256=source[p], function=f, anchor_sha256=h,
|
||||
line_count=n) for p,f,h,n in refs])
|
||||
reachability['EV']['case_id'] = 'ev_stop'
|
||||
index = dict(schema_version=1, task_id='P04-01', formal_acceptance=False,
|
||||
candidate_commit=subprocess.check_output(['git','rev-parse','HEAD'], cwd=ROOT, text=True).strip(),
|
||||
source_map=source, environment=environment, artifacts=artifacts, authority=authority,
|
||||
domains=domains, conditions=conditions, reachability=reachability, review_status='pending_review')
|
||||
index['registered_pins'] = gate.load(gate.SPEC)['current_source_pins']
|
||||
index['versions'] = dict(execution_origin=gate.EXECUTION_ORIGIN,
|
||||
registered=gate.REGISTERED_COMMIT, tested=index['candidate_commit'], integration=None)
|
||||
index['applicability_delta'] = gate.source_delta(gate.REGISTERED_COMMIT, index['candidate_commit'])
|
||||
index['delta_cases'] = {row['path'] + ':' + row['function']: {
|
||||
cid: ('ev_stop' if cid == 'EV' else cid)
|
||||
for cid in gate.boundary_conditions(row['path'], row['function'])}
|
||||
for row in index['applicability_delta']}
|
||||
original_files = {p.name:p.read_bytes() for p in evidence.iterdir()}
|
||||
index = copy.deepcopy(index)
|
||||
def check(name, mutate, error):
|
||||
for p in evidence.iterdir():
|
||||
p.unlink()
|
||||
for name_, data in original_files.items():
|
||||
(evidence / name_).write_bytes(data)
|
||||
candidate = copy.deepcopy(index)
|
||||
mutate(candidate)
|
||||
if name != 'missing_index':
|
||||
(evidence / 'index.json').write_text(json.dumps(candidate))
|
||||
if name == 'duplicate_key':
|
||||
(evidence / 'index.json').write_text('{"schema_version":1,"schema_version":1}')
|
||||
# These are content-layer unit cases. Stage-A real Git/FS controls
|
||||
# live in checker_role_fixtures.py; no production bypass is added.
|
||||
head = index['candidate_commit']
|
||||
with patch.object(gate, 'verify_roles', return_value=(head, {})), patch.dict(
|
||||
os.environ, {'BUILD_OUTPUT_DIR': str(base), 'P04_EVIDENCE_DIR': str(evidence)}):
|
||||
try:
|
||||
gate.verify(base)
|
||||
code, output = 0, {}
|
||||
except (ValueError, OSError, KeyError, TypeError) as exc:
|
||||
code, output = 1, {'error': str(exc)}
|
||||
assert code == (0 if error is None else 1), (name, output)
|
||||
assert error is None or error in output.get('error',''), (name, output)
|
||||
results.append(dict(fixture=name, exit_code=code, result=output))
|
||||
check('synthetic_scalar_only_rejected', lambda i: None, 'native bundle required')
|
||||
check('forged_registered_pin', lambda i: i['registered_pins'].update({'autoload/simulation_runtime.gd':'0'*64}), 'registered pin declaration')
|
||||
check('false_delta', lambda i: i['applicability_delta'].append({'path':'autoload/simulation_runtime.gd','function':'initialize'}), 'git diff/applicability_delta mismatch')
|
||||
check('wrong_reach_anchor', lambda i: i['reachability']['EV']['source_refs'][0].update(anchor_sha256='0'*64), 'unknown reachability anchor')
|
||||
check('missing_index', lambda i: None, 'missing/outside')
|
||||
check('duplicate_key', lambda i: None, 'duplicate JSON key')
|
||||
def symlink(i):
|
||||
(evidence / 'stdout.json').unlink()
|
||||
(evidence / 'stdout.json').symlink_to(evidence / 'native_log.json')
|
||||
check('symlink', symlink, 'symlink')
|
||||
check('hash_mismatch', lambda i: i['artifacts']['stdout'].update(sha256='0'*64), 'artifact hash')
|
||||
check('stale_pin', lambda i: i['source_map'].update({'autoload/simulation_runtime.gd':'0'*64}), 'tested game differs from Git')
|
||||
def receipt_mutation(i):
|
||||
aid = 'power_stop_receipt'
|
||||
value = gate.load(evidence / artifacts[aid]['path'])
|
||||
value['engine_ended'] = False
|
||||
add(aid, value, 'receipt')
|
||||
i['artifacts'][aid] = copy.deepcopy(artifacts[aid])
|
||||
check('unexecuted_receipt', receipt_mutation, 'unexecuted/incomplete receipt')
|
||||
check('pending_authority', lambda i: i['authority']['D05-P04-DEPENDENCY'].update(status='pending'), 'pending/missing authority')
|
||||
check('missing_condition', lambda i: i['conditions'].pop('paused_midnight'), 'mandatory conditions')
|
||||
check('unsupported_reach_false', lambda i: i['reachability']['transport'].update(reach=False, source_refs=[]), 'unsupported reach=false')
|
||||
def owner_mutation(i):
|
||||
aid = 'owner'
|
||||
data = (evidence / 'owner.json').read_bytes() + b' '
|
||||
add(aid, data, 'authority')
|
||||
i['artifacts'][aid] = copy.deepcopy(artifacts[aid])
|
||||
check('owner_hash_mismatch', owner_mutation, 'owner approval hash mismatch')
|
||||
return dict(scope='Synthetic checker checks only; no engine evidence or formal acceptance',
|
||||
py_compile=True, results=results)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(json.dumps(run(), ensure_ascii=False, indent=2))
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Real temp-Git role controls. Root executes after auto-submit; never evidence."""
|
||||
import argparse
|
||||
import copy
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
ROOT = Path(__file__).resolve().parents[5]
|
||||
|
||||
|
||||
def run(build, evidence):
|
||||
build, evidence = build.resolve(strict=True), evidence.resolve(strict=True)
|
||||
original = json.loads((evidence / 'index.json').read_text())
|
||||
results = []
|
||||
env = dict(os.environ, BUILD_OUTPUT_DIR=str(build), PYTHONDONTWRITEBYTECODE='1',
|
||||
GIT_CONFIG_NOSYSTEM='1', GIT_AUTHOR_NAME='P04 fixture', GIT_AUTHOR_EMAIL='fixture@invalid',
|
||||
GIT_COMMITTER_NAME='P04 fixture', GIT_COMMITTER_EMAIL='fixture@invalid')
|
||||
for name in ('GIT_DIR', 'GIT_WORK_TREE', 'GIT_INDEX_FILE', 'GIT_OBJECT_DIRECTORY',
|
||||
'GIT_ALTERNATE_OBJECT_DIRECTORIES'):
|
||||
env.pop(name, None)
|
||||
with tempfile.TemporaryDirectory(prefix='p04-role-fixtures-', dir=build) as temp:
|
||||
base = Path(temp)
|
||||
checkout = base / 'checkout'
|
||||
subprocess.run(['git', 'clone', '--shared', '--no-checkout', str(ROOT), str(checkout)],
|
||||
env=env, check=True, capture_output=True)
|
||||
|
||||
def git(*args):
|
||||
return subprocess.check_output(['git', '-c', 'core.hooksPath=/dev/null', *args], cwd=checkout, env=env).decode().strip()
|
||||
|
||||
actual = git('rev-parse', 'HEAD')
|
||||
anchor = '9b1a18c1eb25ca1300dcd59da78423c0dda2f498'
|
||||
ancestry = subprocess.run(['git', 'merge-base', '--is-ancestor', anchor, actual], cwd=checkout, env=env)
|
||||
submission = git('show', '-s', '--format=%P', actual).split()[1] if ancestry.returncode == 0 else actual
|
||||
git('checkout', '--detach', submission)
|
||||
# Objects created by clone commits/merge-tree remain under build. The
|
||||
# shared canonical objects are consulted as read-only alternates only.
|
||||
probe = base / 'probe.py'
|
||||
probe.write_text(
|
||||
'import importlib.util,json,sys\nfrom pathlib import Path\n'
|
||||
'sys.dont_write_bytecode=True\n'
|
||||
's=importlib.util.spec_from_file_location("gate",Path.cwd()/"tools/check_p04_order_evidence.py")\n'
|
||||
'g=importlib.util.module_from_spec(s);sys.modules[s.name]=g;s.loader.exec_module(g)\n'
|
||||
'with g.isolated_objects():\n'
|
||||
' g.verify_roles(json.loads(Path(sys.argv[1]).read_text()),Path(sys.argv[2]))\n')
|
||||
index_path = base / 'index.json'
|
||||
|
||||
def check(name, index=None, positive=False, full=False):
|
||||
index_path.write_text(json.dumps(original if index is None else index))
|
||||
if full:
|
||||
directory = base / name
|
||||
directory.mkdir()
|
||||
(directory / 'index.json').write_text(index_path.read_text())
|
||||
command = [sys.executable, 'tools/check_p04_order_evidence.py', '--verify-only']
|
||||
process_env = dict(env, P04_EVIDENCE_DIR=str(directory))
|
||||
else:
|
||||
command = [sys.executable, str(probe), str(index_path), str(build)]
|
||||
process_env = env
|
||||
process = subprocess.run(command, cwd=checkout, env=process_env, capture_output=True, text=True)
|
||||
assert (process.returncode == 0) == positive, (name, process.stdout, process.stderr)
|
||||
results.append({'case': name, 'result': 'accepted' if positive else 'rejected',
|
||||
'stdout': process.stdout, 'stderr': process.stderr})
|
||||
|
||||
check('real_submission_positive', positive=True)
|
||||
game = checkout / 'project.godot'
|
||||
original_game = game.read_bytes()
|
||||
game.write_bytes(original_game + b'\n')
|
||||
check('HEAD_tree_FS_mismatch')
|
||||
game.write_bytes(original_game)
|
||||
game.unlink()
|
||||
game.symlink_to(base / 'outside')
|
||||
(base / 'outside').write_bytes(original_game)
|
||||
check('symlink_substitution')
|
||||
game.unlink()
|
||||
game.write_bytes(original_game)
|
||||
unknown = checkout / 'scripts/p04_unknown.gd'
|
||||
unknown.write_text('extends RefCounted\n')
|
||||
check('untracked_substitution')
|
||||
git('add', 'scripts/p04_unknown.gd')
|
||||
git('commit', '-m', 'fixture unknown source')
|
||||
check('unknown_source_commit')
|
||||
git('reset', '--hard', submission)
|
||||
game.unlink()
|
||||
git('add', '-u')
|
||||
git('commit', '-m', 'fixture deleted source')
|
||||
check('deleted_source_commit')
|
||||
git('reset', '--hard', submission)
|
||||
changed = copy.deepcopy(original)
|
||||
changed['product_binding']['tested_product'] = submission
|
||||
check('wrong_tested_revision', changed)
|
||||
for name, key, value in [('forged_candidate', 'candidate_tree', '0' * 40),
|
||||
('code_only_real_candidate', 'code_only', True),
|
||||
('phase_bypass', 'phase', 'candidate')]:
|
||||
changed = copy.deepcopy(original)
|
||||
changed[key] = value
|
||||
check(name, changed)
|
||||
git('checkout', '--detach', anchor)
|
||||
git('merge', '--no-ff', '--no-edit', submission)
|
||||
merged = git('rev-parse', 'HEAD')
|
||||
check('real_integration_positive', positive=True)
|
||||
check('real_final_positive', positive=True)
|
||||
game.unlink()
|
||||
git('add', '-u')
|
||||
git('commit', '--amend', '--no-edit')
|
||||
changed = copy.deepcopy(original)
|
||||
changed['product_binding']['metadata'] = ['project.godot']
|
||||
check('metadata_hiding_deleted_source', changed)
|
||||
git('reset', '--hard', merged)
|
||||
changed = copy.deepcopy(original)
|
||||
changed['conditions']['power_stop']['required_measurements']['charges'] = 9
|
||||
check('forged_index', changed, full=True)
|
||||
return {'scope': 'fixture controls only', 'results': results}
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--build', required=True, type=Path)
|
||||
parser.add_argument('--evidence', required=True, type=Path)
|
||||
args = parser.parse_args()
|
||||
print(json.dumps(run(args.build, args.evidence), indent=2))
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Stage R file-only controls. Synthetic primitive controls plus real raw controls.
|
||||
|
||||
Tests never alter supplied originals or the relocation. In-memory overlays are
|
||||
used for native descriptor/raw adversaries; filesystem adversaries are disposable.
|
||||
"""
|
||||
import argparse
|
||||
import copy
|
||||
import json
|
||||
from pathlib import Path
|
||||
import struct
|
||||
import sys
|
||||
import tempfile
|
||||
from unittest.mock import patch
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
import p04_native_series as s
|
||||
|
||||
|
||||
def run(build):
|
||||
results = []
|
||||
|
||||
def reject(series, name, action):
|
||||
try:
|
||||
action()
|
||||
except (ValueError, KeyError, OSError, TypeError, UnicodeError) as exc:
|
||||
results.append({'series': series, 'class': name, 'result': 'rejected', 'error': str(exc)})
|
||||
else:
|
||||
raise AssertionError('negative accepted: ' + series + '/' + name)
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix='.series-fixtures-', dir=s.HERE) as tmp:
|
||||
base = Path(tmp)
|
||||
data = b'func boundary():\n\treturn 42\n# included trailing comment\n'
|
||||
path = base / 'body.gd'
|
||||
path.write_bytes(data)
|
||||
pin = {'sha256': s.digest(data), 'bytes': len(data)}
|
||||
# A deliberately small resolver fixture bypasses the production-only
|
||||
# fixed 1721-file constructor, but exercises its exact read path.
|
||||
resolver = s.Resolver.__new__(s.Resolver)
|
||||
resolver.base, resolver.used = base, {}
|
||||
resolver.mapping = {'/native/body.gd': dict(pin, relocated=str(path))}
|
||||
resolver.manifest = {'product_source': {'body.gd': dict(pin, path='/native/body.gd')}}
|
||||
assert resolver.read('/native/body.gd', pin) == data
|
||||
assert s.variant(struct.pack('<Ii', 2, 42)).plain() == 42
|
||||
assert s.checked(data, pin, 'positive') == data
|
||||
results.append({'series': 'shared', 'class': 'small_synthetic_positive', 'result': 'pass'})
|
||||
link = base / 'symlink.gd'
|
||||
link.symlink_to(path)
|
||||
for name in s.NATIVE_PINS:
|
||||
context = s.Series.__new__(s.Series)
|
||||
context.build, context.run, context.references = base, s.RUNS[name], {}
|
||||
body_row = {'path': 'body.gd', 'function': 'boundary',
|
||||
'production_body_sha256': s.digest(data), 'registered_body_sha256': s.digest(data)}
|
||||
s.source_body_pins(resolver, [body_row])
|
||||
results.append({'series': name, 'class': 'full_body_positive', 'result': 'pass'})
|
||||
reject(name, 'wrong_source_pin', lambda: resolver.read('/native/body.gd', dict(pin, sha256='0' * 64)))
|
||||
signature = s.digest(data.splitlines(keepends=True)[0])
|
||||
reject(name, 'signature_only_pin', lambda: s.source_body_pins(resolver, [dict(
|
||||
body_row, production_body_sha256=signature, registered_body_sha256=signature)]))
|
||||
reject(name, 'symlink', lambda: s.ordinary(link))
|
||||
reject(name, 'traversal', lambda: context.read_build('../body.gd'))
|
||||
reject(name, 'unmapped_original_path', lambda: resolver.read('/native/unknown.gd', pin))
|
||||
reject(name, 'relocated_manifest_native_mismatch', lambda: resolver.read('/native/body.gd', dict(pin, bytes=len(data)+1)))
|
||||
path.write_bytes(data + b'!')
|
||||
try:
|
||||
reject(name, 'relocated_byte_manifest_mismatch', lambda: resolver.read('/native/body.gd', pin))
|
||||
finally:
|
||||
path.write_bytes(data)
|
||||
reject(name, 'JSON_only_getter_substitute', lambda: s.variant(b'{"getter": {}, "passed": true}'))
|
||||
|
||||
# Each series' real verifier is called for descriptor/raw overlays.
|
||||
# The native descriptor pins are intentionally not refreshed: these
|
||||
# runs and their original native descriptors are immutable inputs.
|
||||
original_read = s.Series.read_build
|
||||
run_name = s.RUNS[name]
|
||||
targets = {
|
||||
'forged_scalar': run_name + '/raw/writer.json',
|
||||
'changed_raw_byte': run_name + '/raw/writer.json',
|
||||
'formal_member_SHA_drift': run_name + '/execution.json',
|
||||
'unowned_missing_PID_end': run_name + '/writer/ended.json',
|
||||
'altered_phase_pair': run_name + '/raw/' + {
|
||||
's1': 'reader-landing-held.var', 's2a': 'reader-C3-recall.var',
|
||||
's2b': 'reader-night_absent-1080-before-facility.var',
|
||||
's3': 'reader-B1-phase-960-TIME_BOUNDARIES.var'}[name],
|
||||
'relabeled_interruption_reason': run_name + '/raw/' + {
|
||||
's1': 'writer-landing-depleted.json', 's2a': 'writer-C1-midnight.json',
|
||||
's2b': 'writer-night_absent-1081.json', 's3': 'writer-B1-after.json'}[name],
|
||||
}
|
||||
for label, target in targets.items():
|
||||
def overlay(self, relative, descriptor=None, target=target, label=label):
|
||||
if relative != target:
|
||||
return original_read(self, relative, descriptor)
|
||||
raw = s.ordinary(self.build / relative).read_bytes()
|
||||
if label in ('changed_raw_byte', 'altered_phase_pair'):
|
||||
raw += b' '
|
||||
else:
|
||||
obj = s.loads(raw)
|
||||
if label == 'unowned_missing_PID_end':
|
||||
obj['event'] = 'ENGINE_STARTED'
|
||||
elif label == 'forged_scalar':
|
||||
obj.update(passed=True, verified_conditions=list(s.RUNS), slots={})
|
||||
elif label == 'formal_member_SHA_drift':
|
||||
slot = next(iter(obj['formal_before']))
|
||||
obj['formal_before'][slot]['world.bin']['sha256'] = '0' * 64
|
||||
elif label == 'relabeled_interruption_reason':
|
||||
if 'service' in obj:
|
||||
obj['service']['interruption_reason'] = 'NO_POWER'
|
||||
else:
|
||||
obj['task']['state_reason'] = 'NO_POWER'
|
||||
raw = json.dumps(obj).encode()
|
||||
if descriptor is not None:
|
||||
s.checked(raw, descriptor, relative)
|
||||
return raw
|
||||
with patch.object(s.Series, 'read_build', overlay):
|
||||
reject(name, label, lambda name=name: getattr(s, 'verify_' + name)(build))
|
||||
return {'scope': 'file-only Stage R controls; immutable descriptor and primitive negatives',
|
||||
'formal_acceptance': False, 'results': results}
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--build', type=Path, required=True)
|
||||
print(json.dumps(run(parser.parse_args().build), indent=2, sort_keys=True))
|
||||
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Deterministic reference-only Stage R collector; never writes the final index."""
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
import p04_native_series as series
|
||||
|
||||
|
||||
def encoded(value):
|
||||
return (json.dumps(value, sort_keys=True, indent=2, ensure_ascii=False) + '\n').encode()
|
||||
|
||||
|
||||
def collect(build):
|
||||
build = Path(build).absolute()
|
||||
destination = build / 'audit-support/p04-raw-collect-r22'
|
||||
series.require(not destination.exists() and not destination.is_symlink(), 'collection destination already exists')
|
||||
for p in (destination.parent, *destination.parents):
|
||||
series.require(not p.is_symlink(), 'collection parent symlink')
|
||||
payloads = {}
|
||||
bindings = {}
|
||||
for name in series.RUNS:
|
||||
result = getattr(series, 'verify_' + name)(build)
|
||||
result['verifier_sha256'] = series.digest(Path(series.__file__).read_bytes())
|
||||
data = encoded(result)
|
||||
filename = name + '.json'
|
||||
payloads[filename] = data
|
||||
bindings[name] = {'path': filename, 'bytes': len(data), 'sha256': series.digest(data),
|
||||
'relocation_manifest_sha256': series.MANIFEST_SHA256}
|
||||
manifest = {'protocol': 'p04-native-series-collection-v1', 'formal_acceptance': False,
|
||||
'stage_A': 'candidate binding stage A pending', 'series': bindings,
|
||||
'relocation_manifest_sha256': series.MANIFEST_SHA256,
|
||||
'relocation_manifest': 'audit-support/p04-r17-full-inputs-r01/MANIFEST.json',
|
||||
'collector_sha256': series.digest(Path(__file__).read_bytes()),
|
||||
'large_payloads_copied': 0}
|
||||
payloads['MANIFEST.json'] = encoded(manifest)
|
||||
estimate = sum(len(v) for v in payloads.values())
|
||||
# The estimate precedes all writes. Reserve extra directory/allocation space.
|
||||
print(json.dumps({'estimated_payload_bytes': estimate, 'files': len(payloads),
|
||||
'available_disk_bytes': shutil.disk_usage(destination.parent).free,
|
||||
'large_payloads_copied': 0}), flush=True)
|
||||
series.require(shutil.disk_usage(destination.parent).free > estimate + 1048576, 'insufficient disk for pointers')
|
||||
destination.mkdir() # exclusive creation: a previous collection is never overwritten
|
||||
for name, data in sorted(payloads.items()):
|
||||
with (destination / name).open('xb') as output:
|
||||
output.write(data)
|
||||
print(json.dumps({'collection': str(destination), 'manifest_sha256': series.digest(payloads['MANIFEST.json']),
|
||||
'actual_payload_bytes': estimate, 'formal_acceptance': False}))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--build', type=Path, required=True)
|
||||
try:
|
||||
collect(parser.parse_args().build)
|
||||
except (ValueError, OSError, KeyError, TypeError) as exc:
|
||||
print(json.dumps({'collected': False, 'error': str(exc)}))
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"task_id": "P04-01",
|
||||
"base_commit": "deb1bcb2374eb378b840ca345db952bd8572cb6e",
|
||||
"status": "blocked",
|
||||
"preexisting_files": ["order-audit.json", "ORDER_AUDIT.md"],
|
||||
"verifier_created": false,
|
||||
"blockers": [
|
||||
"Local inputs specify no evidence index schema, mandatory proof identifiers, raw-proof predicates or authority binding. Implementing an accepting verifier would supplement unspecified requirements.",
|
||||
"D05 remains pending; approved A01 is retained without reconfirmation.",
|
||||
"Verified local historical raw proofs and exact old Runtime source are required for reuse and exact diff. Artifact paths are provenance only."
|
||||
],
|
||||
"read_only_observations": [
|
||||
"Current kernel lines 123-153 and Runtime registrations 364-427 confirm the phase sequence recorded in the preexisting audit.",
|
||||
"Git read-only path history lists ten Runtime revisions; this turn has not scanned their content hashes."
|
||||
],
|
||||
"hash_reconfirmation": "Not completed: shell rejected temporary-file creation for the Python here-document before execution. Preexisting hash claims are not new verification results.",
|
||||
"tests_executed": false,
|
||||
"original_unit_complete": false,
|
||||
"counted_units_added": 0,
|
||||
"next_step": "Supply evidence protocol and verified local historical source/raw proofs; Runner performs formal validation and independent review."
|
||||
}
|
||||
@@ -0,0 +1,440 @@
|
||||
{
|
||||
"revision": "repair29",
|
||||
"status": "SOURCE-READY / NOTRUN; Root measurements separately recorded",
|
||||
"task_id": "P04-01",
|
||||
"current_table": true,
|
||||
"scope": {
|
||||
"conditions": 15,
|
||||
"additional_EV_obligation": true,
|
||||
"domains": 7,
|
||||
"safety_outcomes": [
|
||||
"no stopped charging",
|
||||
"no double midnight settlement",
|
||||
"no orphan reservations"
|
||||
],
|
||||
"packages": 30,
|
||||
"deliveries": 88,
|
||||
"original_credit": "13/88 unchanged; P04 receives zero credit",
|
||||
"CurrentAlphaScope": "unchanged",
|
||||
"main_9b": "frozen; nothing integrated"
|
||||
},
|
||||
"domains": [
|
||||
{
|
||||
"domain": "閉店",
|
||||
"normative_rule": "D-034: direct closure releases waiting requests and continues started service. Approved dependency closure holds progress/payment and preserves reservation/service ID until one completion.",
|
||||
"current_source": [
|
||||
"Runtime _process_tick_commands:5743",
|
||||
"FacilityService _prepare_service_for_tick:853",
|
||||
"Runtime _release_closed_business_queues:12624"
|
||||
],
|
||||
"evidence": "I native_bundle C; R/s2a.json; R/s3.json B1",
|
||||
"approved_authority": [
|
||||
"D-034",
|
||||
"D05-P04-DEPENDENCY"
|
||||
],
|
||||
"pending_D05": [
|
||||
"D05(a) simultaneous reason selection"
|
||||
],
|
||||
"phase": "APPLY_COMMANDS / FACILITY_COMPLETION / FACILITY_QUEUES",
|
||||
"relative_priority": "Command closure before completion; dependency hold during preparation; closed queue release after completion.",
|
||||
"status": "approved",
|
||||
"observation_status": "observed-current"
|
||||
},
|
||||
{
|
||||
"domain": "停電",
|
||||
"normative_rule": "BASELINE:183 mana depletion stops facilities. A legitimately power-stopped service must not progress/charge; retain reservation identity and complete once after recovery. This does not approve a simultaneous-cause priority.",
|
||||
"current_source": [
|
||||
"Runtime _advance_mana:10694",
|
||||
"FacilityService _prepare_service_for_tick:853"
|
||||
],
|
||||
"evidence": "R/s4.json; B/p04-s4-native-r28; R/s3.json B1 combined diagnostic",
|
||||
"approved_authority": [
|
||||
"BASELINE:183",
|
||||
"P04-01 safety acceptance"
|
||||
],
|
||||
"pending_D05": [
|
||||
"D05(a)"
|
||||
],
|
||||
"phase": "MANA → FACILITY_COMPLETION",
|
||||
"relative_priority": "Projection before completion; RELATED chosen before NO_POWER.",
|
||||
"status": "observed-current",
|
||||
"observation_status": "observed-current",
|
||||
"evidence_scope": "S4 probe calls disconnect_nodes directly: isolated NO_POWER, not global depletion. Global depletion appears only in S3 B1 combined diagnostic. S1 covers separate staff/EV safety."
|
||||
},
|
||||
{
|
||||
"domain": "破壊",
|
||||
"normative_rule": "No charge while legitimately failed/stopped and no orphan reservation. Distinguish command destruction before facility processing from late INCIDENTS event destruction after completion; observation is not normative priority approval.",
|
||||
"current_source": [
|
||||
"Runtime _process_tick_commands:5743",
|
||||
"_advance_defense:8577",
|
||||
"_apply_defense_outcome_boundary:9804",
|
||||
"FacilityService _prepare_service_for_tick:853"
|
||||
],
|
||||
"evidence": "R/s3.json B4a/B4b",
|
||||
"approved_authority": [
|
||||
"P04-01 safety acceptance (original); explicit destruction rule pending D05"
|
||||
],
|
||||
"pending_D05": [
|
||||
"D05(b); BASELINE:183 is mana depletion, not destruction authority; destruction_stop registry correction only after approval"
|
||||
],
|
||||
"phase": "APPLY_COMMANDS vs INCIDENTS_AND_EVENTS",
|
||||
"relative_priority": "Command destruction before completion; incident destruction after completion: B4a charges25G then destroys in same tick; B4b next-tick failure/release.",
|
||||
"status": "pending D05",
|
||||
"observation_status": "observed-current"
|
||||
},
|
||||
{
|
||||
"domain": "退出",
|
||||
"normative_rule": "Leave/abandon releases the owned reservation once; no orphan or duplicate charge. Preserve D-034. Completion, queue release and later normal despawn are distinct observations.",
|
||||
"current_source": [
|
||||
"Runtime _execute_leave_facility_queue:7553",
|
||||
"_advance_guest_ai_decisions:8738"
|
||||
],
|
||||
"evidence": "R/s3.json B2a/B2b/B3 and B1 departure history",
|
||||
"approved_authority": [
|
||||
"D-034",
|
||||
"P04-01 safety acceptance"
|
||||
],
|
||||
"pending_D05": [
|
||||
"D05(c)"
|
||||
],
|
||||
"phase": "APPLY_COMMANDS vs AI_DECISIONS",
|
||||
"relative_priority": "Command exit before completion/charging; AI PLANNED_EXIT after them; despawn later.",
|
||||
"status": "pending D05",
|
||||
"observation_status": "observed-current"
|
||||
},
|
||||
{
|
||||
"domain": "課金",
|
||||
"normative_rule": "No stopped charge; one sale per completed service and idempotent resend. Reservation consumption, actual sale and payment key must join by service ID and original priceSnapshot.",
|
||||
"current_source": [
|
||||
"Runtime _advance_facility_upkeep:1855",
|
||||
"_complete_service_transaction:11769",
|
||||
"_apply_service_transaction:11785",
|
||||
"coordinator _process_daily_close:1125"
|
||||
],
|
||||
"evidence": "I native_bundle C; R/s2a.json; R/s4.json",
|
||||
"approved_authority": [
|
||||
"P04-01 safety acceptance",
|
||||
"D05-P04-DEPENDENCY",
|
||||
"D05-P04-SHIFT-1800"
|
||||
],
|
||||
"pending_D05": [
|
||||
"D05(d), beyond approved A01 and18:00; timing/attribution mandatory P04, numeric math D04/P05 separate"
|
||||
],
|
||||
"phase": "TIME_BOUNDARIES → FACILITY_COMPLETION",
|
||||
"relative_priority": "Upkeep/daily close before sale; post-increment sale timestamp, exclusive close period_end;00:00 sale belongs to new day.",
|
||||
"status": "pending D05",
|
||||
"observation_status": "observed-current"
|
||||
},
|
||||
{
|
||||
"domain": "シフト交代",
|
||||
"normative_rule": "Approved [17:59,18:00) work belongs to day staff: complete/pay once at18:00 then switch; night work starts [18:00,18:01).",
|
||||
"current_source": [
|
||||
"Runtime _advance_facilities:8482",
|
||||
"_advance_staff_ai:8658",
|
||||
"StaffService advance_one_minute:700"
|
||||
],
|
||||
"evidence": "I C first1080 sale joins; R/s2b.json no-night1081/overtime",
|
||||
"approved_authority": [
|
||||
"BASELINE:64",
|
||||
"BASELINE:67",
|
||||
"D05-P04-SHIFT-1800"
|
||||
],
|
||||
"pending_D05": [
|
||||
"Already approved; do not re-ask"
|
||||
],
|
||||
"phase": "FACILITY_COMPLETION → AI_DECISIONS",
|
||||
"relative_priority": "18:00 completion/payment before shift handover; day staff [17:59,18:00), night [18:00,18:01).",
|
||||
"status": "approved",
|
||||
"observation_status": "observed-current"
|
||||
},
|
||||
{
|
||||
"domain": "00:00決算",
|
||||
"normative_rule": "A01 approved ownership-prorated upkeep at midnight is retained. One daily close/payroll/upkeep, no duplicate replay/reload; distinguish prior [0,1440) accounting and sale at1440.",
|
||||
"current_source": [
|
||||
"Kernel _handle_time_boundaries:254",
|
||||
"Runtime _advance_operations_time:1898",
|
||||
"coordinator _process_minute_boundaries:882",
|
||||
"_process_daily_close:1125"
|
||||
],
|
||||
"evidence": "I C paused/formal; R/s2a.json nonzero payroll/upkeep, replay and typed restore",
|
||||
"approved_authority": [
|
||||
"BASELINE:71",
|
||||
"BASELINE:72",
|
||||
"A01",
|
||||
"D05-P04-DEPENDENCY"
|
||||
],
|
||||
"pending_D05": [
|
||||
"A01 approved ownership-based midnight upkeep not reopened; extra attribution D05(d)"
|
||||
],
|
||||
"phase": "pre-TIME_BOUNDARIES signals → TIME_BOUNDARIES → FACILITY_COMPLETION",
|
||||
"relative_priority": "Midnight signals, then upkeep/close, then completion.",
|
||||
"status": "approved",
|
||||
"observation_status": "observed-current"
|
||||
}
|
||||
],
|
||||
"source_facts": "REPAIR29_STATIC_SOURCE.json",
|
||||
"authority": "D05_OWNER_APPROVAL.json approves dependency and shift only; original BASELINE/D-034/A01 retain their scope",
|
||||
"historical_superseded": [
|
||||
"REPAIR17_STATUS.md",
|
||||
"REPAIR18_STATUS.md",
|
||||
"REPAIR19_CORRIGENDUM.md",
|
||||
"REPAIR19_STATUS.md",
|
||||
"REPAIR20_CORRIGENDUM.md",
|
||||
"REPAIR20_STATUS.md",
|
||||
"REPAIR21_STATUS.md",
|
||||
"REPAIR22–28 creation-time status and runbooks"
|
||||
],
|
||||
"history_policy": "Creation-time NOT RUN in REPAIR22–28 and runbooks is historical; CURRENT_STATUS.md canonical table overrides execution status. Historical files unchanged.",
|
||||
"evidence_policy": "Root-measured S4r28 4/4 PASS, controls, local8, r22 collection, schema2 index, registered validation PASS28s; worker SOURCE-READY / NOTRUN.",
|
||||
"pending": [
|
||||
"Owner D05 a–d approval or explicit non-mandatory ruling; unknown mandatory rules stay pending",
|
||||
"Post-approval normal checker/registry repair (a61 unchanged here)",
|
||||
"support53cb normal registered rebind",
|
||||
"final full1339 against final checker; none exists against a61",
|
||||
"independent review and integration: P04 first, support on main9b afterward",
|
||||
"nativeWindows obligations individually if required"
|
||||
],
|
||||
"priority_candidates_if_required": "D05_REMAINING_ORDER_CANDIDATE.json",
|
||||
"collection_plan": "s4/S4_ROOT_RUN.md",
|
||||
"formal_acceptance": false,
|
||||
"original_unit_complete": false,
|
||||
"phase_order": [
|
||||
{
|
||||
"priority": 1,
|
||||
"phase": "APPLY_COMMANDS",
|
||||
"status": "observed-current",
|
||||
"handlers": [
|
||||
{
|
||||
"phase": "APPLY_COMMANDS",
|
||||
"handler_id": "simulation_runtime.commands",
|
||||
"function": "_process_tick_commands",
|
||||
"registration_line": 369
|
||||
}
|
||||
],
|
||||
"behavior": "Previous minute: commands → _process_tick_commands:5743. Operations gate requests current tick abort and returns before Clock changes; otherwise commands apply for minute+1."
|
||||
},
|
||||
{
|
||||
"priority": 2,
|
||||
"phase": "TIME_BOUNDARIES",
|
||||
"status": "observed-current",
|
||||
"handlers": [
|
||||
{
|
||||
"phase": "TIME_BOUNDARIES",
|
||||
"handler_id": "simulation_runtime.0_weather",
|
||||
"function": "_advance_weather",
|
||||
"registration_line": 366
|
||||
},
|
||||
{
|
||||
"phase": "TIME_BOUNDARIES",
|
||||
"handler_id": "simulation_runtime.a_upkeep",
|
||||
"function": "_advance_facility_upkeep",
|
||||
"registration_line": 368
|
||||
},
|
||||
{
|
||||
"phase": "TIME_BOUNDARIES",
|
||||
"handler_id": "simulation_runtime.operations",
|
||||
"function": "_advance_operations_time",
|
||||
"registration_line": 1766
|
||||
}
|
||||
],
|
||||
"behavior": "After APPLY: absolute_minute += 1, _sync_calendar_fields, _handle_time_boundaries:254 emits settlement_requested(completed_day) then day_started at00:00, before TIME_BOUNDARIES handlers. Handler order: weather, upkeep, operations. Runtime upkeep:1855; operations:1898 → coordinator _process_minute_boundaries:882: 00:00 upkeep, daily close, 06:00 morning, demand, arrivals (conditional boundaries and early returns)."
|
||||
},
|
||||
{
|
||||
"priority": 3,
|
||||
"phase": "MANA",
|
||||
"status": "observed-current",
|
||||
"handlers": [
|
||||
{
|
||||
"phase": "MANA",
|
||||
"handler_id": "simulation_runtime.mana",
|
||||
"function": "_advance_mana",
|
||||
"registration_line": 379
|
||||
}
|
||||
],
|
||||
"behavior": "Runtime _advance_mana:10694: proxy preflight → mana_network advance → mana_power_projection.apply → MANA_STATE_CHANGED events."
|
||||
},
|
||||
{
|
||||
"priority": 4,
|
||||
"phase": "CONSTRUCTION",
|
||||
"status": "observed-current",
|
||||
"handlers": [
|
||||
{
|
||||
"phase": "CONSTRUCTION",
|
||||
"handler_id": "simulation_runtime.construction",
|
||||
"function": "_advance_construction",
|
||||
"registration_line": 384
|
||||
}
|
||||
],
|
||||
"behavior": "Construction:8064 follows power and precedes logistics."
|
||||
},
|
||||
{
|
||||
"priority": 5,
|
||||
"phase": "LOGISTICS",
|
||||
"status": "observed-current",
|
||||
"handlers": [
|
||||
{
|
||||
"phase": "LOGISTICS",
|
||||
"handler_id": "simulation_runtime.logistics",
|
||||
"function": "_advance_inventory_and_logistics",
|
||||
"registration_line": 389
|
||||
}
|
||||
],
|
||||
"behavior": "Logistics:8472 precedes completion."
|
||||
},
|
||||
{
|
||||
"priority": 6,
|
||||
"phase": "FACILITY_COMPLETION",
|
||||
"status": "observed-current",
|
||||
"handlers": [
|
||||
{
|
||||
"phase": "FACILITY_COMPLETION",
|
||||
"handler_id": "simulation_runtime.facilities",
|
||||
"function": "_advance_facilities",
|
||||
"registration_line": 394
|
||||
}
|
||||
],
|
||||
"behavior": "Runtime _advance_facilities:8482: facility_service.advance_one_minute then kitchen. FacilityService _prepare_service_for_tick:853 checks BROKEN, related_facility_operating (RELATED_FACILITY_INACTIVE), power_available (NO_POWER), staff grace. Only ACTIVE services advance (:569). _complete_service:895 → Runtime _complete_service_transaction:11769 → _apply_service_transaction:11785 posts SERVICE_SALE / SERVICE_CASH_SWEEP at the post-increment minute (:11826–11838)."
|
||||
},
|
||||
{
|
||||
"priority": 7,
|
||||
"phase": "VITALS_AND_ENVIRONMENT",
|
||||
"status": "observed-current",
|
||||
"handlers": [
|
||||
{
|
||||
"phase": "VITALS_AND_ENVIRONMENT",
|
||||
"handler_id": "simulation_runtime.guest_vitals",
|
||||
"function": "_advance_guest_vitals",
|
||||
"registration_line": 399
|
||||
}
|
||||
],
|
||||
"behavior": "Guest vitals:8488 follows completion."
|
||||
},
|
||||
{
|
||||
"priority": 8,
|
||||
"phase": "INCIDENTS_AND_EVENTS",
|
||||
"status": "observed-current",
|
||||
"handlers": [
|
||||
{
|
||||
"phase": "INCIDENTS_AND_EVENTS",
|
||||
"handler_id": "simulation_runtime.authority_incidents",
|
||||
"function": "_advance_authority_incidents",
|
||||
"registration_line": 409
|
||||
},
|
||||
{
|
||||
"phase": "INCIDENTS_AND_EVENTS",
|
||||
"handler_id": "simulation_runtime.defense",
|
||||
"function": "_advance_defense",
|
||||
"registration_line": 414
|
||||
},
|
||||
{
|
||||
"phase": "INCIDENTS_AND_EVENTS",
|
||||
"handler_id": "simulation_runtime.relationship_events",
|
||||
"function": "_advance_relationship_events",
|
||||
"registration_line": 419
|
||||
}
|
||||
],
|
||||
"behavior": "String-sorted authority_incidents:8551 → defense:8577 → relationship_events:8615; after completion/charging."
|
||||
},
|
||||
{
|
||||
"priority": 9,
|
||||
"phase": "FACILITY_QUEUES",
|
||||
"status": "observed-current",
|
||||
"handlers": [
|
||||
{
|
||||
"phase": "FACILITY_QUEUES",
|
||||
"handler_id": "simulation_runtime.guest_queues",
|
||||
"function": "_advance_guest_queues",
|
||||
"registration_line": 404
|
||||
}
|
||||
],
|
||||
"behavior": "_advance_guest_queues:8502 releases closed queues first (_release_closed_business_queues:12624); closed queues spend no patience (:8516–8517)."
|
||||
},
|
||||
{
|
||||
"priority": 10,
|
||||
"phase": "AI_DECISIONS",
|
||||
"status": "observed-current",
|
||||
"handlers": [
|
||||
{
|
||||
"phase": "AI_DECISIONS",
|
||||
"handler_id": "simulation_runtime.staff_ai",
|
||||
"function": "_advance_staff_ai",
|
||||
"registration_line": 424
|
||||
}
|
||||
],
|
||||
"behavior": "_advance_staff_ai:8658: prune elevator waits, elevators, rescue, staff_service, prune waits, staff_commute (shift handover), staffing sync, cleaning, fatigue, rescue and staffing sync again, then _advance_guest_ai_decisions:8738. DECIDING PLANNED_EXIT → LEAVING; despawn occurs only on later minute (:8760–8762)."
|
||||
},
|
||||
{
|
||||
"priority": 11,
|
||||
"phase": "PUBLISH_SNAPSHOT",
|
||||
"status": "observed-current",
|
||||
"handlers": [
|
||||
{
|
||||
"phase": "PUBLISH_SNAPSHOT",
|
||||
"handler_id": "simulation_runtime.snapshot",
|
||||
"function": "_publish_snapshot",
|
||||
"registration_line": 374
|
||||
},
|
||||
{
|
||||
"phase": "PUBLISH_SNAPSHOT",
|
||||
"handler_id": "simulation_runtime.zz_save_checkpoints",
|
||||
"function": "_process_save_checkpoints",
|
||||
"registration_line": 431
|
||||
}
|
||||
],
|
||||
"behavior": "snapshot:12476 then zz_save_checkpoints:4514; persistence follows publication."
|
||||
}
|
||||
],
|
||||
"safety_cases": [
|
||||
{
|
||||
"item": "No charge while paused NO_POWER or RELATED",
|
||||
"status": "original",
|
||||
"phase": "FACILITY_COMPLETION",
|
||||
"source": "FacilityService _prepare_service_for_tick:853, ACTIVE-only gate:569, _complete_service:895",
|
||||
"priority": "Preparation before advance/completion/payment",
|
||||
"proof": "R/s4.json isolated power; I C and R/s2a.json dependency hold",
|
||||
"approval": "Dependency hold approved; both D05(a) options preserve safety"
|
||||
},
|
||||
{
|
||||
"item": "Double-settlement idempotency",
|
||||
"status": "original",
|
||||
"phase": "TIME_BOUNDARIES",
|
||||
"source": "coordinator _process_daily_close:1125; _day_summaries replay:1127–1130; daily-close:N:1137",
|
||||
"priority": "Before facility completion",
|
||||
"proof": "I C; R/s2a.json same-boundary replay and separate-PID restore, unchanged accounting"
|
||||
},
|
||||
{
|
||||
"item": "Orphan reservation release",
|
||||
"status": "original",
|
||||
"phase": "APPLY_COMMANDS / FACILITY_QUEUES",
|
||||
"source": "Runtime _execute_leave_facility_queue:7553 (release keys:7565–7569); _leave_guest_queue_boundary:9178; _release_closed_business_queues:12624; _release_closed_business_request:12642",
|
||||
"priority": "Command release before completion; closed-queue release afterward; closed patience not spent",
|
||||
"proof": "R/s3.json B2a/B2b/B3/B4b; I C; R/s2a.json reservation/service identity and restore"
|
||||
}
|
||||
],
|
||||
"proof_legend": {
|
||||
"label": "Root-measured",
|
||||
"R": "B/audit-support/p04-raw-collect-r22",
|
||||
"I": "B/p04-evidence/index.json schema2; native_bundle/raw_references/versions/source_map/environment bind exact proofs",
|
||||
"pins": "REPAIR29_STATIC_SOURCE.json proof_pins; R/MANIFEST.json pins series and relocation, series retain raw paths/hashes"
|
||||
},
|
||||
"authority_statuses": {
|
||||
"original": [
|
||||
"P04-01 safety acceptance",
|
||||
"BASELINE:64,67,71,72,183"
|
||||
],
|
||||
"approved": [
|
||||
"A01 midnight ownership upkeep",
|
||||
"D05-P04-DEPENDENCY",
|
||||
"D05-P04-SHIFT-1800",
|
||||
"D-034 direct business closure"
|
||||
],
|
||||
"pending D05": [
|
||||
"a power vs closure/dependency",
|
||||
"b destruction",
|
||||
"c exit",
|
||||
"d cost time/day beyond A01 and18:00"
|
||||
],
|
||||
"observed-current": "All phase and source behavior observations; not approval"
|
||||
},
|
||||
"full178_applicability_context": "REPAIR29_STATIC_SOURCE.json full_source_static_comparison: full178 selected1339 includes f5; one test-source mismatch (test_required_kitchen.gd), no game-source mismatch; preserve Runner applicability binding, no blanket1338 identity or a61 full proof."
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"supplied_sources": [
|
||||
{
|
||||
"name": "p04-01-shift-end-repro-r03.gd",
|
||||
"sha256": "97e20c3f68b85bbb85fd37e85e97dec277cca5ebc791107120b0606d1138cc0a",
|
||||
"bytes": 9518,
|
||||
"historical_only": true
|
||||
},
|
||||
{
|
||||
"name": "p04-01-shift-end-repro-native-r03.py",
|
||||
"sha256": "995a9b159533f40f79a4ef639c1338b689e8e98720eed638f8eae169d8a03e11",
|
||||
"bytes": 6268,
|
||||
"historical_only": true
|
||||
},
|
||||
{
|
||||
"name": "p04-01-midnight-boundary-r01.gd",
|
||||
"sha256": "4f587cc90a3bbd310b53c6216e54cda1deacfa87fa4312411b41288e534de527",
|
||||
"bytes": 11677,
|
||||
"historical_only": true
|
||||
},
|
||||
{
|
||||
"name": "p04-01-midnight-boundary-native-r01.py",
|
||||
"sha256": "3da084530941658ee396b05b9343f0342bf2a981962ef3361ed0f82475dd73dd",
|
||||
"bytes": 6317,
|
||||
"historical_only": true
|
||||
},
|
||||
{
|
||||
"name": "p04-01-power-formal-recovery-r04.gd",
|
||||
"sha256": "887d3097fa4178675caf253c428d56cc842b506b8c32d6f7925505f17c32e2ff",
|
||||
"bytes": 10062,
|
||||
"historical_only": true
|
||||
},
|
||||
{
|
||||
"name": "p04-01-power-formal-recovery-native-r04.py",
|
||||
"sha256": "d115a1ec5f985cfa4d7bd751eace0f2f5e93e1f4809434a0e2685c0fe0b8b23e",
|
||||
"bytes": 8425,
|
||||
"historical_only": true
|
||||
},
|
||||
{
|
||||
"name": "p04-01-formal-boundary-reload-r01.gd",
|
||||
"sha256": "56e193a2c1d44c62c83374442b61c63935a69fdc1665ffa09bb549b0dfd8d14b",
|
||||
"bytes": 7624,
|
||||
"historical_only": true
|
||||
},
|
||||
{
|
||||
"name": "p04-01-formal-boundary-reload-native-r03.py",
|
||||
"sha256": "fcd22729c9d045eb9656fae5db6c2116a27ab54fdb24f91c8125925b02a9d47a",
|
||||
"bytes": 8677,
|
||||
"historical_only": true
|
||||
},
|
||||
{
|
||||
"name": "p04-01-destruction-completion-boundary-r02.gd",
|
||||
"sha256": "a9d58e7d5fcdf4793075552ae5be1ba6f5c2d45423b245b3d4617530260df37b",
|
||||
"bytes": 12257,
|
||||
"historical_only": true
|
||||
},
|
||||
{
|
||||
"name": "p04-01-exit-boundary-r01.gd",
|
||||
"sha256": "6d7f32d801e4d8fb241a5e5695d6e31a8e7c47ad8021228ded460efcb1967254",
|
||||
"bytes": 12726,
|
||||
"historical_only": true
|
||||
}
|
||||
],
|
||||
"evidence_references": 39,
|
||||
"raw_reuse_confirmed": 0,
|
||||
"engine_runs": 0,
|
||||
"runner_operations": 0,
|
||||
"formal_acceptance": false
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
# S1 Root run(コード準備のみ)
|
||||
|
||||
S1コード準備完了・未実測・原P04 whole未達。D05 pending、A未検証。
|
||||
engine起動・全回帰・コミットはworkerでは実施しない。
|
||||
assertは各caseの停止中の課金行0、予約の孤立0、到着1回だけ。
|
||||
予約snapshotのstateは文字列名として検査し、未知の名前は孤立checkを失敗にする。
|
||||
ACTIVEのstaff_task予約とtask.reservation_idsを双方向照合する。非ACTIVEの履歴もrawには保持する。
|
||||
費用帰属、金額、EV停止規則を決めない。remaining_minutes保持は生観測として保存する。
|
||||
|
||||
## Rootが実行するコマンド
|
||||
|
||||
以下はSource checkout外のtask buildにplan/cache/rawを保存する。
|
||||
Rootは先に空き容量・他のengine・終了済cacheへのアクセスを確認する。
|
||||
既存出力は削除しない。再試行する場合はplan/outputの両方に新しいr番号を指定する。
|
||||
|
||||
```sh
|
||||
export PYTHONPYCACHEPREFIX='/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build/s1-python-cache'
|
||||
python3 '/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/P04-01-implement/docs/plans/original-completion-2026-10-02/execution/p04-01/s1/run_s1_native.py' --check-only --plan '/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build/p04-s1-root-plan-r02.json' --output '/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build/p04-s1-native-r02'
|
||||
python3 '/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/P04-01-implement/docs/plans/original-completion-2026-10-02/execution/p04-01/s1/run_s1_native.py' --plan '/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build/p04-s1-root-plan-r02.json' --output '/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build/p04-s1-native-r02'
|
||||
```
|
||||
|
||||
既定の製品sourceは
|
||||
`/Users/korizatou114/Projects/とりにく工房ゲーム制作部`。
|
||||
workerのprobeだけを `tests/s3/test_runtime_staff_assignment.gd` にQA overrideする。
|
||||
製品sourceの全選択ファイルをpinし、終了済cacheのselected mapとの完全一致を実runで要求する。
|
||||
sourceの役割変更や不一致を自動承認しない。
|
||||
|
||||
既定cache:
|
||||
`/Users/korizatou114/Projects/TheTree-managed-worktrees/p18-01-model/.runtime/P18-01-CODE/build/p18-relationship-evidence`。
|
||||
workerからreceiptの読取りはOSに拒否された。check-onlyはその具体的path/errorを
|
||||
cache_accessに記録し、engineやdefault playerには触らない。
|
||||
Rootのcheck-onlyでは読める必要がある。実runではreceiptがpassedであること、
|
||||
cacheの存在・全hash・cp -cの別inodeを必須とする。アクセス権やRoot境界は変更しない。
|
||||
|
||||
## 実行順序とexit
|
||||
|
||||
source_refs → QA override 1件 → private stage → bootstrap → cache cp -c/別inode →
|
||||
import 1回 → parse 1回 → writer → 別PID reader →
|
||||
finally owned_outer.finish_captured → fixed_guard。
|
||||
normal watchdogは既存1200秒、importは既存180秒で変更しない。
|
||||
実runのdefault-player保護は既存runnerを使う。
|
||||
|
||||
check-onlyの期待exitは0。engine起動0で入力SHAとplanを出力する。
|
||||
cache_accessが非nullならRoot確認が未完でありnative開始条件を満たさない。
|
||||
実runの期待exitは0(4phase成功、Writer/Reader別PID/profile、6安全性checksずつ、
|
||||
14保存pointの正式復元、Writer保存物不変、owned cleanupとsource/default-player guard成功)。
|
||||
strict phase失敗またはguard失敗は1、native開始前の入力・資源不備は2。
|
||||
最初の失敗で停止し、自動再試行しない。exit0でも原P04 whole合格ではない。
|
||||
|
||||
## 出力とraw一覧
|
||||
|
||||
出力root:
|
||||
`/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build/p04-s1-native-r02/`
|
||||
|
||||
- source_before.json、source_after.json、selected-source-map.json、runner-input.json
|
||||
- runner-started.json、runner-ended.json、execution.json、PID-set.json、outer-owned-finally.json、raw-inventory.json
|
||||
- stage/(別inodeの製品source+QA、.godot clone)、starters/、bootstrap/
|
||||
- cache-reuse.json(全cache hashと終了済receipt pin)、parse-paths.json、contracts.json
|
||||
- import/、parse/、writer/、reader/のstdout.log、stderr.log、engine-native.log、
|
||||
strict runner生成のprocess/profile/start/end/capture証拠。全フォルダを保存する
|
||||
- 各phaseの*-receipt.json、*-actual-phase.json、writer-combined-raw.log、reader-combined-raw.log
|
||||
- raw/writer.json、raw/reader.json(PID/profile、正式slot path、checks/errors)
|
||||
- raw/{writer,reader}-observations.{json,var}
|
||||
- raw/{writer,reader}-{landing,no-safe-landing}-observations.{json,var}
|
||||
- raw/{writer,reader}-{landing,no-safe-landing}-{before,boarded,depleted,stopped,held,landing,recovered}.{json,var}
|
||||
- phase receiptが指すuser://saves/p04_s1/の全正式package。
|
||||
WriterとReader各14slot、各slotのjournal.log、manifest.json、thumbnail.png、world.binを保持する。
|
||||
private startup/runtime profileのlog/lock等も既存runner方式で保持し削除しない
|
||||
|
||||
JSONは閲覧用でVariant型を落とすため、必ずtyped .varと対で読む。
|
||||
case観測はseed、実task.required_items、予約配列、ledger全行、trip/elevator report、
|
||||
domain event、facility/service ID、全Runtime snapshot、コマンド結果を含む。
|
||||
割当時のreserved_before_travelはイベント内の生観測で、正式保存pointとは区別する。
|
||||
Readerは正式4-member packageを自身のprivate profileへ全コピーしてpublic load/restore後、
|
||||
tickせず観測しWriter typed snapshotとの完全一致を確認する。
|
||||
到着イベント履歴はWriter採取rawであることをReader側にも明示する。
|
||||
no-safe-landingの残り時間保持とclosureによるrelease時点はrawで確認する。
|
||||
給電復帰後も対象clinicは直接休業のままにして追加の搬送を防ぐ。
|
||||
|
||||
S2/S3、collector、A仕上げ、登録validate、独立whole review、統合は後続であり本提出の範囲外。
|
||||
|
||||
## worker静的確認の適用範囲
|
||||
|
||||
製品rootのtools/run_all_tests.pyもworkerから読取り拒否されたため、workerのcheck-onlyは
|
||||
--project-rootにSource checkoutを明示した。選択sourceは1300件であり、Rootのcurrent製品1338件の検証を代行しない。
|
||||
Rootは上記既定product_rootで独自のfresh planを作る。worker planを実runに転用しない。
|
||||
|
||||
停電と着床を別規則に分離しない。reserveをpublic commandで0にし、同じ更新境界の前に
|
||||
対象clinicへ直接休業commandを送り、通常tickによる停電・予約解放・安全到着の実順序を記録する。
|
||||
着床不能caseの予約は乗車中保持され得るため、releaseの時点を予測しない。
|
||||
strict runnerの同一test名logはphaseごとにcombined-rawとして退避し、actual-phase/executionの
|
||||
logを退避pathへ結合する。元strict receiptは変更せずstrict_runner_log_pathも残す。
|
||||
+262
@@ -0,0 +1,262 @@
|
||||
extends SceneTree
|
||||
# S1 only; setup follows test_rescue_elevator and transport:94-149.
|
||||
const SEED := 712
|
||||
const TASK := &"s1_rescue_with_tool"
|
||||
var runtime: Variant
|
||||
var clock: Variant
|
||||
var state: Variant
|
||||
var folder := ""
|
||||
var mode := ""
|
||||
var clinic_id: StringName
|
||||
var checks := 0
|
||||
var failures := 0
|
||||
var errors: Array = []
|
||||
var slots: Dictionary = {}
|
||||
var observations: Dictionary = {}
|
||||
var commands: Array = []
|
||||
var arrivals: Array = []
|
||||
var pretravel: Dictionary = {}
|
||||
|
||||
func _initialize() -> void: call_deferred("run")
|
||||
|
||||
# Exactly three safety assertions per case; operational failures are separate.
|
||||
func outcome(ok: bool, label: String) -> void:
|
||||
checks += 1
|
||||
if not ok:
|
||||
failures += 1
|
||||
push_error(label)
|
||||
|
||||
func io(ok: bool, label: String) -> bool:
|
||||
if not ok:
|
||||
errors.append(label)
|
||||
push_error(label)
|
||||
return ok
|
||||
|
||||
func raw(label: String, value: Variant) -> void:
|
||||
var file := FileAccess.open(folder.path_join(mode + "-" + label + ".var"), FileAccess.WRITE)
|
||||
if io(file != null, "typed output " + label):
|
||||
file.store_buffer(var_to_bytes(value)); file.flush(); file.close()
|
||||
file = FileAccess.open(folder.path_join(mode + "-" + label + ".json"), FileAccess.WRITE)
|
||||
if io(file != null, "JSON output " + label):
|
||||
file.store_string(JSON.stringify(value, "\t")); file.flush(); file.close()
|
||||
|
||||
func read_json(path: String) -> Dictionary:
|
||||
var file := FileAccess.open(path, FileAccess.READ)
|
||||
if not io(file != null, "read " + path): return {}
|
||||
var value: Variant = JSON.parse_string(file.get_as_text()); file.close()
|
||||
if not io(value is Dictionary, "dictionary " + path): return {}
|
||||
return value
|
||||
|
||||
func typed(path: String) -> Dictionary:
|
||||
var file := FileAccess.open(path, FileAccess.READ)
|
||||
if not io(file != null, "read typed " + path): return {}
|
||||
var value: Variant = bytes_to_var(file.get_buffer(file.get_length())); file.close()
|
||||
if not io(value is Dictionary, "typed dictionary " + path): return {}
|
||||
return value
|
||||
|
||||
func cleanup() -> void:
|
||||
if runtime != null: runtime.free()
|
||||
if clock != null: clock.free()
|
||||
if state != null: state.free()
|
||||
runtime = null; clock = null; state = null
|
||||
|
||||
func reset() -> bool:
|
||||
cleanup(); commands = []; arrivals = []; pretravel = {}; clinic_id = &""
|
||||
clock = load("res://autoload/game_clock.gd").new()
|
||||
state = load("res://autoload/game_state.gd").new()
|
||||
runtime = load("res://autoload/simulation_runtime.gd").new()
|
||||
clock.reset(1, 6, 0); state.reset()
|
||||
var initialized: Dictionary = runtime.initialize(clock, state, SEED)
|
||||
clock.set_paused(true)
|
||||
runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
|
||||
runtime.domain_events.subscribe(&"ELEVATOR_ARRIVED", &"s1.raw", on_arrival)
|
||||
runtime.domain_events.subscribe(&"STAFF_TASK_ASSIGNED", &"s1.reservation", on_assignment)
|
||||
return io(bool(initialized.get(&"valid", false)), "runtime initialization")
|
||||
|
||||
func on_assignment(event: SimulationDomainEvent) -> void:
|
||||
if TASK in event.entity_ids and pretravel.is_empty():
|
||||
pretravel = capture()
|
||||
pretravel[&"staff_cell"] = runtime.staff_service.get_staff(&"medic").current_cell
|
||||
|
||||
func on_arrival(event: SimulationDomainEvent) -> void:
|
||||
arrivals.append(event.to_dictionary())
|
||||
|
||||
func cmd(kind: StringName, payload: Dictionary) -> bool:
|
||||
var result: CommandResult = runtime.submit_command(kind, payload, &"player")
|
||||
commands.append(result.to_dictionary())
|
||||
return io(result.is_success(), "public command " + String(kind))
|
||||
|
||||
func capture() -> Dictionary:
|
||||
var reservations: Array = []
|
||||
for reservation: InventoryReservation in runtime.inventory_service.get_reservations():
|
||||
reservations.append(reservation.create_snapshot())
|
||||
var ledger: Array = []
|
||||
for entry: LedgerEntry in runtime.economy_service.get_entries(): ledger.append(entry.to_dictionary())
|
||||
var events: Array = []
|
||||
for event: SimulationDomainEvent in runtime.domain_events.recent_events(): events.append(event.to_dictionary())
|
||||
var services: Array = []
|
||||
for facility: FacilityInstance in runtime.facility_service.get_instances():
|
||||
services.append({&"facility_id": facility.instance_id, &"active_service_ids": facility.active_services.keys()})
|
||||
var task: StaffTask = runtime.staff_service.get_task(TASK)
|
||||
return {&"seed": SEED, &"minute": clock.absolute_minute, &"snapshot": runtime.create_snapshot(),
|
||||
&"ledger": ledger, &"reservations": reservations, &"task": task.create_snapshot() if task != null else {},
|
||||
&"trip": runtime.elevators.get_actor_trip(&"staff:medic"),
|
||||
&"elevator_report": runtime.elevators.get_report(), &"domain_events": events,
|
||||
&"arrivals": arrivals.duplicate(true), &"commands": commands.duplicate(true),
|
||||
&"service_ids": services, &"staff_invariants": runtime.staff_service.validate_invariants()}
|
||||
|
||||
func construct() -> bool:
|
||||
if not reset(): return false
|
||||
for spec: Array in [[&"infirmary", Vector3i(-6,0,1)], [&"service_elevator", Vector3i(0,0,1)]]:
|
||||
if not cmd(&"build_facility", {&"definition_id": spec[0], &"origin": spec[1]}): return false
|
||||
clock.advance_minutes(400)
|
||||
for facility: FacilityInstance in runtime.facility_service.get_instances():
|
||||
if facility.definition_id == &"infirmary": clinic_id = facility.instance_id
|
||||
if not io(not clinic_id.is_empty(), "constructed clinic"): return false
|
||||
for id: StringName in [&"patient", &"medic"]:
|
||||
if not cmd(&"register_staff", {&"staff_id": id, &"role_id": &"medic", &"options": {
|
||||
&"current_cell": Vector3i(0,1,1) if id == &"patient" else Vector3i(5,0,1),
|
||||
&"health": 0 if id == &"patient" else 100, &"state": StaffRuntime.State.AVAILABLE,
|
||||
&"role_tags": [&"medic"], &"permitted_task_tags": [&"rescue"]}}): return false
|
||||
# Actual task.required_items uses the ordinary Runtime reservation callback.
|
||||
if not cmd(&"add_inventory_stack", {&"owner_inventory_id": &"staff.supplies", &"item_id": &"tool/cleaning_kit", &"quantity": 1}): return false
|
||||
return cmd(&"create_staff_task", {&"task_id": TASK, &"task_type": &"rescue_pickup", &"priority": 95, &"options": {
|
||||
&"target_entity_id": &"patient", &"target_cell": Vector3i(0,1,1),
|
||||
&"required_role_tags": [&"medic"], &"task_tags": [&"rescue"],
|
||||
&"required_items": {&"tool/cleaning_kit": 1}, &"estimated_minutes": 30,
|
||||
&"interrupt_level": 0, &"emergency": true}})
|
||||
|
||||
func save_point(label: String) -> void:
|
||||
# The raw snapshot is observed immediately before the official save.
|
||||
var point := capture()
|
||||
raw(label, point)
|
||||
var path := "user://saves/p04_s1/" + label
|
||||
var saved := SaveService.save_snapshot(path, point[&"snapshot"])
|
||||
if not io(bool(saved.get(&"ok", false)), "formal save " + label): return
|
||||
var inspected := SaveService.inspect_snapshot(path)
|
||||
io(bool(inspected.get(&"ok", false)) and bool(inspected.get(&"read_only", false)) and var_to_bytes(inspected.get(&"snapshot", {})) == var_to_bytes(point[&"snapshot"]), "formal inspect " + label)
|
||||
slots[label] = ProjectSettings.globalize_path(path)
|
||||
|
||||
func safety(row: Dictionary) -> void:
|
||||
var charge_rows: int = row[&"held_after"][&"ledger"].size() - row[&"reserve_exhausted_before_tick"][&"ledger"].size()
|
||||
outcome(charge_rows == 0, "停止中の課金行0")
|
||||
var orphans := 0
|
||||
var states_known := true
|
||||
# Check each observed boundary, both directions, using actual task ownership.
|
||||
for point_name: StringName in [&"boarded", &"stopped", &"held_after", &"final"]:
|
||||
var point: Dictionary = row[point_name]
|
||||
var tasks: Array = point[&"snapshot"][&"systems"][&"runtime"][&"staff"][&"tasks"]
|
||||
var active: Dictionary = {}
|
||||
for reservation: Dictionary in point[&"reservations"]:
|
||||
var state_name := String(reservation[&"state"])
|
||||
if InventoryReservation.State.keys().find(state_name) < 0:
|
||||
states_known = false
|
||||
continue
|
||||
if state_name != InventoryReservation.State.keys()[InventoryReservation.State.ACTIVE]: continue
|
||||
if String(reservation[&"purpose"]) != "staff_task": continue
|
||||
active[String(reservation[&"reservation_id"])] = reservation
|
||||
var linked := false
|
||||
for task: Dictionary in tasks:
|
||||
if String(task[&"task_id"]) == String(reservation[&"requester_id"]) and String(reservation[&"reservation_id"]) in task[&"reservation_ids"]: linked = true
|
||||
if not linked: orphans += 1
|
||||
for task: Dictionary in tasks:
|
||||
for id: Variant in task[&"reservation_ids"]:
|
||||
if not active.has(String(id)): orphans += 1
|
||||
outcome(orphans == 0 and states_known, "予約の孤立0" if states_known else "予約の不明状態")
|
||||
var counts := {&"staff:medic": 0, &"staff:patient": 0}
|
||||
var stops: Array = row[&"boarded"][&"elevator_report"][&"shafts"][0][&"stops"]
|
||||
var requests: Dictionary = {}
|
||||
for trip: Dictionary in row[&"boarded"][&"elevator_report"][&"trips"]: requests[trip[&"actor_id"]] = trip[&"request_id"]
|
||||
for event: Dictionary in row[&"final"][&"arrivals"]:
|
||||
var payload: Dictionary = event["payload"]
|
||||
var actor := StringName(payload.get(&"actor_id", &""))
|
||||
if counts.has(actor) and payload.get(&"request_id") == requests.get(actor) and payload.get(&"cell") in stops: counts[actor] += 1
|
||||
outcome(counts[&"staff:medic"] == 1 and counts[&"staff:patient"] == 1, "到着1回")
|
||||
row[&"safety_raw"] = {&"stopped_ledger_delta": charge_rows, &"orphans": orphans, &"arrivals_by_actor": counts}
|
||||
|
||||
func writer_case(label: String, blocked: bool) -> void:
|
||||
if not construct(): return
|
||||
var row := {&"seed": SEED, &"case": label, &"before": capture()}
|
||||
save_point(label + "-before")
|
||||
var reserved := false
|
||||
for tick: int in 130:
|
||||
clock.advance_one_minute()
|
||||
var task: StaffTask = runtime.staff_service.get_task(TASK)
|
||||
if not reserved and not task.reservation_ids.is_empty():
|
||||
row[&"reserved_before_travel"] = pretravel.duplicate(true); reserved = true
|
||||
if task.task_type == &"rescue_transport" and runtime.elevators.get_actor_trip(&"staff:patient").get(&"state") == PathService.ELEVATOR_REQUEST_BOARDED: break
|
||||
if not io(reserved and not pretravel.is_empty() and pretravel.get(&"staff_cell") == Vector3i(5,0,1) and not pretravel[&"task"][&"reservation_ids"].is_empty() and runtime.elevators.get_actor_trip(&"staff:patient").get(&"state") == PathService.ELEVATOR_REQUEST_BOARDED, "real reserved task reaches boarded pair"): return
|
||||
row[&"boarded"] = capture(); save_point(label + "-boarded")
|
||||
var stops: Array = runtime.elevators.get_report()[&"shafts"][0][&"stops"]
|
||||
# Public fixture API reused literally from transport test, not a private mutation.
|
||||
if blocked:
|
||||
for cell: Vector3i in stops: runtime.path_service.configure_cell_traffic(cell, 4, 0, true)
|
||||
var reserve: int = state.get_resource(&"mana")
|
||||
if not cmd(&"change_resources", {&"deltas": {&"mana": -reserve}}): return
|
||||
row[&"reserve_exhausted_before_tick"] = capture()
|
||||
if not cmd(&"set_facility_conditions", {&"facility_id": clinic_id, &"conditions": {&"operator_open": false}}): return
|
||||
clock.advance_one_minute(); row[&"depleted"] = capture(); save_point(label + "-depleted")
|
||||
clock.advance_one_minute(); row[&"stopped"] = capture(); save_point(label + "-stopped")
|
||||
clock.advance_minutes(20); row[&"held_after"] = capture(); save_point(label + "-held")
|
||||
row[&"remaining_minutes_before"] = row[&"stopped"][&"elevator_report"][&"shafts"][0][&"remaining_minutes"]
|
||||
row[&"remaining_minutes_after"] = row[&"held_after"][&"elevator_report"][&"shafts"][0][&"remaining_minutes"]
|
||||
row[&"held_remaining_unchanged"] = row[&"remaining_minutes_before"] == row[&"remaining_minutes_after"]
|
||||
if blocked:
|
||||
for cell: Vector3i in stops: runtime.path_service.configure_cell_traffic(cell)
|
||||
clock.advance_one_minute()
|
||||
row[&"landing"] = capture(); save_point(label + "-landing")
|
||||
if not cmd(&"change_resources", {&"deltas": {&"mana": reserve}}): return
|
||||
clock.advance_minutes(45 if blocked else 1)
|
||||
row[&"final"] = capture(); save_point(label + "-recovered")
|
||||
row[&"reservation_release_raw"] = runtime.staff_service.get_task(TASK).create_snapshot()
|
||||
safety(row); observations[label] = row; raw(label + "-observations", row)
|
||||
|
||||
func reader() -> void:
|
||||
var receipt := read_json(folder.path_join("writer.json"))
|
||||
if not io(int(receipt.get("pid", 0)) != OS.get_process_id() and String(receipt.get("profile", "")) != OS.get_user_data_dir(), "distinct Reader PID/profile"): return
|
||||
for label: String in receipt["slots"]:
|
||||
if not reset(): return
|
||||
var original := String(receipt["slots"][label])
|
||||
var target := ProjectSettings.globalize_path("user://saves/p04_s1/" + label)
|
||||
if not io(DirAccess.make_dir_recursive_absolute(target) == OK, "reader package directory"): return
|
||||
var directory := DirAccess.open(original)
|
||||
if not io(directory != null, "writer package exists"): return
|
||||
var members := directory.get_files(); members.sort()
|
||||
if not io(directory.get_directories().is_empty() and members == PackedStringArray(["journal.log", "manifest.json", "thumbnail.png", "world.bin"]), "complete formal package"): return
|
||||
for member: String in members:
|
||||
if not io(DirAccess.copy_absolute(original.path_join(member), target.path_join(member)) == OK and FileAccess.get_sha256(original.path_join(member)) == FileAccess.get_sha256(target.path_join(member)), "exact formal member"): return
|
||||
var loaded := SaveService.load_snapshot(target)
|
||||
if not io(bool(loaded.get(&"ok", false)), "formal Reader load"): return
|
||||
if not io(runtime.restore_snapshot(loaded[&"snapshot"]), "public Reader restore"): return
|
||||
var expected := typed(folder.path_join("writer-" + label + ".var"))
|
||||
if not io(expected.has(&"snapshot"), "writer point exists"): return
|
||||
io(var_to_bytes(runtime.create_snapshot()) == var_to_bytes(expected[&"snapshot"]), "Reader exact typed snapshot")
|
||||
var point := capture() # No tick after formal restore.
|
||||
point[&"writer_observation"] = expected
|
||||
observations[label] = point; slots[label] = target; raw(label, point)
|
||||
for label: String in ["landing", "no-safe-landing"]:
|
||||
var row := typed(folder.path_join("writer-" + label + "-observations.var"))
|
||||
if not io(not row.is_empty(), "writer case exists"): return
|
||||
for pair: Array in [[&"boarded", "boarded"], [&"stopped", "stopped"], [&"held_after", "held"], [&"final", "recovered"]]:
|
||||
var point: Dictionary = observations[label + "-" + pair[1]]
|
||||
for key: StringName in [&"snapshot", &"ledger", &"reservations"]: row[pair[0]][key] = point[key]
|
||||
# Arrival history is Writer raw; Reader proves restored state separately.
|
||||
safety(row); raw(label + "-observations", row)
|
||||
|
||||
func run() -> void:
|
||||
folder = OS.get_environment("THE_TREE_S1_EVIDENCE"); mode = OS.get_environment("THE_TREE_S1_MODE")
|
||||
if folder.is_empty() or mode not in ["writer", "reader"]: quit(2); return
|
||||
if not io(DirAccess.make_dir_recursive_absolute(folder) == OK, "evidence directory"): quit(2); return
|
||||
if mode == "writer":
|
||||
writer_case("landing", false); writer_case("no-safe-landing", true)
|
||||
else: reader()
|
||||
raw("observations", observations)
|
||||
var receipt := {&"pid": OS.get_process_id(), &"profile": OS.get_user_data_dir(), &"slots": slots,
|
||||
&"checks": checks, &"failures": failures, &"operational_errors": errors,
|
||||
&"whole_goal_accepted": false, &"unit_accepted": false}
|
||||
var file := FileAccess.open(folder.path_join(mode + ".json"), FileAccess.WRITE)
|
||||
if io(file != null, "phase receipt"):
|
||||
file.store_string(JSON.stringify(receipt, "\t")); file.flush(); file.close()
|
||||
cleanup()
|
||||
print("S1 EV power: %d checks, %d failures" % [checks, failures])
|
||||
quit(1 if failures or not errors.is_empty() or checks != 6 else 0)
|
||||
@@ -0,0 +1,253 @@
|
||||
#!/usr/bin/env python3
|
||||
"""S1-only native entry. --check-only never loads ownership helpers or starts Godot."""
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
BUILD = Path("/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build")
|
||||
PRODUCT = Path("/Users/korizatou114/Projects/とりにく工房ゲーム制作部")
|
||||
CACHE_RUN = Path("/Users/korizatou114/Projects/TheTree-managed-worktrees/p18-01-model/.runtime/P18-01-CODE/build/p18-relationship-evidence")
|
||||
TEST = "tests/s3/test_runtime_staff_assignment.gd"
|
||||
QA = HERE / "p04_s1_ev_power_probe.gd"
|
||||
ENGINE = Path("/Applications/Godot.app/Contents/MacOS/Godot")
|
||||
MATERIALS = [
|
||||
BUILD / "audit-support/p04-whole-closure-r01/current-patterns/p04-idempotency-paid-formal-native-r01.py",
|
||||
BUILD / "audit-support/p04-whole-closure-r01/current-patterns/idempotency-paid-formal-r01.gd",
|
||||
BUILD / "audit-support/p04-whole-closure-r01/SOURCE_MANIFEST.json",
|
||||
BUILD / "audit-support/p04-native-existing-sources/p04-01-power-formal-recovery-r04.gd",
|
||||
BUILD / "audit-support/p04-native-existing-sources/p04-01-power-formal-recovery-native-r04.py",
|
||||
BUILD / "audit-support/p04-native-existing-sources/SOURCE_MANIFEST.json",
|
||||
BUILD / "audit-support/native-first-current/SOURCE_MANIFEST.json",
|
||||
BUILD / "audit-support/native-first-current/actual-native/runner-input.json",
|
||||
BUILD / "audit-support/p04-current-native-series-r01/SOURCE_MANIFEST.json",
|
||||
BUILD / "audit-support/p04-current-native-series-r01/p04-idempotency-paid-formal-native-r01/runner-input.json",
|
||||
]
|
||||
|
||||
def module(name, path):
|
||||
spec = importlib.util.spec_from_file_location(name, path)
|
||||
value = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(value)
|
||||
return value
|
||||
|
||||
def pin(path):
|
||||
path = Path(path)
|
||||
if path.is_symlink() or not path.is_file():
|
||||
raise ValueError("missing exact input: " + str(path))
|
||||
data = path.read_bytes()
|
||||
return dict(path=str(path), bytes=len(data), sha256=hashlib.sha256(data).hexdigest())
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--project-root", type=Path, default=PRODUCT)
|
||||
parser.add_argument("--output", type=Path, default=BUILD / "p04-s1-native-r01")
|
||||
parser.add_argument("--plan", type=Path, default=BUILD / "p04-s1-plan-r01.json")
|
||||
parser.add_argument("--cache-run", type=Path, default=CACHE_RUN)
|
||||
parser.add_argument("--godot", type=Path, default=ENGINE)
|
||||
parser.add_argument("--check-only", action="store_true")
|
||||
args = parser.parse_args()
|
||||
root = args.project_root.resolve()
|
||||
out = args.output.resolve()
|
||||
r = module("s1_standard_runner", root / "tools/run_all_tests.py")
|
||||
# Preserve the reviewed source_refs -> exactly one QA override order.
|
||||
source_before = r.source_refs(root)
|
||||
refs = dict(source_before)
|
||||
refs[TEST] = pin(QA)
|
||||
expected = {n: dict(bytes=p["bytes"], sha256=p["sha256"]) for n, p in refs.items()}
|
||||
fixed = [pin(p) for p in MATERIALS + [
|
||||
QA, Path(__file__), HERE / "S1_ROOT_RUN.md",
|
||||
root / "tests/transport/test_runtime_transport.gd",
|
||||
root / "tests/staff/test_rescue_elevator.gd",
|
||||
root / "docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json",
|
||||
]]
|
||||
selected = dict(root_before=source_before, map={n: p["sha256"] for n, p in refs.items()}, timeout=1200)
|
||||
cache_note = None
|
||||
cache_receipts = []
|
||||
for leaf in ["runner-ended.json", "runner-input.json"]:
|
||||
path = args.cache_run / leaf
|
||||
try:
|
||||
cache_receipts.append(pin(path))
|
||||
except OSError as caught:
|
||||
cache_note = dict(path=str(path), error=type(caught).__name__ + ": " + str(caught),
|
||||
required_before_native=True)
|
||||
break
|
||||
plan = dict(protocol="p04-s1-preparation-v1", project_root=str(root), output=str(out),
|
||||
source_before=source_before, selected_source_map=selected["map"],
|
||||
QA_overrides=[TEST], fixed_input_pins=fixed, cache_run=str(args.cache_run),
|
||||
cache_receipts=cache_receipts, cache_access=cache_note,
|
||||
phases=["import", "parse", "writer", "reader"],
|
||||
native_engine_launches=0, whole_goal_accepted=False, unit_accepted=False)
|
||||
# Check-only only reads source/materials and writes the plan in task build.
|
||||
# It deliberately does not read default-player data, inspect processes or stat Godot.
|
||||
if args.check_only:
|
||||
args.plan.parent.mkdir(parents=True, exist_ok=True)
|
||||
if args.plan.exists():
|
||||
r.require(r.read(args.plan) == plan, "existing plan differs; use a fresh plan path")
|
||||
else:
|
||||
r.write(args.plan, plan)
|
||||
print(json.dumps(dict(check_only=True, engines=0, source_files=len(source_before),
|
||||
QA_overrides=[TEST], fixed_pins=len(fixed), plan=str(args.plan),
|
||||
plan_sha256=r.sha(args.plan), cache_access=cache_note), ensure_ascii=False))
|
||||
return 0
|
||||
r.require(args.plan.is_file() and r.read(args.plan) == plan, "run exact check-only plan first")
|
||||
def fixed_guard():
|
||||
for value in fixed + cache_receipts:
|
||||
r.require(pin(value["path"]) == value, "fixed input changed: " + value["path"])
|
||||
fixed_guard()
|
||||
# Native admission is Root-only and fails closed on unreadable cache.
|
||||
r.require(cache_note is None, "Root must read exact cache receipts: " + str(cache_note))
|
||||
old = args.cache_run
|
||||
r.require(r.read(old / "runner-ended.json")["passed"], "ended cache run must have passed")
|
||||
old_map = r.read(old / "runner-input.json")["selected_source_map"]
|
||||
current_map = {n: p["sha256"] for n, p in source_before.items()}
|
||||
r.require(old_map == current_map, "ended cache selected source must equal current product")
|
||||
protected = r.default_player(root)
|
||||
outer = module("s1_owned_outer", r.SUPPORT / "owned_outer.py")
|
||||
r.require(not outer.native.helper["live_engines"](), "another Godot owns CPU")
|
||||
engine = args.godot.resolve()
|
||||
r.require(engine.is_file(), "Root native Godot path")
|
||||
r.require(shutil.disk_usage(out.parent).free >= 1024**3 + engine.stat().st_size + 136*1024**2,
|
||||
"reviewed native disk admission")
|
||||
r.require(not out.exists(), "fresh output required; preserve previous attempts")
|
||||
out.mkdir()
|
||||
stage = out / "stage"
|
||||
stage.mkdir()
|
||||
(out / "starters").mkdir()
|
||||
(out / "bootstrap").mkdir()
|
||||
phases = []
|
||||
previous = {}
|
||||
error = None
|
||||
guards = []
|
||||
cleanup = []
|
||||
cleanup_errors = []
|
||||
cache_before = None
|
||||
formal_before = None
|
||||
formal_after = None
|
||||
original_phase = outer.run_phase
|
||||
def run_phase(argv, folder, timeout, env, **kwargs):
|
||||
env = dict(env)
|
||||
if folder.name in ["writer", "reader"]:
|
||||
env.update(THE_TREE_S1_EVIDENCE=str(out / "raw"), THE_TREE_S1_MODE=folder.name)
|
||||
return original_phase(argv, folder, timeout, env, **kwargs)
|
||||
outer.run_phase = run_phase
|
||||
r.write(out / "runner-started.json", dict(started=r.now(), owner_runner_pid=os.getpid(),
|
||||
plan=pin(args.plan), engines_launched=0))
|
||||
try:
|
||||
r.write(out / "source_before.json", source_before)
|
||||
r.write(out / "selected-source-map.json", refs)
|
||||
# Private source stage first, then literal reviewed bootstrap.
|
||||
for name, value in refs.items():
|
||||
target = stage / name
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
r.copy_source(Path(value["path"]), target)
|
||||
r.require(r.inventory(stage, stage=True) == expected, "whole private source")
|
||||
for name, value in r.read(r.SUPPORT / "bootstrap-scripts.json").items():
|
||||
data = value["text"].encode()
|
||||
r.require(hashlib.sha256(data).hexdigest() == value["sha256"], "bootstrap pin")
|
||||
(out / "bootstrap" / name).write_bytes(data)
|
||||
cache = old / "stage/.godot"
|
||||
cache_before = r.inventory(cache)
|
||||
r.require(bool(cache_before), "nonempty ended cache")
|
||||
for name in cache_before:
|
||||
src = cache / name
|
||||
dst = stage / ".godot" / name
|
||||
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
subprocess.run(["/bin/cp", "-c", str(src), str(dst)], check=True)
|
||||
r.require((src.stat().st_dev, src.stat().st_ino) != (dst.stat().st_dev, dst.stat().st_ino),
|
||||
"cache clone is a separate inode")
|
||||
r.require(r.inventory(stage / ".godot") == cache_before, "whole cache clone hashes")
|
||||
r.write(out / "cache-reuse.json", dict(ended_run=str(old), receipts=cache_receipts,
|
||||
whole_cache=cache_before, independent_inode=True,
|
||||
ordinary_import_required=True, ordinary_parse_required=True))
|
||||
paths = ["res://" + n for n in sorted(refs) if n.endswith(".gd")]
|
||||
r.write(out / "parse-paths.json", paths)
|
||||
markers = module("s1_markers", root / "tools/strict_test_markers.py")
|
||||
contracts = markers.load_contracts(root / "docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json")
|
||||
contracts[TEST] = dict(test=TEST, template="S1 EV power: <checks> checks, <failures> failures", required=True)
|
||||
policy = r.read(r.SUPPORT / "suite-policy.json")
|
||||
r.require(TEST not in policy["suite_policies"], "no normal suite watchdog/policy override")
|
||||
r.write(out / "contracts.json", contracts)
|
||||
r.write(out / "runner-input.json", dict(plan, owner_runner_pid=os.getpid(),
|
||||
wrapper_sha256=r.sha(__file__), QA=pin(QA), runner_sha256=r.sha(root / "tools/run_all_tests.py"),
|
||||
default_player_before=protected, fixed_input_pins=fixed,
|
||||
functional_watchdog_seconds=1200, source_adoption=False))
|
||||
diagnostics = module("s1_diagnostics", r.SUPPORT / "diagnostic_gate.py")
|
||||
owners = module("s1_owners", r.SUPPORT / "ordinary_proof_gate.py")
|
||||
audio = module("s1_audio", r.SUPPORT / "audio_evidence_gate.py")
|
||||
public = (stage / "project.godot").read_bytes()
|
||||
for phase, test in [("import", None), ("parse", None), ("writer", TEST), ("reader", TEST)]:
|
||||
if phase == "reader":
|
||||
writer = r.read(out / "raw/writer.json")
|
||||
formal_before = {n: r.inventory(Path(p)) for n, p in writer["slots"].items()}
|
||||
r.require(len(formal_before) == 14, "all fourteen formal writer points")
|
||||
row = r.execute_phase(str(engine), stage, out, phase, test, selected, expected,
|
||||
previous, policy, contracts, paths, public, protected, root,
|
||||
outer, markers, diagnostics, owners, audio)
|
||||
phases.append(row)
|
||||
# execute_phase uses the same test log name; retain each phase's exact log too.
|
||||
if test and row.get("log"):
|
||||
(out / (phase + "-combined-raw.log")).write_bytes((out / row["log"]).read_bytes())
|
||||
row["strict_runner_log_path"] = row["log"]
|
||||
row["log"] = phase + "-combined-raw.log"
|
||||
r.write(out / (phase + "-actual-phase.json"), row)
|
||||
print(json.dumps(dict(phase=phase, passed=row["passed"], error=row.get("validation_error"))), flush=True)
|
||||
r.require(row["passed"], "first strict phase failure: " + phase)
|
||||
if phase in ["writer", "reader"]:
|
||||
receipt = r.read(out / ("raw/" + phase + ".json"))
|
||||
r.require(receipt["checks"] == 6 and receipt["failures"] == 0 and not receipt["operational_errors"],
|
||||
"exact S1 outcome/operational receipt")
|
||||
r.require(receipt["pid"] == row["actual_pid"], "actual native PID/receipt")
|
||||
writer = r.read(out / "raw/writer.json")
|
||||
reader = r.read(out / "raw/reader.json")
|
||||
r.require(writer["pid"] != reader["pid"] and writer["profile"] != reader["profile"], "distinct formal processes")
|
||||
formal_after = {n: r.inventory(Path(p)) for n, p in writer["slots"].items()}
|
||||
r.require(formal_before == formal_after, "Reader leaves Writer packages unchanged")
|
||||
except BaseException as caught:
|
||||
error = type(caught).__name__ + ": " + str(caught)
|
||||
finally:
|
||||
cleanup, cleanup_errors = outer.finish_captured(outer.OWNED)
|
||||
try:
|
||||
r.write(out / "outer-owned-finally.json", dict(cleanup=cleanup, errors=cleanup_errors))
|
||||
except BaseException as caught:
|
||||
guards.append("finally receipt: " + type(caught).__name__ + ": " + str(caught))
|
||||
# Fixed guard always follows ownership cleanup, even if setup or an engine failed.
|
||||
try:
|
||||
fixed_guard()
|
||||
after = r.source_refs(root)
|
||||
r.write(out / "source_after.json", after)
|
||||
r.require(after == source_before, "whole product source unchanged")
|
||||
r.require(r.default_player(root) == protected, "default player unchanged")
|
||||
if (stage / "project.godot").exists():
|
||||
r.require(r.inventory(stage, stage=True) == dict(expected, **previous), "whole private source unchanged")
|
||||
if cache_before is not None:
|
||||
r.require(r.inventory(old / "stage/.godot") == cache_before, "original cache unchanged")
|
||||
except BaseException as caught:
|
||||
guards.append(type(caught).__name__ + ": " + str(caught))
|
||||
pids = [p.pid for p in outer.OWNED]
|
||||
passed = (error is None and not guards and not cleanup_errors and len(phases) == 4
|
||||
and all(p["passed"] for p in phases) and len(pids) == len(set(pids)) == 4
|
||||
and all(p["engine_ended"] for p in cleanup))
|
||||
execution = dict(passed=passed, error=error, guard_errors=guards, phases=phases,
|
||||
actual_captured_PIDs=pids, cleanup=cleanup, cleanup_errors=cleanup_errors,
|
||||
formal_before=formal_before, formal_after=formal_after,
|
||||
whole_goal_accepted=False, unit_accepted=False)
|
||||
r.write(out / "raw-inventory.json", r.inventory(out / "raw"))
|
||||
r.write(out / "PID-set.json", dict(owner_runner_pid=os.getpid(), engine_PIDs=pids))
|
||||
r.write(out / "execution.json", execution)
|
||||
r.write(out / "runner-ended.json", dict(execution, ended=r.now()))
|
||||
print(json.dumps(dict(passed=passed, error=error, guard_errors=guards, output=str(out))))
|
||||
return 0 if passed else 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
raise SystemExit(main())
|
||||
except Exception as caught:
|
||||
print(type(caught).__name__ + ": " + str(caught), file=sys.stderr)
|
||||
raise SystemExit(2)
|
||||
@@ -0,0 +1,122 @@
|
||||
# P04-01 S2a Root run(code-ready提出用・実測未実施)
|
||||
|
||||
repair12確認: worktreeのoperations_coordinator.gd SHA256は
|
||||
`871a248ee101b6df543fd5cf0440b5d4b0f02c21fa145a99f0687888c1bc6f90`。
|
||||
製品パスの直接読取はOSが拒否したが、Root r01のsource_before.jsonに記録された
|
||||
同ファイルの製品pin(130891 bytes)と一致した。
|
||||
|
||||
## repair12の判断と検証境界
|
||||
|
||||
ROOT_S2A_NATIVE_R01_RESULT.json、r01 source_before.json、execution.json、Reader stderr、
|
||||
raw Writer C1 join/C2 recall/C3 midnightとReader C3 branchのJSONを直接確認した。
|
||||
対応する型保持.varも全バイトを読みSHAを確認した。原本を保持し、workerはengineで復号・再実行しない。
|
||||
Readerの失敗はformal equality day_summaryの1件。実勤務はAkaneの1+236分、
|
||||
Bardoの361分(0勤務取消shiftも存在)、維持費は3行がledger_00000082を共有する。
|
||||
|
||||
受入JSON、既存S2条件、ORDER_AUDITは決算内容・決定的順序・重複防止・保存復元を要求し、
|
||||
getterのDictionary挿入順やStringNameキー型の保存前後不変を要求していない。
|
||||
OperationsCoordinator:577-580は内部duplicate、668-710と2943-3065は保存用のString化・sort、
|
||||
restore:734と_parse_historiesは保存形を内部へ採用する。したがって製品変更は不要と判断した。
|
||||
JSON正規化で一致を作らず、create_snapshot.day_summariesのday==1をそのまま取り出して
|
||||
Writer/Readerのvar_to_bytes完全一致を必須とする。raw getterは保持し、再帰typeof・キー型・
|
||||
キー順fingerprint、rawバイト比較結果も保存する。rawとcanonicalは全キーをString(key)で
|
||||
一対一照合し、全値を再帰的に型込みで検査する。StringName/Stringだけは明示した差分種別で
|
||||
照合し、その他の型差・値差・欠落は失敗する。配列の順序も維持する。
|
||||
|
||||
paid.payment>0の各staffを同日の実worked/overtime>0のshiftへ結合し、各shiftの実deltaを
|
||||
照合してからpayroll取引IDを集計する。Bardoは1439→1440でworked+1、Akaneは+0、
|
||||
同分recallは双方+0、overtime差分は0が必須。0勤務取消shiftは実勤務根拠にしない。
|
||||
維持費distinct取引数は1を維持し、売上時点・日帰属は変えない。
|
||||
Runtime全体、ledger/payroll、4member SHA、原本不変、recall typed不変は従来どおり必須。
|
||||
|
||||
Rootはfresh r02 plan/outputでimport/parse/Writer/別PID Readerを各1回だけ正式実行する。
|
||||
r01のFAILをr02の成功として扱わない。worker検証はpy_compileとwrapper --check-onlyのみ。
|
||||
engine0、default/private profile読取0、手動commit0、全回帰0。原30/88、部分点0を維持する。
|
||||
|
||||
repair12 worker検証: py_compile成功、--check-only成功(source1300、QA1、固定pin17、engine0)。
|
||||
終了済cache receiptはPermissionErrorをplanへ記録した。Rootは自身の権限で確認する必要がある。
|
||||
GDScript parseとnativeの成否は未検証で、正式なr02実行に委ねる。
|
||||
|
||||
原P04の完成加点は0。独立したwhole受入の前段の準備資料であり、D05、whole受入、独立レビュー、統合を確定しない。
|
||||
Rootは新しいcode-readyの提出が出るまでengineを起動しない。
|
||||
workerはGodot起動(parse、--check-only、--versionを含む)、S1再実行、全回帰、commitを行わない。
|
||||
|
||||
## 入力の確認
|
||||
|
||||
`BUILD_OUTPUT_DIR/audit-support/p04-whole-closure-r01/current-patterns/` の供給ファイルを直接読んだ結果は以下のとおり。指定値に全一致した。
|
||||
|
||||
| ファイル | bytes | SHA256 |
|
||||
| --- | ---: | --- |
|
||||
| paid-shift-midnight-formal-r01.gd | 7667 | 4c56ff05b1bcf8775bb526a30ed021a4a2630ea4f360a5075f880806b052e6d1 |
|
||||
| p04-paid-shift-midnight-native-r01.py | 6580 | 23bf2e241c7828b6533aa8107b3d33e962d5c53300a71b198e6fe371e0d4f4f0 |
|
||||
| p04-paid-shift-midnight-formal-native-r01.py | 7765 | e3b6eab32dd4fe3dfbff7ae9b67b34dd674236018bdb6bc7c64da25898ff5ed6 |
|
||||
|
||||
`SUPPLEMENT_SOURCE_MANIFEST_R02.json`を読み、上記3ファイルと合わせてwrapperが直接pinする。旧SOURCE_MANIFESTに名前がないことを欠落とは解釈しない。
|
||||
paid-shift-midnight / formal / idempotency-paid-formalの各GDと対応native Python、ROOT_S1_NATIVE_R02_RESULT.json、既存S1の3ファイルもpinする。
|
||||
Root S1結果のSource1338、owned engine4終了は履歴資料でありS2aの実行結果へ転用しない。
|
||||
|
||||
セットアップは原paid-shift-midnight-r01.gdの開始から23:59までと_setup_paid_receptionを機械的に抽出して保持した。
|
||||
wrapperは毎回templateを直接読み、両ブロックのliteral一致を確認する。公的hire、DAY/NIGHT、原catalog賃金、原施設価格・サービス時間、材料1つを変更しない。
|
||||
厨房を1439で閉じる処理を省くことだけがシナリオ差分。公開helperを継承し、snapshot内部への書込みはない。
|
||||
|
||||
## Root手順
|
||||
|
||||
既定製品rootは `/Users/korizatou114/Projects/とりにく工房ゲーム制作部`。
|
||||
以下のBUILD_OUTPUT_DIRは `/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build`。
|
||||
製品rootのfresh source planを作成し、nativeを1回だけ実行する。既存のplan/outputは上書き・削除しない。
|
||||
失敗時は最初の失敗で停止し、自動再試行しない。再実行はRootの判断と新しいplan/outputを必要とする。
|
||||
|
||||
```sh
|
||||
export BUILD_OUTPUT_DIR='/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build'
|
||||
export PYTHONDONTWRITEBYTECODE=1
|
||||
python3 docs/plans/original-completion-2026-10-02/execution/p04-01/s2/run_s2a_native.py --check-only --plan "$BUILD_OUTPUT_DIR/p04-s2a-root-plan-r02.json" --output "$BUILD_OUTPUT_DIR/p04-s2a-native-r02"
|
||||
python3 docs/plans/original-completion-2026-10-02/execution/p04-01/s2/run_s2a_native.py --plan "$BUILD_OUTPUT_DIR/p04-s2a-root-plan-r02.json" --output "$BUILD_OUTPUT_DIR/p04-s2a-native-r02"
|
||||
```
|
||||
|
||||
wrapperのパスはこの提出checkoutのものを使う。実行cwdに依存して製品sourceを選ばず、既定product rootを使用する。
|
||||
workerのcheckout向けcheck-only planはRootの製品planへ転用しない。
|
||||
check-onlyはwrapperのmodeであり、Godot起動0、default/private profile読取0。sourceと固定資料、cache receiptだけを読む。
|
||||
cache receiptが読めない場合は実際のpath/errorをplanに記録し、native admissionは拒否する。
|
||||
|
||||
## ケースと証拠
|
||||
|
||||
Writerはseed424242の独立fresh Runtimeを3回作り、各branchを1回ずつ実施する。
|
||||
|
||||
- C1 midnight_sale_payroll_upkeep:原のclock advanceで1440にserviceを完了し、同じadvanceでDay1決算。実sale transaction IDをservice.paymentのIdempotency記録でservice IDへjoinし、request ID・予約IDと照合する。WAGEのsource_entity_idを実payroll settlement IDへjoinし、各paid rowのstaff_idとsettlement.day(1始まり)をShiftInstance.staff_id/start_day(0始まり)へjoinする。取引IDをdistinctで数え、行数・勤務分数を取引数の代わりにしない。
|
||||
- C2 same_minute_recall:別fresh stateから1440へ到達した直後、current_minute==1440で公開advance_to(1440)を1回呼ぶ。ok/code/payloadとCommandResult全体を記録し、全typed snapshot、ledger、payroll、day_summary、shifts不変を検査。request再送や1443からの過去呼出しで代用しない。
|
||||
- C3 formal_restore:別fresh stateからC1と同じ境界に到達し、公的SaveServiceで1slotを保存。別PID/profileのReaderが4member全コピーとSHA一致、公的load/restore、typed snapshot完全一致、ledger/payroll/canonical day1完全一致とraw getter結合を確認。復元直後の1440でadvance_to(1440)を再度呼び、実結果と再決算0を採取する。
|
||||
|
||||
勤務分は全shiftのbefore/after、worked_minutesとovertime_minutes差分を保持する。
|
||||
孤立予約0は対象serviceの予約と実予約行のrequester_id/purpose/CONSUMEDを照合し、ACTIVE facility_service予約は原requestのreserved_inventory_idsへ照合する。未知stateは失敗する。
|
||||
sale/payroll/upkeep/dutyは全ledgerと正式snapshotに加えjoin行に保持する。停止条件はないため、1440の売上・給与・維持費ledgerを「停止中課金ledger0」と判定しない。「停止中課金0」は実停止条件を満たす別probeの受入条件として残る。
|
||||
|
||||
出力は `BUILD_OUTPUT_DIR/p04-s2a-native-r02/`。rawの各観測はJSON(閲覧用)と.var(型保持)の対で保存する。FAILも原値のまま残す。
|
||||
seed、入力、日、minute、実取引/service/request/予約ID、勤務差分、ledger全行、commands、全Runtime snapshot、operations/Idempotency、upkeep accrual/settlementを保持する。
|
||||
Writer各branchのbefore/midnight/join/branch、C2 recall、Reader C3 branch/recall、phase receiptを保存する。
|
||||
private profile内のWriter/Reader formal package(journal.log、manifest.json、thumbnail.png、world.bin)を保持する。
|
||||
source_before/after、selected-source-map、runner-input、cache-reuse、parse-paths、contracts、各phase raw logs/receipts、退避combined-raw.log、PID-set、outer-owned-finally、execution、runner-ended、raw-inventoryも全保存する。
|
||||
|
||||
## strict guard
|
||||
|
||||
S1 wrapperの手順を同順序で複製:source_refs → 明示QA1 → 別inodeのprivate source/cache → import1 → parse1 → Writer → 別PID Reader → owned finally → 全guard。
|
||||
|
||||
- fresh output、exact check-only plan、全source pin、固定資料pin、供給3pinと補足manifest、literal setup一致
|
||||
- passed終了済cache receiptと製品selected source map完全一致、cache全hash、cp -c別inode
|
||||
- 他engine不在、既存disk admission、既存watchdog/policy/diagnostic/ownership/audio/marker gate
|
||||
- 製品source/default player不変、private source全体不変、原cache不変
|
||||
- Writer/Readerの実PIDとreceipt一致、別PID/profile、4phase/4owned PID、全engine owned終了
|
||||
- formal全member hash一致、Writer原slot不変(Reader失敗時もguard)、typed完全一致
|
||||
- finallyでcaptured owned process cleanup。cleanup/fixed guard失敗も失敗receiptに保持
|
||||
|
||||
exit0でも原P04の完成ではない。native未実行のため、実測checks数・case結果を予測で埋めない。
|
||||
worker検証はpy_compileとwrapper --check-onlyだけ。Godot parseはRootの正式nativeのparse phaseで行う。
|
||||
|
||||
worker静的確認:py_compile成功。wrapper --check-only成功、engine0・private profile読取0、source checkout1300件・QA1・固定pin17件。
|
||||
終了済cacheの `runner-ended.json` はOSからPermissionError(Operation not permitted)で読取拒否された。
|
||||
planはその実path/errorをcache_accessへ記録した。Root製品1338件の確認は代行していない。
|
||||
Rootは製品rootでfresh planを作り、cache receiptを自身の権限で確認する。worker planは実runへ転用しない。
|
||||
|
||||
## 残件
|
||||
|
||||
S2bの夜番不在と残業は勤務生成と時刻条件が別系統なので分ける。
|
||||
本提出の実測、元の停止条件の別probe、独立whole受入・レビュー・必要OS証拠・統合はRoot側の後続。
|
||||
+397
@@ -0,0 +1,397 @@
|
||||
extends "res://tests/s3/test_runtime_actor_hardening.gd"
|
||||
var _mode := ""
|
||||
var _slots: Dictionary = {}
|
||||
var _branches: Dictionary = {}
|
||||
var _commands: Array = []
|
||||
|
||||
func _run() -> void:
|
||||
_evidence_dir = OS.get_environment("THE_TREE_S2A_EVIDENCE")
|
||||
_mode = OS.get_environment("THE_TREE_S2A_MODE")
|
||||
if _evidence_dir.is_empty() or _mode not in ["writer", "reader"]: quit(2); return
|
||||
_expect(DirAccess.make_dir_recursive_absolute(_evidence_dir) == OK, "evidence directory")
|
||||
if _mode == "writer":
|
||||
for branch: String in ["C1", "C2", "C3"]: _writer_branch(branch)
|
||||
else: _reader_branch()
|
||||
_write_var("branches", _branches)
|
||||
var receipt := {&"pid": OS.get_process_id(), &"profile": OS.get_user_data_dir(), &"slots": _slots,
|
||||
&"checks": _checks, &"failures": _failures, &"branches": _branches.keys(),
|
||||
&"whole_goal_accepted": false, &"unit_accepted": false}
|
||||
var file := FileAccess.open(_evidence_dir.path_join(_mode + ".json"), FileAccess.WRITE)
|
||||
_expect(file != null, "phase receipt")
|
||||
if file != null: file.store_string(JSON.stringify(receipt, "\t")); file.flush(); file.close()
|
||||
_cleanup()
|
||||
print("S2a midnight: %d checks, %d failures" % [_checks, _failures])
|
||||
quit(1 if _failures else 0)
|
||||
|
||||
func _writer_branch(branch: String) -> void:
|
||||
_case = _mode + "-" + branch
|
||||
_serial = 0; _expected_daily_wages = 0; _commands.clear(); _observations.clear()
|
||||
_prepare_midnight()
|
||||
var before := _capture()
|
||||
_write_var("before", before)
|
||||
_expect(before[&"active_reservation_orphans"] == 0, "pre-midnight active reservation ownership")
|
||||
_expect(_clock.absolute_minute == 1439 and _runtime.operations_coordinator.current_minute == 1439, "fresh branch before midnight")
|
||||
# Only scenario change: omit dependency closure. The ordinary clock performs
|
||||
# completion and daily close in the same advance. No snapshot mutation.
|
||||
_clock.advance_one_minute()
|
||||
var after := _capture()
|
||||
var joined := _join(before, after)
|
||||
_write_var("midnight", after); _write_var("join", joined)
|
||||
_expect(_clock.absolute_minute == 1440 and _runtime.operations_coordinator.current_minute == 1440, "midnight reached")
|
||||
_expect(after[&"service"][&"completed_at"] == 1440 and joined[&"sales"].size() == 1, "actual one midnight service sale")
|
||||
_expect(joined[&"sale_join_complete"], "sale transaction idempotency joins actual service and reservations")
|
||||
_expect(int(after[&"day_summary"].get(&"closed_at", -1)) == 1440, "same advance closes Day 1")
|
||||
_expect(joined[&"payroll_transaction_ids"].size() == 1 and joined[&"payroll_join_complete"], "actual payroll transaction joins paid Staff and day shifts")
|
||||
_expect(joined[&"upkeep_transaction_ids"].size() == 1, "one distinct actual upkeep transaction")
|
||||
_expect(joined[&"orphans"] == 0, "reservation orphans zero")
|
||||
var row := {&"before": before, &"midnight": after, &"join": joined}
|
||||
if branch == "C2": row[&"recall"] = _recall()
|
||||
if branch == "C3":
|
||||
var path := "user://saves/p04_s2a/C3"
|
||||
var saved := SaveService.save_snapshot(path, after[&"snapshot"])
|
||||
var inspected := SaveService.inspect_snapshot(path)
|
||||
row[&"save_result"] = saved
|
||||
_expect(bool(saved.get(&"ok", false)) and bool(inspected.get(&"ok", false)) and bool(inspected.get(&"read_only", false)), "formal SaveService save/inspect")
|
||||
_expect(var_to_bytes(inspected.get(&"snapshot", {})) == var_to_bytes(after[&"snapshot"]) and var_to_bytes(_runtime.create_snapshot()) == var_to_bytes(after[&"snapshot"]), "save preserves entire typed state")
|
||||
_slots["C3"] = ProjectSettings.globalize_path(path)
|
||||
_branches[branch] = row; _write_var("branch", row)
|
||||
|
||||
func _capture() -> Dictionary:
|
||||
var raw_day: Dictionary = _runtime.operations_coordinator.get_day_summary(1)
|
||||
var canonical_day: Dictionary = {}
|
||||
var day_count := 0
|
||||
for day: Dictionary in _runtime.operations_coordinator.create_snapshot()["day_summaries"]:
|
||||
if int(day["day"]) == 1:
|
||||
canonical_day = day
|
||||
day_count += 1
|
||||
var day_differences: Array = []
|
||||
var day_join := _day_join(raw_day, canonical_day, "$", day_differences)
|
||||
_expect(day_join and day_count == (0 if raw_day.is_empty() else 1), "raw getter joins every canonical day1 key and typed value")
|
||||
var ledger: Array = []
|
||||
for entry: LedgerEntry in _runtime.economy_service.get_entries(): ledger.append(entry.to_dictionary())
|
||||
var reservations: Array = []
|
||||
for reservation: InventoryReservation in _runtime.inventory_service.get_reservations(): reservations.append(reservation.create_snapshot())
|
||||
var service: ServiceInstance = _runtime.facility_service.get_service(_service_id)
|
||||
var staff: Dictionary = _runtime.staff_service.create_snapshot()
|
||||
var active_orphans := 0
|
||||
for reservation: Dictionary in reservations:
|
||||
if InventoryReservation.State.keys().find(String(reservation[&"state"])) < 0: active_orphans += 1
|
||||
if reservation[&"state"] != "ACTIVE" or reservation[&"purpose"] != "facility_service": continue
|
||||
var request: ServiceRequest = _runtime.facility_service.get_request(StringName(reservation[&"requester_id"]))
|
||||
if request == null or not request.reserved_inventory_ids.has(StringName(reservation[&"reservation_id"])): active_orphans += 1
|
||||
if service != null and not service.is_terminal():
|
||||
for id: StringName in service.inventory_reservations:
|
||||
var reservation: InventoryReservation = _runtime.inventory_service.get_reservation(id)
|
||||
if reservation == null or not reservation.is_active() or reservation.requester_id != service.request_id: active_orphans += 1
|
||||
return {&"seed": 424242, &"day": _clock.absolute_minute / 1440 + 1, &"minute": _clock.absolute_minute,
|
||||
&"inputs": {&"facility_id": _facility_id, &"service_id": _service_id, &"request_id": _request_id, &"guest_id": _guest_id, &"original_price": _original_price},
|
||||
&"snapshot": _runtime.create_snapshot(), &"ledger": ledger, &"reservations": reservations, &"active_reservation_orphans": active_orphans,
|
||||
&"service": service.create_snapshot() if service != null else {}, &"staff": staff,
|
||||
&"payroll": staff[&"payroll_settlements"], &"shifts": staff[&"shifts"],
|
||||
&"day_summary": raw_day, &"canonical_day1": canonical_day,
|
||||
&"day_summary_diagnostic": {&"raw": _fingerprint(raw_day), &"canonical": _fingerprint(canonical_day), &"differences": day_differences, &"join_complete": day_join},
|
||||
&"idempotency": {&"commands": _runtime.command_queue.create_snapshot(), &"economy": _runtime.economy_service.create_snapshot()[&"idempotency"]},
|
||||
&"upkeep": {&"accrual": _runtime.facility_upkeep.create_snapshot(), &"settlement": _runtime.upkeep_settlement.create_snapshot()},
|
||||
&"operations": _runtime.operations_coordinator.create_snapshot(),
|
||||
&"commands": _commands.duplicate(true), &"setup_observations": _observations.duplicate(true)}
|
||||
|
||||
func _fingerprint(value: Variant) -> Dictionary:
|
||||
var result := {"typeof": typeof(value), "type": type_string(typeof(value))}
|
||||
if value is Dictionary:
|
||||
var entries: Array = []
|
||||
for key: Variant in value:
|
||||
entries.append({"key": str(key), "key_typeof": typeof(key), "value": _fingerprint(value[key])})
|
||||
result["entries_in_order"] = entries
|
||||
elif value is Array:
|
||||
var children: Array = []
|
||||
for child: Variant in value: children.append(_fingerprint(child))
|
||||
result["elements"] = children
|
||||
return result
|
||||
|
||||
func _day_join(raw: Variant, saved: Variant, path: String, differences: Array) -> bool:
|
||||
if typeof(raw) != typeof(saved):
|
||||
var text_pair := typeof(raw) in [TYPE_STRING, TYPE_STRING_NAME] and typeof(saved) in [TYPE_STRING, TYPE_STRING_NAME]
|
||||
differences.append({"path": path, "kind": "StringName_String" if text_pair else "type_mismatch", "raw_type": typeof(raw), "saved_type": typeof(saved)})
|
||||
return text_pair and String(raw) == String(saved)
|
||||
if raw is Dictionary:
|
||||
var matched: Dictionary = {}
|
||||
var ok: bool = raw.size() == saved.size()
|
||||
var raw_order: Array = []
|
||||
var saved_order: Array = []
|
||||
for key: Variant in saved: saved_order.append(String(key))
|
||||
for key: Variant in raw:
|
||||
var name := String(key)
|
||||
raw_order.append(name)
|
||||
if matched.has(name) or not saved.has(name):
|
||||
ok = false
|
||||
continue
|
||||
matched[name] = true
|
||||
if typeof(key) != TYPE_STRING:
|
||||
differences.append({"path": path + "/" + name, "kind": "key_StringName_String" if typeof(key) == TYPE_STRING_NAME else "invalid_key_type", "raw_type": typeof(key), "saved_type": TYPE_STRING})
|
||||
ok = typeof(key) == TYPE_STRING_NAME and ok
|
||||
ok = _day_join(raw[key], saved[name], path + "/" + name, differences) and ok
|
||||
if raw_order != saved_order: differences.append({"path": path, "kind": "key_order", "raw": raw_order, "saved": saved_order})
|
||||
return ok
|
||||
if raw is Array:
|
||||
if raw.size() != saved.size(): return false
|
||||
var ok := true
|
||||
for index: int in range(raw.size()): ok = _day_join(raw[index], saved[index], path + "/" + str(index), differences) and ok
|
||||
return ok
|
||||
return var_to_bytes(raw) == var_to_bytes(saved)
|
||||
|
||||
func _recall() -> Dictionary:
|
||||
var before := _capture()
|
||||
_expect(_runtime.operations_coordinator.current_minute == 1440, "recall starts at current minute 1440")
|
||||
var result: CommandResult = _runtime.operations_coordinator.advance_to(1440)
|
||||
var command := {&"ok": result.is_success(), &"code": CommandResult.Code.keys()[result.code], &"payload": result.details.duplicate(true), &"result": result.to_dictionary()}
|
||||
var after := _capture()
|
||||
var row := {&"before": before, &"command": command, &"after": after, &"join": _join(before, after)}
|
||||
_write_var("recall", row)
|
||||
_expect(result.is_success() and result.is_replay and int(result.details.get(&"boundary_counts", {}).get(&"daily_closes", -1)) == 0, "actual replay result has zero daily closes")
|
||||
_expect(var_to_bytes(before[&"snapshot"]) == var_to_bytes(after[&"snapshot"]), "same-minute public recall entire typed state unchanged")
|
||||
for key: StringName in [&"ledger", &"payroll", &"day_summary", &"shifts"]:
|
||||
_expect(var_to_bytes(before[key]) == var_to_bytes(after[key]), "no duplicate settlement " + String(key))
|
||||
_expect(row[&"join"][&"orphans"] == 0, "recall reservation orphans zero")
|
||||
return row
|
||||
|
||||
func _join(before: Dictionary, after: Dictionary) -> Dictionary:
|
||||
var sales: Array = []
|
||||
var upkeep: Array = []
|
||||
var transactions: Dictionary = {}
|
||||
var upkeep_transactions: Dictionary = {}
|
||||
var payroll_join: Array = []
|
||||
var complete := true
|
||||
var sale_complete := true
|
||||
var shift_delta: Array = []
|
||||
for shift: Dictionary in after[&"shifts"]:
|
||||
var prior: Dictionary = {}
|
||||
for old: Dictionary in before[&"shifts"]:
|
||||
if old[&"shift_instance_id"] == shift[&"shift_instance_id"]: prior = old
|
||||
shift_delta.append({&"before": prior, &"after": shift, &"worked_minutes_delta": int(shift[&"worked_minutes"]) - int(prior.get(&"worked_minutes", 0)), &"overtime_minutes_delta": int(shift[&"overtime_minutes"]) - int(prior.get(&"overtime_minutes", 0))})
|
||||
for entry: Dictionary in after[&"ledger"]:
|
||||
if entry[&"category"] == "SERVICE_SALE" and entry[&"source_entity_id"] == String(_facility_id) and entry[&"credit_account"] == "GUEST_WALLET:" + String(_guest_id):
|
||||
var payment_id: StringName = _runtime.economy_service.get_transaction_id_for_idempotency_key(StringName("service.payment.%s" % _service_id))
|
||||
sale_complete = sale_complete and String(payment_id) == entry[&"transaction_id"] and int(entry[&"absolute_minute"]) == 1440 and int(entry[&"amount"]) == _original_price
|
||||
sales.append({&"ledger": entry, &"service_id": _service_id, &"reservation_ids": after[&"service"].get(&"inventory_reservations", [])})
|
||||
if entry[&"category"] == "MAINTENANCE_PAYMENT":
|
||||
upkeep.append(entry)
|
||||
upkeep_transactions[entry[&"transaction_id"]] = true
|
||||
if entry[&"category"] != "WAGE": continue
|
||||
var found := false
|
||||
for settlement: Dictionary in after[&"payroll"]:
|
||||
if settlement[&"settlement_id"] != entry[&"source_entity_id"]: continue
|
||||
found = true
|
||||
var settlement_duty_complete := true
|
||||
var paid_count := 0
|
||||
for paid: Dictionary in settlement[&"rows"]:
|
||||
if int(paid[&"payment"]) <= 0: continue
|
||||
paid_count += 1
|
||||
var shifts: Array = []
|
||||
var duty: Array = []
|
||||
var worked_delta := 0
|
||||
var overtime_delta := 0
|
||||
# Settlement day is 1-based; ShiftInstance.start_day is 0-based.
|
||||
for shift: Dictionary in after[&"shifts"]:
|
||||
if shift[&"staff_id"] != paid[&"staff_id"] or int(shift[&"start_day"]) + 1 != int(settlement[&"day"]): continue
|
||||
if int(shift[&"worked_minutes"]) + int(shift[&"overtime_minutes"]) <= 0: continue
|
||||
shifts.append(shift)
|
||||
for delta: Dictionary in shift_delta:
|
||||
if delta[&"after"][&"shift_instance_id"] != shift[&"shift_instance_id"]: continue
|
||||
duty.append(delta)
|
||||
worked_delta += int(delta[&"worked_minutes_delta"])
|
||||
overtime_delta += int(delta[&"overtime_minutes_delta"])
|
||||
var expected_delta := 1 if paid[&"staff_id"] == "staff_bardo" else 0
|
||||
if int(before[&"minute"]) == int(after[&"minute"]): expected_delta = 0
|
||||
var duty_ok := not shifts.is_empty() and duty.size() == shifts.size() and worked_delta == expected_delta and overtime_delta == 0
|
||||
settlement_duty_complete = settlement_duty_complete and duty_ok
|
||||
payroll_join.append({&"transaction": entry, &"settlement": settlement, &"paid_staff": paid, &"day": settlement[&"day"], &"shifts": shifts, &"service_id": _service_id, &"sale_rows": sales, &"upkeep_rows": upkeep, &"duty_delta": duty, &"worked_delta": worked_delta, &"overtime_delta": overtime_delta, &"duty_join_complete": duty_ok})
|
||||
complete = complete and settlement_duty_complete and paid_count > 0
|
||||
if settlement_duty_complete and paid_count > 0: transactions[entry[&"transaction_id"]] = true
|
||||
complete = complete and found
|
||||
var orphans := 0
|
||||
var service: Dictionary = after[&"service"]
|
||||
if service.get(&"inventory_reservations", []).is_empty(): orphans += 1
|
||||
for id: Variant in service.get(&"inventory_reservations", []):
|
||||
var found := false
|
||||
for reservation: Dictionary in after[&"reservations"]:
|
||||
if reservation[&"reservation_id"] == String(id):
|
||||
found = true
|
||||
if reservation[&"requester_id"] != service[&"request_id"] or reservation[&"purpose"] != "facility_service" or reservation[&"state"] != "CONSUMED": orphans += 1
|
||||
if not found: orphans += 1
|
||||
for reservation: Dictionary in after[&"reservations"]:
|
||||
if InventoryReservation.State.keys().find(String(reservation[&"state"])) < 0: orphans += 1
|
||||
if reservation[&"state"] == "ACTIVE" and reservation[&"purpose"] == "facility_service":
|
||||
var request: ServiceRequest = _runtime.facility_service.get_request(StringName(reservation[&"requester_id"]))
|
||||
if request == null or not request.reserved_inventory_ids.has(StringName(reservation[&"reservation_id"])): orphans += 1
|
||||
return {&"sales": sales, &"sale_join_complete": sale_complete and not sales.is_empty(), &"payroll_transaction_ids": transactions.keys(), &"payroll_join": payroll_join,
|
||||
&"payroll_join_complete": complete and not payroll_join.is_empty(), &"upkeep_rows": upkeep, &"upkeep_transaction_ids": upkeep_transactions.keys(), &"duty_delta": shift_delta, &"orphans": orphans}
|
||||
|
||||
func _reader_branch() -> void:
|
||||
_case = "reader-C3"
|
||||
var file := FileAccess.open(_evidence_dir.path_join("writer.json"), FileAccess.READ)
|
||||
_expect(file != null, "Writer receipt available")
|
||||
if file == null: return
|
||||
var writer: Dictionary = JSON.parse_string(file.get_as_text()); file.close()
|
||||
_expect(int(writer["pid"]) != OS.get_process_id() and String(writer["profile"]) != OS.get_user_data_dir(), "distinct Reader PID/profile")
|
||||
var original := String(writer["slots"]["C3"])
|
||||
var target := ProjectSettings.globalize_path("user://saves/p04_s2a/C3")
|
||||
_expect(DirAccess.make_dir_recursive_absolute(target) == OK, "Reader fresh slot")
|
||||
var directory := DirAccess.open(original)
|
||||
_expect(directory != null, "Writer slot available")
|
||||
if directory == null: return
|
||||
var members := directory.get_files(); members.sort()
|
||||
_expect(directory.get_directories().is_empty() and members == PackedStringArray(["journal.log", "manifest.json", "thumbnail.png", "world.bin"]), "all formal package members")
|
||||
var hashes: Dictionary = {}
|
||||
for member: String in members:
|
||||
var sha := FileAccess.get_sha256(original.path_join(member))
|
||||
_expect(DirAccess.copy_absolute(original.path_join(member), target.path_join(member)) == OK, "copy formal member")
|
||||
hashes[member] = {&"original_before": sha, &"copy": FileAccess.get_sha256(target.path_join(member))}
|
||||
_expect(sha == hashes[member][&"copy"], "member SHA matches")
|
||||
file = FileAccess.open(_evidence_dir.path_join("writer-C3-midnight.var"), FileAccess.READ)
|
||||
_expect(file != null, "Writer typed point")
|
||||
if file == null: return
|
||||
var expected: Dictionary = bytes_to_var(file.get_buffer(file.get_length())); file.close()
|
||||
_reset_runtime(424242)
|
||||
var loaded := SaveService.load_snapshot(target)
|
||||
_expect(bool(loaded.get(&"ok", false)), "formal public load")
|
||||
if not bool(loaded.get(&"ok", false)): return
|
||||
_expect(_runtime.restore_snapshot(loaded[&"snapshot"]), "public Runtime restore")
|
||||
_facility_id = StringName(expected[&"inputs"][&"facility_id"]); _service_id = StringName(expected[&"inputs"][&"service_id"])
|
||||
_original_price = int(expected[&"inputs"][&"original_price"])
|
||||
var actual := _capture()
|
||||
_expect(var_to_bytes(actual[&"snapshot"]) == var_to_bytes(expected[&"snapshot"]), "formal entire typed snapshot equality")
|
||||
for key: StringName in [&"ledger", &"payroll", &"canonical_day1"]: _expect(var_to_bytes(actual[key]) == var_to_bytes(expected[key]), "formal equality " + String(key))
|
||||
var raw_equal := var_to_bytes(actual[&"day_summary"]) == var_to_bytes(expected[&"day_summary"])
|
||||
var raw_differences: Array = []
|
||||
var raw_join := _day_join(expected[&"day_summary"], actual[&"canonical_day1"], "$", raw_differences)
|
||||
_expect(raw_join, "Writer raw getter joins Reader canonical day1")
|
||||
_write_var("day-summary-comparison", {&"raw_bytes_equal": raw_equal, &"writer": expected[&"day_summary_diagnostic"], &"reader": actual[&"day_summary_diagnostic"], &"differences": raw_differences})
|
||||
var recall := _recall()
|
||||
for member: String in members:
|
||||
hashes[member][&"original_after"] = FileAccess.get_sha256(original.path_join(member))
|
||||
_expect(hashes[member][&"original_before"] == hashes[member][&"original_after"], "original member unchanged")
|
||||
_slots["C3"] = target
|
||||
_branches["C3"] = {&"expected": expected, &"restored": actual, &"recall": recall, &"member_hashes": hashes}
|
||||
_write_var("branch", _branches["C3"])
|
||||
var _observations: Array[Dictionary] = []
|
||||
var _receipts: Array[Dictionary] = []
|
||||
var _first_receipts: Array[Dictionary] = []
|
||||
var _receipt_hook: Callable
|
||||
var _evidence_dir := ""
|
||||
var _facility_id: StringName = &""
|
||||
var _service_id: StringName = &""
|
||||
var _request_id: StringName = &"p04.order.midnight.request"
|
||||
var _guest_id: StringName = &"p04.order.midnight.guest"
|
||||
var _staff_ids: Array[StringName] = [&"p04.midnight.server.a", &"p04.midnight.server.b"]
|
||||
var _serial := 0
|
||||
var _case := "midnight"
|
||||
var _original_price := 0
|
||||
var _observation_pass := "first"
|
||||
var _first_summary: Dictionary = {}
|
||||
var _paid_reception_id: StringName = &""
|
||||
var _expected_daily_wages := 0
|
||||
|
||||
# Setup below is literal paid-shift-midnight-r01.gd through 23:59, pinned by wrapper.
|
||||
func _prepare_midnight() -> void:
|
||||
_reset_runtime(424242)
|
||||
var enabled: Dictionary = _runtime.enable_production_operations()
|
||||
_expect(bool(enabled.get(&"ok", false)), "public production operations enabled at original Day 1 06:00")
|
||||
if not bool(enabled.get(&"ok", false)): return
|
||||
var facility := _build_cafeteria(true)
|
||||
if facility == null: return
|
||||
_facility_id = facility.instance_id
|
||||
var definition: FacilityDefinition = _runtime.definition_registry.get_facility(facility.definition_id)
|
||||
_original_price = definition.base_price
|
||||
_setup_paid_reception()
|
||||
var cook_id := StringName("fixture.kitchen.worker.%s" % _facility_id)
|
||||
_expect(_cmd(&"set_staff_work_assignment", {&"staff_id": cook_id, &"template_id": &"NIGHT", &"facility_id": facility.related_facility_id}).is_success(), "public original NIGHT assignment replaces inherited 23:59 cook fixture")
|
||||
for id: StringName in _staff_ids:
|
||||
_expect(_cmd(&"register_staff", {&"staff_id": id, &"role_id": &"server", &"options": {&"state": StaffRuntime.State.OFF_DUTY, &"current_cell": Vector3i.ZERO}}).is_success(), "register real workplace Staff " + String(id))
|
||||
_expect(_cmd(&"set_staff_work_assignment", {&"staff_id": id, &"template_id": &"NIGHT", &"facility_id": _facility_id}).is_success(), "original NIGHT 18:00-next06:00 assignment " + String(id))
|
||||
var queue_at := 1440 - definition.base_service_minutes - 1
|
||||
_expect(queue_at > _clock.absolute_minute, "setup finishes before original service start")
|
||||
if queue_at <= _clock.absolute_minute: return
|
||||
_clock.advance_minutes(1079 - _clock.absolute_minute)
|
||||
_observe("paid-before-handover-17-59")
|
||||
_expect(_runtime.facility_service.get_instance(_paid_reception_id).active_staff_ids.has(&"staff_akane"), "normally hired daytime receptionist physically attends before original DAY boundary")
|
||||
_clock.register_tick_handler(_clock.TickPhase.FACILITY_COMPLETION, &"zz.p04.paid.before-handover", func(minute: int):
|
||||
if minute == 1080:
|
||||
_observe("paid-at-completion-before-handover-18-00")
|
||||
_expect(_runtime.facility_service.get_instance(_paid_reception_id).active_staff_ids.has(&"staff_akane"), "last DAY interval retains actual hired worker before handover")
|
||||
)
|
||||
_clock.advance_one_minute(); _observe("paid-after-handover-18-00")
|
||||
_expect(not _runtime.facility_service.get_instance(_paid_reception_id).active_staff_ids.has(&"staff_akane"), "daytime hired receptionist is removed after completion phase")
|
||||
_clock.advance_minutes(queue_at - _clock.absolute_minute)
|
||||
_expect(_runtime.facility_service.get_instance(_paid_reception_id).active_staff_ids.has(&"staff_bardo"), "normally hired NIGHT replacement physically arrives")
|
||||
_expect(_clock.absolute_minute == queue_at and _runtime.operations_coordinator.current_minute == queue_at, "real production operations advance without blocked suffix")
|
||||
if _clock.absolute_minute != queue_at: return
|
||||
for id: StringName in _staff_ids:
|
||||
var worker: StaffRuntime = _runtime.staff_service.get_staff(id)
|
||||
_expect(_runtime.staff_service.requires_attendance(id) and StaffCommuteService.is_at_facility(worker, facility) and worker.current_cell != Vector3i.ZERO, "physical ordinary NIGHT workplace arrival " + String(id))
|
||||
var kitchen: FacilityInstance = _runtime.facility_service.get_instance(facility.related_facility_id)
|
||||
var cook: StaffRuntime = _runtime.staff_service.get_staff(cook_id)
|
||||
_expect(_runtime.staff_service.requires_attendance(cook_id) and StaffCommuteService.is_at_facility(cook, kitchen), "real kitchen NIGHT attendance, not legacy attendance bypass")
|
||||
_expect(_stock(facility.local_inventory_id, &"ingredient/basic_meal", 1, &"p04.midnight.stock"), "one real meal to reserve")
|
||||
_expect(_cmd(&"spawn_guest", {&"guest_id": _guest_id, &"guest_type_id": &"traveler", &"gold": _original_price * 2, &"options": {&"state": GuestRuntime.State.DECIDING, &"current_cell": Vector3i.ZERO}}).is_success(), "public actual Guest wallet")
|
||||
_expect(_cmd(&"create_service_request", {&"facility_id": _facility_id, &"request_id": _request_id, &"guest_ids": [_guest_id]}).is_success(), "actual request original price/duration")
|
||||
_clock.advance_one_minute()
|
||||
var active: Array = _runtime.facility_service.get_active_services(_facility_id)
|
||||
_expect(active.size() == 1, "one actual reserved service starts")
|
||||
if active.size() != 1: return
|
||||
var service: ServiceInstance = active[0]; _service_id = service.service_id
|
||||
_expect(service.started_at + service.duration_minutes == 1440 and service.duration_minutes == definition.base_service_minutes and service.price_snapshot == _original_price, "original service completion coincides with 00:00")
|
||||
_clock.advance_minutes(1439 - _clock.absolute_minute)
|
||||
_expect(service.elapsed_minutes == service.duration_minutes - 1, "one original service minute remains at 23:59")
|
||||
_expect(_runtime.operations_coordinator.get_day_summary(1).is_empty(), "real Day 1 not closed early")
|
||||
|
||||
func _setup_paid_reception() -> void:
|
||||
var definition: FacilityDefinition = _runtime.definition_registry.get_facility(&"reception")
|
||||
_expect(_cmd(&"build_facility", {&"definition_id": &"reception", &"origin": Vector3i(2, 0, WorldService.LAYER_FRONT)}).is_success(), "paid public reception construction")
|
||||
_clock.advance_minutes(definition.build_minutes)
|
||||
for instance: FacilityInstance in _runtime.facility_service.get_instances():
|
||||
if instance.definition_id == &"reception": _paid_reception_id = instance.instance_id
|
||||
_expect(not _paid_reception_id.is_empty(), "reception completes using unchanged construction duration")
|
||||
var catalog: Dictionary = JSON.parse_string(FileAccess.get_file_as_string("res://data/characters/mvp_staff_candidates.json"))
|
||||
for candidate_id: StringName in [&"staff_akane", &"staff_bardo"]:
|
||||
var original_wage := -1
|
||||
for row: Dictionary in catalog["entries"]:
|
||||
if StringName(row["id"]) == candidate_id: original_wage = int(row["daily_wage"])
|
||||
_expect(original_wage > 0 and _cmd(&"hire_staff_candidate", {&"candidate_id": candidate_id}).is_success(), "public hire of existing paid candidate " + String(candidate_id))
|
||||
var worker: StaffRuntime = _runtime.staff_service.get_staff(candidate_id)
|
||||
_expect(worker != null and worker.get_effective_daily_wage() == original_wage, "existing catalog wage unchanged for " + String(candidate_id))
|
||||
_expected_daily_wages += original_wage
|
||||
_expect(_cmd(&"set_staff_work_assignment", {&"staff_id": candidate_id, &"template_id": &"DAY" if candidate_id == &"staff_akane" else &"NIGHT", &"facility_id": _paid_reception_id}).is_success(), "ordinary recurring paid DAY or NIGHT assignment")
|
||||
|
||||
func _cmd(kind: StringName, payload: Dictionary) -> CommandResult:
|
||||
_serial += 1
|
||||
var result: CommandResult = _runtime.submit_command(kind, payload, &"player", &"", 10, -1, StringName("p04.order.shift.command.%d" % _serial))
|
||||
_commands.append({&"kind": kind, &"input": payload.duplicate(true), &"minute": _clock.absolute_minute, &"ok": result.is_success(), &"result": result.to_dictionary()})
|
||||
return result
|
||||
|
||||
func _observe(label: String) -> void:
|
||||
label = _case + "-" + _observation_pass + "-" + label
|
||||
var facility: FacilityInstance = _runtime.facility_service.get_instance(_facility_id)
|
||||
var service: ServiceInstance = _runtime.facility_service.get_service(_service_id)
|
||||
var guest: GuestRuntime = _runtime.guest_service.get_guest(_guest_id)
|
||||
var paid: Array = []
|
||||
for paid_id: StringName in [&"staff_akane", &"staff_bardo"]:
|
||||
var hired: StaffRuntime = _runtime.staff_service.get_staff(paid_id)
|
||||
if hired != null: paid.append(hired.create_snapshot())
|
||||
var reception: FacilityInstance = _runtime.facility_service.get_instance(_paid_reception_id)
|
||||
_observations.append({&"label": label + "-paid-actors", &"minute": _clock.absolute_minute, &"paid_staff": paid, &"paid_reception": reception.create_snapshot() if reception != null else {}, &"expected_catalog_wages": _expected_daily_wages})
|
||||
var workers: Array = []
|
||||
for id: StringName in _staff_ids:
|
||||
var worker: StaffRuntime = _runtime.staff_service.get_staff(id)
|
||||
var shift: ShiftInstance = _runtime.staff_service.get_shift(worker.shift_id)
|
||||
workers.append({&"staff": worker.create_snapshot(), &"shift": shift.create_snapshot() if shift != null else {}, &"is_present": _runtime.staff_commute.is_present(worker, facility), &"requires_attendance": _runtime.staff_service.requires_attendance(id), &"at_facility": StaffCommuteService.is_at_facility(worker, facility), &"within_shift": shift.contains_minute(_clock.absolute_minute) if shift != null else false})
|
||||
_observations.append({&"label": label, &"minute": _clock.absolute_minute, &"workers": workers, &"facility_state": facility.state, &"power_available": facility.power_available, &"active_staff": facility.active_staff_ids.duplicate(), &"service": service.create_snapshot() if service != null else {}, &"request_present": _runtime.facility_service.get_request(_request_id) != null, &"wallet": _runtime.economy_service.get_balance(StringName("GUEST_WALLET:" + String(_guest_id))), &"available_meals": _runtime.inventory_service.available_quantity(facility.local_inventory_id, &"ingredient/basic_meal"), &"reservations": _runtime.inventory_service.create_snapshot(), &"guest": guest.create_snapshot() if guest != null else {}, &"receipts": _receipts.duplicate(true), &"day_summary": _runtime.operations_coordinator.get_day_summary(1), &"operations": _runtime.operations_coordinator.create_snapshot()})
|
||||
_write_var(_observation_pass + "-" + label.trim_prefix(_case + "-" + _observation_pass + "-"), _runtime.create_snapshot())
|
||||
|
||||
func _write_var(label: String, value: Variant) -> void:
|
||||
var file := FileAccess.open(_evidence_dir.path_join(_case + "-" + label + ".var"), FileAccess.WRITE)
|
||||
_expect(file != null, "write whole typed evidence " + label)
|
||||
if file != null: file.store_buffer(var_to_bytes(value)); file.flush(); file.close()
|
||||
var json_file := FileAccess.open(_evidence_dir.path_join(_case + "-" + label + ".json"), FileAccess.WRITE)
|
||||
_expect(json_file != null, "write JSON mirror alongside authoritative typed snapshot")
|
||||
if json_file != null: json_file.store_string(JSON.stringify(value, "\t")); json_file.flush(); json_file.close()
|
||||
|
||||
@@ -0,0 +1,282 @@
|
||||
#!/usr/bin/env python3
|
||||
"""S2a-only native entry. --check-only never loads ownership helpers or starts Godot."""
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
BUILD = Path("/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build")
|
||||
PRODUCT = Path("/Users/korizatou114/Projects/とりにく工房ゲーム制作部")
|
||||
CACHE_RUN = Path("/Users/korizatou114/Projects/TheTree-managed-worktrees/p18-01-model/.runtime/P18-01-CODE/build/p18-relationship-evidence")
|
||||
TEST = "tests/s3/test_runtime_staff_assignment.gd"
|
||||
QA = HERE / "p04_s2a_midnight_probe.gd"
|
||||
ENGINE = Path("/Applications/Godot.app/Contents/MacOS/Godot")
|
||||
PATTERNS = BUILD / "audit-support/p04-whole-closure-r01/current-patterns"
|
||||
SUPPLIED = {
|
||||
"paid-shift-midnight-formal-r01.gd": (7667, "4c56ff05b1bcf8775bb526a30ed021a4a2630ea4f360a5075f880806b052e6d1"),
|
||||
"p04-paid-shift-midnight-native-r01.py": (6580, "23bf2e241c7828b6533aa8107b3d33e962d5c53300a71b198e6fe371e0d4f4f0"),
|
||||
"p04-paid-shift-midnight-formal-native-r01.py": (7765, "e3b6eab32dd4fe3dfbff7ae9b67b34dd674236018bdb6bc7c64da25898ff5ed6"),
|
||||
}
|
||||
MATERIALS = [PATTERNS / n for n in [
|
||||
"paid-shift-midnight-r01.gd", "paid-shift-midnight-formal-r01.gd",
|
||||
"idempotency-paid-formal-r01.gd", "p04-paid-shift-midnight-native-r01.py",
|
||||
"p04-paid-shift-midnight-formal-native-r01.py", "p04-idempotency-paid-formal-native-r01.py",
|
||||
"SUPPLEMENT_SOURCE_MANIFEST_R02.json", "ROOT_S1_NATIVE_R02_RESULT.json",
|
||||
]] + [HERE.parent / "s1" / n for n in ["run_s1_native.py", "p04_s1_ev_power_probe.gd", "S1_ROOT_RUN.md"]]
|
||||
|
||||
def template_guard():
|
||||
"""Read literal setup from supplied template, including original public helpers."""
|
||||
for name, (size, digest) in SUPPLIED.items():
|
||||
actual = pin(PATTERNS / name)
|
||||
if (actual["bytes"], actual["sha256"]) != (size, digest):
|
||||
raise ValueError("supplied input mismatch: " + json.dumps(actual))
|
||||
supplement = json.loads((PATTERNS / "SUPPLEMENT_SOURCE_MANIFEST_R02.json").read_text())
|
||||
for name in SUPPLIED:
|
||||
actual = pin(PATTERNS / name)
|
||||
if not any(Path(row["supplied"]) == PATTERNS / name and
|
||||
(row["bytes"], row["sha256"]) == (actual["bytes"], actual["sha256"])
|
||||
for row in supplement["files"]):
|
||||
raise ValueError("supplement pin mismatch: " + name)
|
||||
original = (PATTERNS / "paid-shift-midnight-r01.gd").read_text()
|
||||
probe = QA.read_text()
|
||||
setup = original[original.index("func _run_midnight()"):original.index('\t_expect(_cmd(&"set_facility_policy", {&"facility_id": kitchen.instance_id')]
|
||||
setup = setup.replace("func _run_midnight() -> void:", "func _prepare_midnight() -> void:")
|
||||
hire = original[original.index("func _setup_paid_reception()"):original.index("func _reopen_dependency")]
|
||||
if setup not in probe or hire not in probe:
|
||||
raise ValueError("probe must retain literal original setup and public paid hire")
|
||||
|
||||
def module(name, path):
|
||||
spec = importlib.util.spec_from_file_location(name, path)
|
||||
value = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(value)
|
||||
return value
|
||||
|
||||
def pin(path):
|
||||
path = Path(path)
|
||||
if path.is_symlink() or not path.is_file():
|
||||
raise ValueError("missing exact input: " + str(path))
|
||||
data = path.read_bytes()
|
||||
return dict(path=str(path), bytes=len(data), sha256=hashlib.sha256(data).hexdigest())
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--project-root", type=Path, default=PRODUCT)
|
||||
parser.add_argument("--output", type=Path, default=BUILD / "p04-s2a-native-r02")
|
||||
parser.add_argument("--plan", type=Path, default=BUILD / "p04-s2a-plan-r02.json")
|
||||
parser.add_argument("--cache-run", type=Path, default=CACHE_RUN)
|
||||
parser.add_argument("--godot", type=Path, default=ENGINE)
|
||||
parser.add_argument("--check-only", action="store_true")
|
||||
args = parser.parse_args()
|
||||
root = args.project_root.resolve()
|
||||
out = args.output.resolve()
|
||||
r = module("s2a_standard_runner", root / "tools/run_all_tests.py")
|
||||
# Preserve the reviewed source_refs -> exactly one QA override order.
|
||||
source_before = r.source_refs(root)
|
||||
refs = dict(source_before)
|
||||
refs[TEST] = pin(QA)
|
||||
template_guard()
|
||||
expected = {n: dict(bytes=p["bytes"], sha256=p["sha256"]) for n, p in refs.items()}
|
||||
fixed = [pin(p) for p in MATERIALS + [
|
||||
QA, Path(__file__), HERE / "S2_ROOT_RUN.md",
|
||||
root / "tests/transport/test_runtime_transport.gd",
|
||||
root / "tests/staff/test_rescue_elevator.gd",
|
||||
root / "docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json",
|
||||
]]
|
||||
selected = dict(root_before=source_before, map={n: p["sha256"] for n, p in refs.items()}, timeout=1200)
|
||||
cache_note = None
|
||||
cache_receipts = []
|
||||
for leaf in ["runner-ended.json", "runner-input.json"]:
|
||||
path = args.cache_run / leaf
|
||||
try:
|
||||
cache_receipts.append(pin(path))
|
||||
except OSError as caught:
|
||||
cache_note = dict(path=str(path), error=type(caught).__name__ + ": " + str(caught),
|
||||
required_before_native=True)
|
||||
break
|
||||
plan = dict(protocol="p04-s2a-preparation-v1", project_root=str(root), output=str(out),
|
||||
source_before=source_before, selected_source_map=selected["map"],
|
||||
QA_overrides=[TEST], fixed_input_pins=fixed, cache_run=str(args.cache_run),
|
||||
cache_receipts=cache_receipts, cache_access=cache_note,
|
||||
phases=["import", "parse", "writer", "reader"],
|
||||
native_engine_launches=0, whole_goal_accepted=False, unit_accepted=False)
|
||||
# Check-only only reads source/materials and writes the plan in task build.
|
||||
# It deliberately does not read default-player data, inspect processes or stat Godot.
|
||||
if args.check_only:
|
||||
args.plan.parent.mkdir(parents=True, exist_ok=True)
|
||||
if args.plan.exists():
|
||||
r.require(r.read(args.plan) == plan, "existing plan differs; use a fresh plan path")
|
||||
else:
|
||||
r.write(args.plan, plan)
|
||||
print(json.dumps(dict(check_only=True, engines=0, source_files=len(source_before),
|
||||
QA_overrides=[TEST], fixed_pins=len(fixed), plan=str(args.plan),
|
||||
plan_sha256=r.sha(args.plan), cache_access=cache_note), ensure_ascii=False))
|
||||
return 0
|
||||
r.require(args.plan.is_file() and r.read(args.plan) == plan, "run exact check-only plan first")
|
||||
def fixed_guard():
|
||||
template_guard()
|
||||
for value in fixed + cache_receipts:
|
||||
r.require(pin(value["path"]) == value, "fixed input changed: " + value["path"])
|
||||
fixed_guard()
|
||||
# Native admission is Root-only and fails closed on unreadable cache.
|
||||
r.require(cache_note is None, "Root must read exact cache receipts: " + str(cache_note))
|
||||
old = args.cache_run
|
||||
r.require(r.read(old / "runner-ended.json")["passed"], "ended cache run must have passed")
|
||||
old_map = r.read(old / "runner-input.json")["selected_source_map"]
|
||||
current_map = {n: p["sha256"] for n, p in source_before.items()}
|
||||
r.require(old_map == current_map, "ended cache selected source must equal current product")
|
||||
protected = r.default_player(root)
|
||||
outer = module("s2a_owned_outer", r.SUPPORT / "owned_outer.py")
|
||||
r.require(not outer.native.helper["live_engines"](), "another Godot owns CPU")
|
||||
engine = args.godot.resolve()
|
||||
r.require(engine.is_file(), "Root native Godot path")
|
||||
r.require(shutil.disk_usage(out.parent).free >= 1024**3 + engine.stat().st_size + 136*1024**2,
|
||||
"reviewed native disk admission")
|
||||
r.require(not out.exists(), "fresh output required; preserve previous attempts")
|
||||
out.mkdir()
|
||||
stage = out / "stage"
|
||||
stage.mkdir()
|
||||
(out / "starters").mkdir()
|
||||
(out / "bootstrap").mkdir()
|
||||
phases = []
|
||||
previous = {}
|
||||
error = None
|
||||
guards = []
|
||||
cleanup = []
|
||||
cleanup_errors = []
|
||||
cache_before = None
|
||||
formal_before = None
|
||||
formal_after = None
|
||||
original_phase = outer.run_phase
|
||||
def run_phase(argv, folder, timeout, env, **kwargs):
|
||||
env = dict(env)
|
||||
if folder.name in ["writer", "reader"]:
|
||||
env.update(THE_TREE_S2A_EVIDENCE=str(out / "raw"), THE_TREE_S2A_MODE=folder.name)
|
||||
return original_phase(argv, folder, timeout, env, **kwargs)
|
||||
outer.run_phase = run_phase
|
||||
r.write(out / "runner-started.json", dict(started=r.now(), owner_runner_pid=os.getpid(),
|
||||
plan=pin(args.plan), engines_launched=0))
|
||||
try:
|
||||
r.write(out / "source_before.json", source_before)
|
||||
r.write(out / "selected-source-map.json", refs)
|
||||
# Private source stage first, then literal reviewed bootstrap.
|
||||
for name, value in refs.items():
|
||||
target = stage / name
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
r.copy_source(Path(value["path"]), target)
|
||||
r.require(r.inventory(stage, stage=True) == expected, "whole private source")
|
||||
for name, value in r.read(r.SUPPORT / "bootstrap-scripts.json").items():
|
||||
data = value["text"].encode()
|
||||
r.require(hashlib.sha256(data).hexdigest() == value["sha256"], "bootstrap pin")
|
||||
(out / "bootstrap" / name).write_bytes(data)
|
||||
cache = old / "stage/.godot"
|
||||
cache_before = r.inventory(cache)
|
||||
r.require(bool(cache_before), "nonempty ended cache")
|
||||
for name in cache_before:
|
||||
src = cache / name
|
||||
dst = stage / ".godot" / name
|
||||
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
subprocess.run(["/bin/cp", "-c", str(src), str(dst)], check=True)
|
||||
r.require((src.stat().st_dev, src.stat().st_ino) != (dst.stat().st_dev, dst.stat().st_ino),
|
||||
"cache clone is a separate inode")
|
||||
r.require(r.inventory(stage / ".godot") == cache_before, "whole cache clone hashes")
|
||||
r.write(out / "cache-reuse.json", dict(ended_run=str(old), receipts=cache_receipts,
|
||||
whole_cache=cache_before, independent_inode=True,
|
||||
ordinary_import_required=True, ordinary_parse_required=True))
|
||||
paths = ["res://" + n for n in sorted(refs) if n.endswith(".gd")]
|
||||
r.write(out / "parse-paths.json", paths)
|
||||
markers = module("s2a_markers", root / "tools/strict_test_markers.py")
|
||||
contracts = markers.load_contracts(root / "docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json")
|
||||
contracts[TEST] = dict(test=TEST, template="S2a midnight: <checks> checks, <failures> failures", required=True)
|
||||
policy = r.read(r.SUPPORT / "suite-policy.json")
|
||||
r.require(TEST not in policy["suite_policies"], "no normal suite watchdog/policy override")
|
||||
r.write(out / "contracts.json", contracts)
|
||||
r.write(out / "runner-input.json", dict(plan, owner_runner_pid=os.getpid(),
|
||||
wrapper_sha256=r.sha(__file__), QA=pin(QA), runner_sha256=r.sha(root / "tools/run_all_tests.py"),
|
||||
default_player_before=protected, fixed_input_pins=fixed,
|
||||
functional_watchdog_seconds=1200, source_adoption=False))
|
||||
diagnostics = module("s2a_diagnostics", r.SUPPORT / "diagnostic_gate.py")
|
||||
owners = module("s2a_owners", r.SUPPORT / "ordinary_proof_gate.py")
|
||||
audio = module("s2a_audio", r.SUPPORT / "audio_evidence_gate.py")
|
||||
public = (stage / "project.godot").read_bytes()
|
||||
for phase, test in [("import", None), ("parse", None), ("writer", TEST), ("reader", TEST)]:
|
||||
if phase == "reader":
|
||||
writer = r.read(out / "raw/writer.json")
|
||||
formal_before = {n: r.inventory(Path(p)) for n, p in writer["slots"].items()}
|
||||
r.require(len(formal_before) == 1, "one C3 formal writer point")
|
||||
row = r.execute_phase(str(engine), stage, out, phase, test, selected, expected,
|
||||
previous, policy, contracts, paths, public, protected, root,
|
||||
outer, markers, diagnostics, owners, audio)
|
||||
phases.append(row)
|
||||
# execute_phase uses the same test log name; retain each phase's exact log too.
|
||||
if test and row.get("log"):
|
||||
(out / (phase + "-combined-raw.log")).write_bytes((out / row["log"]).read_bytes())
|
||||
row["strict_runner_log_path"] = row["log"]
|
||||
row["log"] = phase + "-combined-raw.log"
|
||||
r.write(out / (phase + "-actual-phase.json"), row)
|
||||
print(json.dumps(dict(phase=phase, passed=row["passed"], error=row.get("validation_error"))), flush=True)
|
||||
r.require(row["passed"], "first strict phase failure: " + phase)
|
||||
if phase in ["writer", "reader"]:
|
||||
receipt = r.read(out / ("raw/" + phase + ".json"))
|
||||
r.require(receipt["checks"] > 0 and receipt["failures"] == 0,
|
||||
"S2a actual outcome receipt")
|
||||
r.require(sorted(receipt["branches"]) == (["C1", "C2", "C3"] if phase == "writer" else ["C3"]),
|
||||
"exact independent Writer cases and formal Reader case")
|
||||
r.require(receipt["pid"] == row["actual_pid"], "actual native PID/receipt")
|
||||
writer = r.read(out / "raw/writer.json")
|
||||
reader = r.read(out / "raw/reader.json")
|
||||
r.require(writer["pid"] != reader["pid"] and writer["profile"] != reader["profile"], "distinct formal processes")
|
||||
formal_after = {n: r.inventory(Path(p)) for n, p in writer["slots"].items()}
|
||||
r.require(formal_before == formal_after, "Reader leaves Writer packages unchanged")
|
||||
except BaseException as caught:
|
||||
error = type(caught).__name__ + ": " + str(caught)
|
||||
finally:
|
||||
cleanup, cleanup_errors = outer.finish_captured(outer.OWNED)
|
||||
try:
|
||||
r.write(out / "outer-owned-finally.json", dict(cleanup=cleanup, errors=cleanup_errors))
|
||||
except BaseException as caught:
|
||||
guards.append("finally receipt: " + type(caught).__name__ + ": " + str(caught))
|
||||
# Fixed guard always follows ownership cleanup, even if setup or an engine failed.
|
||||
try:
|
||||
fixed_guard()
|
||||
after = r.source_refs(root)
|
||||
r.write(out / "source_after.json", after)
|
||||
r.require(after == source_before, "whole product source unchanged")
|
||||
r.require(r.default_player(root) == protected, "default player unchanged")
|
||||
if (stage / "project.godot").exists():
|
||||
r.require(r.inventory(stage, stage=True) == dict(expected, **previous), "whole private source unchanged")
|
||||
if cache_before is not None:
|
||||
r.require(r.inventory(old / "stage/.godot") == cache_before, "original cache unchanged")
|
||||
if formal_before is not None:
|
||||
writer = r.read(out / "raw/writer.json")
|
||||
formal_after = {n: r.inventory(Path(p)) for n, p in writer["slots"].items()}
|
||||
r.require(formal_before == formal_after, "Writer formal packages unchanged after Reader, including failure")
|
||||
except BaseException as caught:
|
||||
guards.append(type(caught).__name__ + ": " + str(caught))
|
||||
pids = [p.pid for p in outer.OWNED]
|
||||
passed = (error is None and not guards and not cleanup_errors and len(phases) == 4
|
||||
and all(p["passed"] for p in phases) and len(pids) == len(set(pids)) == 4
|
||||
and all(p["engine_ended"] for p in cleanup))
|
||||
execution = dict(passed=passed, error=error, guard_errors=guards, phases=phases,
|
||||
actual_captured_PIDs=pids, cleanup=cleanup, cleanup_errors=cleanup_errors,
|
||||
formal_before=formal_before, formal_after=formal_after,
|
||||
whole_goal_accepted=False, unit_accepted=False)
|
||||
r.write(out / "raw-inventory.json", r.inventory(out / "raw"))
|
||||
r.write(out / "PID-set.json", dict(owner_runner_pid=os.getpid(), engine_PIDs=pids))
|
||||
r.write(out / "execution.json", execution)
|
||||
r.write(out / "runner-ended.json", dict(execution, ended=r.now()))
|
||||
print(json.dumps(dict(passed=passed, error=error, guard_errors=guards, output=str(out))))
|
||||
return 0 if passed else 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
raise SystemExit(main())
|
||||
except Exception as caught:
|
||||
print(type(caught).__name__ + ": " + str(caught), file=sys.stderr)
|
||||
raise SystemExit(2)
|
||||
@@ -0,0 +1,110 @@
|
||||
# P04-01 S2b code preparation
|
||||
|
||||
変更は本ディレクトリの `p04_s2b_shift_probe.gd`、`run_s2b_native.py`、本書の3ファイルのみ。
|
||||
S1 r02、S2a native r03(ROOT_S2A_NATIVE_R03_RESULT.json と build/p04-s2a-native-r03/)は保持し、再実行しない。
|
||||
原15条件+EV、7領域、3安全結果、原30/88、部分点なしを維持する。本提出による完成加点は0。
|
||||
原P04全体の達成、D05、独立レビュー、統合は確定しない。
|
||||
|
||||
## 構成と固定入力
|
||||
|
||||
QA overrideは `tests/s3/test_runtime_staff_assignment.gd` の1件だけ。
|
||||
単一GDがWriter内で2回fresh Runtime/Clock/GameStateを生成する。Readerも保存点ごと、replay開始時にfresh生成する。
|
||||
型推論によるVariant受取を避け、GDの変数宣言は明示型。state snapshotはString定義と比較する。
|
||||
|
||||
- night_absent: supplied `approved-shift-current-r01.gd` のseed420410、2 DAY servers、実通勤、元のcafeteria/kitchen fixtureを使用。
|
||||
queue_atだけ `boundary + 2 - base_service_minutes - 1` にし、予定完了を1082へ移す。
|
||||
service duration/priceは定義値、Guest planned_exitは元の `boundary + base_service_minutes` のまま。
|
||||
kitchenの物理配置済FLEX cookは既存診断fixtureの前提であり、自然雇用の証拠と混同しない。
|
||||
- overtime: `tests/supply/test_supply_logistics_bridge.gd:40-79` のseed653322、warehouse/kitchen/staff_door施工、
|
||||
catalogue Gina公開hire/DAY assignment、実通勤、購入8、公開request、実pickupから_loadedを得る。
|
||||
synthetic dispatch rejection、expiry snapshot変更、fault injectionは採用しない。
|
||||
実taskのinterrupt_level==0を確認し、元のtarget cellだけ一時blockして通常clockを進める。
|
||||
|
||||
ROOT_S2B_EXISTING_SOURCE_AUDIT.jsonの8ファイルは毎回bytes/SHAを選択rootと照合する。
|
||||
供給18:00 GD、監査JSON、S1/S2a wrapperと関連文書/GD、本3ファイルをplanでpinする。
|
||||
inherit先hardening/kitchen fixtureを含む選択root全sourceもpinする。
|
||||
旧source checkout1300のcheck-onlyは製品1338の代替証拠にしない。
|
||||
|
||||
## 境界判定
|
||||
|
||||
night_absentでは1079を基準に1080/1081で未完了、wallet/player cash/ledger無変更、同じ非空予約IDとACTIVE予約内容、
|
||||
元requestの所有を検査する。DAYの退出とNIGHT登録0を確認し、1081でWAITING_FOR_STAFFを必須にする。
|
||||
退出予定式と元5分graceも検査する。完了を早めたり、元のduration、price、shift値を変更しない。
|
||||
|
||||
overtimeでは1078→1079にworked+1/overtime+0、1079→1080と1080→1081はworked+0/overtime+1を検査する。
|
||||
各tickの総勤務差分は1。正常勤務分と残業分を両方+1と誤読しない。
|
||||
同じstaff/task/shift/request/cargo所有、8個のcargo、crosslinks、is_overtimeを実オブジェクト/snapshotで検査する。
|
||||
各1079/1080/1081のtick後に公開 `StaffService.advance_one_minute(current_minute)` を1回呼び、
|
||||
実戻り値MINUTE_ALREADY_APPLIEDと全Runtime bytes不変で二重勤務記録なしを確認する。
|
||||
private `_update_shift_states` と `record_duty_minute` は呼ばない。
|
||||
解除後は各tickを保存し、safe unloading→元task/request COMPLETED→OFF_DUTY、8個移送と所有解放を確認する。
|
||||
|
||||
## Formal save / Reader
|
||||
|
||||
両ケースの1079/1080/1081を公開SaveServiceで保存・inspectする(合計6slot)。
|
||||
Writer全Runtime snapshotとinspect snapshotのvar_to_bytes完全一致、保存時のlive Runtime不変を必須とする。
|
||||
Readerは別PID/profileで各slotのjournal.log/manifest.json/thumbnail.png/world.binを全コピーし、
|
||||
原本/コピーの全SHAと、load前後/replay後の不変を検査する。余分なmember/directoryは失敗。
|
||||
公開load/restore後はWriterの全Runtimeをvar_to_bytesで比較する。項目の削除、JSON正規化による一致化はしない。
|
||||
|
||||
Readerはさらに各1079 slotをfresh loadして通常clockの2tickを再現し、1080/1081全Runtimeと勤務差分、
|
||||
同分guardを検査する。FACILITY_COMPLETION前後とAI_DECISIONS後のphase順と全snapshotもWriterに一致させる。
|
||||
overtimeはunblock後のCOMPLETED/OFF_DUTYまで全Runtime一致を追加確認する。
|
||||
実coordinatorの有効性/実nullと、Runtime保存Operationsのenabled/coordinator値を別々に記録する。
|
||||
実coordinator不在時はgetter適用外・raw getter未呼出しを明示し、架空のday summaryや診断結果を記録しない。
|
||||
実coordinatorが存在する場合だけraw Dictionary getterと保存用canonical Operationsを別々に保存する。getter joinだけStringName/Stringとkey順差を明示し、
|
||||
型・値・欠落・配列順を検査する。全Runtime typed条件は緩和しない。
|
||||
|
||||
## Rootの正式実行
|
||||
|
||||
Rootだけが製品1338でfresh plan/nativeを実行する。workerはGodot起動(parse/version含む)、Runner操作、全回帰、git書込みをしない。
|
||||
以下は本提出checkoutからRootが実行する手順。既存output/planを上書きせず、失敗時は最初のphaseで停止し自動再試行しない。
|
||||
|
||||
```sh
|
||||
export BUILD_OUTPUT_DIR='/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build'
|
||||
export PYTHONDONTWRITEBYTECODE=1
|
||||
python3 docs/plans/original-completion-2026-10-02/execution/p04-01/s2b/run_s2b_native.py --check-only --plan "$BUILD_OUTPUT_DIR/p04-s2b-root-plan-r01.json" --output "$BUILD_OUTPUT_DIR/p04-s2b-native-r01"
|
||||
python3 docs/plans/original-completion-2026-10-02/execution/p04-01/s2b/run_s2b_native.py --plan "$BUILD_OUTPUT_DIR/p04-s2b-root-plan-r01.json" --output "$BUILD_OUTPUT_DIR/p04-s2b-native-r01"
|
||||
```
|
||||
|
||||
製品root既定値は `/Users/korizatou114/Projects/とりにく工房ゲーム制作部`。
|
||||
native admissionはこのrootかつsource1338を必須にする。worker checkout planをnativeへ転用しない。
|
||||
check-onlyはsource/material/cache receiptのみ読取り、engine0、default/private profile読取り0。
|
||||
cache receiptの読取拒否はplanに実エラーを残し、nativeは拒否する。Rootは自身の環境で確認する。
|
||||
|
||||
S1/S2aと同じsource_refs→QA1→private source stage→終了済cacheの別inode clone→import→parse→Writer→Reader→
|
||||
owned finally cleanup→全guardを維持する。source/default player/private source/original cache不変、
|
||||
固定pin、実PIDとreceipt、4phase/4owned PIDの終了、正式slot不変は失敗時にも検査する。
|
||||
通常policy、watchdog、strict marker/diagnostic/ownership/audio gateを変更しない。
|
||||
|
||||
## 必須出力と残件
|
||||
|
||||
rawにはseed/input、実command結果(継承fixture分もcommand_completedで採取)、minute、実staff/task/shift/request/service ID、
|
||||
reservation/cargo、全ledger、全Runtime、phase観測、duty差分、同分guard結果、save/load結果をJSONと型保持.varの対で残す。
|
||||
Writerのloaded、1078-1079-duty、各1079/1080/1081とsave、phase、branch、unload、Readerの全restored/replayとSHA表を保持する。
|
||||
正式6slotはWriter/Reader private profileに保持する。
|
||||
source_before/after、selected-source-map、runner-input、cache-reuse、parse-paths、contracts、各phaseログ/receipt、
|
||||
combined-raw.log、PID-set、outer-owned-finally、execution、runner-ended、raw-inventoryも保持する。
|
||||
obsolete 500000-byte capに合わせた省略・圧縮はしない。
|
||||
|
||||
worker検証範囲はpy_compile、wrapper --check-only、GDのVariant推論宣言grepのみ。
|
||||
GDScript parse、製品1338 nativeの条件成立、formal replay一致は未実行であり合格を主張しない。
|
||||
Root native実測と正式独立レビューが残る。これらは予定された後続作業であり、未実装を外部blockedとは報告しない。
|
||||
|
||||
worker実施記録: Python py_compile成功。GD grepは `:=` と禁止された2直接呼出しが0件(rg exit1)。
|
||||
既定製品rootでのcheck-onlyは `tools/run_all_tests.py` 読取りがPermissionErrorとなりexit2。
|
||||
明示worktree rootでのcheck-onlyはexit0、source1300、QA1、固定pin13、engine0。
|
||||
終了済cacheのrunner-ended.jsonもPermissionErrorであり、planのcache_accessへ実path/errorを記録した。
|
||||
private/default profileの読取りは0。製品1338のcheck-only成功・native成功を主張しない。
|
||||
最初のworker planは `build/p04-s2b-worker-check-r01.json`。その後の本書/GD調整を含む最終pin確認には
|
||||
fresh `build/p04-s2b-worker-check-r02.json` を使用し、r01を上書きしない。
|
||||
|
||||
## repair15 nullable診断修正
|
||||
|
||||
`_capture`でOperationsCoordinatorをis_instance_validで分岐し、実nullと正式保存値`coordinator: {}`を区別する。
|
||||
全Runtime snapshotは無加工で保持する。fixtureでOperationsをenableせず、既存の境界・保存・replay判定を維持する。
|
||||
同GD全体を静的確認し、OperationsCoordinatorの直接呼出しはこの分岐内の2件のみ。
|
||||
reservation/request/task/shift、FileAccess、DirAccessには既存のnullガードがある。
|
||||
facility/definition/service/guest/worker、_inventory/_request/_worker/_taskの直接参照は既存fixtureの生成・取得成立が前提である。
|
||||
これらの前提が崩れた場合のnullリスクは残るが、任意機能Operationsの無効化による同種の無条件アクセスは他にない。
|
||||
本修正版のRoot fresh r02 nativeは未実測。workerはcode-ready提出のみで受入・readinessを確定しない。
|
||||
+538
@@ -0,0 +1,538 @@
|
||||
extends "res://tests/s3/test_runtime_actor_hardening.gd"
|
||||
|
||||
const REQUEST: StringName = &"supply.bridge_test"
|
||||
const PORTER: StringName = &"staff_gina"
|
||||
var _evidence_dir: String = ""
|
||||
var _mode: String = ""
|
||||
var _case: String = ""
|
||||
var _seed: int = 0
|
||||
var _serial: int = 0
|
||||
var _slots: Dictionary = {}
|
||||
var _branches: Dictionary = {}
|
||||
var _commands: Array = []
|
||||
var _command_results: Array = []
|
||||
var _phases: Array = []
|
||||
var _facility_id: StringName = &""
|
||||
var _service_id: StringName = &""
|
||||
var _request_id: StringName = &"p04.order.shift.request"
|
||||
var _guest_id: StringName = &"p04.order.shift.guest"
|
||||
var _staff_ids: Array[StringName] = [&"p04.order.server.a", &"p04.order.server.b"]
|
||||
var _source: StringName = &""
|
||||
var _destination: StringName = &""
|
||||
var _loaded: Dictionary = {}
|
||||
var _task_id: StringName = &""
|
||||
var _shift_id: StringName = &""
|
||||
var _target: Vector3i = Vector3i.ZERO
|
||||
|
||||
func _run() -> void:
|
||||
_evidence_dir = OS.get_environment("THE_TREE_S2B_EVIDENCE")
|
||||
_mode = OS.get_environment("THE_TREE_S2B_MODE")
|
||||
if _evidence_dir.is_empty() or _mode not in ["writer", "reader"]:
|
||||
quit(2)
|
||||
return
|
||||
_expect(DirAccess.make_dir_recursive_absolute(_evidence_dir) == OK, "evidence directory")
|
||||
if _mode == "writer":
|
||||
for branch: String in ["night_absent", "overtime"]: _writer_branch(branch)
|
||||
else:
|
||||
_reader()
|
||||
_write("branches", _branches)
|
||||
var receipt: Dictionary = {&"pid": OS.get_process_id(), &"profile": OS.get_user_data_dir(),
|
||||
&"slots": _slots, &"checks": _checks, &"failures": _failures, &"branches": _branches.keys(),
|
||||
&"whole_goal_accepted": false, &"unit_accepted": false}
|
||||
_write(_mode, receipt, true)
|
||||
_cleanup()
|
||||
print("S2b shift: %d checks, %d failures" % [_checks, _failures])
|
||||
quit(1 if _failures else 0)
|
||||
|
||||
func _fresh(seed: int) -> void:
|
||||
_seed = seed
|
||||
_serial = 0
|
||||
_commands.clear()
|
||||
_command_results.clear()
|
||||
_phases.clear()
|
||||
_facility_id = &""
|
||||
_service_id = &""
|
||||
_source = &""
|
||||
_destination = &""
|
||||
_task_id = &""
|
||||
_shift_id = &""
|
||||
_target = Vector3i.ZERO
|
||||
_loaded = {}
|
||||
_reset_runtime(seed)
|
||||
_runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
|
||||
_runtime.command_completed.connect(func(result: CommandResult) -> void:
|
||||
_command_results.append({&"minute": _clock.absolute_minute, &"result": result.to_dictionary()})
|
||||
)
|
||||
|
||||
func _cmd(kind: StringName, payload: Dictionary) -> CommandResult:
|
||||
_serial += 1
|
||||
var result: CommandResult = _runtime.submit_command(kind, payload, &"player", &"", 10, -1, StringName("p04.order.shift.command.%d" % _serial))
|
||||
_commands.append({&"kind": kind, &"input": payload.duplicate(true), &"minute": _clock.absolute_minute, &"result": result.to_dictionary()})
|
||||
return result
|
||||
|
||||
func _command(kind: StringName, payload: Dictionary) -> bool:
|
||||
# Supply fixture retains its original public submit defaults.
|
||||
var result: CommandResult = _runtime.submit_command(kind, payload)
|
||||
_commands.append({&"kind": kind, &"input": payload.duplicate(true), &"minute": _clock.absolute_minute, &"result": result.to_dictionary()})
|
||||
return result.is_success()
|
||||
|
||||
func _prepare_night() -> void:
|
||||
_fresh(420410)
|
||||
var facility: FacilityInstance = _build_cafeteria(true)
|
||||
if facility == null: return
|
||||
_facility_id = facility.instance_id
|
||||
var definition: FacilityDefinition = _runtime.definition_registry.get_facility(facility.definition_id)
|
||||
for id: StringName in _staff_ids:
|
||||
_expect(_cmd(&"register_staff", {&"staff_id": id, &"role_id": &"server", &"options": {&"state": StaffRuntime.State.OFF_DUTY, &"current_cell": Vector3i.ZERO}}).is_success(), "register real Staff actor " + String(id))
|
||||
_expect(_cmd(&"set_staff_work_assignment", {&"staff_id": id, &"template_id": &"DAY", &"facility_id": _facility_id}).is_success(), "original DAY 06:00-18:00 shift " + String(id))
|
||||
# Publicly registered Staff actors walk normally. The inherited fixture's
|
||||
# kitchen cook is explicitly placed at the kitchen and uses its existing FLEX
|
||||
# shift; this is not natural hiring/unlock or a proof of the whole labor flow.
|
||||
var boundary: int = ShiftInstance.DAY_END
|
||||
var queue_at: int = boundary + 2 - definition.base_service_minutes - 1
|
||||
_expect(queue_at > _clock.absolute_minute, "real ticks can reach exact admission minute")
|
||||
_clock.advance_minutes(queue_at - _clock.absolute_minute)
|
||||
_expect(facility.active_staff_ids == _staff_ids, "both daytime actors are physically projected before service")
|
||||
for id: StringName in _staff_ids:
|
||||
var worker: StaffRuntime = _runtime.staff_service.get_staff(id)
|
||||
_expect(_runtime.staff_service.requires_attendance(id) and StaffCommuteService.is_at_facility(worker, facility) and worker.current_cell != Vector3i.ZERO, "actual workplace arrival and attendance plan " + String(id))
|
||||
_expect(_stock(facility.local_inventory_id, &"ingredient/basic_meal", 1, &"p04.order.shift.stock"), "one actual meal to reserve")
|
||||
_expect(_cmd(&"spawn_guest", {&"guest_id": _guest_id, &"guest_type_id": &"traveler", &"gold": definition.base_price * 2, &"options": {&"state": GuestRuntime.State.DECIDING, &"current_cell": Vector3i.ZERO, &"planned_exit_at": boundary + definition.base_service_minutes}}).is_success(), "real public Guest actor and wallet")
|
||||
_expect(_cmd(&"create_service_request", {&"facility_id": _facility_id, &"request_id": _request_id, &"guest_ids": [_guest_id]}).is_success(), "original price and duration service request")
|
||||
_clock.advance_one_minute()
|
||||
var active: Array = _runtime.facility_service.get_active_services(_facility_id)
|
||||
_expect(active.size() == 1, "exactly one actual service starts")
|
||||
if active.size() != 1: return
|
||||
var service: ServiceInstance = active[0]; _service_id = service.service_id
|
||||
_expect(service.duration_minutes == definition.base_service_minutes and service.price_snapshot == definition.base_price, "no price or duration override")
|
||||
_expect(service.started_at + service.duration_minutes == boundary + 2, "unchanged duration with admission shifted two minutes")
|
||||
_clock.advance_minutes(boundary - 1 - _clock.absolute_minute)
|
||||
_expect(service.elapsed_minutes == service.duration_minutes - 3, "three real service minutes remain at 17:59")
|
||||
|
||||
func _prepare_overtime() -> void:
|
||||
_fresh(653322)
|
||||
for row: Array in [[&"warehouse", Vector3i(-8, 0, 1)], [&"kitchen", Vector3i(2, 0, 1)], [&"staff_door", Vector3i(0, 0, 1)]]:
|
||||
_expect(_command(&"build_facility", {&"definition_id": row[0], &"origin": row[1]}), "build %s" % row[0])
|
||||
_clock.advance_minutes(180)
|
||||
for facility: FacilityInstance in _runtime.facility_service.get_instances():
|
||||
if facility.definition_id == &"warehouse": _source = facility.instance_id
|
||||
if facility.definition_id == &"kitchen": _destination = facility.instance_id
|
||||
if _source.is_empty() or _destination.is_empty():
|
||||
_expect(false, "normal construction completes")
|
||||
return
|
||||
_expect(_command(&"hire_staff_candidate", {&"candidate_id": PORTER}), "hire the catalogue porter")
|
||||
_expect(_command(&"set_staff_work_assignment", {&"staff_id": PORTER, &"facility_id": _source, &"template_id": &"DAY"}), "assign porter to a repeating real shift")
|
||||
_expect(_worker().state == StaffRuntime.State.COMMUTING, "porter assignment begins a real warehouse commute")
|
||||
for minute in range(40):
|
||||
if _worker().state == StaffRuntime.State.AVAILABLE: break
|
||||
_clock.advance_minutes(1)
|
||||
_expect(_worker().state == StaffRuntime.State.AVAILABLE and StaffCommuteService.is_at_facility(_worker(), _runtime.facility_service.get_instance(_source)), "porter checks in physically before taking a supply job")
|
||||
_expect(_command(&"purchase_supplies", {&"warehouse_id": _source, &"item_id": SupplyCatalog.RAW_FOOD, &"quantity": 8}), "purchase the source stock through the ledger")
|
||||
_expect(_command(&"create_logistics_request", {&"request_id": REQUEST, &"source_inventory_id": _inventory(_source), &"destination_inventory_id": _inventory(_destination), &"item_requirements": {SupplyCatalog.RAW_FOOD: 8}}), "create the reserved supply request")
|
||||
_expect(_quantity(_inventory(_destination)) == 0, "request creation does not deliver anything")
|
||||
var check_in_cell: Vector3i = _worker().current_cell
|
||||
_clock.advance_minutes(1)
|
||||
_expect(_request().assigned_carrier_id == PORTER and _worker().current_cell.distance_to(check_in_cell) <= 1.0, "dispatcher selects the checked-in employee without teleporting")
|
||||
_wait_for(LogisticsRequest.State.DELIVERING, 40)
|
||||
_expect(_request().state == LogisticsRequest.State.DELIVERING and _worker().current_cell.distance_to(check_in_cell) <= 1.0, "physical pickup starts delivery from the checked-in warehouse access without teleporting")
|
||||
if _request().state != LogisticsRequest.State.DELIVERING:
|
||||
return
|
||||
_expect(_quantity(_inventory(_source)) == 0 and _quantity(_cargo()) == 8 and _quantity(_inventory(_destination)) == 0, "pickup moves all eight units into owned cargo only")
|
||||
_expect(_crosslinks(), "loaded Request, Task, reservations and cargo agree")
|
||||
_loaded = _runtime.create_snapshot()
|
||||
|
||||
_write("loaded", {&"seed": _seed, &"snapshot": _loaded, &"commands": _commands, &"command_results": _command_results})
|
||||
_expect(_task().interrupt_level == 0, "actual loaded task is non-interruptible")
|
||||
_task_id = _task().task_id
|
||||
_shift_id = _worker().shift_id
|
||||
_target = _task().target_cell
|
||||
_runtime.path_service.set_cell_temporary_block(_target, true)
|
||||
_clock.advance_minutes(1078 - _clock.absolute_minute)
|
||||
var before: Dictionary = _capture()
|
||||
_clock.advance_one_minute()
|
||||
var after: Dictionary = _capture()
|
||||
_write("1078-1079-duty", _duty_delta(before, after))
|
||||
_expect(_clock.absolute_minute == 1079, "real loaded branch reaches pre-boundary")
|
||||
|
||||
func _wait_for(state: int, minutes: int) -> void:
|
||||
for index: int in minutes:
|
||||
if _request().state == state: return
|
||||
_clock.advance_minutes(1)
|
||||
|
||||
|
||||
func _inventory(id: StringName) -> StringName:
|
||||
return _runtime.facility_service.get_instance(id).local_inventory_id
|
||||
|
||||
|
||||
func _request() -> LogisticsRequest:
|
||||
return _runtime.logistics_requests[REQUEST]
|
||||
|
||||
|
||||
func _worker() -> StaffRuntime:
|
||||
return _runtime.staff_service.get_staff(PORTER)
|
||||
|
||||
|
||||
func _task() -> StaffTask:
|
||||
return _runtime.staff_service.get_task(_worker().current_task_id)
|
||||
|
||||
|
||||
func _cargo() -> StringName:
|
||||
return StringName("cargo:%s" % PORTER)
|
||||
|
||||
|
||||
func _quantity(inventory_id: StringName) -> int:
|
||||
return _runtime.inventory_service.total_quantity(inventory_id, SupplyCatalog.RAW_FOOD)
|
||||
|
||||
|
||||
func _crosslinks() -> bool:
|
||||
return SupplyLogisticsBridge.validate_crosslinks(_runtime.logistics_requests, _runtime.staff_service, _runtime.inventory_service, _runtime.facility_service)
|
||||
|
||||
func _writer_branch(branch: String) -> void:
|
||||
_case = branch
|
||||
if branch == "night_absent": _prepare_night()
|
||||
else: _prepare_overtime()
|
||||
if _clock.absolute_minute != 1079:
|
||||
_expect(false, "setup must reach exact pre-boundary minute")
|
||||
return
|
||||
_connect_phases()
|
||||
var before: Dictionary = _capture()
|
||||
if branch == "overtime": _guard_same_minute()
|
||||
_save_point("1079", before)
|
||||
var previous: Dictionary = before
|
||||
var points: Dictionary = {"1079": before}
|
||||
for minute: int in [1080, 1081]:
|
||||
_clock.advance_one_minute()
|
||||
var actual: Dictionary = _capture()
|
||||
_expect(actual[&"minute"] == minute, "normal clock advances one boundary tick")
|
||||
if branch == "overtime":
|
||||
_write(str(minute) + "-duty", _duty_delta(previous, actual))
|
||||
_guard_same_minute()
|
||||
else:
|
||||
_assert_night(before, actual)
|
||||
_save_point(str(minute), actual)
|
||||
points[str(minute)] = actual
|
||||
previous = actual
|
||||
if branch == "overtime": points["unload"] = _unload()
|
||||
points["phase_observations"] = _phases.duplicate(true)
|
||||
points["commands"] = _commands.duplicate(true)
|
||||
points["command_results_including_inherited_fixture"] = _command_results.duplicate(true)
|
||||
_branches[branch] = points
|
||||
_write("branch", points)
|
||||
|
||||
func _capture() -> Dictionary:
|
||||
var snapshot: Dictionary = _runtime.create_snapshot()
|
||||
var saved_operations: Dictionary = snapshot[&"systems"][&"runtime"][&"operations"]
|
||||
var coordinator: OperationsCoordinator = _runtime.operations_coordinator
|
||||
var coordinator_present: bool = is_instance_valid(coordinator)
|
||||
var getter_diagnostics: Dictionary = {
|
||||
&"operations_enabled": saved_operations[&"enabled"],
|
||||
&"saved_coordinator": saved_operations[&"coordinator"],
|
||||
&"actual_coordinator_present": coordinator_present,
|
||||
&"actual_coordinator_is_null": coordinator == null,
|
||||
&"getter_applicable": coordinator_present,
|
||||
&"raw_getter_called": false,
|
||||
&"getter_status": "not_applicable: actual coordinator absent; raw getter not called",
|
||||
}
|
||||
if coordinator_present:
|
||||
var raw_day: Dictionary = coordinator.get_day_summary(1)
|
||||
var canonical_operations: Dictionary = coordinator.create_snapshot()
|
||||
var canonical_day: Dictionary = {}
|
||||
var day_count: int = 0
|
||||
for day: Dictionary in canonical_operations["day_summaries"]:
|
||||
if int(day["day"]) == 1:
|
||||
canonical_day = day
|
||||
day_count += 1
|
||||
var getter_differences: Array = []
|
||||
_expect(_getter_join(raw_day, canonical_day, "$", getter_differences) and day_count == (0 if raw_day.is_empty() else 1), "raw getter joins canonical save contract without relaxing Runtime equality")
|
||||
getter_diagnostics.merge({&"raw_getter_called": true, &"getter_status": "executed",
|
||||
&"raw_day_getter": raw_day, &"canonical_operations": canonical_operations,
|
||||
&"canonical_day1": canonical_day, &"getter_differences": getter_differences,
|
||||
&"getter_bytes_equal": var_to_bytes(raw_day) == var_to_bytes(canonical_day)}, true)
|
||||
var ledger: Array = []
|
||||
for entry: LedgerEntry in _runtime.economy_service.get_entries(): ledger.append(entry.to_dictionary())
|
||||
var row: Dictionary = {&"seed": _seed, &"minute": _clock.absolute_minute, &"snapshot": snapshot,
|
||||
&"inputs": {&"case": _case, &"seed": _seed, &"facility_id": _facility_id, &"service_id": _service_id,
|
||||
&"source": _source, &"destination": _destination, &"task_id": _task_id, &"shift_id": _shift_id,
|
||||
&"target": _target, &"request_id": _request_id, &"guest_id": _guest_id,
|
||||
&"boundary": ShiftInstance.DAY_END},
|
||||
&"ledger": ledger,
|
||||
&"commands": _commands.duplicate(true), &"command_results": _command_results.duplicate(true)}
|
||||
row.merge(getter_diagnostics)
|
||||
# Raw getters retain their actual StringName keys and insertion order. They
|
||||
# are diagnostics, never substituted for the entire canonical Runtime save.
|
||||
if _case == "night_absent":
|
||||
row[&"inputs"].merge({&"planned_completion": 1082, &"admission_offset": 2,
|
||||
&"kitchen_premise": "original physically placed FLEX cook; not natural hire"})
|
||||
var facility: FacilityInstance = _runtime.facility_service.get_instance(_facility_id)
|
||||
var service: ServiceInstance = _runtime.facility_service.get_service(_service_id)
|
||||
var guest: GuestRuntime = _runtime.guest_service.get_guest(_guest_id)
|
||||
var workers: Array = []
|
||||
var night_count: int = 0
|
||||
var staff_snapshot: Dictionary = _runtime.staff_service.create_snapshot()
|
||||
for shift: Dictionary in staff_snapshot[&"shifts"]:
|
||||
if shift[&"template_id"] == "NIGHT" and not shift[&"cancelled"]: night_count += 1
|
||||
for id: StringName in _staff_ids:
|
||||
var worker: StaffRuntime = _runtime.staff_service.get_staff(id)
|
||||
workers.append(worker.create_snapshot())
|
||||
var reservations: Array = []
|
||||
for id: StringName in service.inventory_reservations:
|
||||
var reservation: InventoryReservation = _runtime.inventory_service.get_reservation(id)
|
||||
_expect(reservation != null, "original service reservation exists")
|
||||
if reservation != null: reservations.append(reservation.create_snapshot())
|
||||
var request: ServiceRequest = _runtime.facility_service.get_request(_request_id)
|
||||
row.merge({&"facility": facility.create_snapshot(), &"service": service.create_snapshot(),
|
||||
&"guest": guest.create_snapshot(), &"workers": workers, &"night_staff_count": night_count,
|
||||
&"reservation_ids": service.inventory_reservations.duplicate(), &"reservations": reservations,
|
||||
&"request": request.create_snapshot() if request != null else {},
|
||||
&"wallet": _runtime.economy_service.get_balance(StringName("GUEST_WALLET:" + String(_guest_id))),
|
||||
&"player_cash": _runtime.economy_service.get_balance(_runtime.PLAYER_CASH_ACCOUNT)})
|
||||
else:
|
||||
row[&"inputs"].merge({&"supply_request_id": REQUEST, &"porter_id": PORTER, &"quantity": 8,
|
||||
&"staff_premise": "public catalogue Gina hire and real DAY commute"})
|
||||
var worker: StaffRuntime = _worker()
|
||||
var task: StaffTask = _runtime.staff_service.get_task(_task_id)
|
||||
var shift: ShiftInstance = _runtime.staff_service.get_shift(_shift_id)
|
||||
row.merge({&"worker": worker.create_snapshot(), &"task": task.create_snapshot() if task != null else {},
|
||||
&"shift": shift.create_snapshot() if shift != null else {}, &"request": _request().create_snapshot(),
|
||||
&"cargo_id": _cargo(), &"cargo_quantity": _quantity(_cargo()),
|
||||
&"source_quantity": _quantity(_inventory(_source)), &"destination_quantity": _quantity(_inventory(_destination)),
|
||||
&"inventory": _runtime.inventory_service.create_snapshot(), &"crosslinks": _crosslinks(),
|
||||
&"reserved_load": _runtime.logistics_dispatcher.get_reserved_load(PORTER)})
|
||||
return row
|
||||
|
||||
func _assert_night(before: Dictionary, actual: Dictionary) -> void:
|
||||
var service: Dictionary = actual[&"service"]
|
||||
_expect(service[&"state"] != "COMPLETED" and int(service[&"elapsed_minutes"]) < int(service[&"duration_minutes"]), "original service remains unfinished")
|
||||
_expect(actual[&"night_staff_count"] == 0, "no NIGHT shift registered")
|
||||
_expect(actual[&"wallet"] == before[&"wallet"] and actual[&"player_cash"] == before[&"player_cash"], "unfinished service charges neither wallet nor player")
|
||||
_expect(var_to_bytes(actual[&"ledger"]) == var_to_bytes(before[&"ledger"]), "no ledger posting while unfinished at boundary")
|
||||
_expect(var_to_bytes(actual[&"reservation_ids"]) == var_to_bytes(before[&"reservation_ids"]) and not actual[&"reservation_ids"].is_empty(), "same nonempty original reservation IDs")
|
||||
_expect(var_to_bytes(actual[&"reservations"]) == var_to_bytes(before[&"reservations"]), "original active reservations unchanged")
|
||||
_expect(not actual[&"request"].is_empty(), "original request retained")
|
||||
for reservation: Dictionary in actual[&"reservations"]:
|
||||
_expect(reservation[&"state"] == "ACTIVE" and reservation[&"requester_id"] == String(_request_id) and reservation[&"purpose"] == "facility_service", "reservation state and original request ownership")
|
||||
_expect(actual[&"request"][&"reserved_inventory_ids"].has(reservation[&"reservation_id"]), "request still owns service reservation")
|
||||
_expect(actual[&"facility"][&"active_staff_ids"].is_empty(), "DAY staff have left cafeteria")
|
||||
for worker: Dictionary in actual[&"workers"]:
|
||||
_expect(worker[&"state"] == "OFF_DUTY", "DAY worker is off duty")
|
||||
_expect(int(actual[&"guest"][&"planned_exit_at"]) == 1080 + int(service[&"duration_minutes"]) and int(actual[&"guest"][&"planned_exit_at"]) > 1081, "original planned exit formula; not early exit")
|
||||
if actual[&"minute"] == 1081:
|
||||
_expect(service[&"state"] == "WAITING_FOR_STAFF", "night_staff_absent at 1081")
|
||||
_expect(int(service[&"staff_grace_deadline"]) == int(service[&"interrupted_at"]) + FacilityService.STAFF_REPLACEMENT_GRACE_MINUTES, "original five-minute replacement grace")
|
||||
|
||||
func _duty_delta(before: Dictionary, after: Dictionary) -> Dictionary:
|
||||
var worked: int = int(after[&"shift"][&"worked_minutes"]) - int(before[&"shift"][&"worked_minutes"])
|
||||
var overtime: int = int(after[&"shift"][&"overtime_minutes"]) - int(before[&"shift"][&"overtime_minutes"])
|
||||
var minute: int = after[&"minute"]
|
||||
var expected_overtime: int = 1 if minute >= ShiftInstance.DAY_END else 0
|
||||
_expect(worked == 1 - expected_overtime and overtime == expected_overtime, "exactly one duty minute in original normal/overtime bucket")
|
||||
_expect(after[&"worker"][&"is_overtime"] == (minute >= ShiftInstance.DAY_END), "actual overtime flag at each minute")
|
||||
for point: Dictionary in [before, after]:
|
||||
_expect(point[&"task"][&"interrupt_level"] == 0, "actual task interrupt_level zero")
|
||||
_expect(point[&"worker"][&"current_task_id"] == String(_task_id) and point[&"task"][&"assigned_staff_id"] == String(PORTER), "same Staff/Task pair")
|
||||
_expect(point[&"worker"][&"shift_id"] == String(_shift_id) and point[&"shift"][&"staff_id"] == String(PORTER), "same real ShiftInstance")
|
||||
_expect(point[&"worker"][&"carried_inventory_id"] == String(_cargo()) and point[&"cargo_quantity"] == 8 and point[&"destination_quantity"] == 0 and point[&"source_quantity"] == 0, "same eight-unit cargo owner while held")
|
||||
_expect(point[&"request"][&"assigned_carrier_id"] == String(PORTER) and point[&"request"][&"state"] == "DELIVERING" and point[&"crosslinks"], "loaded request crosslinks remain valid")
|
||||
return {&"before": before, &"after": after, &"worked_delta": worked, &"overtime_delta": overtime,
|
||||
&"duty_recorded_once": worked == 1 - expected_overtime and overtime == expected_overtime}
|
||||
|
||||
func _guard_same_minute() -> void:
|
||||
var before: Dictionary = _runtime.create_snapshot()
|
||||
var minute: int = _clock.absolute_minute
|
||||
var result: Dictionary = _runtime.staff_service.advance_one_minute(minute)
|
||||
var after: Dictionary = _runtime.create_snapshot()
|
||||
_expect(not bool(result.get(&"ok", true)) and result.get(&"reason") == &"MINUTE_ALREADY_APPLIED", "public same-minute duplicate guard")
|
||||
_expect(var_to_bytes(before) == var_to_bytes(after), "same-minute guard records no second duty minute; all Runtime fields unchanged")
|
||||
_write(str(minute) + "-guard", {&"minute": minute, &"result": result, &"before": before, &"after": after})
|
||||
|
||||
func _unload() -> Dictionary:
|
||||
_runtime.path_service.set_cell_temporary_block(_target, false)
|
||||
var observations: Array = []
|
||||
for index: int in range(40):
|
||||
if _request().state == LogisticsRequest.State.COMPLETED: break
|
||||
_clock.advance_one_minute()
|
||||
observations.append(_capture())
|
||||
var completed: Dictionary = _capture()
|
||||
_expect(_request().state == LogisticsRequest.State.COMPLETED and completed[&"task"][&"state"] == "COMPLETED", "safe unloading completes original request and task")
|
||||
_expect(_quantity(_inventory(_destination)) == 8 and _quantity(_cargo()) == 0 and _crosslinks(), "safe unloading conserves all eight units")
|
||||
_expect(_worker().current_task_id.is_empty() and _worker().carried_inventory_id.is_empty() and _runtime.logistics_dispatcher.get_reserved_load(PORTER) == 0, "completion releases cargo/task/dispatcher ownership")
|
||||
_clock.advance_one_minute()
|
||||
var off: Dictionary = _capture()
|
||||
_expect(_worker().state == StaffRuntime.State.OFF_DUTY and not _worker().is_overtime, "OFF_DUTY after safe completion")
|
||||
var row: Dictionary = {&"observations": observations, &"completed": completed, &"off_duty": off}
|
||||
_write("unload", row)
|
||||
return row
|
||||
|
||||
func _connect_phases() -> void:
|
||||
_clock.register_tick_handler(_clock.TickPhase.FACILITY_COMPLETION, &"aa.p04.s2b.observe", func(minute: int) -> void:
|
||||
if minute in [1080, 1081]: _phase("before-facility")
|
||||
)
|
||||
_clock.register_tick_handler(_clock.TickPhase.FACILITY_COMPLETION, &"zz.p04.s2b.observe", func(minute: int) -> void:
|
||||
if minute in [1080, 1081]: _phase("after-facility")
|
||||
)
|
||||
_clock.register_tick_handler(_clock.TickPhase.AI_DECISIONS, &"zz.p04.s2b.observe", func(minute: int) -> void:
|
||||
if minute in [1080, 1081]: _phase("after-staff")
|
||||
)
|
||||
|
||||
func _phase(label: String) -> void:
|
||||
var row: Dictionary = {&"label": label, &"minute": _clock.absolute_minute, &"snapshot": _runtime.create_snapshot()}
|
||||
_phases.append(row)
|
||||
_write(str(_clock.absolute_minute) + "-" + label, row)
|
||||
|
||||
func _save_point(label: String, point: Dictionary) -> void:
|
||||
_write(label, point)
|
||||
var path: String = "user://saves/p04_s2b/" + _case + "-" + label
|
||||
var result: Dictionary = SaveService.save_snapshot(path, point[&"snapshot"])
|
||||
var inspected: Dictionary = SaveService.inspect_snapshot(path)
|
||||
_expect(bool(result.get(&"ok", false)) and bool(inspected.get(&"ok", false)) and bool(inspected.get(&"read_only", false)), "public formal save and inspect")
|
||||
_expect(var_to_bytes(inspected.get(&"snapshot", {})) == var_to_bytes(point[&"snapshot"]), "formal save retains every typed Runtime field")
|
||||
_expect(var_to_bytes(_runtime.create_snapshot()) == var_to_bytes(point[&"snapshot"]), "saving does not mutate live Runtime")
|
||||
_slots[_case + "-" + label] = ProjectSettings.globalize_path(path)
|
||||
_write(label + "-save", {&"save_result": result, &"inspect_result": inspected})
|
||||
|
||||
func _write(label: String, value: Variant, global_name: bool = false) -> void:
|
||||
var name: String = label if global_name else _mode + "-" + _case + "-" + label
|
||||
var file: FileAccess = FileAccess.open(_evidence_dir.path_join(name + ".var"), FileAccess.WRITE)
|
||||
_expect(file != null, "typed artifact " + name)
|
||||
if file != null:
|
||||
file.store_buffer(var_to_bytes(value)); file.flush(); file.close()
|
||||
file = FileAccess.open(_evidence_dir.path_join(name + ".json"), FileAccess.WRITE)
|
||||
_expect(file != null, "JSON artifact " + name)
|
||||
if file != null:
|
||||
file.store_string(JSON.stringify(value, "\t")); file.flush(); file.close()
|
||||
|
||||
func _read_point(label: String) -> Dictionary:
|
||||
var file: FileAccess = FileAccess.open(_evidence_dir.path_join("writer-" + _case + "-" + label + ".var"), FileAccess.READ)
|
||||
_expect(file != null, "Writer typed artifact " + label)
|
||||
if file == null: return {}
|
||||
var value: Variant = bytes_to_var(file.get_buffer(file.get_length()))
|
||||
file.close()
|
||||
_expect(value is Dictionary, "typed dictionary artifact")
|
||||
return value if value is Dictionary else {}
|
||||
|
||||
func _restore_inputs(point: Dictionary) -> void:
|
||||
var inputs: Dictionary = point[&"inputs"]
|
||||
_seed = int(inputs[&"seed"])
|
||||
_facility_id = StringName(inputs[&"facility_id"])
|
||||
_service_id = StringName(inputs[&"service_id"])
|
||||
_source = StringName(inputs[&"source"])
|
||||
_destination = StringName(inputs[&"destination"])
|
||||
_task_id = StringName(inputs[&"task_id"])
|
||||
_shift_id = StringName(inputs[&"shift_id"])
|
||||
_target = inputs[&"target"]
|
||||
|
||||
func _reader() -> void:
|
||||
var file: FileAccess = FileAccess.open(_evidence_dir.path_join("writer.json"), FileAccess.READ)
|
||||
_expect(file != null, "Writer receipt")
|
||||
if file == null: return
|
||||
var writer: Dictionary = JSON.parse_string(file.get_as_text())
|
||||
file.close()
|
||||
_expect(int(writer["pid"]) != OS.get_process_id() and String(writer["profile"]) != OS.get_user_data_dir(), "independent Reader PID/profile")
|
||||
for branch: String in ["night_absent", "overtime"]:
|
||||
_case = branch
|
||||
var points: Dictionary = {}
|
||||
var hashes: Dictionary = {}
|
||||
for label: String in ["1079", "1080", "1081"]:
|
||||
var key: String = branch + "-" + label
|
||||
var original: String = writer["slots"][key]
|
||||
var target: String = ProjectSettings.globalize_path("user://saves/p04_s2b/" + key)
|
||||
_expect(DirAccess.make_dir_recursive_absolute(target) == OK, "fresh Reader slot")
|
||||
var directory: DirAccess = DirAccess.open(original)
|
||||
_expect(directory != null, "Writer slot accessible")
|
||||
if directory == null: return
|
||||
var members: PackedStringArray = directory.get_files()
|
||||
members.sort()
|
||||
_expect(directory.get_directories().is_empty() and members == PackedStringArray(["journal.log", "manifest.json", "thumbnail.png", "world.bin"]), "exact four-member formal package")
|
||||
var member_hashes: Dictionary = {}
|
||||
for member: String in members:
|
||||
var sha: String = FileAccess.get_sha256(original.path_join(member))
|
||||
_expect(DirAccess.copy_absolute(original.path_join(member), target.path_join(member)) == OK, "copy formal member")
|
||||
var copied_sha: String = FileAccess.get_sha256(target.path_join(member))
|
||||
_expect(sha == copied_sha and not sha.is_empty(), "original/copy SHA equality")
|
||||
member_hashes[member] = {&"original_before": sha, &"copy_before": copied_sha}
|
||||
hashes[key] = {&"original": original, &"copy": target, &"members": member_hashes}
|
||||
_slots[key] = target
|
||||
var expected: Dictionary = _read_point(label)
|
||||
if expected.is_empty(): return
|
||||
points[label] = expected
|
||||
_fresh(int(expected[&"seed"]))
|
||||
_restore_inputs(expected)
|
||||
var loaded: Dictionary = SaveService.load_snapshot(target)
|
||||
_expect(bool(loaded.get(&"ok", false)), "public formal load")
|
||||
if not bool(loaded.get(&"ok", false)): return
|
||||
_expect(var_to_bytes(loaded[&"snapshot"]) == var_to_bytes(expected[&"snapshot"]), "all typed saved fields match Writer")
|
||||
_expect(_runtime.restore_snapshot(loaded[&"snapshot"]), "public Runtime restore")
|
||||
var actual: Dictionary = _capture()
|
||||
_expect(var_to_bytes(actual[&"snapshot"]) == var_to_bytes(expected[&"snapshot"]), "all typed Runtime fields match Writer after restore")
|
||||
_write(label + "-restored", {&"expected": expected, &"actual": actual, &"load_result": loaded})
|
||||
if branch == "night_absent" and label != "1079": _assert_night(points["1079"], actual)
|
||||
# A separate fresh load of the pre-boundary package drives both real ticks.
|
||||
_fresh(int(points["1079"][&"seed"]))
|
||||
_restore_inputs(points["1079"])
|
||||
var pre: Dictionary = SaveService.load_snapshot(_slots[branch + "-1079"])
|
||||
_expect(bool(pre.get(&"ok", false)), "replay public pre-boundary load")
|
||||
if not bool(pre.get(&"ok", false)): return
|
||||
_expect(_runtime.restore_snapshot(pre[&"snapshot"]), "replay public restore")
|
||||
_expect(var_to_bytes(_runtime.create_snapshot()) == var_to_bytes(points["1079"][&"snapshot"]), "replay starts at exact all-field pre-boundary state")
|
||||
_connect_phases()
|
||||
var previous: Dictionary = _capture()
|
||||
if branch == "overtime": _guard_same_minute()
|
||||
for label: String in ["1080", "1081"]:
|
||||
_clock.advance_one_minute()
|
||||
var actual: Dictionary = _capture()
|
||||
_expect(var_to_bytes(actual[&"snapshot"]) == var_to_bytes(points[label][&"snapshot"]), "two-tick replay entire typed equality " + label)
|
||||
if branch == "overtime":
|
||||
_write(label + "-duty", _duty_delta(previous, actual))
|
||||
_guard_same_minute()
|
||||
else: _assert_night(points["1079"], actual)
|
||||
_write(label + "-replay", actual)
|
||||
previous = actual
|
||||
var writer_branch: Dictionary = _read_point("branch")
|
||||
_expect(var_to_bytes(_phases) == var_to_bytes(writer_branch["phase_observations"]), "same boundary phase order and full typed states")
|
||||
if branch == "overtime":
|
||||
var unload: Dictionary = _unload()
|
||||
for label: String in ["completed", "off_duty"]:
|
||||
_expect(var_to_bytes(unload[label][&"snapshot"]) == var_to_bytes(writer_branch["unload"][label][&"snapshot"]), "safe unloading replay all typed fields " + label)
|
||||
for key: String in hashes:
|
||||
var row: Dictionary = hashes[key]
|
||||
for member: String in row[&"members"]:
|
||||
var member_row: Dictionary = row[&"members"][member]
|
||||
member_row[&"original_after"] = FileAccess.get_sha256(String(row[&"original"]).path_join(member))
|
||||
member_row[&"copy_after"] = FileAccess.get_sha256(String(row[&"copy"]).path_join(member))
|
||||
_expect(member_row[&"original_before"] == member_row[&"original_after"] and member_row[&"copy_before"] == member_row[&"copy_after"], "original and copied members unchanged after loads/replay")
|
||||
_branches[branch] = {&"member_hashes": hashes, &"phase_observations": _phases.duplicate(true)}
|
||||
_write("branch", _branches[branch])
|
||||
|
||||
func _getter_join(raw: Variant, saved: Variant, path: String, differences: Array) -> bool:
|
||||
# Only diagnostic getter-to-save joining permits StringName/String and key
|
||||
# ordering differences. All formal/replay Runtime checks above remain bytes.
|
||||
if typeof(raw) != typeof(saved):
|
||||
var text_pair: bool = typeof(raw) in [TYPE_STRING, TYPE_STRING_NAME] and typeof(saved) in [TYPE_STRING, TYPE_STRING_NAME]
|
||||
differences.append({"path": path, "kind": "StringName_String" if text_pair else "type_mismatch", "raw_type": typeof(raw), "saved_type": typeof(saved)})
|
||||
return text_pair and String(raw) == String(saved)
|
||||
if raw is Dictionary:
|
||||
var matched: Dictionary = {}
|
||||
var ok: bool = raw.size() == saved.size()
|
||||
var raw_order: Array = []
|
||||
var saved_order: Array = []
|
||||
for key: Variant in saved: saved_order.append(String(key))
|
||||
for key: Variant in raw:
|
||||
var name: String = String(key)
|
||||
raw_order.append(name)
|
||||
if matched.has(name) or not saved.has(name):
|
||||
ok = false
|
||||
continue
|
||||
matched[name] = true
|
||||
if typeof(key) != TYPE_STRING:
|
||||
differences.append({"path": path + "/" + name, "kind": "key_StringName_String" if typeof(key) == TYPE_STRING_NAME else "invalid_key_type", "raw_type": typeof(key), "saved_type": TYPE_STRING})
|
||||
ok = typeof(key) == TYPE_STRING_NAME and ok
|
||||
ok = _getter_join(raw[key], saved[name], path + "/" + name, differences) and ok
|
||||
if raw_order != saved_order: differences.append({"path": path, "kind": "key_order", "raw": raw_order, "saved": saved_order})
|
||||
return ok
|
||||
if raw is Array:
|
||||
if raw.size() != saved.size(): return false
|
||||
var ok: bool = true
|
||||
for index: int in range(raw.size()): ok = _getter_join(raw[index], saved[index], path + "/" + str(index), differences) and ok
|
||||
return ok
|
||||
return var_to_bytes(raw) == var_to_bytes(saved)
|
||||
|
||||
@@ -0,0 +1,274 @@
|
||||
#!/usr/bin/env python3
|
||||
"""S2b-only native entry. --check-only never loads ownership helpers or starts Godot."""
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
BUILD = Path("/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build")
|
||||
PRODUCT = Path("/Users/korizatou114/Projects/とりにく工房ゲーム制作部")
|
||||
CACHE_RUN = Path("/Users/korizatou114/Projects/TheTree-managed-worktrees/p18-01-model/.runtime/P18-01-CODE/build/p18-relationship-evidence")
|
||||
TEST = "tests/s3/test_runtime_staff_assignment.gd"
|
||||
QA = HERE / "p04_s2b_shift_probe.gd"
|
||||
ENGINE = Path("/Applications/Godot.app/Contents/MacOS/Godot")
|
||||
PATTERNS = BUILD / "audit-support/p04-whole-closure-r01/current-patterns"
|
||||
AUDIT = BUILD / "audit-support/p04-whole-closure-r01/ROOT_S2B_EXISTING_SOURCE_AUDIT.json"
|
||||
MATERIALS = [PATTERNS / "approved-shift-current-r01.gd", AUDIT] + [
|
||||
HERE.parent / folder / name for folder, name in [
|
||||
("s1", "run_s1_native.py"), ("s1", "S1_ROOT_RUN.md"),
|
||||
("s2", "run_s2a_native.py"), ("s2", "p04_s2a_midnight_probe.gd"),
|
||||
("s2", "S2_ROOT_RUN.md"),
|
||||
]
|
||||
]
|
||||
|
||||
def template_guard(root):
|
||||
audit = json.loads(AUDIT.read_text())
|
||||
for row in audit["source_pins"]:
|
||||
actual = pin(root / row["path"])
|
||||
if (actual["bytes"], actual["sha256"]) != (row["production_bytes"], row["production_sha256"]):
|
||||
raise ValueError("Root source audit mismatch: " + row["path"])
|
||||
probe = QA.read_text()
|
||||
if ":=" in probe:
|
||||
raise ValueError("probe must use explicit types; inferred declaration found")
|
||||
for forbidden in ["._update_shift_states(", ".record_duty_minute("]:
|
||||
if forbidden in probe:
|
||||
raise ValueError("unsupported duty mutation: " + forbidden)
|
||||
|
||||
def module(name, path):
|
||||
spec = importlib.util.spec_from_file_location(name, path)
|
||||
value = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(value)
|
||||
return value
|
||||
|
||||
def pin(path):
|
||||
path = Path(path)
|
||||
if path.is_symlink() or not path.is_file():
|
||||
raise ValueError("missing exact input: " + str(path))
|
||||
data = path.read_bytes()
|
||||
return dict(path=str(path), bytes=len(data), sha256=hashlib.sha256(data).hexdigest())
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--project-root", type=Path, default=PRODUCT)
|
||||
parser.add_argument("--output", type=Path, default=BUILD / "p04-s2b-native-r01")
|
||||
parser.add_argument("--plan", type=Path, default=BUILD / "p04-s2b-plan-r01.json")
|
||||
parser.add_argument("--cache-run", type=Path, default=CACHE_RUN)
|
||||
parser.add_argument("--godot", type=Path, default=ENGINE)
|
||||
parser.add_argument("--check-only", action="store_true")
|
||||
args = parser.parse_args()
|
||||
root = args.project_root.resolve()
|
||||
out = args.output.resolve()
|
||||
if not out.is_relative_to(BUILD.resolve()) or not args.plan.resolve().is_relative_to(BUILD.resolve()):
|
||||
raise ValueError("plan/output must stay in task build")
|
||||
r = module("s2b_standard_runner", root / "tools/run_all_tests.py")
|
||||
# Preserve the reviewed source_refs -> exactly one QA override order.
|
||||
source_before = r.source_refs(root)
|
||||
refs = dict(source_before)
|
||||
refs[TEST] = pin(QA)
|
||||
template_guard(root)
|
||||
expected = {n: dict(bytes=p["bytes"], sha256=p["sha256"]) for n, p in refs.items()}
|
||||
fixed = [pin(p) for p in MATERIALS + [
|
||||
QA, Path(__file__), HERE / "S2B_ROOT_RUN.md",
|
||||
root / "tests/transport/test_runtime_transport.gd",
|
||||
root / "tests/staff/test_rescue_elevator.gd",
|
||||
root / "docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json",
|
||||
]]
|
||||
selected = dict(root_before=source_before, map={n: p["sha256"] for n, p in refs.items()}, timeout=1200)
|
||||
cache_note = None
|
||||
cache_receipts = []
|
||||
for leaf in ["runner-ended.json", "runner-input.json"]:
|
||||
path = args.cache_run / leaf
|
||||
try:
|
||||
cache_receipts.append(pin(path))
|
||||
except OSError as caught:
|
||||
cache_note = dict(path=str(path), error=type(caught).__name__ + ": " + str(caught),
|
||||
required_before_native=True)
|
||||
break
|
||||
plan = dict(protocol="p04-s2b-preparation-v1", project_root=str(root), output=str(out),
|
||||
source_before=source_before, selected_source_map=selected["map"],
|
||||
QA_overrides=[TEST], fixed_input_pins=fixed, cache_run=str(args.cache_run),
|
||||
cache_receipts=cache_receipts, cache_access=cache_note,
|
||||
phases=["import", "parse", "writer", "reader"],
|
||||
native_engine_launches=0, whole_goal_accepted=False, unit_accepted=False)
|
||||
# Check-only only reads source/materials and writes the plan in task build.
|
||||
# It deliberately does not read default-player data, inspect processes or stat Godot.
|
||||
if args.check_only:
|
||||
args.plan.parent.mkdir(parents=True, exist_ok=True)
|
||||
if args.plan.exists():
|
||||
r.require(r.read(args.plan) == plan, "existing plan differs; use a fresh plan path")
|
||||
else:
|
||||
r.write(args.plan, plan)
|
||||
print(json.dumps(dict(check_only=True, engines=0, source_files=len(source_before),
|
||||
QA_overrides=[TEST], fixed_pins=len(fixed), plan=str(args.plan),
|
||||
plan_sha256=r.sha(args.plan), cache_access=cache_note), ensure_ascii=False))
|
||||
return 0
|
||||
r.require(root == PRODUCT.resolve() and len(source_before) == 1338, "Root native requires product 1338; worker plan is not native evidence")
|
||||
r.require(args.plan.is_file() and r.read(args.plan) == plan, "run exact check-only plan first")
|
||||
def fixed_guard():
|
||||
template_guard(root)
|
||||
for value in fixed + cache_receipts:
|
||||
r.require(pin(value["path"]) == value, "fixed input changed: " + value["path"])
|
||||
fixed_guard()
|
||||
# Native admission is Root-only and fails closed on unreadable cache.
|
||||
r.require(cache_note is None, "Root must read exact cache receipts: " + str(cache_note))
|
||||
old = args.cache_run
|
||||
r.require(r.read(old / "runner-ended.json")["passed"], "ended cache run must have passed")
|
||||
old_map = r.read(old / "runner-input.json")["selected_source_map"]
|
||||
current_map = {n: p["sha256"] for n, p in source_before.items()}
|
||||
r.require(old_map == current_map, "ended cache selected source must equal current product")
|
||||
protected = r.default_player(root)
|
||||
outer = module("s2b_owned_outer", r.SUPPORT / "owned_outer.py")
|
||||
r.require(not outer.native.helper["live_engines"](), "another Godot owns CPU")
|
||||
engine = args.godot.resolve()
|
||||
r.require(engine.is_file(), "Root native Godot path")
|
||||
r.require(shutil.disk_usage(out.parent).free >= 1024**3 + engine.stat().st_size + 136*1024**2,
|
||||
"reviewed native disk admission")
|
||||
r.require(not out.exists(), "fresh output required; preserve previous attempts")
|
||||
out.mkdir()
|
||||
stage = out / "stage"
|
||||
stage.mkdir()
|
||||
(out / "starters").mkdir()
|
||||
(out / "bootstrap").mkdir()
|
||||
phases = []
|
||||
previous = {}
|
||||
error = None
|
||||
guards = []
|
||||
cleanup = []
|
||||
cleanup_errors = []
|
||||
cache_before = None
|
||||
formal_before = None
|
||||
formal_after = None
|
||||
original_phase = outer.run_phase
|
||||
def run_phase(argv, folder, timeout, env, **kwargs):
|
||||
env = dict(env)
|
||||
if folder.name in ["writer", "reader"]:
|
||||
env.update(THE_TREE_S2B_EVIDENCE=str(out / "raw"), THE_TREE_S2B_MODE=folder.name)
|
||||
return original_phase(argv, folder, timeout, env, **kwargs)
|
||||
outer.run_phase = run_phase
|
||||
r.write(out / "runner-started.json", dict(started=r.now(), owner_runner_pid=os.getpid(),
|
||||
plan=pin(args.plan), engines_launched=0))
|
||||
try:
|
||||
r.write(out / "source_before.json", source_before)
|
||||
r.write(out / "selected-source-map.json", refs)
|
||||
# Private source stage first, then literal reviewed bootstrap.
|
||||
for name, value in refs.items():
|
||||
target = stage / name
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
r.copy_source(Path(value["path"]), target)
|
||||
r.require(r.inventory(stage, stage=True) == expected, "whole private source")
|
||||
for name, value in r.read(r.SUPPORT / "bootstrap-scripts.json").items():
|
||||
data = value["text"].encode()
|
||||
r.require(hashlib.sha256(data).hexdigest() == value["sha256"], "bootstrap pin")
|
||||
(out / "bootstrap" / name).write_bytes(data)
|
||||
cache = old / "stage/.godot"
|
||||
cache_before = r.inventory(cache)
|
||||
r.require(bool(cache_before), "nonempty ended cache")
|
||||
for name in cache_before:
|
||||
src = cache / name
|
||||
dst = stage / ".godot" / name
|
||||
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
subprocess.run(["/bin/cp", "-c", str(src), str(dst)], check=True)
|
||||
r.require((src.stat().st_dev, src.stat().st_ino) != (dst.stat().st_dev, dst.stat().st_ino),
|
||||
"cache clone is a separate inode")
|
||||
r.require(r.inventory(stage / ".godot") == cache_before, "whole cache clone hashes")
|
||||
r.write(out / "cache-reuse.json", dict(ended_run=str(old), receipts=cache_receipts,
|
||||
whole_cache=cache_before, independent_inode=True,
|
||||
ordinary_import_required=True, ordinary_parse_required=True))
|
||||
paths = ["res://" + n for n in sorted(refs) if n.endswith(".gd")]
|
||||
r.write(out / "parse-paths.json", paths)
|
||||
markers = module("s2b_markers", root / "tools/strict_test_markers.py")
|
||||
contracts = markers.load_contracts(root / "docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json")
|
||||
contracts[TEST] = dict(test=TEST, template="S2b shift: <checks> checks, <failures> failures", required=True)
|
||||
policy = r.read(r.SUPPORT / "suite-policy.json")
|
||||
r.require(TEST not in policy["suite_policies"], "no normal suite watchdog/policy override")
|
||||
r.write(out / "contracts.json", contracts)
|
||||
r.write(out / "runner-input.json", dict(plan, owner_runner_pid=os.getpid(),
|
||||
wrapper_sha256=r.sha(__file__), QA=pin(QA), runner_sha256=r.sha(root / "tools/run_all_tests.py"),
|
||||
default_player_before=protected, fixed_input_pins=fixed,
|
||||
functional_watchdog_seconds=1200, source_adoption=False))
|
||||
diagnostics = module("s2b_diagnostics", r.SUPPORT / "diagnostic_gate.py")
|
||||
owners = module("s2b_owners", r.SUPPORT / "ordinary_proof_gate.py")
|
||||
audio = module("s2b_audio", r.SUPPORT / "audio_evidence_gate.py")
|
||||
public = (stage / "project.godot").read_bytes()
|
||||
for phase, test in [("import", None), ("parse", None), ("writer", TEST), ("reader", TEST)]:
|
||||
if phase == "reader":
|
||||
writer = r.read(out / "raw/writer.json")
|
||||
formal_before = {n: r.inventory(Path(p)) for n, p in writer["slots"].items()}
|
||||
r.require(len(formal_before) == 6, "three formal points for each of two fresh branches")
|
||||
row = r.execute_phase(str(engine), stage, out, phase, test, selected, expected,
|
||||
previous, policy, contracts, paths, public, protected, root,
|
||||
outer, markers, diagnostics, owners, audio)
|
||||
phases.append(row)
|
||||
# execute_phase uses the same test log name; retain each phase's exact log too.
|
||||
if test and row.get("log"):
|
||||
(out / (phase + "-combined-raw.log")).write_bytes((out / row["log"]).read_bytes())
|
||||
row["strict_runner_log_path"] = row["log"]
|
||||
row["log"] = phase + "-combined-raw.log"
|
||||
r.write(out / (phase + "-actual-phase.json"), row)
|
||||
print(json.dumps(dict(phase=phase, passed=row["passed"], error=row.get("validation_error"))), flush=True)
|
||||
r.require(row["passed"], "first strict phase failure: " + phase)
|
||||
if phase in ["writer", "reader"]:
|
||||
receipt = r.read(out / ("raw/" + phase + ".json"))
|
||||
r.require(receipt["checks"] > 0 and receipt["failures"] == 0,
|
||||
"S2b actual outcome receipt")
|
||||
r.require(sorted(receipt["branches"]) == ["night_absent", "overtime"],
|
||||
"exact independent Writer cases and formal Reader case")
|
||||
r.require(receipt["pid"] == row["actual_pid"], "actual native PID/receipt")
|
||||
writer = r.read(out / "raw/writer.json")
|
||||
reader = r.read(out / "raw/reader.json")
|
||||
r.require(writer["pid"] != reader["pid"] and writer["profile"] != reader["profile"], "distinct formal processes")
|
||||
formal_after = {n: r.inventory(Path(p)) for n, p in writer["slots"].items()}
|
||||
r.require(formal_before == formal_after, "Reader leaves Writer packages unchanged")
|
||||
except BaseException as caught:
|
||||
error = type(caught).__name__ + ": " + str(caught)
|
||||
finally:
|
||||
cleanup, cleanup_errors = outer.finish_captured(outer.OWNED)
|
||||
try:
|
||||
r.write(out / "outer-owned-finally.json", dict(cleanup=cleanup, errors=cleanup_errors))
|
||||
except BaseException as caught:
|
||||
guards.append("finally receipt: " + type(caught).__name__ + ": " + str(caught))
|
||||
# Fixed guard always follows ownership cleanup, even if setup or an engine failed.
|
||||
try:
|
||||
fixed_guard()
|
||||
after = r.source_refs(root)
|
||||
r.write(out / "source_after.json", after)
|
||||
r.require(after == source_before, "whole product source unchanged")
|
||||
r.require(r.default_player(root) == protected, "default player unchanged")
|
||||
if (stage / "project.godot").exists():
|
||||
r.require(r.inventory(stage, stage=True) == dict(expected, **previous), "whole private source unchanged")
|
||||
if cache_before is not None:
|
||||
r.require(r.inventory(old / "stage/.godot") == cache_before, "original cache unchanged")
|
||||
if formal_before is not None:
|
||||
writer = r.read(out / "raw/writer.json")
|
||||
formal_after = {n: r.inventory(Path(p)) for n, p in writer["slots"].items()}
|
||||
r.require(formal_before == formal_after, "Writer formal packages unchanged after Reader, including failure")
|
||||
except BaseException as caught:
|
||||
guards.append(type(caught).__name__ + ": " + str(caught))
|
||||
pids = [p.pid for p in outer.OWNED]
|
||||
passed = (error is None and not guards and not cleanup_errors and len(phases) == 4
|
||||
and all(p["passed"] for p in phases) and len(pids) == len(set(pids)) == 4
|
||||
and all(p["engine_ended"] for p in cleanup))
|
||||
execution = dict(passed=passed, error=error, guard_errors=guards, phases=phases,
|
||||
actual_captured_PIDs=pids, cleanup=cleanup, cleanup_errors=cleanup_errors,
|
||||
formal_before=formal_before, formal_after=formal_after,
|
||||
whole_goal_accepted=False, unit_accepted=False)
|
||||
r.write(out / "raw-inventory.json", r.inventory(out / "raw"))
|
||||
r.write(out / "PID-set.json", dict(owner_runner_pid=os.getpid(), engine_PIDs=pids))
|
||||
r.write(out / "execution.json", execution)
|
||||
r.write(out / "runner-ended.json", dict(execution, ended=r.now()))
|
||||
print(json.dumps(dict(passed=passed, error=error, guard_errors=guards, output=str(out))))
|
||||
return 0 if passed else 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
raise SystemExit(main())
|
||||
except Exception as caught:
|
||||
print(type(caught).__name__ + ": " + str(caught), file=sys.stderr)
|
||||
raise SystemExit(2)
|
||||
@@ -0,0 +1,95 @@
|
||||
# P04-01 S3 code-ready preparation
|
||||
|
||||
変更は本ディレクトリのGD、Python wrapper、本書の3本のみ。製品修正0。
|
||||
本提出は原P04-01の残る5条件の検証コード準備であり、原成果の加点は0。
|
||||
原7領域、3安全結果、15条件+EV、30/88、部分点0を維持する。
|
||||
S1r02、S2ar03、S2b native r02は保持し再実行しない。
|
||||
|
||||
## 固定入力と6実ケース
|
||||
|
||||
最初に供給SOURCE_MANIFEST.jsonの7ファイルについて全byte SHA256とbyte数の一致を確認した。
|
||||
manifest自体のSHAは `d4993398f7619ca37bcd64c32b79bdee892d44e31f332cc0dd5f31fb7bc28314`。
|
||||
wrapperは同manifest、7供給ファイル、Rootの現行source audit、S2bの3ファイル、本3ファイルをpinする。
|
||||
選択rootの全sourceもsource_refsでpinし、その後にQAを1件だけ明示overrideする。
|
||||
override先はS2bと同じ `tests/s3/test_runtime_staff_assignment.gd`。
|
||||
これはprivate stageだけのoverrideで、継承先ActorHardeningを置き換えない。
|
||||
|
||||
| case | seed | 境界と検証対象 |
|
||||
| --- | --- | --- |
|
||||
| B1 | 420410 | 959に開始済Aと実queue B。960に有限mana枯渇と対象への直接休業。960/961はAの進捗・同じ予約を保持し課金0、Bはrelease・在庫返却・忍耐不消費。元reserveへ公開復旧し、休業のまま962にAだけ1回課金。963で重複なし。 |
|
||||
| B2a | 420430 | 原定義のduration/priceで960完了・課金後に通常退出、961 Despawnで二重課金なし。 |
|
||||
| B2b | 420431 | 959の実queueを960の公開leaveでadmission前にrelease。961通常退出、962 Despawn。課金0。 |
|
||||
| B3 | 420435 | 元traveler.base_patience、実サーバー不足queue。template同様にfixture時刻+base_patienceを境界とし、通常tickで枯渇。ABANDONING→DECIDING→LEAVING→Despawn、返却1回・課金0。planned_exitは元templateの境界値。 |
|
||||
| B4a | 420420 | 元diagnostic automatic DEFENSE Event、foreground=false、durability=0。960の完了・1回課金が後段Event破壊に先行。961で重複なし。 |
|
||||
| B4b | 420421 | 元durationの入場offset1。960は進捗後にEvent破壊、961でFAILED・release・返却1回、課金0。 |
|
||||
|
||||
7件目は作らない。各caseはfresh Runtime/Clock/GameState、厨房は現行ActorHardeningと
|
||||
required_kitchen_setupの物理配置FLEX fixture。自然hire/unlockの証拠ではない。
|
||||
サービス価格・時間は定義取得、capacity/staff_min変更なし、忍耐・elapsedの直接変更なし。
|
||||
旧1299 wrapper、未承認18:00 predicate、本番Operations enableは使用しない。
|
||||
|
||||
D-034(供給DESIGN_DECISIONS.md 252–262行、特に258行)の直接休業では開始済を継続する。
|
||||
関連厨房不稼働によるpauseとは区別する。interruption_reasonは実snapshotのまま残し、
|
||||
RELATED_FACILITY_INACTIVEをNO_POWERへ置換しない。
|
||||
enum snapshotは各create_snapshotのString定義に従い、stateをintへ変換しない。
|
||||
|
||||
## Rawと正式保存
|
||||
|
||||
Writerの各before/after/next、B1 recovered/duplicate_guard、B2b/B3 despawnを正式保存する(計22slot)。
|
||||
各点の全Runtime typed snapshot、入力・seed・serial・実ID・Guest/wallet/planned_exit、
|
||||
service/request/reservation、在庫、ledger、Event instanceを無加工の.varとJSONで一度だけ保存する。
|
||||
各tickの全11phase開始点も同様に保存し、phase順と境界内部の状態を残す。
|
||||
case/indexはrawのpath/SHAを参照し、巨大snapshotのaggregate多重コピーを作らない。
|
||||
全体rawをobsolete 500000-byte capに合わせて省略・圧縮しない。
|
||||
public command入力と結果、継承fixture分を含むcommand_completed結果をcase別rawに保存する。
|
||||
fixture自身の公開command入力・IDは全Runtime command queue履歴にも残る。
|
||||
Operationsは正式snapshotのdisabled/coordinator={}と実coordinator不在を別々に記録し、無関係なgetterを呼ばない。
|
||||
|
||||
公開SaveService.save_snapshot/inspect_snapshotの成功・全typed一致・live Runtime不変を確認する。
|
||||
Readerは別PID/profileで22slotの全member(journal.log/manifest.json/thumbnail.png/world.bin)をコピーし、
|
||||
原/copyの全SHA、load/replay後の両者不変を確認する。余分なmember/directoryは失敗する。
|
||||
全点を公開load/restoreしてWriterの全Runtime typed bytesと一致させる。
|
||||
さらに各beforeをfresh loadし、通常clockで同じ全境界を再生する。
|
||||
B1のfuture recoveryについてserial、idempotency key、command ID、issue minute、元reserveをbeforeに保存する。
|
||||
Readerはserial0から始め、保存済probe入力を復元して未来コマンドだけを実行する。setup keyを再利用しない。
|
||||
全pointとphaseのtyped比較からフィールドを削除せず、JSON正規化や要約への置換も行わない。
|
||||
再生中のphaseはWriterと同じ順・全typed状態、pointは入力・receiptまで含む全typed一致を必須とする。
|
||||
|
||||
## Root手順
|
||||
|
||||
Rootだけが現行製品1338でfresh plan→nativeを1回実行する。
|
||||
worker checkout planを製品nativeへ転用しない。既存plan/outputは上書きしない。
|
||||
|
||||
```sh
|
||||
export BUILD_OUTPUT_DIR='/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build'
|
||||
export PYTHONDONTWRITEBYTECODE=1
|
||||
python3 docs/plans/original-completion-2026-10-02/execution/p04-01/s3/run_s3_native.py --check-only --plan "$BUILD_OUTPUT_DIR/p04-s3-root-plan-r01.json" --output "$BUILD_OUTPUT_DIR/p04-s3-native-r01"
|
||||
python3 docs/plans/original-completion-2026-10-02/execution/p04-01/s3/run_s3_native.py --plan "$BUILD_OUTPUT_DIR/p04-s3-root-plan-r01.json" --output "$BUILD_OUTPUT_DIR/p04-s3-native-r01"
|
||||
```
|
||||
|
||||
製品root既定値: `/Users/korizatou114/Projects/とりにく工房ゲーム制作部`。
|
||||
出力先: `/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build/p04-s3-native-r01/`。
|
||||
一つのprivate stage/cacheでimport1回、parse1回、Writer1回、別PID Reader1回。
|
||||
S2bのstrict guard、policy/watchdog、marker/diagnostic/ownership/audio gateを踏襲する。
|
||||
source/cacheは別inode、owned finally後に全source/default profile/private source/pin/original cacheを検査する。
|
||||
Writer formal slot不変も失敗時を含めて検査し、4phase・4実PIDの終了とreceipt PIDを必須とする。
|
||||
check-onlyはengine0、private/default profileアクセス0、所有helper未読込。cache receiptの実読取エラーはplanに残しnativeを拒否する。
|
||||
|
||||
## 残gapとworker検証
|
||||
|
||||
workerはpy_compile、check-only、型/参照の静的確認だけを行う。
|
||||
Godot parse・native・全回帰・Runner操作・git書込み・他agent起動は0。
|
||||
GDの正式parseと6ケースの実成立、全正式Save/Load/replay一致はRoot nativeまで未実証。
|
||||
S3 raw取得後のcollector/A仕上げ、原15+EV全証拠の適用性照合、D05の原未確定規約、
|
||||
必要なOS別条件、正式独立レビュー・統合は本提出で確定しない。
|
||||
未知の原規約を新たに作らずpendingとし、P04全体完成を主張しない。
|
||||
|
||||
worker実施記録: py_compile成功(出力はtask buildのp04-s3-worker-pycompile.pyc)。
|
||||
GDの明示型変数・loop・関数戻り値、禁止されたpatience/elapsed直接代入なし、継承先literal参照、
|
||||
15のservice method参照、Runtime method/signal参照、括弧整合を静的確認した。Godot parseの代替ではない。
|
||||
製品rootでのcheck-onlyはtools/run_all_tests.py読取りがPermissionErrorでexit2。
|
||||
明示worktree rootではcheck-only成功(source1300、QA1、fixed pins18、engine0)。
|
||||
終了済cacheのrunner-ended.jsonはPermissionErrorであり、planのcache_accessに実path/errorを記録した。
|
||||
製品1338のcheck-only成功やcache再利用の成立は主張しない。Rootが自身の環境で確認する。
|
||||
初回worktree planはbuild/p04-s3-worker-check-r01.json。最終3ファイルのpin確認にはfreshな
|
||||
build/p04-s3-worker-check-r02.jsonを使用し、r01は保持する。
|
||||
+480
@@ -0,0 +1,480 @@
|
||||
extends "res://tests/s3/test_runtime_actor_hardening.gd"
|
||||
|
||||
const CASES: Array[String] = ["B1", "B2a", "B2b", "B3", "B4a", "B4b"]
|
||||
const SEEDS: Dictionary = {"B1": 420410, "B2a": 420430, "B2b": 420431, "B3": 420435, "B4a": 420420, "B4b": 420421}
|
||||
const GUEST: StringName = &"p04.s3.guest.a"
|
||||
const REQUEST: StringName = &"p04.s3.request.a"
|
||||
const QUEUED_GUEST: StringName = &"p04.s3.guest.b"
|
||||
const QUEUED_REQUEST: StringName = &"p04.s3.request.b"
|
||||
const SERVERS: Array[StringName] = [&"p04.order.server.a", &"p04.order.server.b"]
|
||||
var _evidence_dir: String = ""
|
||||
var _mode: String = ""
|
||||
var _case: String = ""
|
||||
var _serial: int = 0
|
||||
var _facility_id: StringName = &""
|
||||
var _service_id: StringName = &""
|
||||
var _boundary: int = 960
|
||||
var _price: int = 0
|
||||
var _duration: int = 0
|
||||
var _patience: int = 0
|
||||
var _reserve: int = 0
|
||||
var _recovery: Dictionary = {}
|
||||
var _commands: Array = []
|
||||
var _results: Array = []
|
||||
var _receipts: Array = []
|
||||
var _phases: Array = []
|
||||
var _points: Dictionary = {}
|
||||
var _branches: Dictionary = {}
|
||||
var _slots: Dictionary = {}
|
||||
|
||||
func _run() -> void:
|
||||
_evidence_dir = OS.get_environment("THE_TREE_S3_EVIDENCE")
|
||||
_mode = OS.get_environment("THE_TREE_S3_MODE")
|
||||
if _evidence_dir.is_empty() or _mode not in ["writer", "reader"]:
|
||||
quit(2)
|
||||
return
|
||||
_expect(DirAccess.make_dir_recursive_absolute(_evidence_dir) == OK, "external evidence directory")
|
||||
if _mode == "writer":
|
||||
for branch: String in CASES:
|
||||
_case = branch
|
||||
_writer_case()
|
||||
else:
|
||||
_reader()
|
||||
_write(_mode, {&"pid": OS.get_process_id(), &"profile": OS.get_user_data_dir(), &"slots": _slots,
|
||||
&"branches": _branches, &"checks": _checks, &"failures": _failures,
|
||||
&"whole_goal_accepted": false, &"unit_accepted": false}, true)
|
||||
_cleanup()
|
||||
print("S3 closure: %d checks, %d failures" % [_checks, _failures])
|
||||
quit(1 if _failures else 0)
|
||||
|
||||
func _fresh() -> void:
|
||||
_serial = 0
|
||||
_facility_id = &""
|
||||
_service_id = &""
|
||||
_boundary = 960
|
||||
_price = 0
|
||||
_duration = 0
|
||||
_patience = 0
|
||||
_reserve = 0
|
||||
_recovery = {}
|
||||
_commands = []
|
||||
_results = []
|
||||
_receipts = []
|
||||
_phases = []
|
||||
_points = {}
|
||||
_reset_runtime(SEEDS[_case])
|
||||
_runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
|
||||
_runtime.command_completed.connect(func(result: CommandResult) -> void:
|
||||
_results.append({&"minute": _clock.absolute_minute, &"result": result.to_dictionary()})
|
||||
_expect(result.is_success(), "actual public command completion " + str(result.command_id))
|
||||
)
|
||||
|
||||
func _cmd(kind: StringName, payload: Dictionary, at: int = -1) -> CommandResult:
|
||||
_serial += 1
|
||||
var key: StringName = StringName("p04.s3.%s.command.%d" % [_case, _serial])
|
||||
var result: CommandResult = _runtime.submit_command(kind, payload, &"player", &"", 10, at, key)
|
||||
_commands.append({&"serial": _serial, &"kind": kind, &"payload": payload.duplicate(true),
|
||||
&"at": at, &"issued_at": _clock.absolute_minute, &"idempotency_key": key, &"result": result.to_dictionary()})
|
||||
_expect(result.is_success(), "public command " + String(kind))
|
||||
return result
|
||||
|
||||
func _queue(guest_id: StringName, request_id: StringName, exit_at: int) -> void:
|
||||
_cmd(&"spawn_guest", {&"guest_id": guest_id, &"guest_type_id": &"traveler", &"gold": _price * 2,
|
||||
&"options": {&"state": GuestRuntime.State.DECIDING, &"current_cell": Vector3i.ZERO, &"planned_exit_at": exit_at}})
|
||||
_cmd(&"create_service_request", {&"facility_id": _facility_id, &"request_id": request_id, &"guest_ids": [guest_id]})
|
||||
var guest: GuestRuntime = _runtime.guest_service.get_guest(guest_id)
|
||||
var request: ServiceRequest = _runtime.facility_service.get_request(request_id)
|
||||
_expect(guest != null and request != null, "actual Guest and request exist")
|
||||
if guest == null or request == null: return
|
||||
_expect(guest.state == GuestRuntime.State.QUEUING and guest.queue_request_id == request_id, "actual queued ownership before advancing clock")
|
||||
_expect(request.quoted_price == _price and request.service_minutes == _duration and request.reserved_inventory_ids.size() == 1, "definition price/duration and one meal reservation")
|
||||
for id: StringName in request.reserved_inventory_ids:
|
||||
var reservation: InventoryReservation = _runtime.inventory_service.get_reservation(id)
|
||||
_expect(reservation != null and reservation.is_active() and reservation.requester_id == request_id, "public queue actually reserves meal")
|
||||
|
||||
func _prepare() -> bool:
|
||||
_fresh()
|
||||
var facility: FacilityInstance = _build_cafeteria(true)
|
||||
if facility == null: return false
|
||||
_facility_id = facility.instance_id
|
||||
var definition: FacilityDefinition = _runtime.definition_registry.get_facility(facility.definition_id)
|
||||
var traveler: GuestDefinition = _runtime.guest_service.get_definition(&"traveler")
|
||||
_price = definition.base_price
|
||||
_duration = definition.base_service_minutes
|
||||
_patience = traveler.base_patience
|
||||
if _case == "B3":
|
||||
_expect(_patience > 1 and _patience < 240, "original patience precedes reservation expiry")
|
||||
if _patience <= 1 or _patience >= 240: return false
|
||||
_boundary = _clock.absolute_minute + _patience
|
||||
_expect(facility.operator_open and facility.is_within_opening_hours(_clock.absolute_minute) and facility.related_facility_operating and facility.active_staff_ids.size() < facility.staff_min, "real no-server queue with operating kitchen")
|
||||
else:
|
||||
for id: StringName in SERVERS:
|
||||
_cmd(&"register_staff", {&"staff_id": id, &"role_id": &"server", &"options": {&"state": StaffRuntime.State.OFF_DUTY, &"current_cell": Vector3i.ZERO}})
|
||||
_cmd(&"set_staff_work_assignment", {&"staff_id": id, &"template_id": &"DAY", &"facility_id": _facility_id})
|
||||
var queue_at: int = 959 if _case == "B2b" else 960 - _duration - 1 + (1 if _case == "B4b" else 0)
|
||||
_expect(queue_at > _clock.absolute_minute, "normal ticks reach queue time")
|
||||
_clock.advance_minutes(queue_at - _clock.absolute_minute)
|
||||
_expect(facility.active_staff_ids == SERVERS, "unchanged real server attendance")
|
||||
for id: StringName in SERVERS:
|
||||
var worker: StaffRuntime = _runtime.staff_service.get_staff(id)
|
||||
_expect(_runtime.staff_service.requires_attendance(id) and StaffCommuteService.is_at_facility(worker, facility) and worker.current_cell != Vector3i.ZERO, "ordinary public server commute")
|
||||
if _case in ["B4a", "B4b"]:
|
||||
var action: EventAction = EventAction.new().configure(&"p04.destroy.action", EventAction.Type.SET_FACILITY_STATE, {&"facility_id": _facility_id, &"conditions": {&"durability": 0, &"suspension_reason": &"DESTROYED"}})
|
||||
_cmd(&"register_event_definition", {&"definition": {&"event_id": &"p04.destroy.event", &"category": &"DEFENSE", &"foreground": false, &"transaction_policy": &"ROLLBACK_ALL", &"automatic_actions": [action.create_snapshot()]}})
|
||||
_expect(_stock(facility.local_inventory_id, &"ingredient/basic_meal", 1, &"p04.order.shift.stock"), "original public stock helper")
|
||||
var exit_at: int = _boundary if _case in ["B2a", "B2b", "B3"] else _boundary + _duration
|
||||
_queue(GUEST, REQUEST, exit_at)
|
||||
if _case == "B3":
|
||||
_clock.advance_minutes(_patience - 1)
|
||||
var guest: GuestRuntime = _runtime.guest_service.get_guest(GUEST)
|
||||
_expect(guest.state == GuestRuntime.State.QUEUING and guest.patience_remaining == 1 and _runtime.facility_service.get_active_services(_facility_id).is_empty(), "normal ticks exhaust original patience, no manual mutation")
|
||||
elif _case == "B2b":
|
||||
_cmd(&"leave_facility_queue", {&"facility_id": _facility_id, &"request_id": REQUEST}, 960)
|
||||
else:
|
||||
_clock.advance_one_minute()
|
||||
var active: Array = _runtime.facility_service.get_active_services(_facility_id)
|
||||
_expect(active.size() == 1, "one public service starts")
|
||||
if active.size() != 1: return false
|
||||
var service: ServiceInstance = active[0]
|
||||
_service_id = service.service_id
|
||||
var offset: int = 1 if _case == "B4b" else 0
|
||||
_expect(service.duration_minutes == _duration and service.price_snapshot == _price and service.started_at + _duration == 960 + offset, "original service completion alignment")
|
||||
_clock.advance_minutes(959 - _clock.absolute_minute)
|
||||
_expect(service.elapsed_minutes == _duration - 1 - offset, "measured boundary-before progress")
|
||||
if _case == "B1":
|
||||
_expect(_stock(facility.local_inventory_id, &"ingredient/basic_meal", 1, &"p04.s3.stock.b"), "second actual meal at 959")
|
||||
_queue(QUEUED_GUEST, QUEUED_REQUEST, 960 + _duration)
|
||||
_reserve = _state.get_resource(&"mana")
|
||||
_expect(_reserve > 0, "original positive finite mana reserve")
|
||||
_cmd(&"change_resources", {&"deltas": {&"mana": -_reserve}}, 960)
|
||||
_cmd(&"set_facility_policy", {&"facility_id": _facility_id, &"patch": {&"business_open": false}}, 960)
|
||||
var snapshot: Dictionary = _runtime.create_snapshot()
|
||||
_recovery = {&"serial": _serial + 1, &"kind": &"change_resources", &"payload": {&"deltas": {&"mana": _reserve}},
|
||||
&"idempotency_key": StringName("p04.s3.%s.command.%d" % [_case, _serial + 1]),
|
||||
&"command_id": snapshot[&"systems"][&"runtime"][&"next_command_id"], &"at": -1, &"issue_minute": 961}
|
||||
if _case in ["B4a", "B4b"]:
|
||||
_cmd(&"enqueue_event", {&"event_id": &"p04.destroy.event", &"actor_ids": [], &"options": {&"event_instance_id": &"p04.destroy.instance"}})
|
||||
_expect(_clock.absolute_minute == _boundary - 1, "exact boundary-before time")
|
||||
return _clock.absolute_minute == _boundary - 1
|
||||
|
||||
func _connect_observations() -> void:
|
||||
_runtime.economy_service.transaction_posted.connect(func(id: StringName, entries: Array) -> void:
|
||||
var rows: Array = []
|
||||
for entry: LedgerEntry in entries: rows.append(entry.to_dictionary())
|
||||
_receipts.append({&"id": id, &"minute": _clock.absolute_minute, &"entries": rows})
|
||||
)
|
||||
_clock.tick_phase_started.connect(func(phase: int, minute: int) -> void:
|
||||
var label: String = "phase-%d-%s" % [minute, SimulationKernel.TickPhase.keys()[phase]]
|
||||
var row: Dictionary = _capture()
|
||||
row[&"phase"] = SimulationKernel.TickPhase.keys()[phase]
|
||||
_assert_phase(row)
|
||||
_phases.append(_write(label, row))
|
||||
)
|
||||
|
||||
func _assert_phase(point: Dictionary) -> void:
|
||||
if point[&"minute"] != _boundary: return
|
||||
var phase: String = point[&"phase"]
|
||||
if _case in ["B4a", "B4b"]:
|
||||
if phase == "INCIDENTS_AND_EVENTS":
|
||||
_expect(point[&"facility"][&"durability"] > 0 and point[&"event"][&"state"] == "QUEUED", "facility phase precedes diagnostic destruction")
|
||||
_expect(point[&"service"][&"state"] == ("COMPLETED" if _case == "B4a" else "ACTIVE"), "actual service state before Event phase")
|
||||
_expect(point[&"receipts"].size() == (1 if _case == "B4a" else 0), "completion payment occurs before destruction, not while stopped")
|
||||
if phase == "FACILITY_QUEUES":
|
||||
_expect(point[&"facility"][&"durability"] == 0 and point[&"event"][&"state"] == "COMPLETED", "Event committed before queue phase")
|
||||
if _case == "B3" and phase == "AI_DECISIONS":
|
||||
var guest: Dictionary = point[&"guests"][String(GUEST)]
|
||||
_expect(guest[&"state"] == "ABANDONING" and guest[&"patience_remaining"] == 0 and guest[&"queue_request_id"].is_empty(), "actual auto-abandon before AI redecision")
|
||||
if _case == "B2b" and phase == "FACILITY_COMPLETION":
|
||||
_expect(point[&"requests"][String(REQUEST)].is_empty() and point[&"active_services"] == 0 and point[&"available_meals"] == 1, "scheduled leave released before admission")
|
||||
if _case == "B1" and phase == "AI_DECISIONS":
|
||||
var queued: Dictionary = point[&"guests"][String(QUEUED_GUEST)]
|
||||
_expect(not queued.is_empty() and queued[&"queue_request_id"].is_empty() and queued[&"patience_remaining"] == _patience, "direct closure releases B before AI with no patience penalty")
|
||||
|
||||
func _capture() -> Dictionary:
|
||||
var snapshot: Dictionary = _runtime.create_snapshot()
|
||||
var operations: Dictionary = snapshot[&"systems"][&"runtime"][&"operations"]
|
||||
var facility: FacilityInstance = _runtime.facility_service.get_instance(_facility_id)
|
||||
var service: ServiceInstance = _runtime.facility_service.get_service(_service_id)
|
||||
var event: EventInstance = _runtime.event_service.get_instance(&"p04.destroy.instance")
|
||||
var actors: Dictionary = {}
|
||||
var requests: Dictionary = {}
|
||||
var wallets: Dictionary = {}
|
||||
for id: StringName in [GUEST, QUEUED_GUEST]:
|
||||
var guest: GuestRuntime = _runtime.guest_service.get_guest(id)
|
||||
actors[String(id)] = guest.create_snapshot() if guest != null else {}
|
||||
wallets[String(id)] = _runtime.economy_service.get_balance(StringName("GUEST_WALLET:" + String(id)))
|
||||
for id: StringName in [REQUEST, QUEUED_REQUEST]:
|
||||
var request: ServiceRequest = _runtime.facility_service.get_request(id)
|
||||
requests[String(id)] = request.create_snapshot() if request != null else {}
|
||||
var reservations: Array = []
|
||||
for reservation: InventoryReservation in _runtime.inventory_service.get_reservations(false): reservations.append(reservation.create_snapshot())
|
||||
var ledger: Array = []
|
||||
for entry: LedgerEntry in _runtime.economy_service.get_entries(): ledger.append(entry.to_dictionary())
|
||||
return {&"snapshot": snapshot, &"minute": _clock.absolute_minute,
|
||||
&"inputs": {&"case": _case, &"seed": SEEDS[_case], &"serial": _serial, &"facility_id": _facility_id, &"service_id": _service_id,
|
||||
&"boundary": _boundary, &"price": _price, &"duration": _duration, &"patience": _patience, &"reserve": _reserve, &"recovery": _recovery.duplicate(true),
|
||||
&"kitchen_premise": "ActorHardening physical FLEX fixture; not natural hire/unlock"},
|
||||
&"facility": facility.create_snapshot(), &"service": service.create_snapshot() if service != null else {},
|
||||
&"event": event.create_snapshot() if event != null else {}, &"guests": actors, &"requests": requests, &"wallets": wallets,
|
||||
&"reservations": reservations, &"ledger": ledger, &"receipts": _receipts.duplicate(true),
|
||||
&"player_cash": _runtime.economy_service.get_balance(_runtime.PLAYER_CASH_ACCOUNT),
|
||||
&"available_meals": _runtime.inventory_service.available_quantity(facility.local_inventory_id, &"ingredient/basic_meal"),
|
||||
&"active_services": _runtime.facility_service.get_active_services(_facility_id).size(),
|
||||
&"operations_enabled": operations[&"enabled"], &"saved_coordinator": operations[&"coordinator"],
|
||||
&"actual_coordinator_absent": _runtime.operations_coordinator == null, &"unrelated_getters_called": false}
|
||||
|
||||
func _labels() -> Array[String]:
|
||||
if _case == "B1": return ["before", "after", "next", "recovered", "duplicate_guard"]
|
||||
if _case in ["B2b", "B3"]: return ["before", "after", "next", "despawn"]
|
||||
return ["before", "after", "next"]
|
||||
|
||||
func _advance(label: String) -> void:
|
||||
if label == "recovered":
|
||||
_expect(_serial + 1 == _recovery[&"serial"] and _clock.absolute_minute == _recovery[&"issue_minute"], "saved future recovery serial and issue minute")
|
||||
var result: CommandResult = _cmd(_recovery[&"kind"], _recovery[&"payload"], _recovery[&"at"])
|
||||
var command: Dictionary = _commands.back()
|
||||
_expect(result.command_id == _recovery[&"command_id"] and command[&"idempotency_key"] == _recovery[&"idempotency_key"], "same future public recovery command ID/key")
|
||||
_clock.advance_one_minute()
|
||||
|
||||
func _writer_case() -> void:
|
||||
if not _prepare(): return
|
||||
_connect_observations()
|
||||
var before: Dictionary = _capture()
|
||||
_assert_before(before)
|
||||
_save_point("before", before)
|
||||
for label: String in _labels():
|
||||
if label == "before": continue
|
||||
_advance(label)
|
||||
var actual: Dictionary = _capture()
|
||||
_assert_outcome(before, actual)
|
||||
_save_point(label, actual)
|
||||
var branch: Dictionary = {&"points": _points.duplicate(true), &"phases": _phases.duplicate(true),
|
||||
&"commands": _write("commands", {&"submitted": _commands, &"completed_including_fixture": _results})}
|
||||
_branches[_case] = _write("case", branch)
|
||||
|
||||
func _assert_before(point: Dictionary) -> void:
|
||||
_expect(not point[&"operations_enabled"] and point[&"actual_coordinator_absent"] and point[&"saved_coordinator"] == {}, "Operations disabled and actual absence measured")
|
||||
var guest: Dictionary = point[&"guests"][String(GUEST)]
|
||||
_expect(guest[&"planned_exit_at"] >= _boundary, "planned exit does not short-circuit queue/service")
|
||||
var expected_count: int = 2 if _case == "B1" else 1
|
||||
_expect(point[&"reservations"].size() == expected_count, "only actual requested meals reserved")
|
||||
for reservation: Dictionary in point[&"reservations"]:
|
||||
_expect(reservation[&"state"] == "ACTIVE" and reservation[&"purpose"] == "facility_service", "actual ACTIVE reservation before boundary")
|
||||
_expect(point[&"available_meals"] == 0, "all stocked meals are reserved")
|
||||
if _case == "B1":
|
||||
var queued: Dictionary = point[&"guests"][String(QUEUED_GUEST)]
|
||||
_expect(queued[&"state"] == "QUEUING" and queued[&"patience_remaining"] == _patience, "B exists at 959 with unchanged original patience")
|
||||
|
||||
func _assert_reservation(before: Dictionary, point: Dictionary, request_id: StringName, state: String, terminal: int) -> void:
|
||||
var prior: Array = []
|
||||
var current: Array = []
|
||||
for row: Dictionary in before[&"reservations"]:
|
||||
if row[&"requester_id"] == String(request_id): prior.append(row)
|
||||
for row: Dictionary in point[&"reservations"]:
|
||||
if row[&"requester_id"] == String(request_id): current.append(row)
|
||||
_expect(prior.size() == 1 and current.size() == 1, "one original reservation per request")
|
||||
if prior.size() != 1 or current.size() != 1: return
|
||||
_expect(prior[0][&"reservation_id"] == current[0][&"reservation_id"] and current[0][&"state"] == state and current[0][&"terminal_at"] == terminal, "same reservation ID/state/terminal time; no second release")
|
||||
if state == "ACTIVE":
|
||||
_expect(var_to_bytes(prior[0]) == var_to_bytes(current[0]), "entire held reservation unchanged")
|
||||
var request: Dictionary = point[&"requests"][String(request_id)]
|
||||
_expect(not request.is_empty() and request[&"reserved_inventory_ids"].has(current[0][&"reservation_id"]), "active reservation has live request owner")
|
||||
else:
|
||||
_expect(point[&"requests"][String(request_id)].is_empty(), "terminal reservation has no orphan request")
|
||||
if state == "RELEASED":
|
||||
var release_count: int = 0
|
||||
var inventory: Dictionary = point[&"snapshot"][&"systems"][&"runtime"][&"inventory"]
|
||||
for operation: Dictionary in inventory[&"idempotency_results"]:
|
||||
var result: Dictionary = operation[&"result"]
|
||||
if operation[&"action"] == "release" and String(result.get(&"reservation_id", "")) == current[0][&"reservation_id"]:
|
||||
release_count += 1
|
||||
_expect(bool(result.get(&"ok", false)) and result.get(&"quantity", 0) == current[0][&"quantity"], "release returned actual reserved quantity, not an orphan repair/retry")
|
||||
_expect(release_count == 1, "one raw inventory release operation for original reservation")
|
||||
|
||||
func _assert_outcome(before: Dictionary, point: Dictionary) -> void:
|
||||
var minute: int = point[&"minute"]
|
||||
var service: Dictionary = point[&"service"]
|
||||
var guest: Dictionary = point[&"guests"][String(GUEST)]
|
||||
var paid: bool = _case in ["B2a", "B4a"] or (_case == "B1" and minute >= 962)
|
||||
_expect(point[&"wallets"][String(GUEST)] == _price * (1 if paid else 2), "actual wallet delta follows boundary")
|
||||
_expect(point[&"receipts"].size() == (1 if paid else 0), "actual payment notification exactly once or zero")
|
||||
_expect(point[&"player_cash"] == before[&"player_cash"] + (_price if paid else 0), "actual player cash delta exactly matches single price")
|
||||
if not paid:
|
||||
_expect(var_to_bytes(point[&"ledger"]) == var_to_bytes(before[&"ledger"]), "zero ledger charge while stopped or abandoned")
|
||||
if paid:
|
||||
var expected_ledger: Array = before[&"ledger"].duplicate(true)
|
||||
for receipt: Dictionary in point[&"receipts"]: expected_ledger.append_array(receipt[&"entries"])
|
||||
_expect(var_to_bytes(point[&"ledger"]) == var_to_bytes(expected_ledger), "whole ledger delta is exactly actual payment receipt, no extra posting")
|
||||
var completed_at: int = 962 if _case == "B1" else 960
|
||||
_expect(service[&"state"] == "COMPLETED" and service[&"completed_at"] == completed_at, "one actual completion")
|
||||
_assert_reservation(before, point, REQUEST, "CONSUMED", completed_at)
|
||||
_expect(point[&"active_services"] == 0, "completed service releases active slot")
|
||||
if _case == "B1":
|
||||
_expect(not point[&"facility"][&"operator_open"], "direct closure remains in force including recovery")
|
||||
_assert_reservation(before, point, QUEUED_REQUEST, "RELEASED", 960)
|
||||
var queued: Dictionary = point[&"guests"][String(QUEUED_GUEST)]
|
||||
if minute == 960: _expect(not queued.is_empty(), "released B measured on closure tick")
|
||||
if not queued.is_empty():
|
||||
_expect(queued[&"queue_request_id"].is_empty() and queued[&"patience_remaining"] == before[&"guests"][String(QUEUED_GUEST)][&"patience_remaining"], "closure releases B without patience consumption; later natural Despawn is allowed")
|
||||
_expect(point[&"wallets"][String(QUEUED_GUEST)] == _price * 2 and point[&"available_meals"] == 1, "B stock returned once and wallet unchanged")
|
||||
if not paid:
|
||||
_expect(not point[&"facility"][&"power_available"] and service[&"state"] == "PAUSED" and service[&"elapsed_minutes"] == before[&"service"][&"elapsed_minutes"], "outage holds original progress; actual interruption_reason retained in raw")
|
||||
_assert_reservation(before, point, REQUEST, "ACTIVE", -1)
|
||||
elif _case in ["B2b", "B3"]:
|
||||
_assert_reservation(before, point, REQUEST, "RELEASED", _boundary)
|
||||
_expect(point[&"active_services"] == 0 and point[&"available_meals"] == 1, "no service/orphan and exact stock return")
|
||||
if minute == _boundary:
|
||||
_expect(not guest.is_empty() and guest[&"state"] == "DECIDING" and guest[&"queue_request_id"].is_empty(), "automatic redecision after release")
|
||||
if _case == "B3": _expect(guest[&"patience_remaining"] == 0, "normal queue tick exhausted original patience")
|
||||
elif minute == _boundary + 1:
|
||||
_expect(not guest.is_empty() and guest[&"state"] == "LEAVING", "normal next-minute exit")
|
||||
else: _expect(guest.is_empty(), "normal Despawn boundary")
|
||||
elif _case == "B2a":
|
||||
_expect(point[&"available_meals"] == 0, "completed meal consumed")
|
||||
if minute == 960: _expect(not guest.is_empty() and guest[&"state"] == "LEAVING" and guest[&"state_entered_at"] == 960, "completion precedes normal planned exit")
|
||||
else: _expect(guest.is_empty(), "next-tick despawn without second payment")
|
||||
else:
|
||||
_expect(point[&"facility"][&"durability"] == 0 and point[&"facility"][&"state"] == "BROKEN" and point[&"event"][&"state"] == "COMPLETED", "actual automatic destruction Event committed")
|
||||
if _case == "B4a": _expect(point[&"available_meals"] == 0, "completion before destruction consumes meal")
|
||||
elif minute == 960:
|
||||
_expect(service[&"state"] == "ACTIVE" and service[&"elapsed_minutes"] == _duration - 1, "late Event follows facility completion phase")
|
||||
_assert_reservation(before, point, REQUEST, "ACTIVE", -1)
|
||||
else:
|
||||
_expect(service[&"state"] == "FAILED" and service[&"elapsed_minutes"] == _duration - 1 and point[&"available_meals"] == 1 and point[&"active_services"] == 0, "next facility tick fails without progress or payment")
|
||||
_assert_reservation(before, point, REQUEST, "RELEASED", 961)
|
||||
# Any remaining active reservation must still belong to the same live request
|
||||
# and started service. Terminal history is retained, never filtered away.
|
||||
for reservation: Dictionary in point[&"reservations"]:
|
||||
if reservation[&"state"] != "ACTIVE": continue
|
||||
var request: Dictionary = point[&"requests"].get(reservation[&"requester_id"], {})
|
||||
_expect(not request.is_empty() and request[&"reserved_inventory_ids"].has(reservation[&"reservation_id"]) and not service.is_empty() and service[&"inventory_reservations"].has(reservation[&"reservation_id"]), "no active reservation orphan")
|
||||
|
||||
func _save_point(label: String, point: Dictionary) -> void:
|
||||
_points[label] = _write(label, point)
|
||||
var path: String = "user://saves/p04_s3/" + _case + "-" + label
|
||||
var saved: Dictionary = SaveService.save_snapshot(path, point[&"snapshot"])
|
||||
var inspected: Dictionary = SaveService.inspect_snapshot(path)
|
||||
_expect(bool(saved.get(&"ok", false)) and bool(inspected.get(&"ok", false)) and bool(inspected.get(&"read_only", false)), "public formal save/inspect")
|
||||
_expect(var_to_bytes(inspected.get(&"snapshot", {})) == var_to_bytes(point[&"snapshot"]), "all typed fields preserved by formal package")
|
||||
_expect(var_to_bytes(_runtime.create_snapshot()) == var_to_bytes(point[&"snapshot"]), "formal saving leaves Runtime unchanged")
|
||||
_slots[_case + "-" + label] = ProjectSettings.globalize_path(path)
|
||||
# Snapshot bytes exist once in point raw and once in the required formal
|
||||
# package; indexes do not embed inspection/load copies of those bytes.
|
||||
|
||||
func _write(label: String, value: Variant, global_name: bool = false) -> Dictionary:
|
||||
var name: String = label if global_name else _mode + "-" + _case + "-" + label
|
||||
var reference: Dictionary = {}
|
||||
for extension: String in ["var", "json"]:
|
||||
var path: String = _evidence_dir.path_join(name + "." + extension)
|
||||
_expect(not FileAccess.file_exists(path), "raw point written exactly once " + name)
|
||||
var file: FileAccess = FileAccess.open(path, FileAccess.WRITE)
|
||||
_expect(file != null, "raw file " + name)
|
||||
if file == null: continue
|
||||
if extension == "var": file.store_buffer(var_to_bytes(value))
|
||||
else: file.store_string(JSON.stringify(value, "\t"))
|
||||
file.flush()
|
||||
file.close()
|
||||
reference[extension] = {&"path": path, &"sha256": FileAccess.get_sha256(path)}
|
||||
return reference
|
||||
|
||||
func _read(reference: Dictionary) -> Dictionary:
|
||||
var item: Dictionary = reference["var"]
|
||||
var path: String = item["path"]
|
||||
_expect(FileAccess.get_sha256(path) == item["sha256"], "raw typed SHA")
|
||||
var json_ref: Dictionary = reference["json"]
|
||||
_expect(FileAccess.get_sha256(json_ref["path"]) == json_ref["sha256"], "raw JSON SHA")
|
||||
var file: FileAccess = FileAccess.open(path, FileAccess.READ)
|
||||
_expect(file != null, "typed input readable")
|
||||
if file == null: return {}
|
||||
var value: Variant = bytes_to_var(file.get_buffer(file.get_length()))
|
||||
file.close()
|
||||
_expect(value is Dictionary, "typed input dictionary")
|
||||
return value if value is Dictionary else {}
|
||||
|
||||
func _restore_inputs(point: Dictionary) -> void:
|
||||
var inputs: Dictionary = point[&"inputs"]
|
||||
_expect(_serial == 0, "Reader starts with serial zero before importing external inputs")
|
||||
_serial = inputs[&"serial"]
|
||||
_facility_id = inputs[&"facility_id"]
|
||||
_service_id = inputs[&"service_id"]
|
||||
_boundary = inputs[&"boundary"]
|
||||
_price = inputs[&"price"]
|
||||
_duration = inputs[&"duration"]
|
||||
_patience = inputs[&"patience"]
|
||||
_reserve = inputs[&"reserve"]
|
||||
_recovery = inputs[&"recovery"].duplicate(true)
|
||||
|
||||
func _load_point(path: String, expected: Dictionary) -> bool:
|
||||
_fresh()
|
||||
_restore_inputs(expected)
|
||||
var loaded: Dictionary = SaveService.load_snapshot(path)
|
||||
_expect(bool(loaded.get(&"ok", false)), "public formal load")
|
||||
if not bool(loaded.get(&"ok", false)): return false
|
||||
_expect(var_to_bytes(loaded[&"snapshot"]) == var_to_bytes(expected[&"snapshot"]), "all saved typed fields equal Writer")
|
||||
var restored: bool = _runtime.restore_snapshot(loaded[&"snapshot"])
|
||||
_expect(restored, "public Runtime restore")
|
||||
_expect(var_to_bytes(_runtime.create_snapshot()) == var_to_bytes(expected[&"snapshot"]), "all restored typed fields equal Writer")
|
||||
return restored
|
||||
|
||||
func _reader() -> void:
|
||||
var file: FileAccess = FileAccess.open(_evidence_dir.path_join("writer.json"), FileAccess.READ)
|
||||
_expect(file != null, "Writer receipt")
|
||||
if file == null: return
|
||||
var writer: Dictionary = JSON.parse_string(file.get_as_text())
|
||||
file.close()
|
||||
_expect(int(writer["pid"]) != OS.get_process_id() and writer["profile"] != OS.get_user_data_dir(), "independent Reader PID/profile")
|
||||
for branch: String in CASES:
|
||||
_case = branch
|
||||
var writer_case: Dictionary = _read(writer["branches"][branch])
|
||||
var hashes: Dictionary = {}
|
||||
var restored_refs: Dictionary = {}
|
||||
for label: String in _labels():
|
||||
var key: String = branch + "-" + label
|
||||
var original: String = writer["slots"][key]
|
||||
var target: String = ProjectSettings.globalize_path("user://saves/p04_s3/" + key)
|
||||
_expect(DirAccess.make_dir_recursive_absolute(target) == OK, "private Reader formal slot")
|
||||
var directory: DirAccess = DirAccess.open(original)
|
||||
_expect(directory != null, "Writer formal slot readable")
|
||||
if directory == null: return
|
||||
var members: PackedStringArray = directory.get_files()
|
||||
members.sort()
|
||||
_expect(directory.get_directories().is_empty() and members == PackedStringArray(["journal.log", "manifest.json", "thumbnail.png", "world.bin"]), "all formal members enumerated")
|
||||
var member_hashes: Dictionary = {}
|
||||
for member: String in members:
|
||||
var sha: String = FileAccess.get_sha256(original.path_join(member))
|
||||
_expect(DirAccess.copy_absolute(original.path_join(member), target.path_join(member)) == OK, "copy every formal member")
|
||||
var copied_sha: String = FileAccess.get_sha256(target.path_join(member))
|
||||
_expect(not sha.is_empty() and sha == copied_sha, "original/copy member SHA equality")
|
||||
member_hashes[member] = {&"original_before": sha, &"copy_before": copied_sha}
|
||||
hashes[key] = {&"original": original, &"copy": target, &"members": member_hashes}
|
||||
_slots[key] = target
|
||||
var expected: Dictionary = _read(writer_case[&"points"][label])
|
||||
if not _load_point(target, expected): return
|
||||
restored_refs[label] = _write(label + "-restored", _capture())
|
||||
var before: Dictionary = _read(writer_case[&"points"]["before"])
|
||||
if not _load_point(_slots[branch + "-before"], before): return
|
||||
_assert_before(_capture())
|
||||
_connect_observations()
|
||||
var replay_refs: Dictionary = {}
|
||||
for label: String in _labels():
|
||||
if label == "before": continue
|
||||
_advance(label)
|
||||
var actual: Dictionary = _capture()
|
||||
var expected: Dictionary = _read(writer_case[&"points"][label])
|
||||
_expect(var_to_bytes(actual) == var_to_bytes(expected), "replay complete typed point including Runtime, inputs, receipts " + label)
|
||||
_assert_outcome(before, actual)
|
||||
replay_refs[label] = _write(label + "-replay", actual)
|
||||
var expected_phases: Array = writer_case[&"phases"]
|
||||
_expect(_phases.size() == expected_phases.size(), "complete phase count")
|
||||
if _phases.size() == expected_phases.size():
|
||||
for index: int in range(_phases.size()):
|
||||
_expect(var_to_bytes(_read(_phases[index])) == var_to_bytes(_read(expected_phases[index])), "same phase order and all typed phase state")
|
||||
for key: String in hashes:
|
||||
var row: Dictionary = hashes[key]
|
||||
for member: String in row[&"members"]:
|
||||
var item: Dictionary = row[&"members"][member]
|
||||
item[&"original_after"] = FileAccess.get_sha256(String(row[&"original"]).path_join(member))
|
||||
item[&"copy_after"] = FileAccess.get_sha256(String(row[&"copy"]).path_join(member))
|
||||
_expect(item[&"original_before"] == item[&"original_after"] and item[&"copy_before"] == item[&"copy_after"], "original/copy unchanged after all loads and replay")
|
||||
_branches[branch] = _write("case", {&"restored": restored_refs, &"replay": replay_refs, &"phases": _phases,
|
||||
&"member_hashes": hashes, &"recovery_commands": _commands, &"replay_completed_commands": _results})
|
||||
@@ -0,0 +1,293 @@
|
||||
#!/usr/bin/env python3
|
||||
"""S3-only native entry. --check-only never loads ownership helpers or starts Godot."""
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
BUILD = Path("/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build")
|
||||
PRODUCT = Path("/Users/korizatou114/Projects/とりにく工房ゲーム制作部")
|
||||
CACHE_RUN = Path("/Users/korizatou114/Projects/TheTree-managed-worktrees/p18-01-model/.runtime/P18-01-CODE/build/p18-relationship-evidence")
|
||||
TEST = "tests/s3/test_runtime_staff_assignment.gd"
|
||||
QA = HERE / "p04_s3_closure_probe.gd"
|
||||
ENGINE = Path("/Applications/Godot.app/Contents/MacOS/Godot")
|
||||
MANIFEST = BUILD / "audit-support/p04-s3-existing-source-r01/SOURCE_MANIFEST.json"
|
||||
MANIFEST_SHA = "d4993398f7619ca37bcd64c32b79bdee892d44e31f332cc0dd5f31fb7bc28314"
|
||||
AUDIT = BUILD / "audit-support/p04-whole-closure-r01/ROOT_S2B_EXISTING_SOURCE_AUDIT.json"
|
||||
CASES = ["B1", "B2a", "B2b", "B3", "B4a", "B4b"]
|
||||
LABELS = {"B1": ["before", "after", "next", "recovered", "duplicate_guard"],
|
||||
"B2a": ["before", "after", "next"], "B2b": ["before", "after", "next", "despawn"],
|
||||
"B3": ["before", "after", "next", "despawn"],
|
||||
"B4a": ["before", "after", "next"], "B4b": ["before", "after", "next"]}
|
||||
SLOTS = sorted(case + "-" + label for case in CASES for label in LABELS[case])
|
||||
MATERIALS = [MANIFEST, AUDIT, HERE.parent / "s2b/run_s2b_native.py",
|
||||
HERE.parent / "s2b/p04_s2b_shift_probe.gd", HERE.parent / "s2b/S2B_ROOT_RUN.md"]
|
||||
|
||||
def template_guard(root):
|
||||
if pin(MANIFEST)["sha256"] != MANIFEST_SHA:
|
||||
raise ValueError("supplied seven-source manifest changed")
|
||||
manifest = json.loads(MANIFEST.read_text())
|
||||
if len(manifest["files"]) != 7:
|
||||
raise ValueError("exact seven source files required")
|
||||
for row in manifest["files"]:
|
||||
actual = pin(row["supplied"])
|
||||
if (actual["bytes"], actual["sha256"]) != (row["bytes"], row["sha256"]):
|
||||
raise ValueError("supplied template SHA mismatch: " + row["supplied"])
|
||||
audit = json.loads(AUDIT.read_text())
|
||||
for row in audit["source_pins"]:
|
||||
actual = pin(root / row["path"])
|
||||
if (actual["bytes"], actual["sha256"]) != (row["production_bytes"], row["production_sha256"]):
|
||||
raise ValueError("Root source audit mismatch: " + row["path"])
|
||||
probe = QA.read_text()
|
||||
if ":=" in probe:
|
||||
raise ValueError("probe requires explicit typed declarations")
|
||||
import re
|
||||
if re.search(r"\bvar\s+\w+\s*=", probe) or re.search(r"\bfor\s+\w+\s+in\b", probe):
|
||||
raise ValueError("untyped declaration/loop")
|
||||
if re.search(r"\.(?:patience_remaining|elapsed_minutes)\s*=(?!=)", probe):
|
||||
raise ValueError("manual patience/progress mutation")
|
||||
for forbidden in ["._update_shift_states(", ".record_duty_minute(", "get_day_summary(",
|
||||
"configure_operations("]:
|
||||
if forbidden in probe:
|
||||
raise ValueError("out-of-scope mutation/getter: " + forbidden)
|
||||
|
||||
|
||||
def module(name, path):
|
||||
spec = importlib.util.spec_from_file_location(name, path)
|
||||
value = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(value)
|
||||
return value
|
||||
|
||||
def pin(path):
|
||||
path = Path(path)
|
||||
if path.is_symlink() or not path.is_file():
|
||||
raise ValueError("missing exact input: " + str(path))
|
||||
data = path.read_bytes()
|
||||
return dict(path=str(path), bytes=len(data), sha256=hashlib.sha256(data).hexdigest())
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--project-root", type=Path, default=PRODUCT)
|
||||
parser.add_argument("--output", type=Path, default=BUILD / "p04-s3-native-r01")
|
||||
parser.add_argument("--plan", type=Path, default=BUILD / "p04-s3-plan-r01.json")
|
||||
parser.add_argument("--cache-run", type=Path, default=CACHE_RUN)
|
||||
parser.add_argument("--godot", type=Path, default=ENGINE)
|
||||
parser.add_argument("--check-only", action="store_true")
|
||||
args = parser.parse_args()
|
||||
root = args.project_root.resolve()
|
||||
out = args.output.resolve()
|
||||
if not out.is_relative_to(BUILD.resolve()) or not args.plan.resolve().is_relative_to(BUILD.resolve()):
|
||||
raise ValueError("plan/output must stay in task build")
|
||||
r = module("s3_standard_runner", root / "tools/run_all_tests.py")
|
||||
# Preserve the reviewed source_refs -> exactly one QA override order.
|
||||
source_before = r.source_refs(root)
|
||||
refs = dict(source_before)
|
||||
refs[TEST] = pin(QA)
|
||||
template_guard(root)
|
||||
expected = {n: dict(bytes=p["bytes"], sha256=p["sha256"]) for n, p in refs.items()}
|
||||
fixed = [pin(p) for p in MATERIALS + [Path(row["supplied"]) for row in json.loads(MANIFEST.read_text())["files"]] + [
|
||||
QA, Path(__file__), HERE / "S3_ROOT_RUN.md",
|
||||
root / "tests/transport/test_runtime_transport.gd",
|
||||
root / "tests/staff/test_rescue_elevator.gd",
|
||||
root / "docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json",
|
||||
]]
|
||||
selected = dict(root_before=source_before, map={n: p["sha256"] for n, p in refs.items()}, timeout=1200)
|
||||
cache_note = None
|
||||
cache_receipts = []
|
||||
for leaf in ["runner-ended.json", "runner-input.json"]:
|
||||
path = args.cache_run / leaf
|
||||
try:
|
||||
cache_receipts.append(pin(path))
|
||||
except OSError as caught:
|
||||
cache_note = dict(path=str(path), error=type(caught).__name__ + ": " + str(caught),
|
||||
required_before_native=True)
|
||||
break
|
||||
plan = dict(protocol="p04-s3-preparation-v1", project_root=str(root), output=str(out),
|
||||
source_before=source_before, selected_source_map=selected["map"],
|
||||
QA_overrides=[TEST], fixed_input_pins=fixed, cache_run=str(args.cache_run),
|
||||
cache_receipts=cache_receipts, cache_access=cache_note,
|
||||
phases=["import", "parse", "writer", "reader"],
|
||||
native_engine_launches=0, whole_goal_accepted=False, unit_accepted=False)
|
||||
# Check-only only reads source/materials and writes the plan in task build.
|
||||
# It deliberately does not read default-player data, inspect processes or stat Godot.
|
||||
if args.check_only:
|
||||
args.plan.parent.mkdir(parents=True, exist_ok=True)
|
||||
if args.plan.exists():
|
||||
r.require(r.read(args.plan) == plan, "existing plan differs; use a fresh plan path")
|
||||
else:
|
||||
r.write(args.plan, plan)
|
||||
print(json.dumps(dict(check_only=True, engines=0, source_files=len(source_before),
|
||||
QA_overrides=[TEST], fixed_pins=len(fixed), plan=str(args.plan),
|
||||
plan_sha256=r.sha(args.plan), cache_access=cache_note), ensure_ascii=False))
|
||||
return 0
|
||||
r.require(root == PRODUCT.resolve() and len(source_before) == 1338, "Root native requires product 1338; worker plan is not native evidence")
|
||||
r.require(args.plan.is_file() and r.read(args.plan) == plan, "run exact check-only plan first")
|
||||
def fixed_guard():
|
||||
template_guard(root)
|
||||
for value in fixed + cache_receipts:
|
||||
r.require(pin(value["path"]) == value, "fixed input changed: " + value["path"])
|
||||
fixed_guard()
|
||||
# Native admission is Root-only and fails closed on unreadable cache.
|
||||
r.require(cache_note is None, "Root must read exact cache receipts: " + str(cache_note))
|
||||
old = args.cache_run
|
||||
r.require(r.read(old / "runner-ended.json")["passed"], "ended cache run must have passed")
|
||||
old_map = r.read(old / "runner-input.json")["selected_source_map"]
|
||||
current_map = {n: p["sha256"] for n, p in source_before.items()}
|
||||
r.require(old_map == current_map, "ended cache selected source must equal current product")
|
||||
protected = r.default_player(root)
|
||||
outer = module("s3_owned_outer", r.SUPPORT / "owned_outer.py")
|
||||
r.require(not outer.native.helper["live_engines"](), "another Godot owns CPU")
|
||||
engine = args.godot.resolve()
|
||||
r.require(engine.is_file(), "Root native Godot path")
|
||||
r.require(shutil.disk_usage(out.parent).free >= 1024**3 + engine.stat().st_size + 136*1024**2,
|
||||
"reviewed native disk admission")
|
||||
r.require(not out.exists(), "fresh output required; preserve previous attempts")
|
||||
out.mkdir()
|
||||
stage = out / "stage"
|
||||
stage.mkdir()
|
||||
(out / "starters").mkdir()
|
||||
(out / "bootstrap").mkdir()
|
||||
phases = []
|
||||
previous = {}
|
||||
error = None
|
||||
guards = []
|
||||
cleanup = []
|
||||
cleanup_errors = []
|
||||
cache_before = None
|
||||
formal_before = None
|
||||
formal_after = None
|
||||
original_phase = outer.run_phase
|
||||
def run_phase(argv, folder, timeout, env, **kwargs):
|
||||
env = dict(env)
|
||||
if folder.name in ["writer", "reader"]:
|
||||
env.update(THE_TREE_S3_EVIDENCE=str(out / "raw"), THE_TREE_S3_MODE=folder.name)
|
||||
return original_phase(argv, folder, timeout, env, **kwargs)
|
||||
outer.run_phase = run_phase
|
||||
r.write(out / "runner-started.json", dict(started=r.now(), owner_runner_pid=os.getpid(),
|
||||
plan=pin(args.plan), engines_launched=0))
|
||||
try:
|
||||
r.write(out / "source_before.json", source_before)
|
||||
r.write(out / "selected-source-map.json", refs)
|
||||
# Private source stage first, then literal reviewed bootstrap.
|
||||
for name, value in refs.items():
|
||||
target = stage / name
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
r.copy_source(Path(value["path"]), target)
|
||||
r.require(r.inventory(stage, stage=True) == expected, "whole private source")
|
||||
for name, value in r.read(r.SUPPORT / "bootstrap-scripts.json").items():
|
||||
data = value["text"].encode()
|
||||
r.require(hashlib.sha256(data).hexdigest() == value["sha256"], "bootstrap pin")
|
||||
(out / "bootstrap" / name).write_bytes(data)
|
||||
cache = old / "stage/.godot"
|
||||
cache_before = r.inventory(cache)
|
||||
r.require(bool(cache_before), "nonempty ended cache")
|
||||
for name in cache_before:
|
||||
src = cache / name
|
||||
dst = stage / ".godot" / name
|
||||
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
subprocess.run(["/bin/cp", "-c", str(src), str(dst)], check=True)
|
||||
r.require((src.stat().st_dev, src.stat().st_ino) != (dst.stat().st_dev, dst.stat().st_ino),
|
||||
"cache clone is a separate inode")
|
||||
r.require(r.inventory(stage / ".godot") == cache_before, "whole cache clone hashes")
|
||||
r.write(out / "cache-reuse.json", dict(ended_run=str(old), receipts=cache_receipts,
|
||||
whole_cache=cache_before, independent_inode=True,
|
||||
ordinary_import_required=True, ordinary_parse_required=True))
|
||||
paths = ["res://" + n for n in sorted(refs) if n.endswith(".gd")]
|
||||
r.write(out / "parse-paths.json", paths)
|
||||
markers = module("s3_markers", root / "tools/strict_test_markers.py")
|
||||
contracts = markers.load_contracts(root / "docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json")
|
||||
contracts[TEST] = dict(test=TEST, template="S3 closure: <checks> checks, <failures> failures", required=True)
|
||||
policy = r.read(r.SUPPORT / "suite-policy.json")
|
||||
r.require(TEST not in policy["suite_policies"], "no normal suite watchdog/policy override")
|
||||
r.write(out / "contracts.json", contracts)
|
||||
r.write(out / "runner-input.json", dict(plan, owner_runner_pid=os.getpid(),
|
||||
wrapper_sha256=r.sha(__file__), QA=pin(QA), runner_sha256=r.sha(root / "tools/run_all_tests.py"),
|
||||
default_player_before=protected, fixed_input_pins=fixed,
|
||||
functional_watchdog_seconds=1200, source_adoption=False))
|
||||
diagnostics = module("s3_diagnostics", r.SUPPORT / "diagnostic_gate.py")
|
||||
owners = module("s3_owners", r.SUPPORT / "ordinary_proof_gate.py")
|
||||
audio = module("s3_audio", r.SUPPORT / "audio_evidence_gate.py")
|
||||
public = (stage / "project.godot").read_bytes()
|
||||
for phase, test in [("import", None), ("parse", None), ("writer", TEST), ("reader", TEST)]:
|
||||
if phase == "reader":
|
||||
writer = r.read(out / "raw/writer.json")
|
||||
formal_before = {n: r.inventory(Path(p)) for n, p in writer["slots"].items()}
|
||||
r.require(sorted(formal_before) == SLOTS, "all 22 formal points for six fresh cases")
|
||||
row = r.execute_phase(str(engine), stage, out, phase, test, selected, expected,
|
||||
previous, policy, contracts, paths, public, protected, root,
|
||||
outer, markers, diagnostics, owners, audio)
|
||||
phases.append(row)
|
||||
# execute_phase uses the same test log name; retain each phase's exact log too.
|
||||
if test and row.get("log"):
|
||||
(out / (phase + "-combined-raw.log")).write_bytes((out / row["log"]).read_bytes())
|
||||
row["strict_runner_log_path"] = row["log"]
|
||||
row["log"] = phase + "-combined-raw.log"
|
||||
r.write(out / (phase + "-actual-phase.json"), row)
|
||||
print(json.dumps(dict(phase=phase, passed=row["passed"], error=row.get("validation_error"))), flush=True)
|
||||
r.require(row["passed"], "first strict phase failure: " + phase)
|
||||
if phase in ["writer", "reader"]:
|
||||
receipt = r.read(out / ("raw/" + phase + ".json"))
|
||||
r.require(receipt["checks"] > 0 and receipt["failures"] == 0,
|
||||
"S3 actual outcome receipt")
|
||||
r.require(sorted(receipt["branches"]) == CASES,
|
||||
"exact six independent Writer/Reader cases")
|
||||
r.require(sorted(receipt["slots"]) == SLOTS, "all formal point slots")
|
||||
r.require(receipt["pid"] == row["actual_pid"], "actual native PID/receipt")
|
||||
writer = r.read(out / "raw/writer.json")
|
||||
reader = r.read(out / "raw/reader.json")
|
||||
r.require(writer["pid"] != reader["pid"] and writer["profile"] != reader["profile"], "distinct formal processes")
|
||||
formal_after = {n: r.inventory(Path(p)) for n, p in writer["slots"].items()}
|
||||
r.require(formal_before == formal_after, "Reader leaves Writer packages unchanged")
|
||||
except BaseException as caught:
|
||||
error = type(caught).__name__ + ": " + str(caught)
|
||||
finally:
|
||||
cleanup, cleanup_errors = outer.finish_captured(outer.OWNED)
|
||||
try:
|
||||
r.write(out / "outer-owned-finally.json", dict(cleanup=cleanup, errors=cleanup_errors))
|
||||
except BaseException as caught:
|
||||
guards.append("finally receipt: " + type(caught).__name__ + ": " + str(caught))
|
||||
# Fixed guard always follows ownership cleanup, even if setup or an engine failed.
|
||||
try:
|
||||
fixed_guard()
|
||||
after = r.source_refs(root)
|
||||
r.write(out / "source_after.json", after)
|
||||
r.require(after == source_before, "whole product source unchanged")
|
||||
r.require(r.default_player(root) == protected, "default player unchanged")
|
||||
if (stage / "project.godot").exists():
|
||||
r.require(r.inventory(stage, stage=True) == dict(expected, **previous), "whole private source unchanged")
|
||||
if cache_before is not None:
|
||||
r.require(r.inventory(old / "stage/.godot") == cache_before, "original cache unchanged")
|
||||
if formal_before is not None:
|
||||
writer = r.read(out / "raw/writer.json")
|
||||
formal_after = {n: r.inventory(Path(p)) for n, p in writer["slots"].items()}
|
||||
r.require(formal_before == formal_after, "Writer formal packages unchanged after Reader, including failure")
|
||||
except BaseException as caught:
|
||||
guards.append(type(caught).__name__ + ": " + str(caught))
|
||||
pids = [p.pid for p in outer.OWNED]
|
||||
passed = (error is None and not guards and not cleanup_errors and len(phases) == 4
|
||||
and all(p["passed"] for p in phases) and len(pids) == len(set(pids)) == 4
|
||||
and all(p["engine_ended"] for p in cleanup))
|
||||
execution = dict(passed=passed, error=error, guard_errors=guards, phases=phases,
|
||||
actual_captured_PIDs=pids, cleanup=cleanup, cleanup_errors=cleanup_errors,
|
||||
formal_before=formal_before, formal_after=formal_after,
|
||||
whole_goal_accepted=False, unit_accepted=False)
|
||||
r.write(out / "raw-inventory.json", r.inventory(out / "raw"))
|
||||
r.write(out / "PID-set.json", dict(owner_runner_pid=os.getpid(), engine_PIDs=pids))
|
||||
r.write(out / "execution.json", execution)
|
||||
r.write(out / "runner-ended.json", dict(execution, ended=r.now()))
|
||||
print(json.dumps(dict(passed=passed, error=error, guard_errors=guards, output=str(out))))
|
||||
return 0 if passed else 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
raise SystemExit(main())
|
||||
except Exception as caught:
|
||||
print(type(caught).__name__ + ": " + str(caught), file=sys.stderr)
|
||||
raise SystemExit(2)
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"status": "NOTRUN",
|
||||
"task_id": "P04-01",
|
||||
"starting_submission": "55f1fb49aaefa5c1b86de1c3eda72392ef6b58f2",
|
||||
"source": "audit-support/p04-s4-source-r28",
|
||||
"plan": "p04-s4-plan-r28.json",
|
||||
"native": "p04-s4-native-r28",
|
||||
"regression_status": "NOTRUN",
|
||||
"native_status": "NOTRUN",
|
||||
"unit_complete": false,
|
||||
"D05": "pending",
|
||||
"files": {
|
||||
"s4/p04_s4_power_probe.gd": {
|
||||
"bytes": 26912,
|
||||
"sha256": "56f8e2786e3aff82097baa06afb4cd13e6e7c06c3d1afa43b9ee35c78a25a999"
|
||||
},
|
||||
"s4/run_s4_native.py": {
|
||||
"bytes": 23233,
|
||||
"sha256": "2d3be39d2b310abac058e27336ac816c234043a0a1dd7b2c52d84994907dd31b"
|
||||
},
|
||||
"s4/S4_ROOT_RUN.md": {
|
||||
"bytes": 21862,
|
||||
"sha256": "ff463e1e6ce8847f658eddc91f1cec3eab3bb8bcadd9a75272138e06083116a2"
|
||||
},
|
||||
"s4/phase_cut_regression.py": {
|
||||
"bytes": 3919,
|
||||
"sha256": "1362078fdd819d057f1f1bab369b9629a11e089a3453da8af4ce21cec251dbad"
|
||||
},
|
||||
"s4/receipt_lifecycle_fixtures.py": {
|
||||
"bytes": 12340,
|
||||
"sha256": "629571495787fdcda82a6a74d98649d4ea424d6fc9c27c5cafa55d4b2881c71f"
|
||||
},
|
||||
"p04_native_series.py": {
|
||||
"bytes": 75900,
|
||||
"sha256": "c1cc5d49dbf4c8422a2208297c665fde8abd569e2c9f8239603d8218ae1ed8fb"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
# S4 finite Root collection r28 — NOT RUN
|
||||
|
||||
This adds only the existing `power_stop` evidence obligation. It adds no counted
|
||||
condition, product change, acceptance or priority approval. Run once after this
|
||||
source batch is auto-submitted. Do not rerun C/S1/S2a/S2b/S3. Do not run the full
|
||||
product suite now. Preserve all existing raw and REPAIR17–21 history.
|
||||
|
||||
From the clean auto-submitted checkout, stage these three exact reviewed files
|
||||
in the fresh own-B source directory. Run the installed executable directly; do not stage or copy it. This staging is a Root action, not a worker
|
||||
execution. The wrapper refuses QA outside own B. QA and fixed inputs stay within registered B; only the exact installed engine is external; external manifest paths are lookup keys and
|
||||
provenance only. Never open their original production locations.
|
||||
|
||||
```sh
|
||||
export B=/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build
|
||||
export E=docs/plans/original-completion-2026-10-02/execution/p04-01
|
||||
export PYTHONDONTWRITEBYTECODE=1
|
||||
python3 "$E/s4/engine_identity_negative_fixtures.py"
|
||||
python3 "$E/s4/receipt_lifecycle_fixtures.py"
|
||||
mkdir "$B/audit-support/p04-s4-source-r28"
|
||||
cp "$E/s4/run_s4_native.py" "$E/s4/p04_s4_power_probe.gd" "$E/s4/S4_ROOT_RUN.md" "$B/audit-support/p04-s4-source-r28/"
|
||||
python3 "$B/audit-support/p04-s4-source-r28/run_s4_native.py" \
|
||||
--project-root "$B/audit-support/p04-r17-full-inputs-r01/product" \
|
||||
--output "$B/p04-s4-native-r28" --plan "$B/p04-s4-plan-r28.json" \
|
||||
--godot /Applications/Godot.app/Contents/MacOS/Godot --check-only
|
||||
python3 "$B/audit-support/p04-s4-source-r28/run_s4_native.py" \
|
||||
--project-root "$B/audit-support/p04-r17-full-inputs-r01/product" \
|
||||
--output "$B/p04-s4-native-r28" --plan "$B/p04-s4-plan-r28.json" \
|
||||
--godot /Applications/Godot.app/Contents/MacOS/Godot
|
||||
```
|
||||
|
||||
Root must stop if either fixture or check-only fails; the commands are not an
|
||||
unconditional batch. Both fixtures start zero engines. The check-only invocation
|
||||
only prepares the pinned plan: zero engine launches. The final invocation
|
||||
is one collection, exactly four sequential owned processes: import,
|
||||
parse, Writer, Reader. Each retains the framework's native diagnostics,
|
||||
ownership, exit/end checks, full stream SHA, default-player protection, private
|
||||
profiles, source-before/after guards and cleanup. Failure stops the collection;
|
||||
do not overwrite or silently repeat it. The ordinary framework creates its
|
||||
normal private user profiles; these are process outputs, not external source
|
||||
inputs. Reader uses only the existing IO1 formal package mapping.
|
||||
|
||||
Exact input footprints:
|
||||
|
||||
- Historical wrapper hashes do not pin this repair. The fresh plan pins
|
||||
its exact bytes and Stage R checks them against the current source checkout.
|
||||
- `B/audit-support/p04-r17-full-inputs-r01/MANIFEST.json`, SHA256
|
||||
`a15f29fa12537f6d8aaf7cabea390922a9f16e3eb9c10981e5742aafc8c4c8a8`.
|
||||
- All 1338 files below that relocation's `product/`, each matched to the
|
||||
manifest by bytes/SHA. No source is omitted and no old path is followed.
|
||||
- Its `fixed/6ce6b28c5985e8a4f13b78bfeb1f1e09f6216d874fb01e0656ec1633bfa01cd7/completion_marker_contracts.json`.
|
||||
- `B/audit-support/p04-review-repair22-r01/ACTUAL_CURRENT_PUBLIC_POWER_FUNCTIONS.json`.
|
||||
- The three staged S4 files above, pinned by the plan, and the installed executable
|
||||
`/Applications/Godot.app/Contents/MacOS/Godot`, pinned directly against the
|
||||
constant SHA before launch, during collection, and explicitly after cleanup.
|
||||
Runner-input retains an immutable initial descriptor and observation prefix;
|
||||
execution and engine-identity retain the final before/during/after identity. The only stage override
|
||||
is `tests/s3/test_runtime_staff_assignment.gd` with the S4 probe. Harness and
|
||||
kitchen fixture are the unchanged product files; inherited sources are
|
||||
included in the full1338 map. The parse roster is all316 `.gd` inputs.
|
||||
- Fresh mode is the default: no cache receipt or cache directory is accessed,
|
||||
read or cloned. The private `stage/.godot` must be absent immediately before
|
||||
the actual full import. All316 scripts are parsed before Writer and Reader.
|
||||
- The engine SHA256 is
|
||||
`c7cccbf8fb143e34e02fd6521e09be2c2b974f0d5db080b19071c9c570718ccf`,
|
||||
required directly of the installed executable. Fresh mode does not obtain engine identity from a cache.
|
||||
- Optional `--cache-run` retains the registered path, passed-run, complete-source
|
||||
and engine identity guards. The known red run remains ineligible. The commands
|
||||
above use no cache. Preserve the old r22 staging and plan unchanged. r22 is an immutable failed
|
||||
proof and remains ineligible. Its diagnostics are at
|
||||
`audit-support/p04-root-d393-s4-r01/ENGINE_STAGING_DIAGNOSTICS.json`.
|
||||
|
||||
Exact output footprints are `B/p04-s4-plan-r28.json` and the fresh
|
||||
`B/p04-s4-native-r28/**`: private selected source stage, complete phase receipts
|
||||
and logs, raw JSON/Variant points and all phase observations, full source maps,
|
||||
PID and cleanup receipts. `formal/{writer,reader}/S4-{boundary,held,recovered}/`
|
||||
mirrors all four formal members (`journal.log`, `manifest.json`, `thumbnail.png`,
|
||||
`world.bin`) with a provenance-only `formal-relocation.json`. R reads these
|
||||
own-B mirrors, never profile paths from old manifests.
|
||||
|
||||
Scenario and source-derived timing:
|
||||
|
||||
1. Use the existing public cafeteria + physical FLEX kitchen fixture and normal
|
||||
DAY server commute. Queue using the definition's original price and duration;
|
||||
advance naturally to duration−1 at959. Derive the cafeteria's sole neighbour
|
||||
from the actual graph and call public `disconnect_nodes` on exactly that edge.
|
||||
2. Advance normally to960. The source's modulo10 evaluation finds an isolated
|
||||
consumer with zero generation/storage. Normal `mana_power_projection.apply`
|
||||
makes only the cafeteria unpowered; preparation reaches `NO_POWER` with its
|
||||
dependency operating. Save **boundary** at960, then **held** at961. Mana stays
|
||||
positive; operator stays open; linked kitchen is Facility `OPEN` and network
|
||||
consumer `ACTIVE`. No closure, damage, shift or exit triggers accompany outage.
|
||||
3. At961 reconnect the same edge. Verify the complete original edge set and
|
||||
graph invariants. Advance each minute, retaining full phase raw. The saved
|
||||
hold deadline is960+10=970; the next permitted normal evaluation is970.
|
||||
Progress, reservation and sale stay held through969. Save **recovered** at970
|
||||
after one completion/payment. Resend connect_nodes on the same edge at970,
|
||||
comparing the complete Runtime before/after;971 is the duplicate guard. Ordinary guest
|
||||
redecision/despawn after completed service is retained in raw, never forced
|
||||
or suppressed; the outage and held service have no exit confound.
|
||||
4. A distinct Reader copies all four members at each point. Normal SaveService
|
||||
load and Runtime restore must reproduce the full typed snapshot, including
|
||||
graph and all unrelated fields. No canonicalization, dropped fields, forced
|
||||
evaluation or hold bypass. Replay from the960 formal boundary through961,
|
||||
reconnect,970 and971; compare complete typed points and phase states. No
|
||||
additional condition is credited for this formal preservation check.
|
||||
|
||||
`REPAIR22_SOURCE_FACTS.json` contains full actual source bodies and graph restore
|
||||
invariants. The source normalizes saved node IDs/reasons, checks valid endpoints,
|
||||
rebuilds symmetric edges, preserves revision/evaluation time and checks storage
|
||||
capacity and consumer allocation. Transient `_last_report` is cleared on normal
|
||||
restore and is not part of Runtime snapshot equality. The derived
|
||||
`related_facility_operating` flag is measured separately and checked after restore.
|
||||
|
||||
This source-only dispatch authorizes no worker execution. Root stages the three
|
||||
files, creates the r28 plan with --check-only, then collects once to the fresh
|
||||
r28 output. Do not read or write r24 outputs, cleanup, stages or plans.
|
||||
Do not rerun S1-S3, C or full178. No engine copy, signing, xattr,
|
||||
codesign call or security-setting change is part of this procedure.
|
||||
Native evidence, registered validation, independent review, support53cb rebind
|
||||
and formal acceptance remain pending. D05 remains pending.
|
||||
|
||||
## REPAIR26 receipt lifecycle audit (static; NOT RUN)
|
||||
|
||||
All names below are relative to the fresh output unless marked otherwise.
|
||||
`r.write` is the unchanged exclusive `open('x')` writer in
|
||||
`tools/run_all_tests.py`. No retry, overwrite, replace or atomic rename is used
|
||||
for a JSON proof. A write attempt may fail or leave a partial file; it is never
|
||||
retried. “At most once” describes wrapper control flow, not a durability claim.
|
||||
The initial input is serialized once with `engine=engine_descriptor`,
|
||||
`engine_identity_before=engine_descriptor` and a deep-copied initial identity.
|
||||
`repin()` itself is unchanged (it returns None); each per-phase receipt uses
|
||||
the descriptor it just appended and the index of that exact observation.
|
||||
|
||||
| Writer / destination | Maximum attempts per run / uniqueness | Success and failure reachability |
|
||||
| --- | --- | --- |
|
||||
| `r.write(args.plan)` outside output | 1, check-only branch returns immediately | Native branch only reads the exact plan; existing plan rejected in check-only |
|
||||
| `runner-started.json` | 1 | First write inside protected setup; exception enters cleanup |
|
||||
| `source_before.json` | 1 | Setup before source copy; no fallback write |
|
||||
| `selected-source-map.json` | 1 | Setup before source copy; no fallback write |
|
||||
| `r.copy_source` to `stage/<source name>` | 1 per unique source-map key | Fresh stage; loop stops on setup failure; source files are not proof receipts |
|
||||
| `write_bytes` to `bootstrap/<name>` | 1 per pinned bootstrap key | The reviewed map names `profile_guard.gd` and `parse_full_dependencies.gd`; setup only |
|
||||
| `/bin/cp -c` to `stage/.godot/<name>` | 1 per cache inventory key | Optional eligible-cache branch only; fresh mode never accesses cache |
|
||||
| `cache-reuse.json` | 0 or 1 | Only after all optional cache clones and guards |
|
||||
| `parse-paths.json` | 1 | Setup after 316-script guard |
|
||||
| `contracts.json` | 1 | Setup after unchanged suite-policy guard |
|
||||
| `runner-input.json` | 1 | Setup; no final read/rewrite; absent if earlier setup fails |
|
||||
| `engine-identity-before-<phase>.json` | 1 each for import, parse, writer, reader | Immediately after that phase's repin; failure stops loop, never resumes it |
|
||||
| `write_bytes` to `writer-combined-raw.log`, `reader-combined-raw.log` | 1 each | Binary copy immediately after returned phase row, even a failed row if it has a log; later phases never reuse names |
|
||||
| `<phase>-actual-phase.json` | 1 each for four unique phases | Before `require(row['passed'])`; mid-loop failure retains its row and stops later phases |
|
||||
| `outer-owned-finally.json` | 1 | Single finally after setup/loop success or failure; cleanup called once; cleanup exception recorded; receipt write exception becomes guard error |
|
||||
| `source_after.json` | 0 or 1 | Single post-cleanup guarded block, after fixed_guard/source_refs succeed; later guard failure does not re-enter or retry the write |
|
||||
| `shutil.copyfile` to `formal/<role>/<slot>/<member>` | 1 per tuple | Only preliminary pass; two unique roles, unique slot map keys, four unique member keys, fresh target directory; exception stops collection |
|
||||
| `formal-relocation.json` | 0 or 1 | Only after successful complete formal mirror |
|
||||
| `raw-inventory.json` | 0 or 1 | Once after mirror branch, including setup/phase/cleanup failure; earlier output-collection exception can skip it |
|
||||
| `PID-set.json` | 0 or 1 | Once after raw inventory; earlier output-collection exception can skip it |
|
||||
| `engine-identity.json` | 1 | One final attempt after cleanup and both after-cleanup/after-output-collection repin attempts, including caught setup/phase/cleanup/collection failures; own write failure records guard error and makes both passed fields false |
|
||||
| `execution.json` | 1 | After identity attempt, with final observations, identity and guard errors |
|
||||
| `runner-ended.json` | 1 | After execution; no retry if final receipt storage itself fails |
|
||||
|
||||
The wrapper has no `write_text` call. The unchanged `execute_phase` dependency
|
||||
also writes the following. Its shared text log is an explicit exception to
|
||||
write-once naming, and is not presented as two independent phase proofs.
|
||||
|
||||
| Dependency destination | Writer and lifecycle |
|
||||
| --- | --- |
|
||||
| `<phase>-startup-profile-before.json` | Exclusive `write`, once per distinct phase before engine start |
|
||||
| `<phase>-profile.json` | Guard report path for parse/writer/reader, one per phase; actual Guard checks file absence before writing |
|
||||
| `starters/<phase>.gd` | `write_text`, once each for writer/reader |
|
||||
| `<phase>/combined-raw.log` | `write_text`, once each for import/parse |
|
||||
| `tests__s3__test_runtime_staff_assignment.gd.log` (general `out/<test>.log`) | Both writer and reader call `write_text` on this same per-test name; reader replaces the shared text. Wrapper preserves separate binary copies as `writer-combined-raw.log` and `reader-combined-raw.log`, retaining `strict_runner_log_path` and the original log hash |
|
||||
| `<phase>-receipt.json` | Exclusive `write`, once per phase after its finally and guards |
|
||||
| `stage/project.godot` | `write_bytes` for private startup settings and restoration in each phase finally; mutable private source, not immutable proof |
|
||||
| `<phase>/**`, private profiles, raw QA outputs | Delegated owned-native capture/engine/QA outputs; not wrapper `r.write`/`write_bytes`/`write_text` calls; their existing ownership/diagnostic/formal guards remain unchanged |
|
||||
|
||||
Path coverage: success traverses each distinct phase once. A failed parse row is
|
||||
recorded, then `require(row['passed'])` jumps to the single finally; no writer or
|
||||
reader names are used. A protected setup failure also enters that finally, then
|
||||
attempts post-cleanup guards and final identity/execution/end receipts. Cleanup
|
||||
exceptions are caught separately, set `cleanup_errors`, and do not prevent final
|
||||
receipts. A post-cleanup fixed/source guard failure is caught without retrying
|
||||
`source_after`; formal/output-collection exceptions now become guard errors and
|
||||
still reach the final repin and identity receipt. A final identity write failure
|
||||
is caught without touching existing bytes and forces failure in execution/end.
|
||||
|
||||
Admission failures before the protected setup (invalid plan/product/engine,
|
||||
live engine, insufficient disk, existing output, directory creation failure)
|
||||
remain fail-closed and do not promise final receipts. They have no repeated
|
||||
wrapper proof write. Persistent storage failure writing execution/end can still
|
||||
prevent a complete terminal receipt pair; no code can guarantee receipt storage
|
||||
when that storage fails. Such runs cannot provide accepted evidence.
|
||||
|
||||
## Fixture scope and remaining gaps
|
||||
|
||||
`receipt_lifecycle_fixtures.py` imports the real wrapper and standard writer.
|
||||
It builds 1338 synthetic source entries including 316 scripts in resolved fresh
|
||||
temporary paths; engine identity, template_guard, disk/default-player, helpers
|
||||
and phase execution are mocked. Four distinct fake PIDs and complete synthetic
|
||||
formal slot members exercise wrapper guards and copying without starting an
|
||||
engine. The actual identity section of `S4Series.__init__` is compiled from its
|
||||
AST, and actual `verify_s4_engine` checks a non-executable temporary data file
|
||||
under fixture-only patched constants. Other verifier sections are not claimed
|
||||
as covered by this synthetic fixture.
|
||||
|
||||
Cases cover success after check-only, parse failure, cleanup exception, setup
|
||||
copy failure, post-cleanup collection failure and final identity write failure.
|
||||
Every exclusive write attempt is counted. Input bytes are compared with their
|
||||
creation bytes before negative mutation. Fresh-output refusal, each missing or
|
||||
tampered phase receipt and mismatched initial-before are negative checks. The
|
||||
fixture's deliberate proof mutations are confined to its disposable temporary
|
||||
files. No registered plan/output/stage is read or changed.
|
||||
|
||||
Reasoning only about the 7af6 wrapper: import writes `engine-identity.json` via
|
||||
exclusive create. Before parse, the same name raises FileExistsError, so the
|
||||
four-phase success assertion cannot pass. After cleanup the old final write
|
||||
tries that same name again and escapes main, preventing final execution/end.
|
||||
Even if that collision alone were removed, the old runner-input final rewrite
|
||||
would hit the exclusive writer, and the required initial/per-phase receipts
|
||||
are absent. No 7af6 execution was performed to establish this reasoning.
|
||||
|
||||
Worker performed source inspection and file edits only: no fixtures, check-only,
|
||||
tests, Git, engines, Runner, agents or commits. Root still must run both fixtures
|
||||
and check-only before any native r27 collection, then exact-version verification
|
||||
and independent review. The registered validation gate remains unchanged at
|
||||
7af6, exit 2, fresh MANIFEST missing; this is the supplied gate record, not a new
|
||||
worker run. Native/product proof, full formal checks, D05 and original P04-01
|
||||
acceptance remain pending. This repair is submitted preparation, not completion.
|
||||
|
||||
|
||||
## REPAIR27 snapshot staff expectation (source-only; NOT RUN)
|
||||
|
||||
Root supplied the r26 result: 15 failures of 934 S4 checks, all at
|
||||
no shift confound; engine-identity/lifecycle fixtures and cleanup passed.
|
||||
This is supplied history, not a worker execution. Preserve r26 raw output,
|
||||
formal records, logs, source stage and plan unchanged.
|
||||
|
||||
The r27 probe SHA256 is `b5e5c81a0762427880416759e30e0a0144ce26749f1db1f26837c3287eeecf35`.
|
||||
FacilityInstance.create_snapshot serializes active_staff_ids through
|
||||
_names_to_strings as Array[String]. The snapshot expectation is now explicit
|
||||
SNAPSHOT_SERVERS: Array[String], containing exactly server a then server b.
|
||||
Runtime registration and attendance still use unchanged SERVERS:
|
||||
Array[StringName]. No values are normalized, fields dropped or invariants
|
||||
relaxed. Inspection of all S4 StringName constant uses found no other snapshot
|
||||
comparison with this mismatch; reservation IDs already compare with
|
||||
String(request_id). S3 lines 111–118 compare live runtime values and remain
|
||||
unchanged.
|
||||
|
||||
The wrapper dynamically pins QA bytes through pin(QA) into the plan and
|
||||
selected source map. S4Series dynamically hashes the current probe and checks
|
||||
the exact receipt/map binding; only its RUNS['s4'] binding advances to r27.
|
||||
The documented hash does not replace dynamic verification.
|
||||
Engine identity and receipt lifecycle logic, the a61 checker, frozen C and all
|
||||
1338 product inputs remain unchanged. The lifecycle mock's disposable output
|
||||
and plan names advance to r27; its behavior is unchanged.
|
||||
|
||||
Worker performed source inspection, edits and SHA256 calculation only. No
|
||||
fixtures, tests, check-only, Git, engine, Runner, agents or commits were run.
|
||||
Root re-runs only the fresh S4 collection and any needed fixture validation,
|
||||
using the r27 commands above. Exact-version evidence, registered validation,
|
||||
independent review, D05 and original acceptance remain pending; this is
|
||||
submitted preparation, not completion.
|
||||
|
||||
## REPAIR28 phase cut (source-only; NOTRUN)
|
||||
|
||||
Root's supplied r27 own-B diagnosis is preserved as history: import, parse and
|
||||
Writer passed (934 checks, zero failures); Reader ran 1445 checks with exactly
|
||||
one failure, complete phase count. ACTUAL_TYPED_PHASE_DIAGNOSIS.json reports
|
||||
all 121 actual Reader phase .var files byte-identical to matching Writer files.
|
||||
This worker did not open or rerun those raw materials. Keep all r27 failed raw,
|
||||
formals, logs, source stages, plans and diagnosis unchanged.
|
||||
|
||||
The kernel applies commands at the old minute, advances absolute_minute, then
|
||||
emits TIME_BOUNDARIES and the remaining phases. The new helper includes minute
|
||||
960 APPLY_COMMANDS using the enum key expression used by the Writer. It also
|
||||
includes all later rows. Before byte comparison the probe asserts the exact
|
||||
121-entry kernel roster, uniqueness, and Writer order. Both existing complete
|
||||
phase count and complete typed var_to_bytes equality checks remain unchanged.
|
||||
Writer row data, typed staff fix, runtime SERVERS, Clock and raw format stay
|
||||
unchanged.
|
||||
|
||||
The current RUNS s4 binding, wrapper defaults and disposable lifecycle fixture
|
||||
path names use r28. Only these path strings change in the wrapper and lifecycle
|
||||
fixture; their engine/lifecycle behavior is unchanged. Stage the three current
|
||||
files using the commands at the top to create fresh source-r28, plan-r28 and
|
||||
native-r28 paths. Do not reuse r27 as passing evidence.
|
||||
|
||||
Regression control status: NOTRUN. Root supplies full paths to ALL 132 actual
|
||||
r27 Writer phase .var files and ALL 121 actual Reader phase .var files:
|
||||
python3 "$E/s4/phase_cut_regression.py" --writer <full Writer phase paths...> --reader <full Reader phase paths...>
|
||||
Use actual own-B raw paths supplied by Root, not fabricated fixtures or old
|
||||
external provenance paths. Paths may be in any argument order; the control
|
||||
reconstructs kernel order from typed minute/phase, checks the complete Writer
|
||||
and Reader rosters, then compares unchanged full bytes. It requires old cut
|
||||
120/rejected, new cut 121/equal, deleted boundary APPLY_COMMANDS/rejected and
|
||||
duplicate/rejected. Mutations are in-memory lists only. Its result is a raw
|
||||
regression control, never an engine/native/acceptance PASS. Missing raw paths
|
||||
remain a Root execution input, not a worker-generated substitute.
|
||||
|
||||
Worker made source edits and calculated bindings only; no tests, fixtures,
|
||||
Git, engine, Runner, agents or commits. The normal validation on submitted
|
||||
55f1fb49aaefa5c1b86de1c3eda72392ef6b58f2 ran once and exited 2 because the current
|
||||
MANIFEST is missing (Root-supplied history). The gate, checker a61, 1338 inputs,
|
||||
C/S1-3/full178 artifacts, five carried formal review findings, and frozen
|
||||
original 13/88 main 9b remain unchanged. D05, independent review, exact-version
|
||||
native proof and original unit acceptance remain pending. This repair does
|
||||
not complete the unit.
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Root-only engine identity controls; tests NOT RUN by worker."""
|
||||
import copy
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from unittest.mock import patch
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
HERE = Path(__file__).resolve().parent
|
||||
spec = importlib.util.spec_from_file_location("series", HERE.parent / "p04_native_series.py")
|
||||
series = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(series)
|
||||
spec = importlib.util.spec_from_file_location("s4", HERE / "run_s4_native.py")
|
||||
s4 = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(s4)
|
||||
|
||||
|
||||
def run():
|
||||
# Read only the real installed executable. Never copy or write an engine.
|
||||
pin = s4.engine_pin(s4.ENGINE)
|
||||
identity = dict(before=pin, during=[pin], after=pin)
|
||||
results = []
|
||||
|
||||
def reject(label, action):
|
||||
try:
|
||||
action()
|
||||
except (ValueError, OSError, KeyError, TypeError) as caught:
|
||||
results.append(dict(case=label, result="rejected", error=str(caught)))
|
||||
else:
|
||||
raise AssertionError("negative accepted: " + label)
|
||||
|
||||
# Same bytes/SHA cannot admit an own-B bare copy or another outside-B path.
|
||||
for label, path in [
|
||||
("copied bare engine in B with same SHA", s4.BUILD / "audit-support/p04-s4-source-r26/Godot"),
|
||||
("other outside-B path", Path("/Applications/OtherGodot.app/Contents/MacOS/Godot")),
|
||||
]:
|
||||
other = dict(pin, path=str(path))
|
||||
reject(label, lambda other=other: series.verify_s4_engine(other, identity))
|
||||
reject(label + " wrapper", lambda path=path: s4.engine_pin(path))
|
||||
reject("SHA mismatch", lambda: series.verify_s4_engine(dict(pin, sha256="0" * 64), identity))
|
||||
with patch.object(s4, "ENGINE_SHA256", "0" * 64):
|
||||
reject("wrapper SHA mismatch", lambda: s4.engine_pin(s4.ENGINE))
|
||||
changed = copy.deepcopy(identity)
|
||||
changed["after"]["inode"] += 1
|
||||
reject("after-identity mismatch", lambda: series.verify_s4_engine(pin, changed))
|
||||
reject("wrapper after-identity mismatch",
|
||||
lambda: s4.engine_pin(s4.ENGINE, dict(pin, inode=pin["inode"] + 1)))
|
||||
series.verify_s4_engine(pin, identity)
|
||||
results.append(dict(case="exact installed path", result="accepted"))
|
||||
return dict(scope="read-only engine identity fixtures", results=results, formal_acceptance=False)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(json.dumps(run(), indent=2, sort_keys=True))
|
||||
@@ -0,0 +1,452 @@
|
||||
extends "res://tests/s3/test_runtime_actor_hardening.gd"
|
||||
|
||||
const CASES: Array[String] = ["S4"]
|
||||
const SEEDS: Dictionary = {"S4": 420440}
|
||||
const GUEST: StringName = &"p04.s4.guest.a"
|
||||
const REQUEST: StringName = &"p04.s4.request.a"
|
||||
const QUEUED_GUEST: StringName = &"p04.s4.guest.b"
|
||||
const QUEUED_REQUEST: StringName = &"p04.s4.request.b"
|
||||
const SERVERS: Array[StringName] = [&"p04.order.server.a", &"p04.order.server.b"]
|
||||
const SNAPSHOT_SERVERS: Array[String] = ["p04.order.server.a", "p04.order.server.b"]
|
||||
var _evidence_dir: String = ""
|
||||
var _mode: String = ""
|
||||
var _case: String = ""
|
||||
var _serial: int = 0
|
||||
var _facility_id: StringName = &""
|
||||
var _service_id: StringName = &""
|
||||
var _boundary: int = 960
|
||||
var _price: int = 0
|
||||
var _duration: int = 0
|
||||
var _patience: int = 0
|
||||
var _reserve: int = 0
|
||||
var _recovery: Dictionary = {}
|
||||
var _commands: Array = []
|
||||
var _results: Array = []
|
||||
var _receipts: Array = []
|
||||
var _phases: Array = []
|
||||
var _points: Dictionary = {}
|
||||
var _branches: Dictionary = {}
|
||||
var _slots: Dictionary = {}
|
||||
|
||||
func _run() -> void:
|
||||
_evidence_dir = OS.get_environment("THE_TREE_S4_EVIDENCE")
|
||||
_mode = OS.get_environment("THE_TREE_S4_MODE")
|
||||
if _evidence_dir.is_empty() or _mode not in ["writer", "reader"]:
|
||||
quit(2)
|
||||
return
|
||||
_expect(DirAccess.make_dir_recursive_absolute(_evidence_dir) == OK, "external evidence directory")
|
||||
if _mode == "writer":
|
||||
for branch: String in CASES:
|
||||
_case = branch
|
||||
_writer_case()
|
||||
else:
|
||||
_reader()
|
||||
_write(_mode, {&"pid": OS.get_process_id(), &"profile": OS.get_user_data_dir(), &"slots": _slots,
|
||||
&"branches": _branches, &"checks": _checks, &"failures": _failures,
|
||||
&"whole_goal_accepted": false, &"unit_accepted": false}, true)
|
||||
_cleanup()
|
||||
print("S4 power: %d checks, %d failures" % [_checks, _failures])
|
||||
quit(1 if _failures else 0)
|
||||
|
||||
func _fresh() -> void:
|
||||
_serial = 0
|
||||
_facility_id = &""
|
||||
_service_id = &""
|
||||
_boundary = 960
|
||||
_price = 0
|
||||
_duration = 0
|
||||
_patience = 0
|
||||
_reserve = 0
|
||||
_recovery = {}
|
||||
_commands = []
|
||||
_results = []
|
||||
_receipts = []
|
||||
_phases = []
|
||||
_points = {}
|
||||
_reset_runtime(SEEDS[_case])
|
||||
_runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
|
||||
_runtime.command_completed.connect(func(result: CommandResult) -> void:
|
||||
_results.append({&"minute": _clock.absolute_minute, &"result": result.to_dictionary()})
|
||||
_expect(result.is_success(), "actual public command completion " + str(result.command_id))
|
||||
)
|
||||
|
||||
func _cmd(kind: StringName, payload: Dictionary, at: int = -1) -> CommandResult:
|
||||
_serial += 1
|
||||
var key: StringName = StringName("p04.s4.%s.command.%d" % [_case, _serial])
|
||||
var result: CommandResult = _runtime.submit_command(kind, payload, &"player", &"", 10, at, key)
|
||||
_commands.append({&"serial": _serial, &"kind": kind, &"payload": payload.duplicate(true),
|
||||
&"at": at, &"issued_at": _clock.absolute_minute, &"idempotency_key": key, &"result": result.to_dictionary()})
|
||||
_expect(result.is_success(), "public command " + String(kind))
|
||||
return result
|
||||
|
||||
func _queue(guest_id: StringName, request_id: StringName, exit_at: int) -> void:
|
||||
_cmd(&"spawn_guest", {&"guest_id": guest_id, &"guest_type_id": &"traveler", &"gold": _price * 2,
|
||||
&"options": {&"state": GuestRuntime.State.DECIDING, &"current_cell": Vector3i.ZERO, &"planned_exit_at": exit_at}})
|
||||
_cmd(&"create_service_request", {&"facility_id": _facility_id, &"request_id": request_id, &"guest_ids": [guest_id]})
|
||||
var guest: GuestRuntime = _runtime.guest_service.get_guest(guest_id)
|
||||
var request: ServiceRequest = _runtime.facility_service.get_request(request_id)
|
||||
_expect(guest != null and request != null, "actual Guest and request exist")
|
||||
if guest == null or request == null: return
|
||||
_expect(guest.state == GuestRuntime.State.QUEUING and guest.queue_request_id == request_id, "actual queued ownership before advancing clock")
|
||||
_expect(request.quoted_price == _price and request.service_minutes == _duration and request.reserved_inventory_ids.size() == 1, "definition price/duration and one meal reservation")
|
||||
for id: StringName in request.reserved_inventory_ids:
|
||||
var reservation: InventoryReservation = _runtime.inventory_service.get_reservation(id)
|
||||
_expect(reservation != null and reservation.is_active() and reservation.requester_id == request_id, "public queue actually reserves meal")
|
||||
|
||||
func _prepare() -> bool:
|
||||
_fresh()
|
||||
var facility: FacilityInstance = _build_cafeteria(true)
|
||||
if facility == null: return false
|
||||
_facility_id = facility.instance_id
|
||||
var definition: FacilityDefinition = _runtime.definition_registry.get_facility(facility.definition_id)
|
||||
_price = definition.base_price
|
||||
_duration = definition.base_service_minutes
|
||||
_patience = _runtime.guest_service.get_definition(&"traveler").base_patience
|
||||
for id: StringName in SERVERS:
|
||||
_cmd(&"register_staff", {&"staff_id": id, &"role_id": &"server", &"options": {&"state": StaffRuntime.State.OFF_DUTY, &"current_cell": Vector3i.ZERO}})
|
||||
_cmd(&"set_staff_work_assignment", {&"staff_id": id, &"template_id": &"DAY", &"facility_id": _facility_id})
|
||||
_clock.advance_minutes(_boundary - _duration - 1 - _clock.absolute_minute)
|
||||
_expect(facility.active_staff_ids == SERVERS, "normal public staff attendance")
|
||||
_expect(_stock(facility.local_inventory_id, &"ingredient/basic_meal", 1, &"p04.s4.stock"), "one actual meal")
|
||||
_queue(GUEST, REQUEST, _boundary + _duration)
|
||||
_clock.advance_one_minute()
|
||||
var active: Array = _runtime.facility_service.get_active_services(_facility_id)
|
||||
_expect(active.size() == 1, "one started service")
|
||||
if active.size() != 1: return false
|
||||
var service: ServiceInstance = active[0]
|
||||
_service_id = service.service_id
|
||||
_expect(service.started_at + _duration == _boundary, "source definition duration aligns completion")
|
||||
_clock.advance_minutes(_boundary - 1 - _clock.absolute_minute)
|
||||
var graph: Dictionary = _runtime.mana_network_service.create_snapshot()
|
||||
var adjacent: Array = []
|
||||
for edge: Array in graph[&"edges"]:
|
||||
if edge.has(String(_facility_id)):
|
||||
adjacent.append(edge[1] if edge[0] == String(_facility_id) else edge[0])
|
||||
_expect(adjacent.size() == 1, "actual sole cafeteria neighbour")
|
||||
if adjacent.size() != 1: return false
|
||||
_recovery = {&"neighbour": StringName(adjacent[0]), &"original_graph": graph,
|
||||
&"interval": ManaNetworkService.EVALUATION_INTERVAL_MINUTES,
|
||||
&"hold": ManaNetworkService.MINIMUM_OFF_HOLD_MINUTES}
|
||||
_expect(_boundary % int(_recovery[&"interval"]) == 0, "boundary is normal evaluation")
|
||||
_expect(_runtime.mana_network_service.disconnect_nodes(_facility_id, _recovery[&"neighbour"]), "public disconnect exact existing edge")
|
||||
_expect(_runtime.mana_network_service.validate_invariants()[&"valid"], "symmetric cut graph invariants")
|
||||
_reserve = _state.get_resource(&"mana")
|
||||
_expect(_reserve > 0 and service.elapsed_minutes == _duration - 1, "positive mana and natural duration-1 progress")
|
||||
return _clock.absolute_minute == _boundary - 1
|
||||
|
||||
func _connect_observations() -> void:
|
||||
_runtime.economy_service.transaction_posted.connect(func(id: StringName, entries: Array) -> void:
|
||||
var rows: Array = []
|
||||
for entry: LedgerEntry in entries: rows.append(entry.to_dictionary())
|
||||
_receipts.append({&"id": id, &"minute": _clock.absolute_minute, &"entries": rows})
|
||||
)
|
||||
_clock.tick_phase_started.connect(func(phase: int, minute: int) -> void:
|
||||
var label: String = "phase-%d-%s" % [minute, SimulationKernel.TickPhase.keys()[phase]]
|
||||
var row: Dictionary = _capture()
|
||||
row[&"phase"] = SimulationKernel.TickPhase.keys()[phase]
|
||||
_assert_phase(row)
|
||||
_phases.append(_write(label, row))
|
||||
)
|
||||
|
||||
func _assert_phase(point: Dictionary) -> void:
|
||||
# Every phase is retained as complete typed raw; semantics are checked at points.
|
||||
_expect(point[&"snapshot"][&"game_state"][&"resources"][&"mana"] > 0, "no global depletion")
|
||||
|
||||
func _capture() -> Dictionary:
|
||||
var snapshot: Dictionary = _runtime.create_snapshot()
|
||||
var operations: Dictionary = snapshot[&"systems"][&"runtime"][&"operations"]
|
||||
var facility: FacilityInstance = _runtime.facility_service.get_instance(_facility_id)
|
||||
var service: ServiceInstance = _runtime.facility_service.get_service(_service_id)
|
||||
var event: EventInstance = _runtime.event_service.get_instance(&"p04.destroy.instance")
|
||||
var kitchen: FacilityInstance = _runtime.facility_service.get_instance(facility.related_facility_id)
|
||||
var actors: Dictionary = {}
|
||||
var requests: Dictionary = {}
|
||||
var wallets: Dictionary = {}
|
||||
for id: StringName in [GUEST, QUEUED_GUEST]:
|
||||
var guest: GuestRuntime = _runtime.guest_service.get_guest(id)
|
||||
actors[String(id)] = guest.create_snapshot() if guest != null else {}
|
||||
wallets[String(id)] = _runtime.economy_service.get_balance(StringName("GUEST_WALLET:" + String(id)))
|
||||
for id: StringName in [REQUEST, QUEUED_REQUEST]:
|
||||
var request: ServiceRequest = _runtime.facility_service.get_request(id)
|
||||
requests[String(id)] = request.create_snapshot() if request != null else {}
|
||||
var reservations: Array = []
|
||||
for reservation: InventoryReservation in _runtime.inventory_service.get_reservations(false): reservations.append(reservation.create_snapshot())
|
||||
var ledger: Array = []
|
||||
for entry: LedgerEntry in _runtime.economy_service.get_entries(): ledger.append(entry.to_dictionary())
|
||||
return {&"snapshot": snapshot, &"minute": _clock.absolute_minute,
|
||||
&"inputs": {&"case": _case, &"seed": SEEDS[_case], &"serial": _serial, &"facility_id": _facility_id, &"service_id": _service_id,
|
||||
&"boundary": _boundary, &"price": _price, &"duration": _duration, &"patience": _patience, &"reserve": _reserve, &"recovery": _recovery.duplicate(true),
|
||||
&"kitchen_premise": "ActorHardening physical FLEX fixture; not natural hire/unlock"},
|
||||
&"related_facility_operating": facility.related_facility_operating, &"kitchen": kitchen.create_snapshot(), &"graph_invariants": _runtime.mana_network_service.validate_invariants(),
|
||||
&"facility": facility.create_snapshot(), &"service": service.create_snapshot() if service != null else {},
|
||||
&"event": event.create_snapshot() if event != null else {}, &"guests": actors, &"requests": requests, &"wallets": wallets,
|
||||
&"reservations": reservations, &"ledger": ledger, &"receipts": _receipts.duplicate(true),
|
||||
&"player_cash": _runtime.economy_service.get_balance(_runtime.PLAYER_CASH_ACCOUNT),
|
||||
&"available_meals": _runtime.inventory_service.available_quantity(facility.local_inventory_id, &"ingredient/basic_meal"),
|
||||
&"active_services": _runtime.facility_service.get_active_services(_facility_id).size(),
|
||||
&"operations_enabled": operations[&"enabled"], &"saved_coordinator": operations[&"coordinator"],
|
||||
&"actual_coordinator_absent": _runtime.operations_coordinator == null, &"unrelated_getters_called": false}
|
||||
|
||||
func _labels() -> Array[String]:
|
||||
return ["boundary", "held", "recovered"]
|
||||
|
||||
func _advance(label: String) -> void:
|
||||
if label == "duplicate_guard":
|
||||
var before_replay: Dictionary = _runtime.create_snapshot()
|
||||
var accepted: bool = _runtime.mana_network_service.connect_nodes(_facility_id, _recovery[&"neighbour"])
|
||||
var after_replay: Dictionary = _runtime.create_snapshot()
|
||||
_expect(accepted and var_to_bytes(before_replay) == var_to_bytes(after_replay), "public reconnect replay changes no Runtime field")
|
||||
_write("reconnect-replay", {&"before": before_replay, &"after": after_replay, &"accepted": accepted})
|
||||
if label == "recovered":
|
||||
_expect(_clock.absolute_minute == _boundary + 1, "reconnect only after held observation")
|
||||
_expect(_runtime.mana_network_service.connect_nodes(_facility_id, _recovery[&"neighbour"]), "reconnect exact cut edge")
|
||||
var graph: Dictionary = _runtime.mana_network_service.create_snapshot()
|
||||
_expect(var_to_bytes(graph[&"edges"]) == var_to_bytes(_recovery[&"original_graph"][&"edges"]), "all original edges restored")
|
||||
_expect(_runtime.mana_network_service.validate_invariants()[&"valid"], "reconnected graph invariants")
|
||||
var node: Dictionary = _runtime.mana_network_service.get_node(_facility_id)
|
||||
var resume_at: int = int(node[&"hold_until"])
|
||||
var interval: int = int(_recovery[&"interval"])
|
||||
while resume_at % interval != 0: resume_at += 1
|
||||
_expect(resume_at > _clock.absolute_minute and resume_at < _boundary + _duration, "finite normal hold/evaluation recovery before exit")
|
||||
while _clock.absolute_minute < resume_at:
|
||||
_clock.advance_one_minute()
|
||||
if _clock.absolute_minute < resume_at: _assert_stopped(_capture())
|
||||
else:
|
||||
_clock.advance_one_minute()
|
||||
|
||||
func _writer_case() -> void:
|
||||
if not _prepare(): return
|
||||
var before: Dictionary = _capture()
|
||||
_assert_before(before)
|
||||
_points["before"] = _write("before", before)
|
||||
_connect_observations()
|
||||
for label: String in _labels():
|
||||
_advance(label)
|
||||
var actual: Dictionary = _capture()
|
||||
_assert_outcome(before, actual)
|
||||
_save_point(label, actual)
|
||||
_advance("duplicate_guard")
|
||||
var guard: Dictionary = _capture()
|
||||
_assert_outcome(before, guard)
|
||||
_points["duplicate_guard"] = _write("duplicate_guard", guard)
|
||||
_branches[_case] = _write("case", {&"points": _points.duplicate(true), &"phases": _phases.duplicate(true),
|
||||
&"commands": _write("commands", {&"submitted": _commands, &"completed_including_fixture": _results})})
|
||||
|
||||
func _assert_before(point: Dictionary) -> void:
|
||||
_assert_unconfounded(point)
|
||||
_expect(point[&"service"][&"state"] == "ACTIVE" and point[&"facility"][&"power_available"], "before normal projection remains powered")
|
||||
_expect(point[&"service"][&"elapsed_minutes"] == _duration - 1 and point[&"reservations"].size() == 1, "natural duration-1 and one reservation")
|
||||
|
||||
func _assert_unconfounded(point: Dictionary) -> void:
|
||||
_expect(not point[&"operations_enabled"] and point[&"actual_coordinator_absent"], "existing isolated fixture")
|
||||
_expect(point[&"facility"][&"operator_open"] and point[&"related_facility_operating"], "open operator and operating dependency")
|
||||
_expect(point[&"facility"][&"durability"] > 0 and point[&"event"].is_empty(), "no destruction event")
|
||||
_expect(point[&"kitchen"][&"power_available"] and point[&"kitchen"][&"state"] == "OPEN", "kitchen stays active/powered")
|
||||
_expect(point[&"snapshot"][&"game_state"][&"resources"][&"mana"] > 0, "positive global reserve")
|
||||
_expect(point[&"facility"][&"active_staff_ids"] == SNAPSHOT_SERVERS, "no shift confound")
|
||||
if point[&"service"][&"state"] != "COMPLETED":
|
||||
_expect(point[&"guests"][String(GUEST)][&"planned_exit_at"] > point[&"minute"] and point[&"guests"][String(GUEST)][&"state"] == "RECEIVING_SERVICE", "no exit at outage/hold")
|
||||
_expect(point[&"graph_invariants"][&"valid"], "graph allocation/storage/symmetry valid")
|
||||
_expect(_runtime.mana_network_service.get_consumer_state(StringName(point[&"kitchen"][&"instance_id"])) == ManaNetworkService.ConsumerState.ACTIVE, "kitchen network consumer ACTIVE")
|
||||
|
||||
func _assert_stopped(point: Dictionary) -> void:
|
||||
_assert_unconfounded(point)
|
||||
_expect(not point[&"facility"][&"power_available"] and point[&"service"][&"state"] == "PAUSED" and point[&"service"][&"interruption_reason"] == "NO_POWER", "actual isolated NO_POWER path")
|
||||
_expect(point[&"service"][&"elapsed_minutes"] == _duration - 1 and point[&"receipts"].is_empty(), "stopped progress held and no payment")
|
||||
|
||||
func _assert_reservation(before: Dictionary, point: Dictionary, request_id: StringName, state: String, terminal: int) -> void:
|
||||
var prior: Array = []
|
||||
var current: Array = []
|
||||
for row: Dictionary in before[&"reservations"]:
|
||||
if row[&"requester_id"] == String(request_id): prior.append(row)
|
||||
for row: Dictionary in point[&"reservations"]:
|
||||
if row[&"requester_id"] == String(request_id): current.append(row)
|
||||
_expect(prior.size() == 1 and current.size() == 1, "one original reservation per request")
|
||||
if prior.size() != 1 or current.size() != 1: return
|
||||
_expect(prior[0][&"reservation_id"] == current[0][&"reservation_id"] and current[0][&"state"] == state and current[0][&"terminal_at"] == terminal, "same reservation ID/state/terminal time; no second release")
|
||||
if state == "ACTIVE":
|
||||
_expect(var_to_bytes(prior[0]) == var_to_bytes(current[0]), "entire held reservation unchanged")
|
||||
var request: Dictionary = point[&"requests"][String(request_id)]
|
||||
_expect(not request.is_empty() and request[&"reserved_inventory_ids"].has(current[0][&"reservation_id"]), "active reservation has live request owner")
|
||||
else:
|
||||
_expect(point[&"requests"][String(request_id)].is_empty(), "terminal reservation has no orphan request")
|
||||
if state == "RELEASED":
|
||||
var release_count: int = 0
|
||||
var inventory: Dictionary = point[&"snapshot"][&"systems"][&"runtime"][&"inventory"]
|
||||
for operation: Dictionary in inventory[&"idempotency_results"]:
|
||||
var result: Dictionary = operation[&"result"]
|
||||
if operation[&"action"] == "release" and String(result.get(&"reservation_id", "")) == current[0][&"reservation_id"]:
|
||||
release_count += 1
|
||||
_expect(bool(result.get(&"ok", false)) and result.get(&"quantity", 0) == current[0][&"quantity"], "release returned actual reserved quantity, not an orphan repair/retry")
|
||||
_expect(release_count == 1, "one raw inventory release operation for original reservation")
|
||||
|
||||
func _assert_outcome(before: Dictionary, point: Dictionary) -> void:
|
||||
_assert_unconfounded(point)
|
||||
var service: Dictionary = point[&"service"]
|
||||
_expect(service[&"service_id"] == before[&"service"][&"service_id"], "same service ID")
|
||||
var paid: bool = service[&"state"] == "COMPLETED"
|
||||
if not paid:
|
||||
_assert_stopped(point)
|
||||
_expect(var_to_bytes(point[&"ledger"]) == var_to_bytes(before[&"ledger"]), "zero stopped ledger delta")
|
||||
_assert_reservation(before, point, REQUEST, "ACTIVE", -1)
|
||||
else:
|
||||
var node: Dictionary = _runtime.mana_network_service.get_node(_facility_id)
|
||||
_expect(point[&"facility"][&"power_available"] and service[&"elapsed_minutes"] == _duration and service[&"completed_at"] >= int(node[&"hold_until"]), "resume and complete after hold")
|
||||
_expect(point[&"receipts"].size() == 1, "one payment only")
|
||||
var expected: Array = before[&"ledger"].duplicate(true)
|
||||
for receipt: Dictionary in point[&"receipts"]: expected.append_array(receipt[&"entries"])
|
||||
_expect(var_to_bytes(expected) == var_to_bytes(point[&"ledger"]), "whole ledger exactly one receipt")
|
||||
_assert_reservation(before, point, REQUEST, "CONSUMED", service[&"completed_at"])
|
||||
_expect(point[&"wallets"][String(GUEST)] == _price * (1 if paid else 2) and point[&"player_cash"] == before[&"player_cash"] + (_price if paid else 0), "exact one price or zero")
|
||||
|
||||
func _save_point(label: String, point: Dictionary) -> void:
|
||||
_points[label] = _write(label, point)
|
||||
var path: String = "user://saves/p04_s4/" + _case + "-" + label
|
||||
var saved: Dictionary = SaveService.save_snapshot(path, point[&"snapshot"])
|
||||
var inspected: Dictionary = SaveService.inspect_snapshot(path)
|
||||
_expect(bool(saved.get(&"ok", false)) and bool(inspected.get(&"ok", false)) and bool(inspected.get(&"read_only", false)), "public formal save/inspect")
|
||||
_expect(var_to_bytes(inspected.get(&"snapshot", {})) == var_to_bytes(point[&"snapshot"]), "all typed fields preserved by formal package")
|
||||
_expect(var_to_bytes(_runtime.create_snapshot()) == var_to_bytes(point[&"snapshot"]), "formal saving leaves Runtime unchanged")
|
||||
_slots[_case + "-" + label] = ProjectSettings.globalize_path(path)
|
||||
# Snapshot bytes exist once in point raw and once in the required formal
|
||||
# package; indexes do not embed inspection/load copies of those bytes.
|
||||
|
||||
func _write(label: String, value: Variant, global_name: bool = false) -> Dictionary:
|
||||
var name: String = label if global_name else _mode + "-" + _case + "-" + label
|
||||
var reference: Dictionary = {}
|
||||
for extension: String in ["var", "json"]:
|
||||
var path: String = _evidence_dir.path_join(name + "." + extension)
|
||||
_expect(not FileAccess.file_exists(path), "raw point written exactly once " + name)
|
||||
var file: FileAccess = FileAccess.open(path, FileAccess.WRITE)
|
||||
_expect(file != null, "raw file " + name)
|
||||
if file == null: continue
|
||||
if extension == "var": file.store_buffer(var_to_bytes(value))
|
||||
else: file.store_string(JSON.stringify(value, "\t"))
|
||||
file.flush()
|
||||
file.close()
|
||||
reference[extension] = {&"path": path, &"sha256": FileAccess.get_sha256(path)}
|
||||
return reference
|
||||
|
||||
func _read(reference: Dictionary) -> Dictionary:
|
||||
var item: Dictionary = reference["var"]
|
||||
var path: String = item["path"]
|
||||
_expect(FileAccess.get_sha256(path) == item["sha256"], "raw typed SHA")
|
||||
var json_ref: Dictionary = reference["json"]
|
||||
_expect(FileAccess.get_sha256(json_ref["path"]) == json_ref["sha256"], "raw JSON SHA")
|
||||
var file: FileAccess = FileAccess.open(path, FileAccess.READ)
|
||||
_expect(file != null, "typed input readable")
|
||||
if file == null: return {}
|
||||
var value: Variant = bytes_to_var(file.get_buffer(file.get_length()))
|
||||
file.close()
|
||||
_expect(value is Dictionary, "typed input dictionary")
|
||||
return value if value is Dictionary else {}
|
||||
|
||||
func _restore_inputs(point: Dictionary) -> void:
|
||||
var inputs: Dictionary = point[&"inputs"]
|
||||
_expect(_serial == 0, "Reader starts with serial zero before importing external inputs")
|
||||
_serial = inputs[&"serial"]
|
||||
_facility_id = inputs[&"facility_id"]
|
||||
_service_id = inputs[&"service_id"]
|
||||
_boundary = inputs[&"boundary"]
|
||||
_price = inputs[&"price"]
|
||||
_duration = inputs[&"duration"]
|
||||
_patience = inputs[&"patience"]
|
||||
_reserve = inputs[&"reserve"]
|
||||
_recovery = inputs[&"recovery"].duplicate(true)
|
||||
|
||||
func _load_point(path: String, expected: Dictionary) -> bool:
|
||||
_fresh()
|
||||
_restore_inputs(expected)
|
||||
var loaded: Dictionary = SaveService.load_snapshot(path)
|
||||
_expect(bool(loaded.get(&"ok", false)), "public formal load")
|
||||
if not bool(loaded.get(&"ok", false)): return false
|
||||
_expect(var_to_bytes(loaded[&"snapshot"]) == var_to_bytes(expected[&"snapshot"]), "all saved typed fields equal Writer")
|
||||
var restored: bool = _runtime.restore_snapshot(loaded[&"snapshot"])
|
||||
_expect(restored, "public Runtime restore")
|
||||
_expect(var_to_bytes(_runtime.create_snapshot()) == var_to_bytes(expected[&"snapshot"]), "all restored typed fields equal Writer")
|
||||
return restored
|
||||
|
||||
func _is_replayed_phase(row: Dictionary) -> bool:
|
||||
return int(row[&"minute"]) > _boundary or (int(row[&"minute"]) == _boundary and row[&"phase"] == SimulationKernel.TickPhase.keys()[SimulationKernel.TickPhase.APPLY_COMMANDS])
|
||||
|
||||
func _assert_expected_phase_roster(references: Array) -> void:
|
||||
var expected_pairs: Array = []
|
||||
for minute: int in range(_boundary, _boundary + 11):
|
||||
for phase: int in range(SimulationKernel.TickPhase.size()):
|
||||
expected_pairs.append([minute if phase == SimulationKernel.TickPhase.APPLY_COMMANDS else minute + 1, SimulationKernel.TickPhase.keys()[phase]])
|
||||
var actual_pairs: Array = []
|
||||
var seen: Dictionary = {}
|
||||
for reference: Dictionary in references:
|
||||
var row: Dictionary = _read(reference)
|
||||
var pair: Array = [int(row[&"minute"]), row[&"phase"]]
|
||||
_expect(not seen.has(pair), "expected phase roster has no duplicate minute/phase")
|
||||
seen[pair] = true
|
||||
actual_pairs.append(pair)
|
||||
_expect(references.size() == 121 and expected_pairs.size() == 121, "expected phase roster exactly 11 phases x 11 replayed minutes")
|
||||
_expect(actual_pairs == expected_pairs, "expected phase roster in Writer/kernel order with no missing minute/phase")
|
||||
|
||||
func _reader() -> void:
|
||||
var file: FileAccess = FileAccess.open(_evidence_dir.path_join("writer.json"), FileAccess.READ)
|
||||
_expect(file != null, "Writer receipt")
|
||||
if file == null: return
|
||||
var writer: Dictionary = JSON.parse_string(file.get_as_text())
|
||||
file.close()
|
||||
_expect(int(writer["pid"]) != OS.get_process_id() and writer["profile"] != OS.get_user_data_dir(), "independent Reader PID/profile")
|
||||
for branch: String in CASES:
|
||||
_case = branch
|
||||
var writer_case: Dictionary = _read(writer["branches"][branch])
|
||||
var hashes: Dictionary = {}
|
||||
var restored_refs: Dictionary = {}
|
||||
for label: String in _labels():
|
||||
var key: String = branch + "-" + label
|
||||
var original: String = writer["slots"][key]
|
||||
var target: String = ProjectSettings.globalize_path("user://saves/p04_s4/" + key)
|
||||
_expect(DirAccess.make_dir_recursive_absolute(target) == OK, "private Reader formal slot")
|
||||
var directory: DirAccess = DirAccess.open(original)
|
||||
_expect(directory != null, "Writer formal slot readable")
|
||||
if directory == null: return
|
||||
var members: PackedStringArray = directory.get_files()
|
||||
members.sort()
|
||||
_expect(directory.get_directories().is_empty() and members == PackedStringArray(["journal.log", "manifest.json", "thumbnail.png", "world.bin"]), "all formal members enumerated")
|
||||
var member_hashes: Dictionary = {}
|
||||
for member: String in members:
|
||||
var sha: String = FileAccess.get_sha256(original.path_join(member))
|
||||
_expect(DirAccess.copy_absolute(original.path_join(member), target.path_join(member)) == OK, "copy every formal member")
|
||||
var copied_sha: String = FileAccess.get_sha256(target.path_join(member))
|
||||
_expect(not sha.is_empty() and sha == copied_sha, "original/copy member SHA equality")
|
||||
member_hashes[member] = {&"original_before": sha, &"copy_before": copied_sha}
|
||||
hashes[key] = {&"original": original, &"copy": target, &"members": member_hashes}
|
||||
_slots[key] = target
|
||||
var expected: Dictionary = _read(writer_case[&"points"][label])
|
||||
if not _load_point(target, expected): return
|
||||
restored_refs[label] = _write(label + "-restored", _capture())
|
||||
var before: Dictionary = _read(writer_case[&"points"]["before"])
|
||||
var boundary: Dictionary = _read(writer_case[&"points"]["boundary"])
|
||||
if not _load_point(_slots[branch + "-boundary"], boundary): return
|
||||
_assert_stopped(_capture())
|
||||
_connect_observations()
|
||||
var replay_refs: Dictionary = {}
|
||||
for label: String in ["held", "recovered", "duplicate_guard"]:
|
||||
_advance(label)
|
||||
var actual: Dictionary = _capture()
|
||||
var expected: Dictionary = _read(writer_case[&"points"][label])
|
||||
_expect(var_to_bytes(actual) == var_to_bytes(expected), "replay complete typed point including Runtime, inputs, receipts " + label)
|
||||
_assert_outcome(before, actual)
|
||||
replay_refs[label] = _write(label + "-replay", actual)
|
||||
var expected_phases: Array = []
|
||||
for reference: Dictionary in writer_case[&"phases"]:
|
||||
if _is_replayed_phase(_read(reference)): expected_phases.append(reference)
|
||||
_assert_expected_phase_roster(expected_phases)
|
||||
_expect(_phases.size() == expected_phases.size(), "complete phase count")
|
||||
if _phases.size() == expected_phases.size():
|
||||
for index: int in range(_phases.size()):
|
||||
_expect(var_to_bytes(_read(_phases[index])) == var_to_bytes(_read(expected_phases[index])), "same phase order and all typed phase state")
|
||||
for key: String in hashes:
|
||||
var row: Dictionary = hashes[key]
|
||||
for member: String in row[&"members"]:
|
||||
var item: Dictionary = row[&"members"][member]
|
||||
item[&"original_after"] = FileAccess.get_sha256(String(row[&"original"]).path_join(member))
|
||||
item[&"copy_after"] = FileAccess.get_sha256(String(row[&"copy"]).path_join(member))
|
||||
_expect(item[&"original_before"] == item[&"original_after"] and item[&"copy_before"] == item[&"copy_after"], "original/copy unchanged after all loads and replay")
|
||||
_branches[branch] = _write("case", {&"restored": restored_refs, &"replay": replay_refs, &"phases": _phases,
|
||||
&"member_hashes": hashes, &"recovery_commands": _commands, &"replay_completed_commands": _results})
|
||||
@@ -0,0 +1,83 @@
|
||||
"""Read actual r27 phase .var paths only; no engine, fixtures or acceptance claim.
|
||||
|
||||
Root supplies every Writer and Reader phase file via --writer and --reader.
|
||||
Argument order is immaterial: observation order is reconstructed from kernel
|
||||
phase/minute keys, never from lexicographic filenames. Typed bytes stay intact.
|
||||
"""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
from p04_native_series import require, variant
|
||||
|
||||
PHASES = ('APPLY_COMMANDS', 'TIME_BOUNDARIES', 'MANA', 'CONSTRUCTION',
|
||||
'LOGISTICS', 'FACILITY_COMPLETION', 'VITALS_AND_ENVIRONMENT',
|
||||
'INCIDENTS_AND_EVENTS', 'FACILITY_QUEUES', 'AI_DECISIONS', 'PUBLISH_SNAPSHOT')
|
||||
BOUNDARY = 960
|
||||
ROSTER = [(minute if phase == PHASES[0] else minute + 1, phase)
|
||||
for minute in range(BOUNDARY, BOUNDARY + 11) for phase in PHASES]
|
||||
|
||||
|
||||
def load(paths, role):
|
||||
rows, pins = [], []
|
||||
for path in paths:
|
||||
require(path.is_absolute() and path.is_file() and not path.is_symlink(), 'full regular raw path required')
|
||||
require(path.name.startswith(role + '-S4-phase-') and path.suffix == '.var', 'actual role phase .var required')
|
||||
raw = path.read_bytes()
|
||||
node = variant(raw)
|
||||
minute, phase = node.at('minute'), node.at('phase')
|
||||
require(minute.kind == 2 and phase.kind == 4 and phase.value in PHASES, 'typed minute/phase')
|
||||
rows.append(((minute.value, phase.value), raw))
|
||||
pins.append(dict(path=str(path), bytes=len(raw), sha256=hashlib.sha256(raw).hexdigest()))
|
||||
rows.sort(key=lambda row: (row[0][0] + (row[0][1] == PHASES[0]), PHASES.index(row[0][1])))
|
||||
return rows, pins
|
||||
|
||||
|
||||
def validate(expected, reader):
|
||||
require(len(expected) == len(reader) == 121, 'complete phase count')
|
||||
require([key for key, _ in expected] == ROSTER, 'Writer roster missing/duplicate/order')
|
||||
require([key for key, _ in reader] == ROSTER, 'Reader roster missing/duplicate/order')
|
||||
require(all(a == b for a, b in zip(expected, reader)), 'same phase order and all typed phase bytes')
|
||||
|
||||
|
||||
def rejected(expected, reader):
|
||||
try:
|
||||
validate(expected, reader)
|
||||
except ValueError:
|
||||
return True
|
||||
raise ValueError('negative control unexpectedly accepted')
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--writer', nargs='+', type=Path, required=True)
|
||||
parser.add_argument('--reader', nargs='+', type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
writer, wpins = load(args.writer, 'writer')
|
||||
reader, rpins = load(args.reader, 'reader')
|
||||
require(len(writer) == 132 and len({key for key, _ in writer}) == 132, 'complete unique actual Writer input')
|
||||
require([key for key, _ in writer] ==
|
||||
[(minute if phase == PHASES[0] else minute + 1, phase)
|
||||
for minute in range(BOUNDARY - 1, BOUNDARY + 11) for phase in PHASES],
|
||||
'full Writer kernel roster')
|
||||
old = [row for row in writer if row[0][0] > BOUNDARY]
|
||||
new = [row for row in writer if row[0][0] > BOUNDARY or
|
||||
(row[0][0] == BOUNDARY and row[0][1] == PHASES[0])]
|
||||
require(len(old) == 120, 'old cut must produce 120')
|
||||
old_rejected = rejected(old, reader)
|
||||
validate(new, reader)
|
||||
require(new[0][0] == (BOUNDARY, PHASES[0]), 'boundary APPLY_COMMANDS first')
|
||||
deleted_rejected = rejected(new[1:], reader)
|
||||
duplicated_rejected = rejected([new[0], *new], reader)
|
||||
print(json.dumps(dict(status='CONTROL_CONFIRMED', engine_run=False, acceptance=False,
|
||||
old_count=len(old), old_rejected=old_rejected, new_count=len(new),
|
||||
all_typed_bytes_in_order=True, deleted_apply_rejected=deleted_rejected,
|
||||
duplicate_rejected=duplicated_rejected, writer=wpins, reader=rpins), indent=2))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
+255
@@ -0,0 +1,255 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Root-only synthetic receipt lifecycle fixtures. No engine/process is started.
|
||||
|
||||
Uses the real main, exclusive JSON writer, source_refs and inventories. The
|
||||
identity section is compiled directly from S4Series.__init__, not reimplemented.
|
||||
This is not native/product/ownership/formal acceptance evidence.
|
||||
"""
|
||||
import ast
|
||||
from collections import Counter
|
||||
from contextlib import ExitStack, redirect_stdout
|
||||
import copy
|
||||
import importlib.util
|
||||
import io
|
||||
import json
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import patch
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
HERE = Path(__file__).resolve().parent
|
||||
ROOT = next(p for p in HERE.parents if (p / 'tools/run_all_tests.py').is_file())
|
||||
|
||||
|
||||
def load(name, path):
|
||||
spec = importlib.util.spec_from_file_location(name, path)
|
||||
value = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(value)
|
||||
return value
|
||||
|
||||
|
||||
s4 = load('receipt_s4', HERE / 'run_s4_native.py')
|
||||
r = load('receipt_standard', ROOT / 'tools/run_all_tests.py')
|
||||
series = load('receipt_series', HERE.parent / 'p04_native_series.py')
|
||||
|
||||
|
||||
def identity_code():
|
||||
tree = ast.parse((HERE.parent / 'p04_native_series.py').read_text())
|
||||
cls = next(n for n in tree.body if isinstance(n, ast.ClassDef) and n.name == 'S4Series')
|
||||
init = next(n for n in cls.body if isinstance(n, ast.FunctionDef) and n.name == '__init__')
|
||||
start = next(i for i, n in enumerate(init.body) if isinstance(n, ast.Expr) and
|
||||
isinstance(n.value, ast.Call) and isinstance(n.value.func, ast.Name) and
|
||||
n.value.func.id == 'verify_s4_engine')
|
||||
end = next(i for i in range(start + 1, len(init.body))
|
||||
if "execution['error']" in ast.unparse(init.body[i]))
|
||||
return compile(ast.Module(body=init.body[start:end], type_ignores=[]), '<real S4 identity section>', 'exec')
|
||||
|
||||
|
||||
def verify_identity(out, descriptor):
|
||||
# The real engine verifier reads only the synthetic ordinary file here.
|
||||
# Production constants/functions on disk are never changed.
|
||||
with patch.object(series, 'S4_ENGINE_PATH', descriptor['path']), \
|
||||
patch.object(series, 'S4_ENGINE_SHA256', descriptor['sha256']):
|
||||
namespace = dict(vars(series), inputs=r.read(out / 'runner-input.json'),
|
||||
execution=r.read(out / 'execution.json'),
|
||||
self=SimpleNamespace(read=lambda name: (out / name).read_bytes()))
|
||||
exec(identity_code(), namespace)
|
||||
|
||||
|
||||
def rejected(action):
|
||||
try:
|
||||
action()
|
||||
except (ValueError, AssertionError, OSError, KeyError, TypeError):
|
||||
return
|
||||
raise AssertionError('negative fixture was accepted')
|
||||
|
||||
|
||||
def case(base, failure=None):
|
||||
product = base / 'product'
|
||||
support = base / 'support'
|
||||
staged = base / 'source'
|
||||
for path in (product, support, staged):
|
||||
path.mkdir(parents=True)
|
||||
|
||||
def put(path, data):
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_bytes(data)
|
||||
|
||||
# Real 1338-entry source_refs and 316-entry script roster, synthetic bytes.
|
||||
scripts = [s4.TEST, 'tests/transport/test_runtime_transport.gd',
|
||||
'tests/staff/test_rescue_elevator.gd']
|
||||
scripts += ['scripts/fixture_%03d.gd' % i for i in range(313)]
|
||||
for name in scripts:
|
||||
put(product / name, b'extends Node\n')
|
||||
for name in ('project.godot', 'export_presets.cfg', 'Makefile', 'AGENTS.md'):
|
||||
put(product / name, b'fixture\n')
|
||||
put(product / 'tools/run_all_tests.py', (ROOT / 'tools/run_all_tests.py').read_bytes())
|
||||
for i in range(1017):
|
||||
put(product / ('data/fixture_%04d.txt' % i), b'fixture\n')
|
||||
assert len(r.source_refs(product)) == 1338
|
||||
qa = staged / 'p04_s4_power_probe.gd'
|
||||
put(qa, b'extends Node\n# synthetic override\n')
|
||||
put(staged / 'run_s4_native.py', (HERE / 'run_s4_native.py').read_bytes())
|
||||
put(staged / 'S4_ROOT_RUN.md', b'synthetic fixture material\n')
|
||||
contracts = base / 'contracts.json'
|
||||
r.write(contracts, {})
|
||||
material = base / 'material.json'
|
||||
r.write(material, {})
|
||||
r.write(support / 'suite-policy.json', {'suite_policies': {}})
|
||||
r.write(support / 'bootstrap-scripts.json', {
|
||||
'parse_full_dependencies.gd': {'text': 'fixture\n', 'sha256': r.sha(product / 'Makefile')}})
|
||||
engine = base / 'synthetic-engine-data'
|
||||
put(engine, b'not an executable; never launched\n')
|
||||
st = engine.stat()
|
||||
descriptor = dict(path=str(engine), resolved_path=str(engine), bytes=st.st_size,
|
||||
sha256=r.sha(engine), mtime_ns=st.st_mtime_ns, mode=st.st_mode,
|
||||
device=st.st_dev, inode=st.st_ino, ctime_ns=st.st_ctime_ns)
|
||||
out = base / 'p04-s4-native-r28'
|
||||
plan = base / 'p04-s4-plan-r28.json'
|
||||
attempts, created, phases = Counter(), {}, []
|
||||
cleanup_calls = []
|
||||
real_write, real_inventory = r.write, r.inventory
|
||||
protected = dict(path=str(base / 'default-player'), existed=False, files={}, logs_and_locks_included=True)
|
||||
|
||||
def write(path, value):
|
||||
path = Path(path)
|
||||
attempts[path] += 1
|
||||
if failure == 'identity-write' and path == out / 'engine-identity.json':
|
||||
raise OSError('injected final identity write error')
|
||||
real_write(path, value) # unchanged open('x'), including collisions
|
||||
created[path] = path.read_bytes()
|
||||
|
||||
def finish(owned):
|
||||
cleanup_calls.append(list(owned))
|
||||
if failure == 'cleanup':
|
||||
raise RuntimeError('injected cleanup error')
|
||||
return [dict(pid=p.pid, engine_ended=True) for p in owned], []
|
||||
|
||||
outer = SimpleNamespace(native=SimpleNamespace(helper={'live_engines': lambda: []}),
|
||||
OWNED=[SimpleNamespace(pid=91001+i) for i in range(4)],
|
||||
finish_captured=finish, run_phase=lambda *a, **k: None)
|
||||
|
||||
def execute(engine_arg, stage, output, phase, test, selected, expected, previous,
|
||||
policy, contracts_arg, paths, public, player, root, *helpers):
|
||||
phases.append(phase)
|
||||
assert engine_arg == str(engine) and output == out and player == protected
|
||||
assert r.source_refs(root) == selected['root_before']
|
||||
assert r.inventory(stage, stage=True) == dict(expected, **previous)
|
||||
pid = outer.OWNED[len(phases)-1].pid
|
||||
row = dict(phase=phase, passed=not (failure == 'parse' and phase == 'parse'), actual_pid=pid)
|
||||
if test:
|
||||
slots = {}
|
||||
for slot in s4.SLOTS:
|
||||
directory = base / 'profiles' / phase / slot
|
||||
for member in ('journal.log', 'manifest.json', 'thumbnail.png', 'world.bin'):
|
||||
put(directory / member, (slot + '/' + member).encode())
|
||||
slots[slot] = str(directory)
|
||||
(out / 'raw').mkdir(exist_ok=True)
|
||||
write(out / ('raw/' + phase + '.json'), dict(checks=1, failures=0, branches=['S4'],
|
||||
slots=slots, pid=pid, profile=str(base / 'profiles' / phase)))
|
||||
# Reproduce the runner's shared text log and let main copy its bytes.
|
||||
log = test.replace('/', '__') + '.log'
|
||||
(out / log).write_text(phase + '\n')
|
||||
row['log'] = log
|
||||
return row
|
||||
|
||||
def inventory(path, **kwargs):
|
||||
if failure == 'output' and Path(path) == out / 'raw':
|
||||
raise OSError('injected post-cleanup inventory error')
|
||||
return real_inventory(path, **kwargs)
|
||||
|
||||
def copy_source(source, target):
|
||||
if failure == 'setup':
|
||||
raise OSError('injected setup copy error')
|
||||
shutil.copyfile(source, target)
|
||||
|
||||
def modules(name, path):
|
||||
if name == 's4_standard_runner':
|
||||
return r
|
||||
if name == 's4_owned_outer':
|
||||
return outer
|
||||
if name == 's4_markers':
|
||||
return SimpleNamespace(load_contracts=lambda path: {})
|
||||
if name in ('s4_diagnostics', 's4_owners', 's4_audio'):
|
||||
return SimpleNamespace()
|
||||
raise AssertionError('unexpected helper import: ' + name)
|
||||
|
||||
with ExitStack() as stack:
|
||||
for obj, values in [(s4, dict(BUILD=base, PRODUCT=product, HERE=staged, QA=qa,
|
||||
ENGINE=engine, CONTRACTS=contracts, MATERIALS=[material],
|
||||
__file__=str(staged / 'run_s4_native.py'), module=modules)),
|
||||
(r, dict(SUPPORT=support, write=write, execute_phase=execute,
|
||||
copy_source=copy_source, inventory=inventory))]:
|
||||
for key, value in values.items():
|
||||
stack.enter_context(patch.object(obj, key, value))
|
||||
pin_mock = stack.enter_context(patch.object(s4, 'engine_pin', return_value=descriptor))
|
||||
guard_mock = stack.enter_context(patch.object(s4, 'template_guard', return_value=None))
|
||||
player_mock = stack.enter_context(patch.object(r, 'default_player', return_value=protected))
|
||||
disk_mock = stack.enter_context(patch.object(s4.shutil, 'disk_usage',
|
||||
return_value=SimpleNamespace(free=10 * 1024**3)))
|
||||
stack.enter_context(redirect_stdout(io.StringIO()))
|
||||
argv = ['run_s4_native.py', '--project-root', str(product), '--output', str(out),
|
||||
'--plan', str(plan), '--godot', str(engine)]
|
||||
with patch.object(sys, 'argv', argv + ['--check-only']):
|
||||
assert s4.main() == 0
|
||||
assert not phases and not cleanup_calls and not out.exists()
|
||||
with patch.object(sys, 'argv', argv):
|
||||
result = s4.main()
|
||||
rejected(s4.main) # same output must be rejected before any new writes
|
||||
assert pin_mock.called and guard_mock.called and player_mock.called and disk_mock.called
|
||||
|
||||
assert result == (0 if failure is None else 1)
|
||||
assert len(cleanup_calls) == 1
|
||||
assert all(count == 1 for count in attempts.values()), attempts
|
||||
for leaf in ('outer-owned-finally.json', 'engine-identity.json', 'execution.json', 'runner-ended.json'):
|
||||
assert attempts[out / leaf] == 1
|
||||
if failure != 'identity-write' or leaf != 'engine-identity.json':
|
||||
assert (out / leaf).is_file()
|
||||
if failure != 'setup':
|
||||
assert (out / 'runner-input.json').read_bytes() == created[out / 'runner-input.json']
|
||||
assert r.read(out / 'runner-input.json')['engine_identity_initial']['after'] is None
|
||||
execution = r.read(out / 'execution.json')
|
||||
assert execution['passed'] is (failure is None)
|
||||
assert 'FileExistsError' not in json.dumps(execution)
|
||||
assert phases == ([] if failure == 'setup' else ['import', 'parse'] if failure == 'parse'
|
||||
else ['import', 'parse', 'writer', 'reader'])
|
||||
if failure == 'cleanup':
|
||||
assert execution['cleanup_errors'] == ['injected cleanup error']
|
||||
if failure in ('output', 'identity-write'):
|
||||
assert execution['guard_errors']
|
||||
if failure is None:
|
||||
assert len(list(out.glob('engine-identity*.json'))) == 5
|
||||
verify_identity(out, descriptor)
|
||||
assert (out / 'writer-combined-raw.log').read_bytes() == b'writer\n'
|
||||
assert (out / 'reader-combined-raw.log').read_bytes() == b'reader\n'
|
||||
# Mutations below affect only this disposable fixture's synthetic proofs.
|
||||
for phase in ('import', 'parse', 'writer', 'reader'):
|
||||
path = out / ('engine-identity-before-' + phase + '.json')
|
||||
original = path.read_bytes()
|
||||
path.write_text('{}')
|
||||
rejected(lambda: verify_identity(out, descriptor))
|
||||
path.unlink()
|
||||
rejected(lambda: verify_identity(out, descriptor))
|
||||
path.write_bytes(original)
|
||||
path = out / 'runner-input.json'
|
||||
initial = r.read(path)
|
||||
changed = copy.deepcopy(initial)
|
||||
changed['engine_identity_initial']['before']['inode'] += 1
|
||||
path.write_text(json.dumps(changed))
|
||||
rejected(lambda: verify_identity(out, descriptor))
|
||||
path.write_bytes(created[path])
|
||||
verify_identity(out, descriptor)
|
||||
return dict(case=failure or 'success', passed=True, native_engine_launches=0)
|
||||
|
||||
|
||||
def run():
|
||||
with tempfile.TemporaryDirectory(prefix='s4-receipt-fixtures-') as temporary:
|
||||
return [case(Path(temporary).resolve() / (failure or 'success'), failure)
|
||||
for failure in (None, 'parse', 'cleanup', 'setup', 'output', 'identity-write')]
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(json.dumps(dict(results=run(), formal_acceptance=False), indent=2))
|
||||
@@ -0,0 +1,405 @@
|
||||
#!/usr/bin/env python3
|
||||
"""S4-only native entry. --check-only never loads ownership helpers or starts Godot."""
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
import copy
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import stat
|
||||
import sys
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
BUILD = Path("/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build")
|
||||
PRODUCT = BUILD / "audit-support/p04-r17-full-inputs-r01/product"
|
||||
CACHE_RUN = BUILD / "p04-final-product-full-r01"
|
||||
TEST = "tests/s3/test_runtime_staff_assignment.gd"
|
||||
QA = HERE / "p04_s4_power_probe.gd"
|
||||
ENGINE = Path("/Applications/Godot.app/Contents/MacOS/Godot")
|
||||
ENGINE_SHA256 = "c7cccbf8fb143e34e02fd6521e09be2c2b974f0d5db080b19071c9c570718ccf"
|
||||
MANIFEST = BUILD / "audit-support/p04-r17-full-inputs-r01/MANIFEST.json"
|
||||
MANIFEST_SHA = "a15f29fa12537f6d8aaf7cabea390922a9f16e3eb9c10981e5742aafc8c4c8a8"
|
||||
CONTRACTS = BUILD / "audit-support/p04-r17-full-inputs-r01/fixed/6ce6b28c5985e8a4f13b78bfeb1f1e09f6216d874fb01e0656ec1633bfa01cd7/completion_marker_contracts.json"
|
||||
CASES = ["S4"]
|
||||
LABELS = {"S4": ["boundary", "held", "recovered"]}
|
||||
SLOTS = ["S4-" + label for label in sorted(LABELS["S4"])]
|
||||
MATERIALS = [MANIFEST, BUILD / "audit-support/p04-review-repair22-r01/ACTUAL_CURRENT_PUBLIC_POWER_FUNCTIONS.json"]
|
||||
|
||||
def template_guard(root):
|
||||
if root != PRODUCT.resolve() or not HERE.is_relative_to(BUILD.resolve()):
|
||||
raise ValueError("Root must stage this exact QA/wrapper/plan inside own B")
|
||||
if pin(MANIFEST)["sha256"] != MANIFEST_SHA:
|
||||
raise ValueError("immutable full R17 relocation manifest")
|
||||
manifest = json.loads(MANIFEST.read_text())
|
||||
for name, source in manifest["product_source"].items():
|
||||
# Old absolute paths are lookup keys only. Never open them.
|
||||
row = manifest["original_to_relocated"][source["path"]]
|
||||
actual = pin(root / name)
|
||||
if Path(row["relocated"]).resolve() != root / name or any(
|
||||
actual[k] != source[k] or actual[k] != row[k] for k in ("bytes", "sha256")):
|
||||
raise ValueError("relocated product differs: " + name)
|
||||
probe = QA.read_text()
|
||||
import re
|
||||
if ":=" in probe or re.search(r"\bvar\s+\w+\s*=", probe) or re.search(r"\bfor\s+\w+\s+in\b", probe):
|
||||
raise ValueError("explicit typed declarations/loops required")
|
||||
if re.search(r"\.(?:patience_remaining|elapsed_minutes|power_available|capacity|hold_until)\s*=(?!=)", probe):
|
||||
raise ValueError("forbidden direct state mutation")
|
||||
for forbidden in ["evaluate_now(", "configure_operations(", "._update_shift_states(", ".record_duty_minute("]:
|
||||
if forbidden in probe:
|
||||
raise ValueError("forbidden bypass: " + forbidden)
|
||||
# Constants and public bodies are read from the actual selected source, not a memo.
|
||||
mana = (root / "scripts/mana/mana_network_service.gd").read_text()
|
||||
for key in ("EVALUATION_INTERVAL_MINUTES", "MINIMUM_OFF_HOLD_MINUTES"):
|
||||
if not re.search(r"const " + key + r" := 10\b", mana):
|
||||
raise ValueError("source timing changed: " + key)
|
||||
|
||||
|
||||
def module(name, path):
|
||||
spec = importlib.util.spec_from_file_location(name, path)
|
||||
value = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(value)
|
||||
return value
|
||||
|
||||
def pin(path):
|
||||
path = Path(path)
|
||||
if path.is_symlink() or not path.is_file():
|
||||
raise ValueError("missing exact input: " + str(path))
|
||||
data = path.read_bytes()
|
||||
return dict(path=str(path), bytes=len(data), sha256=hashlib.sha256(data).hexdigest())
|
||||
|
||||
def engine_pin(path, expected=None):
|
||||
path = Path(path)
|
||||
if path != ENGINE or any(p.is_symlink() for p in (path, *path.parents)):
|
||||
raise ValueError("exact installed engine required; no symlink or relocated input")
|
||||
if path.resolve(strict=True) != ENGINE:
|
||||
raise ValueError("installed engine resolved path changed")
|
||||
before = path.stat()
|
||||
if not stat.S_ISREG(before.st_mode):
|
||||
raise ValueError("installed engine must be regular")
|
||||
digest = hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
after = path.stat()
|
||||
fields = ("st_dev", "st_ino", "st_size", "st_mtime_ns", "st_ctime_ns", "st_mode")
|
||||
if any(getattr(before, k) != getattr(after, k) for k in fields):
|
||||
raise ValueError("engine changed while hashing")
|
||||
if any(p.is_symlink() for p in (path, *path.parents)) or path.resolve(strict=True) != ENGINE:
|
||||
raise ValueError("engine path changed while hashing")
|
||||
value = dict(path=str(path), resolved_path=str(path.resolve(strict=True)),
|
||||
bytes=after.st_size, mtime_ns=after.st_mtime_ns, mode=after.st_mode,
|
||||
device=after.st_dev, inode=after.st_ino, ctime_ns=after.st_ctime_ns, sha256=digest)
|
||||
if digest != ENGINE_SHA256:
|
||||
raise ValueError("engine differs from installed engine constant")
|
||||
if expected is not None and value != expected:
|
||||
raise ValueError("engine identity changed")
|
||||
return value
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--project-root", type=Path, default=PRODUCT)
|
||||
parser.add_argument("--output", type=Path, default=BUILD / "p04-s4-native-r28")
|
||||
parser.add_argument("--plan", type=Path, default=BUILD / "p04-s4-plan-r28.json")
|
||||
parser.add_argument("--cache-run", type=Path, default=None,
|
||||
help="optional passed registered cache; default is fresh, without cache access")
|
||||
parser.add_argument("--godot", type=Path, default=ENGINE)
|
||||
parser.add_argument("--check-only", action="store_true")
|
||||
args = parser.parse_args()
|
||||
engine_descriptor = engine_pin(args.godot)
|
||||
if args.cache_run is not None and args.cache_run.resolve() != CACHE_RUN.resolve():
|
||||
raise ValueError("only registered own-B full-run cache")
|
||||
root = args.project_root.resolve()
|
||||
out = args.output.resolve()
|
||||
if not out.is_relative_to(BUILD.resolve()) or not args.plan.resolve().is_relative_to(BUILD.resolve()):
|
||||
raise ValueError("plan/output must stay in task build")
|
||||
if out.exists() or out.is_symlink():
|
||||
raise ValueError("fresh output required")
|
||||
if args.check_only and (args.plan.exists() or args.plan.is_symlink()):
|
||||
raise ValueError("fresh plan required")
|
||||
r = module("s4_standard_runner", root / "tools/run_all_tests.py")
|
||||
# Preserve the reviewed source_refs -> exactly one QA override order.
|
||||
source_before = r.source_refs(root)
|
||||
refs = dict(source_before)
|
||||
refs[TEST] = pin(QA)
|
||||
template_guard(root)
|
||||
expected = {n: dict(bytes=p["bytes"], sha256=p["sha256"]) for n, p in refs.items()}
|
||||
fixed = [pin(p) for p in MATERIALS + [
|
||||
QA, Path(__file__), HERE / "S4_ROOT_RUN.md",
|
||||
root / "tests/transport/test_runtime_transport.gd",
|
||||
root / "tests/staff/test_rescue_elevator.gd",
|
||||
CONTRACTS,
|
||||
]]
|
||||
selected = dict(root_before=source_before, map={n: p["sha256"] for n, p in refs.items()}, timeout=1200)
|
||||
cache_note = None
|
||||
cache_receipts = []
|
||||
if args.cache_run is not None:
|
||||
# Reject failed caches before reading their inputs or cache contents.
|
||||
r.require(r.read(args.cache_run / "runner-ended.json")["passed"] is True,
|
||||
"failed cache is ineligible")
|
||||
for leaf in (["runner-ended.json", "runner-input.json"] if args.cache_run is not None else []):
|
||||
path = args.cache_run / leaf
|
||||
try:
|
||||
cache_receipts.append(pin(path))
|
||||
except OSError as caught:
|
||||
cache_note = dict(path=str(path), error=type(caught).__name__ + ": " + str(caught),
|
||||
required_before_native=True)
|
||||
break
|
||||
plan = dict(protocol="p04-s4-preparation-v1", project_root=str(root), output=str(out), engine_path=str(args.godot.resolve()),
|
||||
source_before=source_before, selected_source_map=selected["map"],
|
||||
QA_overrides=[TEST], fixed_input_pins=fixed,
|
||||
cache_run=str(args.cache_run) if args.cache_run is not None else None,
|
||||
import_mode="cache" if args.cache_run is not None else "fresh",
|
||||
engine_sha256=ENGINE_SHA256, engine=engine_descriptor,
|
||||
cache_receipts=cache_receipts, cache_access=cache_note,
|
||||
phases=["import", "parse", "writer", "reader"],
|
||||
native_engine_launches=0, whole_goal_accepted=False, unit_accepted=False)
|
||||
# Check-only only reads source/materials and writes the plan in task build.
|
||||
# Reads engine identity; never launches Godot or inspects players/processes.
|
||||
if args.check_only:
|
||||
args.plan.parent.mkdir(parents=True, exist_ok=True)
|
||||
r.write(args.plan, plan)
|
||||
print(json.dumps(dict(check_only=True, engines=0, source_files=len(source_before),
|
||||
QA_overrides=[TEST], fixed_pins=len(fixed), plan=str(args.plan),
|
||||
plan_sha256=r.sha(args.plan), cache_access=cache_note), ensure_ascii=False))
|
||||
return 0
|
||||
r.require(root == PRODUCT.resolve() and len(source_before) == 1338, "Root native requires product 1338; worker plan is not native evidence")
|
||||
r.require(args.plan.is_file() and r.read(args.plan) == plan, "run exact check-only plan first")
|
||||
engine_observations = []
|
||||
engine_identity = dict(before=engine_descriptor, during=[], after=None)
|
||||
def repin(label):
|
||||
try:
|
||||
value = engine_pin(args.godot, engine_descriptor)
|
||||
engine_observations.append(dict(stage=label, descriptor=value))
|
||||
if label == "after-cleanup":
|
||||
engine_identity["after"] = value
|
||||
else:
|
||||
engine_identity["during"].append(value)
|
||||
except BaseException as caught:
|
||||
engine_observations.append(dict(stage=label, error=str(caught)))
|
||||
raise
|
||||
def fixed_guard():
|
||||
repin("cleanup-fixed-guard")
|
||||
template_guard(root)
|
||||
for value in fixed + cache_receipts:
|
||||
r.require(pin(value["path"]) == value, "fixed input changed: " + value["path"])
|
||||
fixed_guard()
|
||||
old = args.cache_run
|
||||
if old is not None:
|
||||
# Native cache admission remains Root-only and fails closed.
|
||||
r.require(cache_note is None, "Root must read exact cache receipts: " + str(cache_note))
|
||||
r.require(r.read(old / "runner-ended.json")["passed"], "ended cache run must have passed")
|
||||
cache_input = r.read(old / "runner-input.json")
|
||||
r.require(cache_input["engine_sha256"] == ENGINE_SHA256, "cache engine identity")
|
||||
old_map = cache_input["selected_source_map"]
|
||||
current_map = {n: p["sha256"] for n, p in source_before.items()}
|
||||
r.require({k: v for k, v in old_map.items() if k != "tools/check_p04_order_evidence.py"} == current_map and len(old_map) == 1339, "immutable full cache: all 1338 product inputs equal; checker is not parsed/executed by product suites")
|
||||
protected = r.default_player(root)
|
||||
outer = module("s4_owned_outer", r.SUPPORT / "owned_outer.py")
|
||||
r.require(not outer.native.helper["live_engines"](), "another Godot owns CPU")
|
||||
engine = ENGINE
|
||||
repin("native-admission")
|
||||
r.require(shutil.disk_usage(out.parent).free >= 1024**3 + engine.stat().st_size + 136*1024**2,
|
||||
"reviewed native disk admission")
|
||||
r.require(not out.exists(), "fresh output required; preserve previous attempts")
|
||||
out.mkdir()
|
||||
stage = out / "stage"
|
||||
stage.mkdir()
|
||||
(out / "starters").mkdir()
|
||||
(out / "bootstrap").mkdir()
|
||||
phases = []
|
||||
previous = {}
|
||||
error = None
|
||||
guards = []
|
||||
cleanup = []
|
||||
cleanup_errors = []
|
||||
cache_before = None
|
||||
formal_before = None
|
||||
formal_after = None
|
||||
original_phase = outer.run_phase
|
||||
def run_phase(argv, folder, timeout, env, **kwargs):
|
||||
env = dict(env)
|
||||
if folder.name in ["writer", "reader"]:
|
||||
env.update(THE_TREE_S4_EVIDENCE=str(out / "raw"), THE_TREE_S4_MODE=folder.name)
|
||||
return original_phase(argv, folder, timeout, env, **kwargs)
|
||||
outer.run_phase = run_phase
|
||||
try:
|
||||
r.write(out / "runner-started.json", dict(started=r.now(), owner_runner_pid=os.getpid(),
|
||||
plan=pin(args.plan), engines_launched=0))
|
||||
r.write(out / "source_before.json", source_before)
|
||||
r.write(out / "selected-source-map.json", refs)
|
||||
# Private source stage first, then literal reviewed bootstrap.
|
||||
for name, value in refs.items():
|
||||
target = stage / name
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
r.copy_source(Path(value["path"]), target)
|
||||
r.require(r.inventory(stage, stage=True) == expected, "whole private source")
|
||||
for name, value in r.read(r.SUPPORT / "bootstrap-scripts.json").items():
|
||||
data = value["text"].encode()
|
||||
r.require(hashlib.sha256(data).hexdigest() == value["sha256"], "bootstrap pin")
|
||||
(out / "bootstrap" / name).write_bytes(data)
|
||||
if old is not None:
|
||||
cache = old / "stage/.godot"
|
||||
cache_before = r.inventory(cache)
|
||||
r.require(bool(cache_before), "nonempty ended cache")
|
||||
for name in cache_before:
|
||||
src = cache / name
|
||||
dst = stage / ".godot" / name
|
||||
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
subprocess.run(["/bin/cp", "-c", str(src), str(dst)], check=True)
|
||||
r.require((src.stat().st_dev, src.stat().st_ino) != (dst.stat().st_dev, dst.stat().st_ino),
|
||||
"cache clone is a separate inode")
|
||||
r.require(r.inventory(stage / ".godot") == cache_before, "whole cache clone hashes")
|
||||
r.write(out / "cache-reuse.json", dict(ended_run=str(old), receipts=cache_receipts,
|
||||
whole_cache=cache_before, independent_inode=True,
|
||||
ordinary_import_required=True, ordinary_parse_required=True))
|
||||
paths = ["res://" + n for n in sorted(refs) if n.endswith(".gd")]
|
||||
r.require(len(paths) == 316, "complete 316-script parse roster")
|
||||
r.write(out / "parse-paths.json", paths)
|
||||
markers = module("s4_markers", root / "tools/strict_test_markers.py")
|
||||
contracts = markers.load_contracts(CONTRACTS)
|
||||
contracts[TEST] = dict(test=TEST, template="S4 power: <checks> checks, <failures> failures", required=True)
|
||||
policy = r.read(r.SUPPORT / "suite-policy.json")
|
||||
r.require(TEST not in policy["suite_policies"], "no normal suite watchdog/policy override")
|
||||
r.write(out / "contracts.json", contracts)
|
||||
r.write(out / "runner-input.json", dict(plan, owner_runner_pid=os.getpid(),
|
||||
wrapper_sha256=r.sha(__file__), QA=pin(QA), engine=engine_descriptor,
|
||||
engine_identity_before=engine_descriptor, engine_identity_initial=copy.deepcopy(engine_identity),
|
||||
runner_sha256=r.sha(root / "tools/run_all_tests.py"),
|
||||
default_player_before=protected, fixed_input_pins=fixed,
|
||||
functional_watchdog_seconds=1200, source_adoption=False))
|
||||
diagnostics = module("s4_diagnostics", r.SUPPORT / "diagnostic_gate.py")
|
||||
owners = module("s4_owners", r.SUPPORT / "ordinary_proof_gate.py")
|
||||
audio = module("s4_audio", r.SUPPORT / "audio_evidence_gate.py")
|
||||
public = (stage / "project.godot").read_bytes()
|
||||
for phase, test in [("import", None), ("parse", None), ("writer", TEST), ("reader", TEST)]:
|
||||
if phase == "reader":
|
||||
writer = r.read(out / "raw/writer.json")
|
||||
formal_before = {n: r.inventory(Path(p)) for n, p in writer["slots"].items()}
|
||||
r.require(sorted(formal_before) == SLOTS, "exact three formal points for isolated power case")
|
||||
if phase == "import" and old is None:
|
||||
r.require(not (stage / ".godot").exists() and not (stage / ".godot").is_symlink(),
|
||||
"fresh private .godot absent at actual import")
|
||||
repin("before-" + phase)
|
||||
r.write(out / ("engine-identity-before-" + phase + ".json"),
|
||||
dict(engine_observations[-1], observation_index=len(engine_observations)-1))
|
||||
row = r.execute_phase(str(engine), stage, out, phase, test, selected, expected,
|
||||
previous, policy, contracts, paths, public, protected, root,
|
||||
outer, markers, diagnostics, owners, audio)
|
||||
phases.append(row)
|
||||
# execute_phase writer/reader share out/<test>.log via write_text.
|
||||
# Keep binary per-phase copies under these existing unique names.
|
||||
# The shared runner log is not write-once; see S4_ROOT_RUN.md.
|
||||
if test and row.get("log"):
|
||||
(out / (phase + "-combined-raw.log")).write_bytes((out / row["log"]).read_bytes())
|
||||
row["strict_runner_log_path"] = row["log"]
|
||||
row["log"] = phase + "-combined-raw.log"
|
||||
r.write(out / (phase + "-actual-phase.json"), row)
|
||||
print(json.dumps(dict(phase=phase, passed=row["passed"], error=row.get("validation_error"))), flush=True)
|
||||
r.require(row["passed"], "first strict phase failure: " + phase)
|
||||
if phase in ["writer", "reader"]:
|
||||
receipt = r.read(out / ("raw/" + phase + ".json"))
|
||||
r.require(receipt["checks"] > 0 and receipt["failures"] == 0,
|
||||
"S4 actual outcome receipt")
|
||||
r.require(sorted(receipt["branches"]) == CASES,
|
||||
"exact one independent Writer/Reader case")
|
||||
r.require(sorted(receipt["slots"]) == SLOTS, "all formal point slots")
|
||||
r.require(receipt["pid"] == row["actual_pid"], "actual native PID/receipt")
|
||||
repin("after-collection")
|
||||
writer = r.read(out / "raw/writer.json")
|
||||
reader = r.read(out / "raw/reader.json")
|
||||
r.require(writer["pid"] != reader["pid"] and writer["profile"] != reader["profile"], "distinct formal processes")
|
||||
formal_after = {n: r.inventory(Path(p)) for n, p in writer["slots"].items()}
|
||||
r.require(formal_before == formal_after, "Reader leaves Writer packages unchanged")
|
||||
except BaseException as caught:
|
||||
error = type(caught).__name__ + ": " + str(caught)
|
||||
finally:
|
||||
try:
|
||||
cleanup, cleanup_errors = outer.finish_captured(outer.OWNED)
|
||||
except BaseException as caught:
|
||||
cleanup_errors.append(str(caught))
|
||||
try:
|
||||
r.write(out / "outer-owned-finally.json", dict(cleanup=cleanup, errors=cleanup_errors))
|
||||
except BaseException as caught:
|
||||
guards.append("finally receipt: " + type(caught).__name__ + ": " + str(caught))
|
||||
# Explicit re-pin after cleanup is independent of other fixed-input guards.
|
||||
try:
|
||||
repin("after-cleanup")
|
||||
except BaseException as caught:
|
||||
guards.append(type(caught).__name__ + ": " + str(caught))
|
||||
# Fixed guard always follows ownership cleanup, even if setup or an engine failed.
|
||||
try:
|
||||
fixed_guard()
|
||||
after = r.source_refs(root)
|
||||
r.write(out / "source_after.json", after)
|
||||
r.require(after == source_before, "whole product source unchanged")
|
||||
r.require(r.default_player(root) == protected, "default player unchanged")
|
||||
if (stage / "project.godot").exists():
|
||||
r.require(r.inventory(stage, stage=True) == dict(expected, **previous), "whole private source unchanged")
|
||||
if cache_before is not None:
|
||||
r.require(r.inventory(old / "stage/.godot") == cache_before, "original cache unchanged")
|
||||
if formal_before is not None:
|
||||
writer = r.read(out / "raw/writer.json")
|
||||
formal_after = {n: r.inventory(Path(p)) for n, p in writer["slots"].items()}
|
||||
r.require(formal_before == formal_after, "Writer formal packages unchanged after Reader, including failure")
|
||||
except BaseException as caught:
|
||||
guards.append(type(caught).__name__ + ": " + str(caught))
|
||||
pids = [p.pid for p in outer.OWNED]
|
||||
passed = (error is None and not guards and not cleanup_errors and len(phases) == 4
|
||||
and all(p["passed"] for p in phases) and len(pids) == len(set(pids)) == 4
|
||||
and all(p["engine_ended"] for p in cleanup))
|
||||
execution = dict(passed=passed, error=error, guard_errors=guards, phases=phases,
|
||||
actual_captured_PIDs=pids, cleanup=cleanup, cleanup_errors=cleanup_errors,
|
||||
formal_before=formal_before, formal_after=formal_after,
|
||||
whole_goal_accepted=False, unit_accepted=False)
|
||||
# IO1 has already copied all four members into a separate Reader profile.
|
||||
# Mirror those exact complete directories to own B for read-only R verification.
|
||||
# Original profile paths are provenance after this step, never verifier inputs.
|
||||
try:
|
||||
if passed:
|
||||
relocated = {}
|
||||
for role in ("writer", "reader"):
|
||||
receipt = r.read(out / ("raw/" + role + ".json"))
|
||||
relocated[role] = {}
|
||||
for slot, original in receipt["slots"].items():
|
||||
target = out / "formal" / role / slot
|
||||
target.mkdir(parents=True)
|
||||
members = r.inventory(Path(original))
|
||||
r.require(set(members) == {"journal.log", "manifest.json", "thumbnail.png", "world.bin"}, "all formal members")
|
||||
for member in members:
|
||||
shutil.copyfile(Path(original) / member, target / member)
|
||||
r.require(r.inventory(target) == members == formal_before[slot], "whole formal mirror")
|
||||
relocated[role][slot] = dict(provenance=original, relative=str(target.relative_to(out)), members=members)
|
||||
r.write(out / "formal-relocation.json", relocated)
|
||||
r.write(out / "raw-inventory.json", r.inventory(out / "raw"))
|
||||
r.write(out / "PID-set.json", dict(owner_runner_pid=os.getpid(), engine_PIDs=pids))
|
||||
except BaseException as caught:
|
||||
guards.append("output collection: " + type(caught).__name__ + ": " + str(caught))
|
||||
passed = False
|
||||
execution["passed"] = False
|
||||
try:
|
||||
repin("after-output-collection")
|
||||
except BaseException as caught:
|
||||
guards.append(str(caught))
|
||||
passed = False
|
||||
execution["passed"] = False
|
||||
try:
|
||||
r.write(out / "engine-identity.json",
|
||||
dict(observations=engine_observations, identity=engine_identity))
|
||||
except BaseException as caught:
|
||||
guards.append("engine identity receipt: " + type(caught).__name__ + ": " + str(caught))
|
||||
passed = False
|
||||
execution["passed"] = False
|
||||
execution["engine_observations"] = engine_observations
|
||||
execution["engine_identity"] = engine_identity
|
||||
r.write(out / "execution.json", execution)
|
||||
r.write(out / "runner-ended.json", dict(execution, ended=r.now()))
|
||||
print(json.dumps(dict(passed=passed, error=error, guard_errors=guards, output=str(out))))
|
||||
return 0 if passed else 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
raise SystemExit(main())
|
||||
except Exception as caught:
|
||||
print(type(caught).__name__ + ": " + str(caught), file=sys.stderr)
|
||||
raise SystemExit(2)
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user