Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
29a641c540 | ||
|
|
cc3a377841 | ||
|
|
877dee40bc | ||
|
|
c0bc92a469 | ||
|
|
3bba4f34f0 | ||
|
|
dd20d5b96c | ||
|
|
64617018ed | ||
|
|
5475391647 | ||
|
|
9767b0a28c | ||
|
|
a19737b160 |
@@ -0,0 +1,177 @@
|
|||||||
|
# 初期監査の検証器
|
||||||
|
|
||||||
|
## repair7 定義(以前の版識別説明に優先)
|
||||||
|
|
||||||
|
registered は deb1bcb、execution_origin は 9767b0a、checker は実際に
|
||||||
|
実行したcheckerのcommitとbytes、product_target は
|
||||||
|
`{main_anchor, submitted, composed_tree}` とする。checkerのSHAを製品版に
|
||||||
|
代用しない。提出後にしか確定しない自己blob照合は未実施としてRootが検証する。
|
||||||
|
|
||||||
|
`duty_payments` は決算日のpayroll settlement取引のうち、
|
||||||
|
ShiftInstance.record_duty_minute の勤務recordとstaff_id/day単位で
|
||||||
|
結合できる取引の件数。期待値は1のまま。独立した通貨項目ではない。
|
||||||
|
collectorはpayroll行と勤務recordの実rawを結合し、結合不能なら失敗する。
|
||||||
|
|
||||||
|
融資の公開返済API `repay_loan_principal` とledger分類
|
||||||
|
`LOAN_PRINCIPAL_REPAID` / `financing_principal_outflows` は存在する。
|
||||||
|
自動返済や全UIの完成は主張しない。「融資機構がない」という説明は撤回する。
|
||||||
|
|
||||||
|
midnight_replayは同minuteの決算API再呼出しによる実測までpending。
|
||||||
|
1442完了やservice request再送を1440売上・決算API再呼出しの証明にしない。
|
||||||
|
shift_completionはapproved-shift-current-r01.gdの1080観測を使う。
|
||||||
|
D05のEV停止帰属、停電と休業の厳密な順序、費用時点の未確定部分はpending。
|
||||||
|
全case収集、登録validate、独立whole review、統合準備の順序を維持する。
|
||||||
|
本修理での試験・engine実行はworkerでは行わない。
|
||||||
|
|
||||||
|
## repair6 現在値(2026-10-16)
|
||||||
|
|
||||||
|
旧値: C全体は `native ordering bytes` によりblocked、実applicabilityは拒否、
|
||||||
|
whole native未確認。以下の「今回のPython確認と入力不整合」は旧bundleの履歴。
|
||||||
|
新値: 補正済み `build_dir/audit-support/native-first-current/` に対し
|
||||||
|
verify_native、ownership/save/ledger個別検査、applicabilityが成功した。
|
||||||
|
checker_native_fixtures.py、checker_negative_fixtures.py、
|
||||||
|
checker_applicability_fixtures.py --native-bundle は各1回実行しexit 0。
|
||||||
|
旧23負例と新2負例(signature行だけのSHA、後続コメントを除いたSHA)を全て拒否。
|
||||||
|
既存fixture14件も拒否、A/Bの登録版・production版・行移動正対照成功、3負例拒否。
|
||||||
|
正規bundleを合成補正せず検査し、結果・旧値・新版SHAはc-native-validation.jsonに保存。
|
||||||
|
全文rawの `old == new` および
|
||||||
|
`sha256(new[function]) == production_body_sha256 == registered_body_sha256` が必須。
|
||||||
|
whole gateは未実行。原15条件+EV、7領域、安全性3結果、30/88の分母、予算、
|
||||||
|
repair上限は変更せず、formal_original_credit=0。正式受入はRunner・独立レビュー・
|
||||||
|
オーナー判断待ち。以下の旧blocked記述を現在のC検査結果として扱わない。
|
||||||
|
|
||||||
|
## C段階追補(本節がrepair4以前の状態説明に優先)
|
||||||
|
|
||||||
|
`verify_native(base)` は供給済み `native-first-current` 形式の有限adapter。
|
||||||
|
Rootのprobe/Reader/wrapperは起動・再実装しない。A/B、verify、coverage、
|
||||||
|
boundary_conditions、15条件、reachable EV、7領域、3安全性結果は維持する。
|
||||||
|
既存scalar検査は宣言の整合性検査として残すが、これだけでは成功しない。
|
||||||
|
|
||||||
|
whole gateのindexには追加で `native_bundle` が必要。
|
||||||
|
`manifest` は evidence directory相対の `OWNERSHIP_RAW_PROVENANCE.json`、
|
||||||
|
`sha256` はその実SHA。manifestの親をbundle rootとして以下を照合する。
|
||||||
|
絶対パス・親参照・symlinkは既存safe規則で拒否する。
|
||||||
|
native production_commitはtested HEADと一致させる。別版資料をA/Bの一致だけで
|
||||||
|
流用しない。今回のproduction版とworker checkoutは区別したまま保持する。
|
||||||
|
|
||||||
|
1. SOURCE_MANIFESTの76 fileとOWNERSHIP_RAW_PROVENANCEの96 fileは、
|
||||||
|
各bytesとSHAを実体から再計算する。由来のsource絶対パスは開かない。
|
||||||
|
3 runのimport/parse/repro、formalのwriter/reader、合計10プロセスは
|
||||||
|
started→process→endedのPID、owner、argv、時刻順、実終了codeを照合する。
|
||||||
|
execution・phase receipt・actual phase・cleanupのPID集合と埋込記録も一致必須。
|
||||||
|
stdout/stderr/native logのSHA、通常終了、cleanupのwait結果とerrors空を要求する。
|
||||||
|
Writer/Readerは別PID・別profileで、profile receipt・環境・5 slotのパスも結合する。
|
||||||
|
2. production_commitは3結果資料とapplicabilityで一致し、specの6登録pinと
|
||||||
|
production Git blobから計算した6 current pinを区別する。各runのsource_beforeと
|
||||||
|
selected_source_mapにも結合する。20関数は既存A/Bと同じ全文raw比較を行う。
|
||||||
|
後続トップレベルdoc commentを含む抽出全文rawのSHAだけを許可する。
|
||||||
|
signature行のみ、コメント除外prefix等の候補は許可しない。
|
||||||
|
3. `observations.json` の5点と対応 `midnight-*.json` を結合する。
|
||||||
|
receiptはtransaction IDを一意にし、row重複を拒否し、順序・多重度を保持した
|
||||||
|
台帳差分との完全一致を要求する。Readerの生receipt、first/replayも同じ行に結合する。
|
||||||
|
SERVICE_SALEにservice_idを要求しない。after snapshotの
|
||||||
|
`/systems/runtime/economy/idempotency` の `service.payment.<service_id>` から
|
||||||
|
transaction_idを得て、sale・guest wallet・facility revenue・cash sweepを結合する。
|
||||||
|
固定のPID・価格・transaction番号を使わない。元価格と時間はproduction Gitの
|
||||||
|
facility .tres、run inputのpin、初期facility/service/requestと照合する。
|
||||||
|
PAUSEDはstateに加え `interruption_reason=RELATED_FACILITY_INACTIVE` を要求し、
|
||||||
|
進捗・service ID・予約IDを保持、売上0、財布差分0を再計算する。
|
||||||
|
request存在系列、予約requester/facility/ACTIVE→CONSUMED、復帰1回課金を照合する。
|
||||||
|
最後のGuest despawn時は残存台帳から財布を再計算し、Guest辞書の存在を偽定しない。
|
||||||
|
maintenanceは同一transaction・施設・金額のaccrual/paymentを対にする。
|
||||||
|
発生1439と支払い1440は同分扱いせず、観測したmidnightとその直前として照合する。
|
||||||
|
4. 正式保存は5点×4member×writer/reader=40実ファイルを照合する。
|
||||||
|
execution.formal_before/afterの20 member map一致と実SHA/bytes一致を要求する。
|
||||||
|
typed 14件はwriter-persisted 5、reader-loaded 5、reader-replayed 4(before以外)。
|
||||||
|
原order-evidence .varおよびrun固定input pinと実SHAを照合する。
|
||||||
|
.var decodeは行わず、値の判定はnative JSON receipt/stateを根拠とする。
|
||||||
|
|
||||||
|
このadapterが返す条件はdependency_stop/recovery_completion/paused_midnightの
|
||||||
|
有限系列だけ。正式保存member同一性は、全save_restore条件達成を意味しない。
|
||||||
|
whole gateは固定15条件+EVとこの結果の差集合を未収集としてfailする。
|
||||||
|
非ゼロ給与×シフト交代×00:00、command再送、停電→閉店同Tick、退出/予約解放、
|
||||||
|
破壊、EV等をscalar=trueで埋めることはできない。原条件の加点は常に0。
|
||||||
|
|
||||||
|
### 今回のPython確認と入力不整合
|
||||||
|
|
||||||
|
供給rawのownership/save/ledger個別検査は成功。applicability資料は
|
||||||
|
`native ordering bytes` で拒否する。4関数のbody SHAが複数行シグネチャの
|
||||||
|
途中までしか含まないためで、詳細はEVIDENCE_COLLECTION.mdに記録した。
|
||||||
|
実raw全体を合格とは報告しない。
|
||||||
|
|
||||||
|
checker_native_fixtures.pyはallowed_paths内の一時コピーで23負例を検査する。
|
||||||
|
通常の負例ではmanifest SHAも再計算し、hashだけを理由とする失敗を避ける。
|
||||||
|
1byte改竄、member欠落、PID/profile同一化、ended異常、cleanup欠落、
|
||||||
|
receipt二重化/削除、PAUSED売上、予約孤立、財布差分、idempotency誤結合、
|
||||||
|
commit/pin不一致、typed/member変更、scalar自己申告を全て拒否する。
|
||||||
|
PAUSED売上と予約孤立にはJSON側も同時変更する対抗例を含む。
|
||||||
|
一時コピーのapplicabilityのみGit全文hashへ差し替えた合成正対照はあるが、
|
||||||
|
これを正規native証拠として保存・採用しない。
|
||||||
|
|
||||||
|
旧synthetic_controlはnative rawを持たないため現在はexit 1が正しい。
|
||||||
|
既存13負例も維持し、全14件を拒否。A/Bの正対照と3負例も維持する。
|
||||||
|
本番の未収集indexに対する重複validateは行わない。
|
||||||
|
|
||||||
|
## repair4差分(未完成・blocked)
|
||||||
|
|
||||||
|
今回のA/B実装では、以下の旧説明の「6不変pin=実checkout」および
|
||||||
|
reachabilityのstart/end/text形式を置き換えた。詳細と未供給入力は
|
||||||
|
`EVIDENCE_COLLECTION.md` を参照。Cのowned_outer/raw台帳結合とD/Eの残項目が
|
||||||
|
未完成のため、このcheckerを正式受入には使用しない。
|
||||||
|
|
||||||
|
- `registered_pins` はspec.current_source_pinsと一致し、登録Git blob SHAをcheckerが再計算する。
|
||||||
|
- `versions` はexecution_origin(9767b0aの完全SHA)、registered(deb1bcbの完全SHA)、
|
||||||
|
tested(HEAD)、integration(null)の4キー。candidate_commitもHEADを維持。
|
||||||
|
- `applicability_delta` は登録版..HEADのautoload/scriptsのGD変更関数の
|
||||||
|
path/function配列。checkerの計算順と完全一致。`delta_cases` は
|
||||||
|
`path:function` → 条件ID → engine case ID。条件集合はchecker内固定の保守的判定。
|
||||||
|
- 全autoload/scripts GDがsource_map必須で、実checkoutとHEADのraw一致も必須。
|
||||||
|
- reachability.source_refsはpath/sha256/function/anchor_sha256/line_count。
|
||||||
|
絶対行位置は使用せず、関数内のraw本文アンカーを一意に解決する。
|
||||||
|
- 20順序関数は固定抽出規則で登録Git blobとtestedをraw比較する。
|
||||||
|
|
||||||
|
Python確認結果: 既存合成正対照1件exit 0、負例13件exit 1。
|
||||||
|
別のA/B対照は登録=testedと実production Git blobの合法delta(25変更単位)の
|
||||||
|
2件成功、両版で+7行のアンカー解決成功、delta欠落/境界case欠落/版誤表示の
|
||||||
|
3負例を拒否した。全てengine証拠ではなくPython検証のみ。
|
||||||
|
owned/count/restore/別候補receipt流用の新負例は未実装であり、上記に含めない。
|
||||||
|
|
||||||
|
以下はrepair3時点の記録。置換対象以外もC修正完了の証明として扱わない。
|
||||||
|
|
||||||
|
`python3 tools/check_p04_order_evidence.py --verify-only` は環境変数 `BUILD_OUTPUT_DIR` の `p04-evidence/index.json` を読み、書込み・試験・Runner操作をしない。欠落時は非0。原30/88の受入・完了を確定しない。
|
||||||
|
|
||||||
|
D05-P04-DEPENDENCYとD05-P04-SHIFT-1800は承認済み。固定必須条件と証拠が揃えばmachine_verified=true、formal_acceptance=false、exit 0。残るD05の帰属pendingを一括failにしない。必須authority pending・証拠欠落は非0。独立レビュー・通常統合までは正式受入0。
|
||||||
|
|
||||||
|
内部JSON形式は `P04_ORDER_EVIDENCE_PROTOCOL.json` を基に次の有限サブセットを実装した。重複JSONキー、NaN/Infinity、絶対・親参照・symlink・重複artifact pathを拒否する。全ハッシュは生バイトSHA256。indexに合格booleanや試験数があっても判定には使用しない。
|
||||||
|
|
||||||
|
- `schema_version: 1`, `task_id: P04-01`, checkout HEADと一致する `candidate_commit`, `formal_acceptance: false`。
|
||||||
|
- `source_map`: repository相対パス→SHA256。specの6不変pinと旧map内autoload/scriptsの全GDScript依存を必須とする。追加sourceも実ファイルと照合する。
|
||||||
|
- `environment`: 非空 `os` と `engine`, `configuration`, `inputs` の証拠ディレクトリ相対パス→SHA256 map。engineは実行バイナリのコピー。OSの自己申告だけではnative証明にならない。
|
||||||
|
- `artifacts`: ID→`path`, `bytes`(整数), `sha256`, `type`。typeは observation/state/receipt/stdout/native_log/save/authority のいずれか。
|
||||||
|
- `authority`: ref→`status`(original/approved), `artifact_id`。raw文書の存在を確認するが内容の原規約適合は独立レビューを要する。pendingは失敗。
|
||||||
|
- `domains`: 正確な7領域名→`source_refs`, `authority_refs`, `cases`, `invariants`, `applicability`。source_refは `path/function/sha256`。applicabilityはindexと同じ `source_map/environment`。
|
||||||
|
- 各caseは一意な `id` とartifact IDの `input/before/operation/after/receipt`。receipt JSONは正整数 `start_pid`、同じ `end_pid`、整数 `exit_code: 0`、`engine_ended: true`、一致する `source_map/environment`、raw `stdout/native_log` artifact IDを要求する。receiptのcase_idとinput/before/operation/afterのartifact_hashesを照合する。caseにもraw_origin=engineを要求する。ログの意味と実行由来の独立確認はRoot/reviewerが行う。
|
||||||
|
- 各invariantは `artifact_id/json_pointer/operator/authority_ref/raw_origin`。raw_originはengineのみ。root derivationはformula検証未対応のため拒否する。RFC6901 pointerでraw JSONから値を読む。eqはexpectedとの型を含むliteral一致、nonnegativeはboolean以外の数値が0以上、count_eqは配列/辞書長と整数expectedの一致。same_literal/distinctは `paired_artifact_id/paired_json_pointer` の値との型を含む比較。未知operatorは失敗。
|
||||||
|
|
||||||
|
## 固定必須条件
|
||||||
|
|
||||||
|
checkerのREQUIREDが唯一の必須条件定義であり、indexから条件を受け取らない。conditionsはそのIDと完全一致するmap。各値はcase_id、固定順のauthority_refs、nativeWindows: pending。各条件は指定domainのengine caseと全metricのeq invariantを必要とする。観測はcase.afterの /conditions/<condition_id>/<metric> に置き、invariantのcase_id、artifact_id、pointer、expectedを固定定義と照合する。pass booleanや試験数は使わない。測定値は実際のengine観測から出すこと。手書き合成fixtureはchecker分岐検証専用であり、受入証拠には使用不可。
|
||||||
|
|
||||||
|
- 停止課金0: power_stop / destruction_stop / dependency_stop。依存先停止は進捗差分0・予約とservice ID保持も必要。
|
||||||
|
- 完了・課金1回: shift_completion(1080、交代前)/ recovery_completion / same_minute_replay(再送・重複生成0)。
|
||||||
|
- 孤立0: closure_release / exit_release / patience_release / paused_midnight。直接休業ではD-034の開始済み継続と待機予約解放を区別。停止日跨ぎは停止中課金0と復帰完了課金各1回。
|
||||||
|
- 決算1回: midnight_settlement / midnight_replay / save_restore。売上・給与・維持費・勤務分の支払い各1回、非ゼロ給与を必須とし、帰属先は新しく決めない。
|
||||||
|
- night_staff_absent: 1081、夜番0、WAITING_FOR_STAFF。
|
||||||
|
- overtime_task: 既存interrupt_level=0の残業と勤務分記録1回(staff_service:1409-1412)。
|
||||||
|
|
||||||
|
authorityの参照名はREQUIREDに固定。BASELINE文書、D-034文書、既存A01を記録した入力草案のraw bytesをcheckoutと照合する。A01を再承認しない。承認2条件はauthority typeの同梱D05_OWNER_APPROVAL.jsonと同一SHA256(4583ee9aad45c4db183d72df4420df48aa8e0897e3b5efda13016f0b5cdbd68e)を要求する。origin=direct_user_reply、formal_acceptance=falseと承認IDも確認する。
|
||||||
|
|
||||||
|
save_restore条件にはsave_artifact(type=save)とrestore_receiptを追加する。元caseのreceiptのsaved_sha256、readerのrestored_sha256を保存raw hashと照合し、別の正整数PID・終了・exit 0・source/environment一致・stdout/native_logを要求する。保存packageの意味的完全性・正式保存APIの利用は生証拠の独立レビュー対象。
|
||||||
|
|
||||||
|
reachabilityはEV/transportのみ。各source_refはpath/sha256/start/end/textで、固定REACH_REFSの経路を全て含み、実際の行本文と一致すること。reason必須。救護搬送は課金予約reach=false。EVはスタッフ移動前予約経路があるためreach=trueとcase_idを必要とする。停電domainのengine case.afterのev.reserved_before_travel=true、power_stopped=true、orphan_reservations=0のeq invariantを要求する。客の到着後予約だけを根拠にEV全体を除外できない。
|
||||||
|
|
||||||
|
specは契約文字列hashと照合し、checkoutの末尾LF一つの表現差だけを許容する。その他のraw hashは正規化しない。
|
||||||
|
|
||||||
|
## worker確認
|
||||||
|
|
||||||
|
checker_negative_fixtures.pyはallowed_paths内の一時ディレクトリをBUILD_OUTPUT_DIRに使用し、終了後削除する。py_compile、対照用合成index(exit 0、正式受入false)、指定10負例(各exit 1、対応エラー)を確認済み。実engine試験・Runner操作は0。Runnerがp04-acceptanceを正式実行し、Rootが現版適用性、別セッションのreviewerが生証拠由来と意味を独立確認する。
|
||||||
@@ -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の他領域を一括解決したとは扱わない。"
|
||||||
|
}
|
||||||
@@ -0,0 +1,233 @@
|
|||||||
|
# P04-01: C段階の結合とrepair4収集契約の履歴
|
||||||
|
|
||||||
|
## repair6 現在値(2026-10-16、以下の旧blocked記述に優先)
|
||||||
|
|
||||||
|
旧値: 実applicabilityは `native ordering bytes` で拒否、C全体blocked、
|
||||||
|
whole native未確認。新値: 補正済みbundleのverify_native成功、
|
||||||
|
ownership/save/ledger個別検査とapplicability成功。供給bundleへの書込みは0。
|
||||||
|
全文rawの登録版/production一致と両body SHAの全文SHA一致だけを認める。
|
||||||
|
後続コメント除外prefixを許す暫定処理は削除した。
|
||||||
|
|
||||||
|
下記3本のPythonコマンドを各1回実行しexit 0を確認。
|
||||||
|
native fixtureは既存23負例+signature行SHA/コメント除外SHAの2負例を全て拒否。
|
||||||
|
合成補正コピーは廃止し、補正済み供給rawをそのまま正対照に使用。
|
||||||
|
既存fixture14件拒否、A/Bの登録版・production版・行移動正対照成功、3負例拒否、
|
||||||
|
native applicability成功。実行結果、旧検査値、新旧bundle pin、検査コードSHAは
|
||||||
|
c-native-validation.jsonに記録。verify_nativeは有限C検査でありwhole gateは未実行。
|
||||||
|
原未達の条件、formal_original_credit=0、15条件+EV/7領域/安全性3結果、
|
||||||
|
30/88の分母、予算、repair上限を維持する。Root追加paid/再送は今回の範囲外。
|
||||||
|
engine、全回帰、製品、Runner、config、DB、git書込み、他agentは全て0。
|
||||||
|
正式検証・独立レビュー・オーナー判断待ち。以下の「今回」は旧検査履歴を指す。
|
||||||
|
|
||||||
|
## C段階の追補対象
|
||||||
|
|
||||||
|
供給rawの現在位置は `build_dir/audit-support/native-first-current/`。
|
||||||
|
所有権、台帳・service・予約、正式保存の結合を追加する対象であり、
|
||||||
|
この資料の存在だけでは原受入を満たさない。formal_original_creditは0を維持する。
|
||||||
|
|
||||||
|
## 今回のC実装と入力blocker
|
||||||
|
|
||||||
|
変更はchecker、Python fixtureと本ディレクトリ内のdocsだけ。
|
||||||
|
Root helperはコピー・再実装せず、既存native成果物を読む。
|
||||||
|
結合の正確な規則はCHECKER_PROTOCOL.mdのC追補を参照。
|
||||||
|
新規engine/全回帰/Runner操作/git書込み/他agent起動は全て0。
|
||||||
|
旧39rawを読了・採用した数も0。repair4-context.jsonの旧履歴は保持した。
|
||||||
|
|
||||||
|
供給rawでは次の個別Python検査が成功した。
|
||||||
|
|
||||||
|
- OWNERSHIP_RAW_PROVENANCEの96 fileのbytes/SHAと10プロセスのlifecycle/cleanup。
|
||||||
|
- Writer/Readerの5点×4member、元20member不変、typed .var 14件の実SHA結合。
|
||||||
|
- 生receipt 8行、service.paymentのidempotency経由での1回課金、財布差分、
|
||||||
|
PAUSED時の進捗・予約保持、復帰後の消費、維持費発生1439→支払い1440。
|
||||||
|
|
||||||
|
ただし `POST_P18_CURRENT_SOURCE_APPLICABILITY.json` の4関数の
|
||||||
|
registered_body_sha256/production_body_sha256は本文を含まない。
|
||||||
|
実Git blobで検算すると、以下の関数先頭からのprefix SHAに一致した。
|
||||||
|
|
||||||
|
| path | function | hash対象のbyte数(本文を含まない) |
|
||||||
|
|---|---|---:|
|
||||||
|
| autoload/simulation_runtime.gd | _complete_service_transaction | 140 |
|
||||||
|
| scripts/facility/facility_service.gd | _prepare_service_for_tick | 87 |
|
||||||
|
| scripts/facility/facility_service.gd | _complete_service | 113 |
|
||||||
|
| scripts/facility/facility_service.gd | _fail_service | 119 |
|
||||||
|
|
||||||
|
4つとも複数行引数の後の `) -> ...` より前で終わる。全文比較は別途成立するが、
|
||||||
|
報告されたbody hashとの一致は成立しないため、実bundleは
|
||||||
|
`native ordering bytes` でfailする。workerはこの供給資料もmanifestも変更していない。
|
||||||
|
Root側で同じproduction Git blobの全文を対象にapplicabilityとそのmanifestを
|
||||||
|
再生成する必要がある。engineの再起動を必要とする不整合ではない。
|
||||||
|
旧暫定処理では後続トップレベルdoc commentの有無だけの2関数差をraw prefix一致
|
||||||
|
として扱っていた。repair6で撤去し、現在は全文SHA一致のみ必須とする。
|
||||||
|
|
||||||
|
この入力不整合が残るため、C段階の実raw全体の成功は未確認(blocked)。
|
||||||
|
これは原P04-01未達とは別のblockerであり、修正済みの合成コピーで全C検査が
|
||||||
|
通ることを原証拠の合格へ換算しない。
|
||||||
|
|
||||||
|
## 有限の検証
|
||||||
|
|
||||||
|
実行したのは次のPython確認のみ。全て一時ファイルをallowed_paths内へ限定し削除する。
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python3 -B docs/plans/original-completion-2026-10-02/execution/p04-01/checker_native_fixtures.py /Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build/audit-support/native-first-current
|
||||||
|
python3 -B docs/plans/original-completion-2026-10-02/execution/p04-01/checker_negative_fixtures.py
|
||||||
|
python3 -B docs/plans/original-completion-2026-10-02/execution/p04-01/checker_applicability_fixtures.py --native-bundle /Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build/audit-support/native-first-current
|
||||||
|
```
|
||||||
|
|
||||||
|
Cの結果は `c-native-validation.json`。23負例を拒否し、実rawの3個別検査成功、
|
||||||
|
実applicability拒否、修正した合成applicabilityの組合せ正対照成功を区別する。
|
||||||
|
既存fixtureはscalarのみの旧正対照を含む14件を拒否。
|
||||||
|
A/Bは登録版・production版の正対照と行移動の正対照、3負例、C資料の拒否を確認した。
|
||||||
|
未収集indexに対するwhole gate/validateは再実行していない。
|
||||||
|
|
||||||
|
## 固定の原未達
|
||||||
|
|
||||||
|
非ゼロ給与のシフト交代×00:00、command再送、停電→閉店同Tick、退出/予約孤立、
|
||||||
|
破壊、EVは未収集。正式保存のSHA同一性は全原条件のsave_restore証明とは別。
|
||||||
|
15条件、reachable EV、7領域、安全性3結果を減らさず、未収集をfalse/pending扱いとする。
|
||||||
|
原30/88のformal_original_credit=0、独立レビュー・通常統合未実施を維持する。
|
||||||
|
|
||||||
|
以下はrepair4時点の履歴であり、C追補の実装・検証状態には上記が優先する。
|
||||||
|
|
||||||
|
本修正は未完成であり、収集開始・machine gate・原受入の承認ではない。
|
||||||
|
原30/88、7領域、部分点0を維持する。D05全体はpending、既存の承認2条件を
|
||||||
|
超える規則は作らない。D-034/A01の再承認は求めない。
|
||||||
|
|
||||||
|
## 読取確認
|
||||||
|
|
||||||
|
`.review-material` はこのcheckoutには存在しない。指定された実体
|
||||||
|
`build_dir/audit-support/p04-native-existing-sources/` のwrapper 4本とGD probe
|
||||||
|
6本、および `tools/run_all_tests.py`、`tools/regression/owned_outer.py` を確認した。
|
||||||
|
10本のraw SHA/sizeは配布SOURCE_MANIFEST.jsonと一致し、`repair4-context.json`
|
||||||
|
に保存した。外部旧worktreeのartifactパスは由来としてのみ扱った。
|
||||||
|
|
||||||
|
実checkout HEADは `9767b0a28ca8d252520307dbbac60a1d8cfb87ab`、Runtimeは
|
||||||
|
`d52a995ae715542bbc550f68d35889540658275d12e04ae0b21ac276f5843f39`。
|
||||||
|
提供されたproduction資料の版は `5f6bc11d6bb6e92d22d787989b219fef3a435bc1`、
|
||||||
|
Runtimeは `7f01b2dcdf28bc965c7588173c2dc70054633faf7204b1ac38ba0f4369331b47`。
|
||||||
|
productionのGit objectも存在する。production資料を実checkoutのsource_mapとして
|
||||||
|
流用しない。登録版は `deb1bcb2374eb378b840ca345db952bd8572cb6e`。
|
||||||
|
|
||||||
|
## A/Bの変更
|
||||||
|
|
||||||
|
checkerはspecのcurrent_source_pinsを登録pinとして保持し、登録Git blobのSHAを
|
||||||
|
独立計算する。indexはregistered_pinsとversionsのexecution_origin/registered/
|
||||||
|
tested/integrationを分離する。integrationは未統合のためnullのみ。
|
||||||
|
testedのautoload/scripts全GDはcheckoutとHEAD blobの一致を必須にする。
|
||||||
|
|
||||||
|
20関数の抽出はchecker内固定。行頭func/static funcから次の行頭関数までの
|
||||||
|
raw bytesを比較し、コメント・空白・改行を正規化しない。先頭宣言は
|
||||||
|
`<top-level>` 単位としてdiffに含める。追加・削除関数も含む。
|
||||||
|
`git diff --no-ext-diff --no-renames --name-only -z base..HEAD -- autoload/ scripts/`
|
||||||
|
で変更GDを列挙し、Git blobから変更関数を計算する。applicability_deltaは
|
||||||
|
path/function順の配列と完全一致させる。未知の変更も無影響と推定せず、
|
||||||
|
delta_casesに保守的な全条件被覆を要求する。境界case IDの存在は後続coverageで
|
||||||
|
検証するが、**Cの生証拠結合は未実装なので、この段階では適用可の最終判断に使えない**。
|
||||||
|
|
||||||
|
REACH_REFSはpath/function/anchor_sha256/line_countに変更。実tested関数内で
|
||||||
|
本文raw bytesのアンカーを一意に解決する。旧固定範囲が隣接関数へ跨がる
|
||||||
|
reserve/rescue箇所は関数単位に分割した。行位置が7行移動しても解決し、
|
||||||
|
本文変更・欠落・重複アンカーは失敗する。
|
||||||
|
|
||||||
|
## 新collectorを作らず残す既存実行パターン
|
||||||
|
|
||||||
|
Root側の既存wrapper修正で保持すべき順序は、`r.source_refs(R)` →
|
||||||
|
明示QA上書きしたrefs → `r.copy_source` によるprivate stage → bootstrap →
|
||||||
|
cacheの `cp -c` と別inode確認 → parse-paths/contracts → `execute_phase` →
|
||||||
|
finallyの `owned_outer.finish_captured` → fixed_guardと不変性確認。
|
||||||
|
このworkerはwrapperを起動・変更していない。適用可能なパラメータ化patchと
|
||||||
|
全不足probeはまだ提出できていないため、以下は実行済みの契約ではなく残作業である。
|
||||||
|
|
||||||
|
- R/OUT/QA/testpath/cache/input manifestは最終tested checkoutに対して固定する。
|
||||||
|
1299件数や旧source-after.json一致をauthorityにしない。現r.source_refsがauthority。
|
||||||
|
- checkout mapと、testpathにQAを明示上書きしたselected mapを別に記録する。
|
||||||
|
source_map=tested、probeの元pathとSHA、QA_overrides、実stageの同SHAを結合する。
|
||||||
|
候補製品source overrideは持ち込まない。
|
||||||
|
- source_before/after、default_player_before/after、doc4_before/after、stage inventory、
|
||||||
|
parse-paths、contracts、wrapper raw、phase receipt、started/ended、
|
||||||
|
outer-owned-finally、execution.jsonを保存する。booleanだけでは不変性の代用にならない。
|
||||||
|
- case receiptはphaseのactual_pidとexecution.actual_captured_PIDs、cleanupの全PID集合に
|
||||||
|
結合する。cleanup_errorsが空、全childのengine_ended、終了codeを実成果物間で照合する。
|
||||||
|
- 各条件のscalarに加え、生台帳・service/settlement ID・予約配列とのcount_eqまたは
|
||||||
|
same_literalが必要。save_restoreはWriter保存直前と正式Reader復元直後の
|
||||||
|
台帳・予約のsame_literal、および別PIDを要求する。nonceやログ文言では代用しない。
|
||||||
|
|
||||||
|
## 負履歴(起動禁止)
|
||||||
|
|
||||||
|
10本すべてのSHAはrepair4-context.jsonに記録した。特に以下を再実行しない。
|
||||||
|
|
||||||
|
- shift-end-repro-r03.gdは1080で「課金なし・予約保持」を期待し、承認済みの
|
||||||
|
完了段階先行と矛盾する。対応native-r03.pyには旧1299 pinもある。
|
||||||
|
- formal-boundary-reload-native-r03.pyは旧candidate sealと製品2件overrideを使う。
|
||||||
|
SHAを書換えて別候補receiptとして使わない。
|
||||||
|
- formal-boundary-reload-r01.gdのmanifest.source_refs==1299も旧前提。
|
||||||
|
- midnight native-r01とpower native-r04も固定1299と旧full-run cacheに依存する。
|
||||||
|
power readerは旧Writerの二つの正式保存slotに依存し、fresh Writerではない。
|
||||||
|
|
||||||
|
## 15+EVの被覆確認(未収集)
|
||||||
|
|
||||||
|
同一プロセス内でも条件ごとのseed、before/after、case receiptを保持する。
|
||||||
|
以下のartifact名は既存probeが出すもの。不足欄を合格扱いにしない。
|
||||||
|
|
||||||
|
| 条件 | 既存probe / phase / artifact | 不足 |
|
||||||
|
|---|---|---|
|
||||||
|
| midnight_settlement, midnight_replay | midnight-boundary-r01 / repro / observations.json, midnight-*.var | countとraw台帳の結合、同minute再送の明示 |
|
||||||
|
| paused_midnight | midnight-boundary-r01は開業サービスのみ | 停止した同一serviceの日跨ぎ系列 |
|
||||||
|
| night_staff_absent | shift-end-repro-r03 / repro / 18-01観測 | 旧誤期待を除いた独立caseと非完了service |
|
||||||
|
| power_stop, recovery_completion | power-formal-recovery-r04 / reader-a,b / recovery-*.var | fresh停止入力、台帳JSON、旧Writer依存の解消 |
|
||||||
|
| dependency_stop | 供給6 probe内に専用系列なし | 関連施設停止・進捗/予約/service ID保持 |
|
||||||
|
| exit_release | exit-boundary-r01 / repro / queued-*.var, observations.json | raw stateとcount結合 |
|
||||||
|
| patience_release, closure_release | 供給6 probe内に専用系列なし | 待機解放、開始済み継続を区別したcase |
|
||||||
|
| same_minute_replay | exit/destruction/midnightはsnapshot巻戻し再実行 | 同minuteコマンド再送の独立条件 |
|
||||||
|
| shift_completion | shift-end-repro-r03 / repro / 18-00各phase観測 | 誤期待の修正、課金1回のraw結合 |
|
||||||
|
| overtime_task | 供給6 probe内に専用系列なし | interrupt_level=0残業・勤務記録1回 |
|
||||||
|
| destruction_stop | destruction-completion-boundary-r02 / repro / unfinished-*.var | raw台帳結合。completeは別帰結として維持 |
|
||||||
|
| save_restore | formal-boundary-reload-r01 / writer,reader / *.var, writer.json, reader.json | final版の入力seal、JSON台帳/予約、owned PID結合 |
|
||||||
|
| EV | 供給6 probe内に専用系列なし | staff予約→EV移動→停止→到着の実状態 |
|
||||||
|
|
||||||
|
transportはreach=falseのソース根拠のみで、別engine条件は追加しない。
|
||||||
|
|
||||||
|
## 再利用の有限条件と未供給資料
|
||||||
|
|
||||||
|
input.jsonのevidence_refsは旧証拠の参照であり、生証拠そのものではない。
|
||||||
|
今回の供給先で確認できたのはsource、監査、適用性診断とgate失敗ログである。
|
||||||
|
旧39件のraw receipt/state/packageを照合して採用した件数は0。
|
||||||
|
「20関数一致」だけで39件を採用することは禁止する。
|
||||||
|
|
||||||
|
必要な追加のローカルraw資料:
|
||||||
|
|
||||||
|
1. 旧39件のexecution/phase/started/ended/outer-owned-finally/log/state一式と
|
||||||
|
元source/config/input/environment pin。既存input.jsonのreference SHAで照合する。
|
||||||
|
2. formal-boundary-reload-input-r02.json
|
||||||
|
(wrapper記録SHA `195cb767e597008d1767aa0b6c8300711b83e8cf101863166e7932d9bd4ed612`)
|
||||||
|
と、そのwhole_typed_inputs、元exact_execution、保存package全4メンバー。
|
||||||
|
3. power original writer.json
|
||||||
|
(wrapper記録SHA `cf3850c262012e5e52743bfb8b41af7ef5608689fb10c059c8a494216b0aff52`)
|
||||||
|
と参照slot。旧candidateは比較用負履歴に限定する。
|
||||||
|
|
||||||
|
原則は最終の正確な版で再実行する。登録版証拠を再利用する有限条件は、A(i)の
|
||||||
|
20関数raw一致、A(ii)の全delta一致、A(iii)の変更境界をtested engine caseで被覆し、
|
||||||
|
さらに当該旧caseのsource/config/input/environmentとraw状態を独立照合できること。
|
||||||
|
relationship restore/schema変更は正式Writer/Reader再実行、build/path/arrivalに
|
||||||
|
関係する変更は当該経路に到達するcaseの再実行が必要。今回の資料だけでは再利用0。
|
||||||
|
|
||||||
|
## Rootのコマンドと残る作業
|
||||||
|
|
||||||
|
現在実行できるのは次のPython確認のみ(engineプロセス0)。
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python3 docs/plans/original-completion-2026-10-02/execution/p04-01/checker_applicability_fixtures.py
|
||||||
|
python3 docs/plans/original-completion-2026-10-02/execution/p04-01/checker_negative_fixtures.py
|
||||||
|
BUILD_OUTPUT_DIR=/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build python3 tools/check_p04_order_evidence.py --verify-only
|
||||||
|
```
|
||||||
|
|
||||||
|
最後のコマンドはindex未収集ならexit 1が期待値。Python正対照はengine証明ではない。
|
||||||
|
**Cのchecker強化、Dのparameter patch/不足probe、Eのowned/count/restore/他候補流用負例は未完成。**
|
||||||
|
旧checkerのscalar/receipt自己申告を受入可能な証拠とみなしてはならない。
|
||||||
|
現在のcheckerを正式受入に使用せず、修正全体をblockedとしてRootへ返す。
|
||||||
|
|
||||||
|
native収集の正確なコマンド列は、上記入力と未完成patchがないため提示できない。
|
||||||
|
仮のコマンドを実行可能と偽って提示しない。共有stageでimport+parseを2プロセス、
|
||||||
|
midnight/power/exit/shift/destruction/save Writer/save Reader/EVを8プロセスに
|
||||||
|
束ねられれば計10プロセスが計画目安になるが、これは確定数・目標数ではない。
|
||||||
|
条件ごとに必要な状態系列とguard成立を優先する。
|
||||||
|
Rootのnative収集→validate→別セッション独立レビュー→通常統合は未実施。
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
# P04-01 監査状態
|
||||||
|
|
||||||
|
原単位は未完了。D05-P04-DEPENDENCY と D05-P04-SHIFT-1800 は direct_user_reply により承認済み(同梱 D05_OWNER_APPROVAL.json、SHA256 4583ee9aad45c4db183d72df4420df48aa8e0897e3b5efda13016f0b5cdbd68e)。承認は試験合格を意味しない。A01の00:00保有時間按分維持費は承認済みで再確認しない。
|
||||||
|
|
||||||
|
正式試験はRunnerが実行する。旧証跡参照は来歴であり、現版の生証跡・ソース・設定・入力・環境の適用性確認なしに合格として再利用しない。
|
||||||
|
|
||||||
|
## 現行ソースの観測
|
||||||
|
|
||||||
|
`scripts/core/simulation_kernel.gd:123-153` の順序は APPLY_COMMANDS → 時計増分・日境界signal → TIME_BOUNDARIES → MANA → CONSTRUCTION → LOGISTICS → FACILITY_COMPLETION → VITALS_AND_ENVIRONMENT → INCIDENTS_AND_EVENTS → FACILITY_QUEUES → AI_DECISIONS → PUBLISH_SNAPSHOT。同phase内はhandler IDの辞書順(242行)。Runtimeの登録は364-427行。時計や製品ロジックは変更していない。
|
||||||
|
|
||||||
|
| 領域 | 現行呼出しと先後 | 原本・承認・不足 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 閉店 | `_advance_facilities` の後、`_advance_guest_queues` が `_release_closed_business_queues` を実行し、patienceより先に予約解放を試みる。 | 直接休業はD-034の開始済サービス継続。依存先休業は承認済D05の進捗・課金停止、予約・ID保持、復帰後1回完了。旧business試験は部分候補。open serviceの00:00証拠を代用しない。 |
|
||||||
|
| 停電 | `_advance_mana` → `mana_power_projection.apply` → FacilityService `_prepare_service_for_tick`。NO_POWERはpause、復電はresume。 | 草案の961停止/962復電/963不変は生証跡未確認。進捗・予約・帳簿・通知を現版で確認する必要がある。 |
|
||||||
|
| 破壊 | commandはAPPLY_COMMANDSで先行。`_advance_defense` は完了後のINCIDENTS。BROKENはサービス準備時に失敗。 | 先行破壊44と後段完了45を区別。失敗診断も保持し、原本適合と断定しない。 |
|
||||||
|
| 退出 | 公開queue leave → 完了 → INCIDENTS → queue patience解放 → AI再計画/予定退出。LEAVINGのdespawnは入状態分より後。 | 公開leave・自動patience・overdue退出の正式保存/別PID証跡は参照のみ。孤立0を現版で証明する必要がある。 |
|
||||||
|
| 課金 | `_complete_service` callback → `_complete_service_transaction` → `_apply_service_transaction`。予約消費後、service IDによるpayment keyで記帳し、通知scopeを終了。 | 原価格・時間を変えない。静的なidempotency keyの存在や試験数だけで二重課金・二重生成なしとしない。 |
|
||||||
|
| シフト交代 | 完了後の `_advance_staff_ai` 内でstaff更新・commute・staffing投影。 | 原本64/67行の18:00交代を保持。承認済D05により[17:59,18:00)は昼番、18:00完了・課金後に交代。夜番は[18:00,18:01)から。1080診断を承認規約に格上げしない。 |
|
||||||
|
| 00:00決算 | TIME_BOUNDARIESのweather/upkeep/operationsが完了より先。OperationsCoordinatorのdaily closeと同分service売上は別区間。 | 原本71/72行、承認済A01を保持。1440一回/1441不変の生証跡と適用性が必要。給与0fixtureを非ゼロ給与証拠に数えない。 |
|
||||||
|
|
||||||
|
## 再利用の制限
|
||||||
|
|
||||||
|
詳細ハッシュ・7領域の不足は `order-audit.json` に記録した。ローカル旧mapはSHA256 `df8328ea8b6a7bc400caad8a52f6ead710c01942a726e5a6b530b82411c55da5`、1299件、欠落0件。主要6ソースの指定pinは一致。旧mapとの差は14件で、契約記載のRuntime/main/preview/performance/path/2試験の7件に加え、Kenney同梱サンプル/URL7件がバイト不一致だった。追加7件は後述のRunner支援入力で分類し、素材を編集していない。
|
||||||
|
|
||||||
|
修正入力で旧Runtimeのローカルrawコピーを受領し、SHA256 `2d7e2cea5424585e9aa4302ab66d1fc4dbaefe743ce4328fccc1efa2f19b82e4` を確認した。`git diff --no-index` による現Runtimeとの差分は4箇所で、客候補経路batch、施設入口の経路費用比較、客/スタッフの移動時間推定、connector credit helper抽出に関わる。Runtime内のphase登録・施設完了順序はこの差分では変わらないが、変更されたPathServiceと到達性・選択・到着・勤務見積りへの影響を個別確認する必要がある。追加7素材差分はRunner supportがCRLF→LFのみと報告し、現raw hashを照合した。旧raw素材は未供給のため正規化一致を独立再現したとは扱わない。
|
||||||
|
|
||||||
|
ローカル入力は旧summary/map/draftであり、生の保存package・stdout・engineログ・開始終了記録・設定・入力・環境証拠は提供されていない。外部worktreeのartifactパスは来歴に留め、読んでいない。正式保存8境界27状態、fresh Reader A/B、P04-02/fullの成功記載は未検証参照で、P04-01受入を意味しない。
|
||||||
|
|
||||||
|
## 提出状態と有限検証計画
|
||||||
|
|
||||||
|
今回の承認2条件と既存権限(BASELINE:64/67/71/72/183、D-034、A01)を分け、checker内の固定条件レジストリに対応付けた。費用時点・日跨ぎの帰属など残るD05はpendingのまま補完しないが、一括failの理由にはしない。売上・給与・維持費・勤務分の各1回、非ゼロ給与、停止の00:00またぎでも完了課金1回・予約孤立0は必須である。全条件を満たすとmachine_verified=true、formal_acceptance=false、exit 0。必須条件のauthorityがpendingなら失敗する。
|
||||||
|
|
||||||
|
承認4文書は全てバイト数とSHA256を再計算し、SOURCE_MANIFESTと一致。必要な承認JSONのみ2073 bytesを同梱した。初期空き容量約1.8GiB、旧39 artifactの複製なし。39件の原パスは来歴であり、今回のミラーは承認4文書のみ。生証拠・入力・設定・環境が不足しているので、境界の適用性合格は宣言しない。
|
||||||
|
|
||||||
|
順序関数20本は今回個別に本文を再ハッシュし、供給されたhistorical_body_sha256と20/20一致した(詳細はJSON repair3)。旧監査の29本とは合算しない。旧Runtime rawも再ハッシュし、git diff --no-indexでは5個の@@区間(候補batchの開始・終了が別区間)、論理的な変更は記録の4箇所と確認した。関数一致は経路・候補・到着の適用性を保証しない。PathService変更を含め、停電・破壊・退出の影響ケースはRootが生証拠と設定を照合し、不適用の場合だけ新しい有限境界証拠を作る。production Runtime 4696df4aとcheckout d52a995の差も統合前にRootが確認する。
|
||||||
|
|
||||||
|
### 到達性
|
||||||
|
|
||||||
|
- 営業時間による閉店: autoload/scriptsの検索ではset_opening_hoursの定義とサービスの委譲のみ。既定[0,1440)で製品呼出しなし。直接休業と依存先休業の証拠は引き続き必須。
|
||||||
|
- 客の通常EV移動: Runtime:8898-8922の到着後にqueueを要求し、7421-7445で予約する。この移動前予約経路は対象外。
|
||||||
|
- スタッフEV: 対象外にできない。公開create_staff_task(Runtime:329、6733-6742)でrequired_itemsを渡せる。task_dispatcher:256-261、293-294、309-314が移動前に予約し、Runtime:9141-9170が在庫予約を実施、12009-12013で移動へ進み、8755-8763、8946-8987からEVを利用する。set_powered(elevator_service:279-284)による停電はこの予約保持経路に到達する。checkerはEV全体のreach=falseを拒否し、予約保持中停電のengine caseと孤立0 invariantを要求する。EV停止規則や期限は新設しない。
|
||||||
|
- 救護搬送: Runtime:12023-12030は治療状態を更新するだけ、12976-13050は救護タスク生成・移動・取消でrequired_items・課金処理を持たない。物理搬送自体でなく、課金・予約の到達性がfalse。静的根拠の行・本文・hashを必須とし、7領域の分母に加えない。
|
||||||
|
|
||||||
|
### 有限検証と残件
|
||||||
|
|
||||||
|
workerはpython3 -m py_compileとchecker_negative_fixtures.pyを実行。index欠落、重複キー、symlink、hash不一致、stale pin、未実行receipt、pending authority、必須条件欠落、根拠なしreach=false、承認hash不一致の10例が期待した理由でexit 1。合成対照のみexit 0であり、エンジン由来の製品証拠ではない。最初の対照実行はspec pinで失敗した。契約のspec文字列は末尾LFなし、checkout JSONは末尾LF一つ付きであり、その厳密な表現差のみ許容後、上記確認が通った。仕様ファイルは変更していない。
|
||||||
|
|
||||||
|
1. Runnerがp04-acceptanceを正式実行する。
|
||||||
|
2. Rootが旧証拠の適用性と不足を独立確認する。非ゼロ給与の00:00、停止の00:00またぎ、再送、正式保存→別PID復元、夜番不在18:01、既存残業タスク、予約保持中EV停電の証拠を揃える。
|
||||||
|
3. 別セッション独立レビューと通常統合のfinalまで正式受入加算0。nativeWindowsは条件ごとにpending。
|
||||||
|
|
||||||
|
エンジン実行・Runner操作・Git書込み・他エージェント起動は0。原30パッケージ/88成果の既存1単位内の準備であり、成果追加0、正式完了0。
|
||||||
+65
@@ -0,0 +1,65 @@
|
|||||||
|
{
|
||||||
|
"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."
|
||||||
|
}
|
||||||
@@ -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ではありません。
|
||||||
+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,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))
|
||||||
+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))
|
||||||
+167
@@ -0,0 +1,167 @@
|
|||||||
|
#!/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
|
||||||
|
|
||||||
|
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}')
|
||||||
|
proc = subprocess.run(['python3', str(CHECKER), '--verify-only'],
|
||||||
|
env=dict(os.environ, BUILD_OUTPUT_DIR=str(base)), text=True, capture_output=True)
|
||||||
|
output = json.loads(proc.stdout)
|
||||||
|
assert proc.returncode == (0 if error is None else 1), (name, proc.stdout, proc.stderr)
|
||||||
|
assert error is None or error in output.get('error',''), (name, output)
|
||||||
|
results.append(dict(fixture=name, exit_code=proc.returncode, 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}), 'stale pin')
|
||||||
|
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,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,520 @@
|
|||||||
|
{
|
||||||
|
"input_hashes": {
|
||||||
|
"docs/plans/original-completion-2026-10-02/execution/p04-01-input-r01/ORDER_CONTRACT_DRAFT.md": "c4dd8b907321b8859ba0744b9ae3d1447615d98fbe548983f9bb9fcad2d9d989",
|
||||||
|
"docs/plans/original-completion-2026-10-02/execution/p04-01-input-r01/input.json": "1286f865656aafb6146c7bcd95c92c53c00b6c706f3f690d96e998d5e45e2b79",
|
||||||
|
"docs/plans/original-completion-2026-10-02/execution/p04-01-input-r01/historical-audit.json": "90d2dbd3c04aed7ab409afa4f05f884832a4661cc506d28b818c72aadc352950",
|
||||||
|
"docs/plans/original-completion-2026-10-02/execution/p04-01-input-r01/historical-source-map.json": "df8328ea8b6a7bc400caad8a52f6ead710c01942a726e5a6b530b82411c55da5"
|
||||||
|
},
|
||||||
|
"source_pins": {
|
||||||
|
"autoload/simulation_runtime.gd": {
|
||||||
|
"expected": "d52a995ae715542bbc550f68d35889540658275d12e04ae0b21ac276f5843f39",
|
||||||
|
"actual": "d52a995ae715542bbc550f68d35889540658275d12e04ae0b21ac276f5843f39",
|
||||||
|
"matches": true
|
||||||
|
},
|
||||||
|
"scripts/facility/facility_service.gd": {
|
||||||
|
"expected": "ceb4a3e9224587423f273cdb2b56e6843ab176ac1fc4fe79dd498b5ed5c33b77",
|
||||||
|
"actual": "ceb4a3e9224587423f273cdb2b56e6843ab176ac1fc4fe79dd498b5ed5c33b77",
|
||||||
|
"matches": true
|
||||||
|
},
|
||||||
|
"scripts/economy/economy_service.gd": {
|
||||||
|
"expected": "95aedd0a0421c93e8fa39f0f76fa890cd0a46905d9a25c88c8b17c4d6d47728e",
|
||||||
|
"actual": "95aedd0a0421c93e8fa39f0f76fa890cd0a46905d9a25c88c8b17c4d6d47728e",
|
||||||
|
"matches": true
|
||||||
|
},
|
||||||
|
"scripts/guest/guest_service.gd": {
|
||||||
|
"expected": "56880410e9a00816f9a95d30821982955532de8dd976980934e12016a9f2b21e",
|
||||||
|
"actual": "56880410e9a00816f9a95d30821982955532de8dd976980934e12016a9f2b21e",
|
||||||
|
"matches": true
|
||||||
|
},
|
||||||
|
"scripts/staff/staff_service.gd": {
|
||||||
|
"expected": "355ad5c3c9f85dc4d4ffc3bc4293e36f81078c403d1662512a735b2c8d815fb3",
|
||||||
|
"actual": "355ad5c3c9f85dc4d4ffc3bc4293e36f81078c403d1662512a735b2c8d815fb3",
|
||||||
|
"matches": true
|
||||||
|
},
|
||||||
|
"scripts/mana/mana_network_service.gd": {
|
||||||
|
"expected": "96f195cf2a28b0966d12cd7c6fab86d13eae97c8342d07d70f52e598fbf49af9",
|
||||||
|
"actual": "96f195cf2a28b0966d12cd7c6fab86d13eae97c8342d07d70f52e598fbf49af9",
|
||||||
|
"matches": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"historical_map_entries": 1299,
|
||||||
|
"differences": [
|
||||||
|
{
|
||||||
|
"path": "assets/kenney/tiny_dungeon/Tiled/sampleMap.tmx",
|
||||||
|
"old_sha256": "fc545787837e2655c9d2078cc1b167df0d9ea2755f6ec3912c7987e8501339e3",
|
||||||
|
"current_sha256": "3849e083e3d4f6a71dbf725d8473e93fa6ef8cdc9d314368eee15c980cf41775"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "assets/kenney/tiny_dungeon/Tiled/sampleSheet.tsx",
|
||||||
|
"old_sha256": "02f1b90cfdb37bcccddc83d074954272aaffd297ed4fabce7bb670f2f3f4be71",
|
||||||
|
"current_sha256": "a2cca2c5e50b14023ce00f5d4112fb0b8264bfff9407d7a5198656e28d67fa16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "assets/kenney/tiny_dungeon/Visit Kenney.url",
|
||||||
|
"old_sha256": "0dd1a0b295582cd3185547f0c1d92b4d5392c7782b90a428e192944a496052e0",
|
||||||
|
"current_sha256": "6787defa3ecb6118622e1903b65802c0200628fae5164b51b4fd467f92f3f284"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "assets/kenney/tiny_dungeon/Visit Patreon.url",
|
||||||
|
"old_sha256": "84be5ef26b07905fa7e196e943c9807bf84138c3a71d87f51c6fd57b85c48c34",
|
||||||
|
"current_sha256": "2fcba31333935f4aa35e9de2e74e2b7ba1b1a8011540e08016ff6d81e9dd7490"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "assets/kenney/tiny_town/Tiny Dungeon (base pack).url",
|
||||||
|
"old_sha256": "6cf442be20ad3364ba87183461744da9e979b5d4f243c83e2404f2cf884500d9",
|
||||||
|
"current_sha256": "6d93bb958f9b7d5f21ceb8cbd24c184dc5c9f87aee7c680b336ecba7a89823d8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "assets/kenney/tiny_town/Visit Kenney.url",
|
||||||
|
"old_sha256": "0dd1a0b295582cd3185547f0c1d92b4d5392c7782b90a428e192944a496052e0",
|
||||||
|
"current_sha256": "6787defa3ecb6118622e1903b65802c0200628fae5164b51b4fd467f92f3f284"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "assets/kenney/tiny_town/Visit Patreon.url",
|
||||||
|
"old_sha256": "84be5ef26b07905fa7e196e943c9807bf84138c3a71d87f51c6fd57b85c48c34",
|
||||||
|
"current_sha256": "2fcba31333935f4aa35e9de2e74e2b7ba1b1a8011540e08016ff6d81e9dd7490"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "autoload/simulation_runtime.gd",
|
||||||
|
"old_sha256": "2d7e2cea5424585e9aa4302ab66d1fc4dbaefe743ce4328fccc1efa2f19b82e4",
|
||||||
|
"current_sha256": "d52a995ae715542bbc550f68d35889540658275d12e04ae0b21ac276f5843f39"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "scenes/main/main.gd",
|
||||||
|
"old_sha256": "a7ae44c680eb9b70a117ddc8c6cbf0ca290b8e8d485eac41b4122698fde2a620",
|
||||||
|
"current_sha256": "a3d7d7ddd08a8df1f987806e60accf51792d728cf71c06e6b822f5a31d8ed3e3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "scenes/main/world_preview.gd",
|
||||||
|
"old_sha256": "3df29be32d3a016d8fb9dc1ee2bd32e2360ac8fad9810293f6be9b58d26046e0",
|
||||||
|
"current_sha256": "75d5b0bd23204f5a485540512e44b89e1d2a4596b368f70177a238ba078ff42d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "scripts/core/performance_budget.gd",
|
||||||
|
"old_sha256": "79d18e8f0dcc3932801cd02ea8a8ba037af02ef57665387cba8dbe3b3e9af917",
|
||||||
|
"current_sha256": "0e2278f88d29919a0634fc1b527328a0d0c12d87e2cd612f3eee1526aabdb073"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "scripts/world/path_service.gd",
|
||||||
|
"old_sha256": "2e68653e4898387cb5a76402813a10595bd5ca7d5fb841c7ed8c55af77d7a918",
|
||||||
|
"current_sha256": "447135729a1106639a6eee6754af3195c8a8b458ca26980ae91fa39d80dcfbd4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "tests/s1/test_path_service.gd",
|
||||||
|
"old_sha256": "0f48bb44c27e32f08477b9aed61a0db5c8808219490928f734c3570a07efb251",
|
||||||
|
"current_sha256": "17d9fd42b35004376bf96e435f53d6bce41e79258ec7f6daf3ab04ae5d8c3e1d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "tests/ui/test_pixel_tower.gd",
|
||||||
|
"old_sha256": "93bdbb195f2ceec07dbe052e0d0a3017d84b56f1e8ec46b87be8d98105ec0f88",
|
||||||
|
"current_sha256": "f149f4b5507247dadf623e777e90b9bdb81f54324b406a8f55adaf89b649866a"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"missing_map_paths": [],
|
||||||
|
"task_id": "P04-01",
|
||||||
|
"base_commit": "deb1bcb2374eb378b840ca345db952bd8572cb6e",
|
||||||
|
"status": "submitted",
|
||||||
|
"original_unit_complete": false,
|
||||||
|
"counted_units_added": 0,
|
||||||
|
"tests_executed": false,
|
||||||
|
"authority": {
|
||||||
|
"owner_approved": {
|
||||||
|
"artifact": "D05_OWNER_APPROVAL.json",
|
||||||
|
"sha256": "4583ee9aad45c4db183d72df4420df48aa8e0897e3b5efda13016f0b5cdbd68e",
|
||||||
|
"bytes": 2073,
|
||||||
|
"origin": "direct_user_reply",
|
||||||
|
"formal_acceptance": false,
|
||||||
|
"conditions": [
|
||||||
|
"D05-P04-DEPENDENCY",
|
||||||
|
"D05-P04-SHIFT-1800"
|
||||||
|
],
|
||||||
|
"approval_is_not_test_pass": true
|
||||||
|
},
|
||||||
|
"existing": [
|
||||||
|
"BASELINE:64/67/71/72/183",
|
||||||
|
"D-034 docs/DESIGN_DECISIONS.md:252-258",
|
||||||
|
"A01 already approved; not reopened"
|
||||||
|
],
|
||||||
|
"remaining_pending": "Cost/day attribution remains pending; do not infer new policy. Required safety counts remain mandatory; no blanket D05 rejection."
|
||||||
|
},
|
||||||
|
"phase_order": "scripts/core/simulation_kernel.gd:123-153: APPLY_COMMANDS, clock increment/calendar boundary signals, TIME_BOUNDARIES, MANA, CONSTRUCTION, LOGISTICS, FACILITY_COMPLETION, VITALS_AND_ENVIRONMENT, INCIDENTS_AND_EVENTS, FACILITY_QUEUES, AI_DECISIONS, PUBLISH_SNAPSHOT. Handler IDs sorted lexically within phase at line242.",
|
||||||
|
"domains": [
|
||||||
|
{
|
||||||
|
"domain": "閉店",
|
||||||
|
"current_functions": [
|
||||||
|
"simulation_runtime._advance_facilities",
|
||||||
|
"simulation_runtime._advance_guest_queues",
|
||||||
|
"simulation_runtime._release_closed_business_queues",
|
||||||
|
"simulation_runtime._release_closed_business_request"
|
||||||
|
],
|
||||||
|
"order": "Direct closure follows D-034: release waiting requests, continue started services. Dependency closure pauses progress/payment and preserves reservation/service IDs until recovery under D05-P04-DEPENDENCY.",
|
||||||
|
"old_evidence": "Historical phases 001/002: business closure/operations; midnight open-service proof cannot establish closure behavior.",
|
||||||
|
"missing": "Exact-version direct/dependency closure evidence; approval is supplied, not a pass."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"domain": "停電",
|
||||||
|
"current_functions": [
|
||||||
|
"simulation_runtime._advance_mana",
|
||||||
|
"mana_power_projection.apply",
|
||||||
|
"FacilityService._prepare_service_for_tick",
|
||||||
|
"FacilityService._complete_service"
|
||||||
|
],
|
||||||
|
"order": "Commands precede MANA power projection, then facility completion. NO_POWER pauses active service and power recovery resumes it.",
|
||||||
|
"old_evidence": "Draft reports formal 961 stop/962 recovery, fresh Reader A/B, one consume/notification and unchanged economy at 963; raw not supplied.",
|
||||||
|
"missing": "Exact-source/config/input/environment raw stop/recovery proofs including same reservations and all ledgers."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"domain": "破壊",
|
||||||
|
"current_functions": [
|
||||||
|
"simulation_runtime._process_tick_commands",
|
||||||
|
"simulation_runtime._advance_defense",
|
||||||
|
"FacilityService._prepare_service_for_tick",
|
||||||
|
"FacilityService._fail_service"
|
||||||
|
],
|
||||||
|
"order": "Command destruction precedes facility processing; INCIDENTS_AND_EVENTS destruction follows completion. BROKEN fails during service preparation.",
|
||||||
|
"old_evidence": "Draft reports early failure44/charge0/release once, late destruction after completion45 and next-minute failure for incomplete service.",
|
||||||
|
"missing": "Verified raw early/late destruction and failed diagnostic history; current infrastructure/path applicability."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"domain": "退出",
|
||||||
|
"current_functions": [
|
||||||
|
"simulation_runtime._execute_leave_facility_queue",
|
||||||
|
"simulation_runtime._leave_guest_queue_boundary",
|
||||||
|
"simulation_runtime._advance_guest_queues",
|
||||||
|
"simulation_runtime._advance_guest_ai_decisions"
|
||||||
|
],
|
||||||
|
"order": "Public leave command precedes completion; patience release occurs in queues; AI replanning/planned exit follows. LEAVING despawns only after state-entered minute.",
|
||||||
|
"old_evidence": "Draft reports public reservation release, automatic45-minute patience and completion-before-exit boundaries.",
|
||||||
|
"missing": "Exact-version leave/patience and overdue-LEAVING reload proofs, no orphan reservations and replay generation checks."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"domain": "課金",
|
||||||
|
"current_functions": [
|
||||||
|
"FacilityService._complete_service",
|
||||||
|
"simulation_runtime._complete_service_transaction",
|
||||||
|
"simulation_runtime._apply_service_transaction",
|
||||||
|
"EconomyService.post"
|
||||||
|
],
|
||||||
|
"order": "Completion callback consumes reservations then posts service.payment.<service_id>; notification scope ends after transaction effects. Static code alone does not prove all boundary rollback paths.",
|
||||||
|
"old_evidence": "P04-02 and historical full are references only; test counts do not establish P04-01 original acceptance.",
|
||||||
|
"missing": "Raw ledger/inventory/notification proof for stopped, failed, replay and paid completion paths; closure/shift rule dependent proof."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"domain": "シフト交代",
|
||||||
|
"current_functions": [
|
||||||
|
"simulation_runtime._advance_facilities",
|
||||||
|
"simulation_runtime._advance_staff_ai",
|
||||||
|
"StaffService.advance_one_minute",
|
||||||
|
"simulation_runtime._sync_facility_staffing"
|
||||||
|
],
|
||||||
|
"order": "Approved D05-P04-SHIFT-1800: [17:59,18:00) belongs to day shift, completion/payment once at 18:00 before switching; night shift begins [18:00,18:01).",
|
||||||
|
"old_evidence": "Historical phase083 hardening includes shift-end interruption; draft retains diagnostic1080 predicate failure without claiming original violation.",
|
||||||
|
"missing": "Applicable 1080 completion/replay, 1081 WAITING_FOR_STAFF with no night staff, and existing overtime task evidence."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"domain": "00:00決算",
|
||||||
|
"current_functions": [
|
||||||
|
"simulation_runtime._advance_facility_upkeep",
|
||||||
|
"simulation_runtime._advance_operations_time",
|
||||||
|
"OperationsCoordinator._process_minute_boundaries",
|
||||||
|
"OperationsCoordinator._process_daily_close",
|
||||||
|
"simulation_runtime._complete_service_transaction"
|
||||||
|
],
|
||||||
|
"order": "TIME_BOUNDARIES weather/upkeep/operations precedes facility completion; prior day[0,1440) accounting differs from sale at1440. A01 upkeep already approved.",
|
||||||
|
"old_evidence": "Draft reports real settlement/payroll/upkeep once, service25 once, unchanged ledgers/history1441; zero payroll fixture is not proof of nonzero payroll.",
|
||||||
|
"missing": "Current raw midnight/retry/reload idempotency proof and applicable nonzero payroll boundary; full original conformity review."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"old_evidence_applicability": {
|
||||||
|
"raw_available_for_reuse": false,
|
||||||
|
"assessment": "39 references in supplied recheck retain old Runtime 2d7e2cea provenance; raw artifacts not supplied here. 20 function bodies independently rehashed in this checkout, matching supplied historical hashes, not the old 29-function count. No blanket boundary reuse.",
|
||||||
|
"source": "14 byte differences, zero missing mapped paths; six immutable current pins match.",
|
||||||
|
"config_input_environment": "Not established by raw local evidence; each required proof must pin these independently.",
|
||||||
|
"runtime_git": "Verified supplied old SHA2d7e2cea; git diff --no-index compared exact raw old/current Runtime. Four hunks affect _project_guest_candidates batching, _find_facility_access_path cost selection, _estimated_actor_path_minutes and extracted connector-credit helper. Completion/phase registration unchanged in this file; callees PathService changed, therefore reachability, choice, arrival and staff estimates require finite applicability evidence.",
|
||||||
|
"known_difference_impact": "Runtime/PathService may alter infrastructure, movement, staffing, guest selection and recovery boundary reachability; UI/main/preview and performance budget changes prevent blanket whole-full reuse. Two changed tests cannot supply old exact-version evidence. Seven additional Kenney metadata/sample byte differences require applicability classification, not product edits."
|
||||||
|
},
|
||||||
|
"blockers": [
|
||||||
|
"D05 dependency closure and 18:00 interval are approved; approval is not test success.",
|
||||||
|
"Raw 39 historical artifacts, settings/input/environment and engine receipts are not mirrored here. Their hashes in recheck are provenance, not verified current evidence.",
|
||||||
|
"Nonzero-payroll midnight, paused midnight, replay/separate-PID save restoration and reachable EV reservation safety need applicable raw evidence.",
|
||||||
|
"Root must independently assess affected power/destruction/exit paths and production Runtime 4696df4a versus checkout d52a995 before integration."
|
||||||
|
],
|
||||||
|
"validation_plan": [
|
||||||
|
"Runner runs p04-acceptance; worker only py_compile and synthetic negative fixture checks.",
|
||||||
|
"Root verifies source/config/input/environment applicability for power, destruction, exit cases affected by four Runtime hunks and PathService changes; fresh finite boundaries only if reuse fails.",
|
||||||
|
"Require nonzero payroll midnight, stop crossing midnight, same-minute resend, formal save and different PID restore, no-night-staff 18:01, existing overtime task, and reachable reserved-staff EV safety evidence.",
|
||||||
|
"Do not reopen A01 or invent cost/day attribution. Pending unrelated D05 does not fail the whole gate.",
|
||||||
|
"Root rechecks production Runtime 4696df4a against immutable checkout d52a995, then independent review in separate session and normal integration. Formal credit remains zero."
|
||||||
|
],
|
||||||
|
"repair_support": {
|
||||||
|
"hashes": {
|
||||||
|
"support.json": "36f7443daabf37070ecc331e708a6749816630bf0466ebb2a814aaf647a84410",
|
||||||
|
"runtime-diff.patch": "7ba08537c5e86d0505f2b4df56d29877064c70acc4fa9f12089fefa09e78b707",
|
||||||
|
"historical-runtime.txt": "2d7e2cea5424585e9aa4302ab66d1fc4dbaefe743ce4328fccc1efa2f19b82e4"
|
||||||
|
},
|
||||||
|
"historical_runtime_verified": true,
|
||||||
|
"production_differences": 7,
|
||||||
|
"managed_differences": 14,
|
||||||
|
"additional7_classification": "Runner support reports CRLF to LF only; current raw hashes checked. Old raw metadata absent, normalization not independently reproduced; no acceptance equivalence claim."
|
||||||
|
},
|
||||||
|
"repair3": {
|
||||||
|
"space_available": "1.8 GiB before work; bounded source reads and ~small text changes, no raw archive copies needed",
|
||||||
|
"approval_material_verification": [
|
||||||
|
{
|
||||||
|
"file": "D05_OWNER_APPROVAL.json",
|
||||||
|
"bytes": 2073,
|
||||||
|
"sha256": "4583ee9aad45c4db183d72df4420df48aa8e0897e3b5efda13016f0b5cdbd68e",
|
||||||
|
"manifest_matches": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "README.md",
|
||||||
|
"bytes": 1029,
|
||||||
|
"sha256": "7b1c8992a8c5298d3ad416a5ff667a49f6e1eafea149fb6abb9e91e517e47c0f",
|
||||||
|
"manifest_matches": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "recheck.json",
|
||||||
|
"bytes": 46479,
|
||||||
|
"sha256": "44b32fa297c3c8f81691505878232756b3d63e0d5a801a6ae15173b84894bd22",
|
||||||
|
"manifest_matches": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "OPUS_D05_CANDIDATE.json",
|
||||||
|
"bytes": 6653,
|
||||||
|
"sha256": "54cd83971467744b1009e72c56e7b5d558c97073540d027640e486f7b5b5a34b",
|
||||||
|
"manifest_matches": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ordering_functions": [
|
||||||
|
{
|
||||||
|
"path": "scripts/core/simulation_kernel.gd",
|
||||||
|
"function": "advance_one_minute",
|
||||||
|
"line": 122,
|
||||||
|
"sha256": "b9587ef722d8c8640248025462819374ae5eca1c3d8bbe600b6bc9c328567777",
|
||||||
|
"matches_supplied_historical_body_hash": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "scripts/core/simulation_kernel.gd",
|
||||||
|
"function": "_run_phase",
|
||||||
|
"line": 236,
|
||||||
|
"sha256": "52461eeb288892a4bb4b6a99570185af08a30758ef88b0046a911baaa6f318e1",
|
||||||
|
"matches_supplied_historical_body_hash": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "scripts/core/simulation_kernel.gd",
|
||||||
|
"function": "_handle_time_boundaries",
|
||||||
|
"line": 254,
|
||||||
|
"sha256": "2ced47a2911b36def9d89de82a43afc69148ead44d19db6a2a6c7bce5a910ae9",
|
||||||
|
"matches_supplied_historical_body_hash": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "scripts/core/command_queue.gd",
|
||||||
|
"function": "_comes_before",
|
||||||
|
"line": 409,
|
||||||
|
"sha256": "21f7f9f7c0923121fd0a022a2f7546b7bb7117a9979c1ed25d0b33b96e28ab28",
|
||||||
|
"matches_supplied_historical_body_hash": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "autoload/simulation_runtime.gd",
|
||||||
|
"function": "_process_tick_commands",
|
||||||
|
"line": 5736,
|
||||||
|
"sha256": "9de66a4bb84b12304300d49085f44546412319bd4aee91e67b61becf97e50294",
|
||||||
|
"matches_supplied_historical_body_hash": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "autoload/simulation_runtime.gd",
|
||||||
|
"function": "_advance_operations_time",
|
||||||
|
"line": 1894,
|
||||||
|
"sha256": "511580d1fac3d2785072e94c2981065fa47ba18b1c97122922645dac5c0d94ab",
|
||||||
|
"matches_supplied_historical_body_hash": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "autoload/simulation_runtime.gd",
|
||||||
|
"function": "_run_operations_deferred_suffix",
|
||||||
|
"line": 1683,
|
||||||
|
"sha256": "12075307b12cbb66ffda47a26097194a07fad9d6467e3e557bd7269315c98982",
|
||||||
|
"matches_supplied_historical_body_hash": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "autoload/simulation_runtime.gd",
|
||||||
|
"function": "_advance_mana",
|
||||||
|
"line": 10633,
|
||||||
|
"sha256": "05c54626ce6e355acd92994ab80aaf2177c5cf194885e66934552139e087599e",
|
||||||
|
"matches_supplied_historical_body_hash": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "autoload/simulation_runtime.gd",
|
||||||
|
"function": "_advance_facilities",
|
||||||
|
"line": 8421,
|
||||||
|
"sha256": "f1efd0c23bdd60d1ef19a21879640b53b8b4d88844c0c881fe7cd724387ec274",
|
||||||
|
"matches_supplied_historical_body_hash": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "autoload/simulation_runtime.gd",
|
||||||
|
"function": "_advance_guest_vitals",
|
||||||
|
"line": 8427,
|
||||||
|
"sha256": "8226ca18c983ac576a74e772cc3088e9fe52b362ffba1b71255bd1d7623c1234",
|
||||||
|
"matches_supplied_historical_body_hash": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "autoload/simulation_runtime.gd",
|
||||||
|
"function": "_advance_staff_ai",
|
||||||
|
"line": 8597,
|
||||||
|
"sha256": "0e222987f7c5e25583dbaba8fe63784aa563715067396e8d31d10bd191e7179b",
|
||||||
|
"matches_supplied_historical_body_hash": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "autoload/simulation_runtime.gd",
|
||||||
|
"function": "_advance_guest_ai_decisions",
|
||||||
|
"line": 8677,
|
||||||
|
"sha256": "e33b41294e88dba840affd5cc0a5c58fe5d46f5d98a80a8d5f4e907a3b9a7b93",
|
||||||
|
"matches_supplied_historical_body_hash": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "autoload/simulation_runtime.gd",
|
||||||
|
"function": "_release_closed_business_queues",
|
||||||
|
"line": 12562,
|
||||||
|
"sha256": "3a05fbe94877eeb750fb0d692501954555ab6a8005448ab3b6023dab9bad816b",
|
||||||
|
"matches_supplied_historical_body_hash": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "autoload/simulation_runtime.gd",
|
||||||
|
"function": "_release_closed_business_request",
|
||||||
|
"line": 12580,
|
||||||
|
"sha256": "6dbde3a3d425e47a049dcc4f05eae6f01fe9784f473c0fb1019a0633c239d83b",
|
||||||
|
"matches_supplied_historical_body_hash": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "autoload/simulation_runtime.gd",
|
||||||
|
"function": "_complete_service_transaction",
|
||||||
|
"line": 11707,
|
||||||
|
"sha256": "f2b6e96a8eeef3dd3f8419b4d9d5ef4e1d13241600ce970bdd78a924428cac6c",
|
||||||
|
"matches_supplied_historical_body_hash": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "scripts/facility/facility_service.gd",
|
||||||
|
"function": "advance_one_minute",
|
||||||
|
"line": 544,
|
||||||
|
"sha256": "59c0e89b4dca291e3ec02601b3f7c5544dba7c823fc137e0e7a50e7d5319e0ed",
|
||||||
|
"matches_supplied_historical_body_hash": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "scripts/facility/facility_service.gd",
|
||||||
|
"function": "_prepare_service_for_tick",
|
||||||
|
"line": 853,
|
||||||
|
"sha256": "b35c2c0fb23a04258b71995dd7c3b6dfacbbcdf36106e2e3348acef0e18d21cc",
|
||||||
|
"matches_supplied_historical_body_hash": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "scripts/facility/facility_service.gd",
|
||||||
|
"function": "_complete_service",
|
||||||
|
"line": 895,
|
||||||
|
"sha256": "494edfd11cb3e896d73c2c6fdbe7c1193efce6d869b105e526d2f1dfd1168142",
|
||||||
|
"matches_supplied_historical_body_hash": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "scripts/facility/facility_service.gd",
|
||||||
|
"function": "_fail_service",
|
||||||
|
"line": 933,
|
||||||
|
"sha256": "56f61b255217bddb4c8a6ad3af6ed1f9edc3b6a6e928dacf2105be6698936de5",
|
||||||
|
"matches_supplied_historical_body_hash": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "scripts/operations/operations_coordinator.gd",
|
||||||
|
"function": "_process_minute_boundaries",
|
||||||
|
"line": 882,
|
||||||
|
"sha256": "1312166dc17aee0ef470069a73ff3fe6b86d11d04ca7de76f3d04f1d2d14674e",
|
||||||
|
"matches_supplied_historical_body_hash": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ordering_function_count": 20,
|
||||||
|
"historical_29_not_combined": true,
|
||||||
|
"nativeWindows": {
|
||||||
|
"power_stop": "pending",
|
||||||
|
"destruction_stop": "pending",
|
||||||
|
"dependency_stop": "pending",
|
||||||
|
"shift_completion": "pending",
|
||||||
|
"recovery_completion": "pending",
|
||||||
|
"same_minute_replay": "pending",
|
||||||
|
"closure_release": "pending",
|
||||||
|
"exit_release": "pending",
|
||||||
|
"patience_release": "pending",
|
||||||
|
"paused_midnight": "pending",
|
||||||
|
"midnight_settlement": "pending",
|
||||||
|
"midnight_replay": "pending",
|
||||||
|
"save_restore": "pending",
|
||||||
|
"night_staff_absent": "pending",
|
||||||
|
"overtime_task": "pending",
|
||||||
|
"EV": "pending"
|
||||||
|
},
|
||||||
|
"spec_representation": {
|
||||||
|
"raw_sha256": "d28aadb044633f7d804de5a92c8d4c4a7438dfbf44c3243dca978951f4def25f",
|
||||||
|
"contract_sha256": "9ec722e7dfb0bee178d291624a026420c08412574e238bba1fde012c7a6f7881",
|
||||||
|
"difference": "exactly one final LF; no spec edits"
|
||||||
|
},
|
||||||
|
"worker_validation": {
|
||||||
|
"py_compile": "passed",
|
||||||
|
"negative_fixture_count": 10,
|
||||||
|
"negative_results": "all exit 1 with intended error; synthetic control exit 0/formal_acceptance=false",
|
||||||
|
"engine_runs": 0,
|
||||||
|
"runner_operations": 0,
|
||||||
|
"formal_acceptance": false
|
||||||
|
},
|
||||||
|
"runtime_diff_hunks": 5,
|
||||||
|
"reachability": {
|
||||||
|
"guest_EV": {
|
||||||
|
"reach": false,
|
||||||
|
"scope": "normal guest automatic service reservation before arrival",
|
||||||
|
"source_refs": [
|
||||||
|
"Runtime:8898-8922 arrival -> queue -> create_service_request",
|
||||||
|
"Runtime:7421-7445 reserve -> create request"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"staff_EV": {
|
||||||
|
"reach": true,
|
||||||
|
"scope": "public create_staff_task required_items can reserve before EV travel",
|
||||||
|
"source_refs": [
|
||||||
|
"Runtime:329,6733-6742 public task creation",
|
||||||
|
"task_dispatcher.gd:256-261,293-294,309-314 reserve before assignment",
|
||||||
|
"Runtime:5320,9141-9170 inventory callback",
|
||||||
|
"Runtime:12009-12013 begin_moving",
|
||||||
|
"Runtime:8755-8763,8946-8987 EV travel",
|
||||||
|
"elevator_service.gd:279-284 set_powered"
|
||||||
|
],
|
||||||
|
"required": "power-stop case with reserved-before-travel items; orphan_reservations=0; nativeWindows pending"
|
||||||
|
},
|
||||||
|
"transport": {
|
||||||
|
"reach": false,
|
||||||
|
"scope": "rescue transport payment/reservation only; physical movement is reachable",
|
||||||
|
"source_refs": [
|
||||||
|
"Runtime:12023-12030 completion heals only",
|
||||||
|
"Runtime:12976-13050 rescue pickup/transport creation, no required_items/reservation/payment"
|
||||||
|
],
|
||||||
|
"counted_domain_added": false
|
||||||
|
},
|
||||||
|
"opening_hours": {
|
||||||
|
"reach": false,
|
||||||
|
"scope": "production time-of-day closure",
|
||||||
|
"source_refs": [
|
||||||
|
"facility_instance.gd default [0,1440)",
|
||||||
|
"set_opening_hours: definitions/service wrapper only; no autoload caller"
|
||||||
|
],
|
||||||
|
"direct_closure_remains_required": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"path_applicability": {
|
||||||
|
"source_refs": [
|
||||||
|
"scripts/world/path_service.gd:1055-1101",
|
||||||
|
"scripts/world/path_service.gd:1302-1319",
|
||||||
|
"Runtime:9222-9268,9296-9300,12798-12812,12838-12848"
|
||||||
|
],
|
||||||
|
"power": "Stop/resume core unchanged, but if fixture reaches a facility through guest candidate/connector routes or staff arrival, pins/function equality alone do not prove the same stopped-service boundary. Root must inspect route and arrival observations.",
|
||||||
|
"destruction": "Command-before-completion and incident-after-completion order unchanged; infrastructure/connector destruction can alter selected/access path and arrival, so only those route-dependent cases need current applicability confirmation.",
|
||||||
|
"exit": "Release callbacks unchanged; MOVING target selection/arrival/overdue exit can use the changed paths, so confirm their raw state and arrival minute before reuse.",
|
||||||
|
"path_service": "Batch only with connectors, default layers and empty access; settled tree path/cost, fallback on unusable tree/wrapped cost. Caller estimate can affect arrival even though phase functions match.",
|
||||||
|
"verdict": "No affected case accepted from summaries alone; raw source/config/input/environment evidence remains a Root prerequisite."
|
||||||
|
},
|
||||||
|
"runtime_logical_change_groups": 4,
|
||||||
|
"runtime_diff_note": "Default git diff emits 5 textual hunks: batch begin/end split into two; four logical change groups in commander direction."
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,927 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Read-only P04 evidence preflight. Never grants original acceptance.
|
||||||
|
|
||||||
|
Required conditions and authority bindings are fixed here, never supplied by an index.
|
||||||
|
See execution/p04-01/CHECKER_PROTOCOL.md for the supported input subset.
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
from contextlib import contextmanager
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
SPEC = ROOT / 'docs/spec/acceptance/P04-01.json'
|
||||||
|
SPEC_HASH = '9ec722e7dfb0bee178d291624a026420c08412574e238bba1fde012c7a6f7881'
|
||||||
|
DOMAINS = {'閉店', '停電', '破壊', '退出', '課金', 'シフト交代', '00:00決算'}
|
||||||
|
OWNER_HASH = '4583ee9aad45c4db183d72df4420df48aa8e0897e3b5efda13016f0b5cdbd68e'
|
||||||
|
REGISTERED_COMMIT = 'deb1bcb2374eb378b840ca345db952bd8572cb6e'
|
||||||
|
EXECUTION_ORIGIN = '9767b0a28ca8d252520307dbbac60a1d8cfb87ab'
|
||||||
|
CHECKER_PATH = 'tools/check_p04_order_evidence.py'
|
||||||
|
DOC_SCOPE = 'docs/plans/original-completion-2026-10-02/execution/p04-01/'
|
||||||
|
MAIN_ANCHOR = '9694f50ee63750192376ca69bc7dda3f884784c5'
|
||||||
|
SELECTION_SHA = '320ca668732b4ff72fc4449998af2327d4e5c527b06896740806aaf0487282b4'
|
||||||
|
_GIT_ENV = None
|
||||||
|
# Fixed membership, never supplied by the evidence index.
|
||||||
|
ORDER_FUNCTIONS = {
|
||||||
|
'scripts/core/simulation_kernel.gd': ('advance_one_minute', '_run_phase', '_handle_time_boundaries'),
|
||||||
|
'scripts/core/command_queue.gd': ('_comes_before',),
|
||||||
|
'autoload/simulation_runtime.gd': (
|
||||||
|
'_process_tick_commands', '_advance_operations_time', '_run_operations_deferred_suffix',
|
||||||
|
'_advance_mana', '_advance_facilities', '_advance_guest_vitals', '_advance_staff_ai',
|
||||||
|
'_advance_guest_ai_decisions', '_release_closed_business_queues',
|
||||||
|
'_release_closed_business_request', '_complete_service_transaction'),
|
||||||
|
'scripts/facility/facility_service.gd': (
|
||||||
|
'advance_one_minute', '_prepare_service_for_tick', '_complete_service', '_fail_service'),
|
||||||
|
'scripts/operations/operations_coordinator.gd': ('_process_minute_boundaries',),
|
||||||
|
}
|
||||||
|
# Condition -> (domain, authority refs, required engine measurements).
|
||||||
|
# Measurements describe safety/counts, not the still-pending attribution policy.
|
||||||
|
REQUIRED = {
|
||||||
|
'power_stop': ('停電', ('BASELINE:183',), {'charges': 0}),
|
||||||
|
'destruction_stop': ('破壊', ('BASELINE:183',), {'charges': 0}),
|
||||||
|
'dependency_stop': ('閉店', ('D05-P04-DEPENDENCY',), {'charges': 0, 'progress_delta': 0, 'reservation_ids_preserved': True, 'service_id_preserved': True}),
|
||||||
|
'shift_completion': ('シフト交代', ('BASELINE:64', 'BASELINE:67', 'D05-P04-SHIFT-1800'), {'minute': 1080, 'completions': 1, 'charges': 1, 'completion_before_shift': True}),
|
||||||
|
'recovery_completion': ('課金', ('D05-P04-DEPENDENCY',), {'completions': 1, 'charges': 1}),
|
||||||
|
'same_minute_replay': ('課金', ('D05-P04-DEPENDENCY', 'D05-P04-SHIFT-1800'), {'completions': 1, 'charges': 1, 'duplicate_generations': 0, 'same_minute_replayed': True}),
|
||||||
|
'closure_release': ('閉店', ('D-034',), {'orphan_reservations': 0, 'released_once': True, 'started_service_continues': True}),
|
||||||
|
'exit_release': ('退出', ('D-034',), {'orphan_reservations': 0, 'released_once': True}),
|
||||||
|
'patience_release': ('退出', ('D-034',), {'orphan_reservations': 0, 'released_once': True}),
|
||||||
|
'paused_midnight': ('00:00決算', ('D05-P04-DEPENDENCY', 'BASELINE:71', 'BASELINE:72'), {'orphan_reservations': 0, 'crossed_midnight_while_paused': True, 'stopped_charges': 0, 'completions': 1, 'charges': 1}),
|
||||||
|
'midnight_settlement': ('00:00決算', ('BASELINE:71', 'BASELINE:72', 'A01'), {'settlements': 1, 'sales_payments': 1, 'payroll_payments': 1, 'upkeep_payments': 1, 'duty_payments': 1, 'nonzero_payroll': True}),
|
||||||
|
'midnight_replay': ('00:00決算', ('BASELINE:71', 'BASELINE:72', 'A01'), {'settlements': 1, 'same_minute_replayed': True, 'duplicate_payments': 0}),
|
||||||
|
'save_restore': ('00:00決算', ('BASELINE:71', 'BASELINE:72', 'A01'), {'settlements': 1, 'duplicate_payments': 0, 'formal_save_restored': True}),
|
||||||
|
'night_staff_absent': ('シフト交代', ('BASELINE:67', 'D05-P04-SHIFT-1800'), {'minute': 1081, 'state': 'WAITING_FOR_STAFF', 'night_staff_count': 0}),
|
||||||
|
'overtime_task': ('シフト交代', ('BASELINE:67',), {'interrupt_level': 0, 'is_overtime': True, 'duty_recorded_once': True}),
|
||||||
|
}
|
||||||
|
REACH_REFS = {
|
||||||
|
'EV': [
|
||||||
|
('autoload/simulation_runtime.gd', '_execute_create_staff_task', '8b83aff45ead128b70931343271dbd03a1024b90238d1f46f93e4856290aa0d8', 10),
|
||||||
|
('scripts/staff/task_dispatcher.gd', '_reserve_pair', '48e2e593c117bd675236a165a294c9cc6233f87018010249cd8e675aff01fc3c', 6),
|
||||||
|
('autoload/simulation_runtime.gd', '_reserve_staff_task_items', '4f2add093de47cb08abfa45e9d2285f00d906c9bceadb2d98ffa42faae9ede67', 28),
|
||||||
|
('autoload/simulation_runtime.gd', '_advance_staff_task_travel', '53937aefeec533a5f16169bba4dd6a69b5246101d09de77ca95db7ca174c63ce', 2),
|
||||||
|
('scripts/transport/elevator_service.gd', 'set_powered', '925f4bf2d109421956fb2958981758d7d84a1ca586a9d4eb51572b8b9677cfba', 6)],
|
||||||
|
'transport': [
|
||||||
|
('autoload/simulation_runtime.gd', '_on_staff_task_completed', '46daaa0197ce85c08883614112efb2d12a2cc0a02fe14ffec8314c73cbba61fc', 8),
|
||||||
|
('autoload/simulation_runtime.gd', '_handle_rescue_arrival', '11f8d43d0c089b95006009eddfc8bd3d4375a666b29ff19f36e1fa008f3946dd', 24),
|
||||||
|
('autoload/simulation_runtime.gd', '_advance_staff_rescue', '86bb31b40606fd624cc6b273dfdf7e3e4b3e1bc9997df825dd74136efb69a38f', 52)],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def require(condition, message):
|
||||||
|
if not condition:
|
||||||
|
raise ValueError(message)
|
||||||
|
|
||||||
|
|
||||||
|
def sha(path):
|
||||||
|
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def unique(pairs):
|
||||||
|
result = {}
|
||||||
|
for key, value in pairs:
|
||||||
|
require(key not in result, 'duplicate JSON key: ' + key)
|
||||||
|
result[key] = value
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def load(path):
|
||||||
|
return json.loads(path.read_text(encoding='utf-8'), object_pairs_hook=unique,
|
||||||
|
parse_constant=lambda x: require(False, 'nonfinite JSON: ' + x))
|
||||||
|
|
||||||
|
|
||||||
|
def safe(base, relative):
|
||||||
|
require(isinstance(relative, str) and relative, 'invalid path')
|
||||||
|
p = Path(relative)
|
||||||
|
require(not p.is_absolute() and all(x not in {'.', '..'} for x in relative.split('/')),
|
||||||
|
'unsafe path: ' + relative)
|
||||||
|
current = base
|
||||||
|
for part in p.parts:
|
||||||
|
current = current / part
|
||||||
|
require(not current.is_symlink(), 'symlink: ' + relative)
|
||||||
|
require(current.resolve().is_relative_to(base.resolve()) and current.is_file(),
|
||||||
|
'missing/outside file: ' + relative)
|
||||||
|
return current
|
||||||
|
|
||||||
|
|
||||||
|
def pins(mapping, base):
|
||||||
|
require(isinstance(mapping, dict) and mapping, 'missing pin map')
|
||||||
|
for name, digest in mapping.items():
|
||||||
|
require(isinstance(digest, str) and re.fullmatch('[0-9a-f]{64}', digest), 'invalid hash: ' + name)
|
||||||
|
require(sha(safe(base, name)) == digest, 'stale pin: ' + name)
|
||||||
|
|
||||||
|
|
||||||
|
def pointer(value, path):
|
||||||
|
require(isinstance(path, str) and (path == '' or path.startswith('/')), 'invalid JSON pointer')
|
||||||
|
for token in path.split('/')[1:]:
|
||||||
|
require(not re.search('~(?![01])', token), 'invalid pointer escape')
|
||||||
|
token = token.replace('~1', '/').replace('~0', '~')
|
||||||
|
if isinstance(value, list):
|
||||||
|
require(re.fullmatch('0|[1-9][0-9]*', token) is not None, 'invalid array index')
|
||||||
|
value = value[int(token)]
|
||||||
|
else:
|
||||||
|
require(isinstance(value, dict) and token in value, 'missing pointer: ' + path)
|
||||||
|
value = value[token]
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def literal(a, b):
|
||||||
|
return type(a) is type(b) and json.dumps(a, sort_keys=True) == json.dumps(b, sort_keys=True)
|
||||||
|
|
||||||
|
|
||||||
|
def git_bytes(*args):
|
||||||
|
env = _GIT_ENV if _GIT_ENV is not None else dict(os.environ, GIT_OPTIONAL_LOCKS='0')
|
||||||
|
return subprocess.check_output(['git', '--no-replace-objects', *args], cwd=ROOT, env=env)
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def isolated_objects():
|
||||||
|
"""merge-tree writes only disposable objects; canonical refs/index stay untouched.
|
||||||
|
|
||||||
|
Keep this context alive while reading the composed tree. No stage switch can
|
||||||
|
bypass it. The alternate is only an object lookup path, never a write target.
|
||||||
|
"""
|
||||||
|
global _GIT_ENV
|
||||||
|
require(_GIT_ENV is None, 'nested object store')
|
||||||
|
objects = git_bytes('rev-parse', '--path-format=absolute', '--git-path', 'objects').decode().strip()
|
||||||
|
with tempfile.TemporaryDirectory(prefix='p04-object-audit-') as temporary:
|
||||||
|
env = dict(os.environ)
|
||||||
|
for key in ('GIT_OBJECT_DIRECTORY', 'GIT_ALTERNATE_OBJECT_DIRECTORIES',
|
||||||
|
'GIT_INDEX_FILE', 'GIT_QUARANTINE_PATH'):
|
||||||
|
env.pop(key, None)
|
||||||
|
env.update(GIT_OBJECT_DIRECTORY=temporary, GIT_ALTERNATE_OBJECT_DIRECTORIES=objects,
|
||||||
|
GIT_OPTIONAL_LOCKS='0', GIT_NO_REPLACE_OBJECTS='1')
|
||||||
|
_GIT_ENV = env
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
_GIT_ENV = None
|
||||||
|
|
||||||
|
|
||||||
|
def tree_entries(tree):
|
||||||
|
entries = {}
|
||||||
|
for row in git_bytes('ls-tree', '-r', '-z', tree).split(b'\0'):
|
||||||
|
if not row:
|
||||||
|
continue
|
||||||
|
metadata, encoded = row.split(b'\t', 1)
|
||||||
|
mode, kind, oid = metadata.decode().split()
|
||||||
|
path = encoded.decode('utf-8')
|
||||||
|
require(kind == 'blob' and mode in {'100644', '100755'}, 'nonordinary tree entry: ' + path)
|
||||||
|
require(path not in entries, 'duplicate tree path')
|
||||||
|
entries[path] = (mode, oid)
|
||||||
|
return entries
|
||||||
|
|
||||||
|
|
||||||
|
def selected_path(path):
|
||||||
|
parts = path.split('/')
|
||||||
|
return (parts[0] in {'autoload', 'scripts', 'scenes', 'data', 'tests', 'assets', 'tools'}
|
||||||
|
and len(parts) > 1 and parts[-1] != '.DS_Store' and '__pycache__' not in parts
|
||||||
|
or path in {'project.godot', 'export_presets.cfg', 'Makefile', 'AGENTS.md'})
|
||||||
|
|
||||||
|
|
||||||
|
def allowed_path(path):
|
||||||
|
return path == CHECKER_PATH or path.startswith(DOC_SCOPE)
|
||||||
|
|
||||||
|
|
||||||
|
def product_manifest(entries):
|
||||||
|
result = {}
|
||||||
|
for path, (_, oid) in sorted(entries.items()):
|
||||||
|
if selected_path(path) and path != CHECKER_PATH:
|
||||||
|
data = git_bytes('cat-file', 'blob', oid)
|
||||||
|
result[path] = {'bytes': len(data), 'sha256': hashlib.sha256(data).hexdigest()}
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def manifest_digest(manifest):
|
||||||
|
return hashlib.sha256(json.dumps(manifest, sort_keys=True, separators=(',', ':')).encode()).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def supplied_file(directory, descriptor):
|
||||||
|
"""Read supplied material in place, never copy it or follow provenance paths."""
|
||||||
|
name = descriptor['path']
|
||||||
|
path = Path(name)
|
||||||
|
if path.is_absolute():
|
||||||
|
support = directory / 'audit-support'
|
||||||
|
roots = [support / n for n in ('p04-whole-closure-r01', 'native-first-current',
|
||||||
|
'p04-current-native-series-r01')]
|
||||||
|
matches = [root for root in roots if path.is_relative_to(root)]
|
||||||
|
require(len(matches) == 1, 'unsupplied external material: ' + name)
|
||||||
|
path = safe(matches[0], path.relative_to(matches[0]).as_posix())
|
||||||
|
else:
|
||||||
|
path = safe(directory / 'p04-evidence', name)
|
||||||
|
require(type(descriptor.get('bytes')) is int and path.stat().st_size == descriptor['bytes']
|
||||||
|
and sha(path) == descriptor.get('sha256'), 'supplied material pin: ' + name)
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def verify_product_target(index, directory, checker_commit):
|
||||||
|
"""Four actual roles, complete source selection, and candidate equality.
|
||||||
|
|
||||||
|
This does not establish any native boundary condition. Runner input files
|
||||||
|
are checked against Git blobs, never against a Root-reported digest alone.
|
||||||
|
"""
|
||||||
|
binding = index['product_binding']
|
||||||
|
target = binding['product_target']
|
||||||
|
require(set(target) == {'main_anchor', 'submitted', 'composed_tree'}, 'product target shape')
|
||||||
|
require(target['main_anchor'] == MAIN_ANCHOR, 'main anchor changed')
|
||||||
|
for key in target:
|
||||||
|
require(re.fullmatch('[0-9a-f]{40}', target[key]) is not None, 'full product target OID')
|
||||||
|
for commit in (REGISTERED_COMMIT, EXECUTION_ORIGIN, target['main_anchor'], target['submitted'], checker_commit):
|
||||||
|
require(git_bytes('cat-file', '-t', commit).strip() == b'commit', 'role is not a commit')
|
||||||
|
require(_GIT_ENV is not None, 'merge-tree needs isolated object store')
|
||||||
|
composed = git_bytes('-c', 'rerere.enabled=false', 'merge-tree', '--write-tree',
|
||||||
|
target['main_anchor'], target['submitted']).decode().strip()
|
||||||
|
require(composed == target['composed_tree'], 'recomputed composed tree mismatch')
|
||||||
|
anchor, submission, tree = [tree_entries(t) for t in (MAIN_ANCHOR, target['submitted'], composed)]
|
||||||
|
selection = git_bytes('show', MAIN_ANCHOR + ':tools/run_all_tests.py')
|
||||||
|
require(hashlib.sha256(selection).hexdigest() == SELECTION_SHA, 'source selection authority')
|
||||||
|
changed = git_bytes('diff', '--no-ext-diff', '--no-renames', '--name-only', '-z',
|
||||||
|
REGISTERED_COMMIT, target['submitted']).split(b'\0')
|
||||||
|
require(all(allowed_path(p.decode()) for p in changed if p), 'submission outside registered scope')
|
||||||
|
for path in anchor.keys() | tree.keys():
|
||||||
|
if anchor.get(path) != tree.get(path):
|
||||||
|
require(allowed_path(path), 'new/changed path outside scope: ' + path)
|
||||||
|
baseline = product_manifest(anchor)
|
||||||
|
product = product_manifest(tree)
|
||||||
|
require(len(baseline) == 1338 and product == baseline, 'complete selected product mismatch')
|
||||||
|
metadata = sorted(p for p in anchor if not selected_path(p) and anchor[p] == tree.get(p))
|
||||||
|
require(len(metadata) == 3139, 'unchanged existing metadata membership changed')
|
||||||
|
require(set(p for p in tree if selected_path(p)) == set(product) | {CHECKER_PATH},
|
||||||
|
'unknown selected source cannot be metadata')
|
||||||
|
own = Path(__file__).read_bytes()
|
||||||
|
for role, entries in (('submitted', submission), ('composed', tree),
|
||||||
|
('checker', tree_entries(checker_commit))):
|
||||||
|
require(CHECKER_PATH in entries and git_bytes('cat-file', 'blob', entries[CHECKER_PATH][1]) == own,
|
||||||
|
'checker self blob not verified: ' + role)
|
||||||
|
tested = binding['tested_product']
|
||||||
|
require(git_bytes('cat-file', '-t', tested).strip() == b'commit'
|
||||||
|
and product_manifest(tree_entries(tested)) == product, 'tested product manifest mismatch')
|
||||||
|
candidate = tree_entries(index['candidate_tree'])
|
||||||
|
require(candidate == tree, 'candidate product/QA/collector/tree bytes differ from target')
|
||||||
|
runners = binding['runner_inputs']
|
||||||
|
require(isinstance(runners, list) and runners, 'actual runner inputs missing')
|
||||||
|
runner_paths = set()
|
||||||
|
for descriptor in runners:
|
||||||
|
runner_path = supplied_file(directory, descriptor)
|
||||||
|
require(runner_path not in runner_paths, 'duplicate runner input')
|
||||||
|
runner_paths.add(runner_path)
|
||||||
|
native = load(runner_path)
|
||||||
|
before = native['source_before']
|
||||||
|
require(set(before) == set(product) and all(
|
||||||
|
{'bytes': v['bytes'], 'sha256': v['sha256']} == product[p] for p, v in before.items()),
|
||||||
|
'runner source_before differs from complete product')
|
||||||
|
require(native['runner_sha256'] == product['tools/run_all_tests.py']['sha256'], 'runner source pin')
|
||||||
|
overrides = native['QA_overrides']
|
||||||
|
require(isinstance(overrides, list) and len(overrides) == 1
|
||||||
|
and overrides[0].startswith('tests/') and overrides[0] in product, 'one declared QA override')
|
||||||
|
selected = {p: v['sha256'] for p, v in product.items()}
|
||||||
|
selected[overrides[0]] = native['QA']['sha256']
|
||||||
|
require(native['selected_source_map'] == selected, 'selected source/QA override mismatch')
|
||||||
|
# A relocation is explicit and hash-bound; never open the original path.
|
||||||
|
materials = descriptor['materials']
|
||||||
|
fixed = native.get('fixed_input_pins', [])
|
||||||
|
require(isinstance(fixed, list), 'native fixed input pin list')
|
||||||
|
needed = fixed + [native['QA']]
|
||||||
|
require(set(materials) == {p['path'] for p in needed}, 'runner input/config material membership')
|
||||||
|
for pin in needed:
|
||||||
|
real = supplied_file(directory, materials[pin['path']])
|
||||||
|
require(real.stat().st_size == pin['bytes'] and sha(real) == pin['sha256'], 'native input/QA bytes')
|
||||||
|
wrapper = supplied_file(directory, descriptor['wrapper'])
|
||||||
|
require(sha(wrapper) == native['wrapper_sha256'], 'native wrapper bytes')
|
||||||
|
digest = manifest_digest(product)
|
||||||
|
require(index['versions'] == {
|
||||||
|
'registered': REGISTERED_COMMIT, 'execution_origin': EXECUTION_ORIGIN,
|
||||||
|
'checker': {'commit': checker_commit, 'sha256': hashlib.sha256(own).hexdigest()},
|
||||||
|
'tested': {'product_target': target, 'manifest_sha256': digest}}, 'four version roles')
|
||||||
|
require(index['source_map'] == {p: v['sha256'] for p, v in product.items()}, 'product source map')
|
||||||
|
return composed, {'manifest_sha256': digest, 'existing_metadata': metadata,
|
||||||
|
'runner_paths': runner_paths,
|
||||||
|
'tested_product': tested, 'product_target': target}
|
||||||
|
|
||||||
|
|
||||||
|
def function_bytes(data):
|
||||||
|
"""Top-level named func through next named func, preserving every raw byte.
|
||||||
|
|
||||||
|
The prefix is an explicit non-function unit. Nested lambdas do not start
|
||||||
|
a unit. Comments, whitespace, signatures and trailing declarations remain
|
||||||
|
included; uncertain edits fail conservatively, never normalize as equal.
|
||||||
|
"""
|
||||||
|
matches = list(re.finditer(rb'(?m)^(?:static )?func ([A-Za-z_][A-Za-z_0-9]*)\(', data))
|
||||||
|
result = {'<top-level>': data[:matches[0].start()] if matches else data}
|
||||||
|
for i, match in enumerate(matches):
|
||||||
|
name = match[1].decode('ascii')
|
||||||
|
require(name not in result, 'ambiguous function: ' + name)
|
||||||
|
result[name] = data[match.start():matches[i + 1].start() if i + 1 < len(matches) else len(data)]
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_anchor(data, function, digest, line_count):
|
||||||
|
body = function_bytes(data).get(function)
|
||||||
|
require(body is not None, 'reachability function missing: ' + function)
|
||||||
|
lines = body.splitlines(keepends=True)
|
||||||
|
hits = [b''.join(lines[i:i + line_count]) for i in range(len(lines) - line_count + 1)
|
||||||
|
if hashlib.sha256(b''.join(lines[i:i + line_count])).hexdigest() == digest]
|
||||||
|
require(len(hits) == 1, 'reachability anchor missing/ambiguous: ' + function)
|
||||||
|
return hits[0]
|
||||||
|
|
||||||
|
|
||||||
|
def source_delta(base, head):
|
||||||
|
"""Compute changed function units from Git, including added/deleted files.
|
||||||
|
|
||||||
|
Only game autoload/scripts GDScript belongs to this applicability scope.
|
||||||
|
The complete tested map still pins every supplied dependency separately.
|
||||||
|
"""
|
||||||
|
paths = git_bytes('diff', '--no-ext-diff', '--no-renames', '--name-only', '-z',
|
||||||
|
base + '..' + head, '--', 'autoload/', 'scripts/').split(b'\0')
|
||||||
|
base_paths = set(git_bytes('ls-tree', '-r', '--name-only', '-z', base).split(b'\0'))
|
||||||
|
head_paths = set(git_bytes('ls-tree', '-r', '--name-only', '-z', head).split(b'\0'))
|
||||||
|
delta = []
|
||||||
|
for encoded in sorted(p for p in paths if p.endswith(b'.gd')):
|
||||||
|
path = encoded.decode('utf-8')
|
||||||
|
old = function_bytes(git_bytes('show', base + ':' + path)) if encoded in base_paths else {}
|
||||||
|
new = function_bytes(git_bytes('show', head + ':' + path)) if encoded in head_paths else {}
|
||||||
|
for name in sorted(old.keys() | new.keys()):
|
||||||
|
if old.get(name) != new.get(name):
|
||||||
|
delta.append({'path': path, 'function': name})
|
||||||
|
return delta
|
||||||
|
|
||||||
|
|
||||||
|
def boundary_conditions(path, function):
|
||||||
|
# Conservative coverage: new/changed Runtime setup can reach every boundary.
|
||||||
|
if path == 'autoload/simulation_runtime.gd':
|
||||||
|
if any(word in function for word in ('relationship', 'restore', 'snapshot', 'save')):
|
||||||
|
return {'save_restore'}
|
||||||
|
return set(REQUIRED) | {'EV'}
|
||||||
|
if '/relationship/' in path or '/save/' in path:
|
||||||
|
return {'save_restore'}
|
||||||
|
if any(part in path for part in ('/world/', '/transport/', '/staff/')):
|
||||||
|
return {'exit_release', 'closure_release', 'shift_completion', 'EV', 'save_restore'}
|
||||||
|
# No inferred non-impact exemption for previously unseen dependencies.
|
||||||
|
return set(REQUIRED) | {'EV'}
|
||||||
|
|
||||||
|
|
||||||
|
def applicability(index, spec, source, head):
|
||||||
|
registered = spec['current_source_pins']
|
||||||
|
require(index.get('registered_pins') == registered, 'registered pin declaration')
|
||||||
|
for path, digest in registered.items():
|
||||||
|
require(hashlib.sha256(git_bytes('show', REGISTERED_COMMIT + ':' + path)).hexdigest() == digest,
|
||||||
|
'registered Git pin: ' + path)
|
||||||
|
# head is the independently recomputed product tree, not the checker HEAD.
|
||||||
|
game_paths = git_bytes('ls-tree', '-r', '--name-only', '-z', head, '--', 'autoload/', 'scripts/').split(b'\0')
|
||||||
|
for encoded in game_paths:
|
||||||
|
if not encoded.endswith(b'.gd'):
|
||||||
|
continue
|
||||||
|
path = encoded.decode('utf-8')
|
||||||
|
require(path in source and source[path] == hashlib.sha256(git_bytes('show', head + ':' + path)).hexdigest(),
|
||||||
|
'tested game differs from Git: ' + path)
|
||||||
|
delta = source_delta(REGISTERED_COMMIT, head)
|
||||||
|
require(index.get('applicability_delta') == delta, 'git diff/applicability_delta mismatch')
|
||||||
|
for path, names in ORDER_FUNCTIONS.items():
|
||||||
|
old = function_bytes(git_bytes('show', REGISTERED_COMMIT + ':' + path))
|
||||||
|
new = function_bytes(git_bytes('show', head + ':' + path))
|
||||||
|
for name in names:
|
||||||
|
require(name in old and name in new and old[name] == new[name], 'stale ordering function: ' + path + ':' + name)
|
||||||
|
bindings = index.get('delta_cases')
|
||||||
|
require(isinstance(bindings, dict) and set(bindings) == {r['path'] + ':' + r['function'] for r in delta}, 'delta case bindings')
|
||||||
|
for row in delta:
|
||||||
|
key = row['path'] + ':' + row['function']
|
||||||
|
required = boundary_conditions(row['path'], row['function'])
|
||||||
|
cases = bindings[key]
|
||||||
|
require(isinstance(cases, dict) and set(cases) == required, 'boundary delta coverage: ' + key)
|
||||||
|
for cid, case_id in cases.items():
|
||||||
|
declaration = index['reachability']['EV'] if cid == 'EV' else index['conditions'][cid]
|
||||||
|
require(case_id == declaration.get('case_id'), 'boundary delta engine case: ' + key)
|
||||||
|
|
||||||
|
|
||||||
|
def coverage(index, raw, source, product_tree):
|
||||||
|
authority = index['authority']
|
||||||
|
for ref in {r for _, refs, _ in REQUIRED.values() for r in refs}:
|
||||||
|
entry = authority.get(ref, {})
|
||||||
|
require(entry.get('status') in {'original', 'approved'}, 'pending/missing authority: ' + ref)
|
||||||
|
aid = entry.get('artifact_id')
|
||||||
|
require(aid in raw and index['artifacts'][aid]['type'] == 'authority', 'raw authority: ' + ref)
|
||||||
|
if ref.startswith('D05-P04-'):
|
||||||
|
require(sha(raw[aid]) == OWNER_HASH, 'owner approval hash mismatch')
|
||||||
|
owner = load(raw[aid])
|
||||||
|
require(owner.get('origin') == 'direct_user_reply' and owner.get('formal_acceptance') is False,
|
||||||
|
'owner approval scope')
|
||||||
|
require(ref in {v['id'] for v in owner['approved_choices']}, 'owner approval condition')
|
||||||
|
else:
|
||||||
|
path = ('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')
|
||||||
|
require(sha(raw[aid]) == sha(safe(ROOT, path)), 'authority document mismatch: ' + ref)
|
||||||
|
conditions = index.get('conditions')
|
||||||
|
require(isinstance(conditions, dict) and set(conditions) == set(REQUIRED), 'mandatory conditions missing/unknown')
|
||||||
|
for cid, (domain_name, refs, expected) in REQUIRED.items():
|
||||||
|
condition = conditions[cid]
|
||||||
|
require(condition.get('authority_refs') == list(refs), 'condition authority: ' + cid)
|
||||||
|
require(condition.get('nativeWindows') == 'pending', 'nativeWindows condition: ' + cid)
|
||||||
|
domain = index['domains'][domain_name]
|
||||||
|
require(set(refs) <= set(domain['authority_refs']), 'domain authority: ' + cid)
|
||||||
|
cases = [c for c in domain['cases'] if c['id'] == condition.get('case_id')]
|
||||||
|
require(len(cases) == 1, 'mandatory engine case: ' + cid)
|
||||||
|
case = cases[0]
|
||||||
|
require(case.get('raw_origin') == 'engine', 'case origin: ' + cid)
|
||||||
|
observation = load(raw[case['after']])
|
||||||
|
measurements = observation.get('conditions', {}).get(cid)
|
||||||
|
require(isinstance(measurements, dict), 'missing engine measurements: ' + cid)
|
||||||
|
for metric, value in expected.items():
|
||||||
|
require(literal(measurements.get(metric), value), cid + ': measurement ' + metric)
|
||||||
|
require(any(p.get('case_id') == case['id'] and p.get('artifact_id') == case['after']
|
||||||
|
and p.get('json_pointer') == '/conditions/' + cid + '/' + metric
|
||||||
|
and p.get('operator') == 'eq' and literal(p.get('expected'), value)
|
||||||
|
and p.get('authority_ref') in refs for p in domain['invariants']),
|
||||||
|
cid + ': missing mandatory invariant ' + metric)
|
||||||
|
if cid == 'save_restore':
|
||||||
|
writer = load(raw[case['receipt']])
|
||||||
|
reader_id = condition.get('restore_receipt')
|
||||||
|
require(reader_id in raw, 'missing restore receipt')
|
||||||
|
reader = load(raw[reader_id])
|
||||||
|
require(type(reader.get('start_pid')) is int and reader['start_pid'] > 0
|
||||||
|
and reader['start_pid'] != writer['start_pid']
|
||||||
|
and reader.get('end_pid') == reader['start_pid']
|
||||||
|
and type(reader.get('exit_code')) is int and reader['exit_code'] == 0
|
||||||
|
and reader.get('engine_ended') is True, 'separate PID restore receipt')
|
||||||
|
require(reader.get('source_map') == source and reader.get('environment') == index['environment'], 'restore applicability')
|
||||||
|
require(reader.get('case_id') == case['id'] and
|
||||||
|
reader.get('artifact_hashes') == writer.get('artifact_hashes'), 'restore case artifact binding')
|
||||||
|
save_id = condition.get('save_artifact')
|
||||||
|
require(save_id in raw and index['artifacts'][save_id]['type'] == 'save', 'formal save artifact')
|
||||||
|
require(writer.get('saved_sha256') == sha(raw[save_id]) == reader.get('restored_sha256'), 'save/restore identity')
|
||||||
|
require(reader.get('stdout') in raw and reader.get('native_log') in raw, 'restore logs')
|
||||||
|
reachability = index.get('reachability')
|
||||||
|
require(isinstance(reachability, dict) and set(reachability) == {'EV', 'transport'}, 'reachability required')
|
||||||
|
for name, decision in reachability.items():
|
||||||
|
refs = decision.get('source_refs')
|
||||||
|
require(isinstance(refs, list) and refs and isinstance(decision.get('reason'), str)
|
||||||
|
and decision['reason'].strip(), 'unsupported reach=false: ' + name)
|
||||||
|
for ref in refs:
|
||||||
|
require(ref.get('path') in source and ref.get('sha256') == source[ref['path']], 'reachability source pin')
|
||||||
|
identity = (ref['path'], ref.get('function'), ref.get('anchor_sha256'), ref.get('line_count'))
|
||||||
|
require(identity in REACH_REFS[name], 'unknown reachability anchor')
|
||||||
|
resolve_anchor(git_bytes('show', product_tree + ':' + ref['path']), *identity[1:])
|
||||||
|
require(len(refs) == len(REACH_REFS[name]) and set(REACH_REFS[name]) == {
|
||||||
|
(r['path'], r['function'], r['anchor_sha256'], r['line_count']) for r in refs},
|
||||||
|
'missing static reachability chain: ' + name)
|
||||||
|
# Staff dispatch reserves task items before travel. A guest-only exclusion
|
||||||
|
# must not hide this reachable EV reservation path.
|
||||||
|
if name == 'EV':
|
||||||
|
require(decision.get('reach') is True, 'EV staff reservation path is reachable')
|
||||||
|
domain = index['domains']['停電']
|
||||||
|
cases = [c for c in domain['cases'] if c['id'] == decision.get('case_id')]
|
||||||
|
require(len(cases) == 1 and cases[0].get('raw_origin') == 'engine', 'EV engine case')
|
||||||
|
case = cases[0]
|
||||||
|
ev = load(raw[case['after']]).get('ev', {})
|
||||||
|
require(ev.get('reserved_before_travel') is True and ev.get('power_stopped') is True,
|
||||||
|
'EV reserved staff power-stop scenario')
|
||||||
|
require(any(p.get('case_id') == case['id'] and p.get('artifact_id') == case['after']
|
||||||
|
and p.get('json_pointer') == '/ev/orphan_reservations' and p.get('operator') == 'eq'
|
||||||
|
and literal(p.get('expected'), 0) for p in domain['invariants']), 'EV orphan invariant')
|
||||||
|
else:
|
||||||
|
require(decision.get('reach') is False, 'transport reservation/payment exclusion')
|
||||||
|
|
||||||
|
|
||||||
|
NATIVE_RUNS = {
|
||||||
|
'actual-native': ('import', 'parse', 'repro'),
|
||||||
|
'dependency-midnight-native': ('import', 'parse', 'repro'),
|
||||||
|
'dependency-formal-native': ('import', 'parse', 'writer', 'reader'),
|
||||||
|
}
|
||||||
|
SAVE_POINTS = ('before', 'midnight', 'paused', 'after', 'next')
|
||||||
|
SAVE_MEMBERS = {'journal.log', 'manifest.json', 'thumbnail.png', 'world.bin'}
|
||||||
|
|
||||||
|
|
||||||
|
def native_file(base, name, pin):
|
||||||
|
path = safe(base, name)
|
||||||
|
require(type(pin.get('bytes')) is int and path.stat().st_size == pin['bytes'], 'native bytes: ' + name)
|
||||||
|
require(sha(path) == pin.get('sha256'), 'native SHA: ' + name)
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def native_cleanup(value):
|
||||||
|
require(value.get('engine_ended') is True and value.get('errors') == []
|
||||||
|
and literal(value.get('actual_process_exit_code'), 0), 'native cleanup failed')
|
||||||
|
attempts = value.get('attempts')
|
||||||
|
require(isinstance(attempts, list) and attempts, 'native cleanup attempts missing')
|
||||||
|
for attempt in attempts:
|
||||||
|
require(attempt.get('errors') == [] and attempt.get('kill_attempted') is False
|
||||||
|
and attempt.get('wait_completed') is True
|
||||||
|
and literal(attempt.get('wait_returned_code'), 0), 'native cleanup abnormal wait')
|
||||||
|
|
||||||
|
|
||||||
|
def native_ownership(base):
|
||||||
|
provenance = load(safe(base, 'OWNERSHIP_RAW_PROVENANCE.json'))['copied']
|
||||||
|
targets = set()
|
||||||
|
for row in provenance:
|
||||||
|
require(row['target'] not in targets, 'duplicate native provenance target')
|
||||||
|
targets.add(row['target'])
|
||||||
|
native_file(base, row['target'], row)
|
||||||
|
pids = {}
|
||||||
|
for run, phases in NATIVE_RUNS.items():
|
||||||
|
execution = load(safe(base, run + '/execution.json'))
|
||||||
|
inputs = load(safe(base, run + '/runner-input.json'))
|
||||||
|
require([p['phase'] for p in execution['phases']] == list(phases), 'native phase membership')
|
||||||
|
run_pids = []
|
||||||
|
cleanups = []
|
||||||
|
for phase, phase_receipt in zip(phases, execution['phases']):
|
||||||
|
prefix = run + '/' + phase + '/'
|
||||||
|
names = ('started.json', 'process.json', 'ended.json', 'outer-owned-finally.json',
|
||||||
|
'adapter-owned-cleanup.json', 'stdout.log', 'stderr.log', 'engine-native.log')
|
||||||
|
require({prefix + n for n in names} <= targets, 'native provenance lifecycle missing')
|
||||||
|
start, process, end, outer, cleanup = [load(safe(base, prefix + n)) for n in names[:5]]
|
||||||
|
pid = start['actual_pid']
|
||||||
|
require(type(pid) is int and pid > 0 and pid not in pids.values(), 'native PID reused')
|
||||||
|
require(start['pid'] == process['pid'] == end['pid'] == end['actual_pid'] == pid,
|
||||||
|
'native PID chain')
|
||||||
|
require(start['event'] == 'ENGINE_STARTED' and end['event'] == 'ENGINE_ENDED'
|
||||||
|
and start['owner_pid'] == end['owner_pid'] == inputs['owner_runner_pid']
|
||||||
|
and start['command'] == start['argv'] == process['argv'] == end['argv'] == end['command']
|
||||||
|
and start['monotonic_ns'] < end['monotonic_ns'], 'native lifecycle identity')
|
||||||
|
require(process['actual_start_binding'] == start and process['actual_end_binding'] == end,
|
||||||
|
'native process bindings')
|
||||||
|
require(start['current_process_identity']['pid'] == pid
|
||||||
|
and start['current_process_identity']['ppid'] == start['owner_pid'], 'native owner identity')
|
||||||
|
require(load(safe(base, run + '/' + phase + '-receipt.json')) == phase_receipt
|
||||||
|
== load(safe(base, run + '/' + phase + '-actual-phase.json')), 'native phase receipt binding')
|
||||||
|
for record in (process, end, phase_receipt):
|
||||||
|
require(literal(record.get('exit_code'), 0), 'native abnormal exit')
|
||||||
|
require(end.get('engine_ended') is True and end.get('timed_out') is False
|
||||||
|
and process.get('timed_out') is False and end.get('lifecycle_error') is None
|
||||||
|
and process.get('lifecycle_error') is None and literal(end.get('actual_process_exit_code'), 0)
|
||||||
|
and phase_receipt.get('engine_ended') is True, 'native abnormal end')
|
||||||
|
require(phase_receipt['actual_pid'] == pid and phase_receipt['native_process'] == process,
|
||||||
|
'native execution process binding')
|
||||||
|
require(outer['actual_captured_pids'] == [pid] and outer['outer_cleanup_errors'] == []
|
||||||
|
and outer.get('all_captured_ended') is True and outer.get('adapter_exception') is None
|
||||||
|
and len(outer['cleanup']) == 1 and outer['cleanup'][0]['pid'] == pid
|
||||||
|
and outer['cleanup'][0]['outer_cleanup_errors'] == []
|
||||||
|
and outer['cleanup'][0]['cleanup'] == cleanup == process['adapter_owned_cleanup'],
|
||||||
|
'native owned cleanup binding')
|
||||||
|
native_cleanup(cleanup)
|
||||||
|
for stream in ('stdout', 'stderr'):
|
||||||
|
require(sha(safe(base, prefix + stream + '.log')) == process[stream + '_sha256'],
|
||||||
|
'native stream hash')
|
||||||
|
for field in ('stdout', 'stderr'):
|
||||||
|
require(safe(base, prefix + field + '.log').is_file(), 'native log missing')
|
||||||
|
native_file(base, prefix + 'engine-native.log', phase_receipt['raw_native_log'])
|
||||||
|
pids[run + '/' + phase] = pid
|
||||||
|
run_pids.append(pid)
|
||||||
|
cleanups.extend(outer['cleanup'])
|
||||||
|
require(execution['actual_captured_PIDs'] == run_pids and execution['cleanup'] == cleanups
|
||||||
|
and execution['cleanup_errors'] == [], 'native execution cleanup membership')
|
||||||
|
formal = 'dependency-formal-native/'
|
||||||
|
records = [load(safe(base, formal + 'restart-evidence/' + role + '.json')) for role in ('writer', 'reader')]
|
||||||
|
require(records[0]['profile'] != records[1]['profile'], 'native writer reader profile reused')
|
||||||
|
for role, record in zip(('writer', 'reader'), records):
|
||||||
|
require(record['pid'] == pids[formal + role], 'native writer reader PID binding')
|
||||||
|
profile = load(safe(base, formal + role + '-profile.json'))
|
||||||
|
phase = load(safe(base, formal + role + '-receipt.json'))
|
||||||
|
require(record['profile'] == profile['actual_user_data_dir'] == profile['expected_user_data_dir']
|
||||||
|
== phase['controlled_environment']['THE_TREE_PACK_PROFILE_EXPECTED'], 'native profile binding')
|
||||||
|
require(record['slots'] == {p: record['profile'] + '/saves/p04_boundary/' + p for p in SAVE_POINTS},
|
||||||
|
'native formal profile slots')
|
||||||
|
return pids
|
||||||
|
|
||||||
|
|
||||||
|
def native_applicability(base):
|
||||||
|
"""Production evidence identity, separate from A/B checkout applicability."""
|
||||||
|
app = load(safe(base, 'POST_P18_CURRENT_SOURCE_APPLICABILITY.json'))
|
||||||
|
production = app['production_commit']
|
||||||
|
require(re.fullmatch('[0-9a-f]{40}', production) is not None, 'native production commit')
|
||||||
|
registered = load(SPEC)['current_source_pins']
|
||||||
|
pin_rows = app['registered_source_pins']
|
||||||
|
require(len(pin_rows) == len(registered) and {r['path'] for r in pin_rows} == set(registered),
|
||||||
|
'native current_source_pins membership')
|
||||||
|
current = {}
|
||||||
|
for row in pin_rows:
|
||||||
|
path = row['path']
|
||||||
|
current[path] = hashlib.sha256(git_bytes('show', production + ':' + path)).hexdigest()
|
||||||
|
require(row['registered_sha256'] == registered[path] and row['production_sha256'] == current[path],
|
||||||
|
'native current_source_pins mismatch')
|
||||||
|
rows = app['ordering_functions']
|
||||||
|
require(len(rows) == sum(map(len, ORDER_FUNCTIONS.values())) and
|
||||||
|
{(r['path'], r['function']) for r in rows} == {(p, f) for p, fs in ORDER_FUNCTIONS.items() for f in fs},
|
||||||
|
'native ordering membership')
|
||||||
|
for path, functions in ORDER_FUNCTIONS.items():
|
||||||
|
old = function_bytes(git_bytes('show', REGISTERED_COMMIT + ':' + path))
|
||||||
|
new = function_bytes(git_bytes('show', production + ':' + path))
|
||||||
|
for function in functions:
|
||||||
|
row = next(r for r in rows if (r['path'], r['function']) == (path, function))
|
||||||
|
body = new[function]
|
||||||
|
require(old[function] == body and hashlib.sha256(body).hexdigest() ==
|
||||||
|
row['production_body_sha256'] == row['registered_body_sha256'],
|
||||||
|
'native ordering bytes')
|
||||||
|
for run, result in zip(NATIVE_RUNS, ('SHIFT_CURRENT', 'DEPENDENCY_MIDNIGHT', 'DEPENDENCY_FORMAL')):
|
||||||
|
summary = load(safe(base, 'ROOT_APPROVED_' + result + '_RESULT.json'))
|
||||||
|
require(summary['production_commit'] == production, 'native production commit mismatch')
|
||||||
|
inputs = load(safe(base, run + '/runner-input.json'))
|
||||||
|
for path, digest in current.items():
|
||||||
|
require(inputs['source_before'][path]['sha256'] == digest
|
||||||
|
== inputs['selected_source_map'][path], 'native input source pin mismatch')
|
||||||
|
return production
|
||||||
|
|
||||||
|
|
||||||
|
def native_saves(base):
|
||||||
|
prefix = 'dependency-formal-native/'
|
||||||
|
execution = load(safe(base, prefix + 'execution.json'))
|
||||||
|
before, after = execution['formal_before'], execution['formal_after']
|
||||||
|
require(set(before) == set(after) == set(SAVE_POINTS) and before == after, 'native original members changed')
|
||||||
|
summary = load(safe(base, 'ROOT_APPROVED_DEPENDENCY_FORMAL_RESULT.json'))
|
||||||
|
members = summary['formal_member_pins']
|
||||||
|
require(len(members) == 20 and {(r['point'], r['member']) for r in members} ==
|
||||||
|
{(p, m) for p in SAVE_POINTS for m in SAVE_MEMBERS}, 'native formal member membership')
|
||||||
|
for point in SAVE_POINTS:
|
||||||
|
require(set(before[point]) == SAVE_MEMBERS, 'native original member missing')
|
||||||
|
for member in SAVE_MEMBERS:
|
||||||
|
for role in ('writer', 'reader'):
|
||||||
|
path = native_file(base, prefix + 'formal-packages/' + role + '/' + point + '/' + member, before[point][member])
|
||||||
|
row = next(r for r in members if (r['point'], r['member']) == (point, member))
|
||||||
|
require(sha(path) == row['sha256'], 'native formal member pin')
|
||||||
|
typed = summary['typed_outputs_14_sha_match_original_native']
|
||||||
|
expected = {role + '-' + p + suffix + '.var': p for p in SAVE_POINTS
|
||||||
|
for role, suffix in (('writer', '-persisted'), ('reader', '-loaded'))}
|
||||||
|
expected.update({'reader-replayed-' + p + '.var': p for p in SAVE_POINTS if p != 'before'})
|
||||||
|
require(len(typed) == len(expected) and {Path(r['output']).name for r in typed} == set(expected),
|
||||||
|
'native typed member membership')
|
||||||
|
inputs = load(safe(base, prefix + 'runner-input.json'))['fixed_input_pins']
|
||||||
|
for row in typed:
|
||||||
|
name = Path(row['output']).name
|
||||||
|
original = 'midnight-' + expected[name] + '.var'
|
||||||
|
require(Path(row['original']).name == original, 'native typed original identity')
|
||||||
|
source = safe(base, 'dependency-midnight-native/order-evidence/' + original)
|
||||||
|
output = safe(base, prefix + 'restart-evidence/' + name)
|
||||||
|
require(sha(source) == sha(output) == row['sha256'], 'native typed SHA mismatch')
|
||||||
|
pin = [p for p in inputs if p['path'] == row['original']]
|
||||||
|
require(len(pin) == 1, 'native typed input missing')
|
||||||
|
native_file(base, 'dependency-midnight-native/order-evidence/' + original, pin[0])
|
||||||
|
|
||||||
|
|
||||||
|
def receipt_rows(receipts):
|
||||||
|
"""Keep multiplicity: sets must never hide duplicated ledger rows."""
|
||||||
|
require(isinstance(receipts, list), 'native receipts missing')
|
||||||
|
ids, rows = set(), []
|
||||||
|
for receipt in receipts:
|
||||||
|
require(receipt['id'] not in ids and receipt['rows'], 'native duplicate/empty receipt')
|
||||||
|
ids.add(receipt['id'])
|
||||||
|
for row in receipt['rows']:
|
||||||
|
require(row['transaction_id'] == receipt['id'] and type(row['amount']) is int
|
||||||
|
and row['amount'] >= 0, 'native receipt transaction')
|
||||||
|
require(row not in rows, 'native duplicate receipt row')
|
||||||
|
rows.append(row)
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
def native_ledger(base):
|
||||||
|
prefix = 'dependency-midnight-native/order-evidence/'
|
||||||
|
observations = load(safe(base, prefix + 'observations.json'))
|
||||||
|
series = observations['observations']
|
||||||
|
require(len(series) == len(SAVE_POINTS), 'native observation series missing')
|
||||||
|
initial = series[0]
|
||||||
|
service = initial['service']
|
||||||
|
require(service['state'] == 'ACTIVE' and service['elapsed_minutes'] == service['duration_minutes'] - 1,
|
||||||
|
'native initial service progress')
|
||||||
|
identity = ('service_id', 'request_id', 'facility_id', 'guest_ids', 'inventory_reservations', 'price_snapshot')
|
||||||
|
require(service['inventory_reservations'] and len(service['guest_ids']) == 1, 'native service identity missing')
|
||||||
|
guest_id = service['guest_ids'][0]
|
||||||
|
account = 'GUEST_WALLET:' + guest_id
|
||||||
|
final_state = load(safe(base, prefix + 'midnight-after.json'))['systems']['runtime']
|
||||||
|
links = [r for r in final_state['economy']['idempotency'] if r['key'] == 'service.payment.' + service['service_id']]
|
||||||
|
require(len(links) == 1, 'native service idempotency missing/duplicate')
|
||||||
|
transaction = links[0]['transaction_id']
|
||||||
|
previous_rows = []
|
||||||
|
states = []
|
||||||
|
for position, (point, observation) in enumerate(zip(SAVE_POINTS, series)):
|
||||||
|
snapshot = load(safe(base, prefix + 'midnight-' + point + '.json'))
|
||||||
|
state = snapshot['systems']['runtime']
|
||||||
|
states.append(state)
|
||||||
|
facilities = [f for f in state['facilities']['instances'] if f['instance_id'] == service['facility_id']]
|
||||||
|
require(len(facilities) == 1, 'native facility identity')
|
||||||
|
facility = facilities[0]
|
||||||
|
if position == 0:
|
||||||
|
definition_id = facility['definition_id']
|
||||||
|
require(re.fullmatch('[a-zA-Z0-9_]+', definition_id) is not None, 'native definition identity')
|
||||||
|
definition_path = 'data/facilities/' + definition_id + '.tres'
|
||||||
|
production = load(safe(base, 'POST_P18_CURRENT_SOURCE_APPLICABILITY.json'))['production_commit']
|
||||||
|
definition = git_bytes('show', production + ':' + definition_path)
|
||||||
|
inputs = load(safe(base, 'dependency-midnight-native/runner-input.json'))
|
||||||
|
require(inputs['selected_source_map'][definition_path] ==
|
||||||
|
inputs['source_before'][definition_path]['sha256'] == hashlib.sha256(definition).hexdigest(),
|
||||||
|
'native original definition pin')
|
||||||
|
for field, value in (('base_price', service['price_snapshot']), ('base_service_minutes', service['duration_minutes'])):
|
||||||
|
matches = re.findall(rb'(?m)^' + field.encode() + rb' = ([0-9]+)$', definition)
|
||||||
|
require(len(matches) == 1 and int(matches[0]) == value == facility[field], 'native original definition value')
|
||||||
|
current = observation['service']
|
||||||
|
require(all(current[k] == service[k] for k in identity), 'native service identity changed')
|
||||||
|
require(observation['minute'] == initial['minute'] + position == snapshot['clock']['absolute_minute'],
|
||||||
|
'native minute series')
|
||||||
|
require(current in state['facilities']['service_history'] or any(
|
||||||
|
current in f['active_services'] for f in state['facilities']['instances']), 'native service snapshot binding')
|
||||||
|
require(observation['reservations'] == state['inventory'], 'native reservation snapshot binding')
|
||||||
|
rows = receipt_rows(observation['receipts'])
|
||||||
|
ledger = state['economy']['entries']
|
||||||
|
initial_ledger = states[0]['economy']['entries']
|
||||||
|
require(ledger[:len(initial_ledger)] == initial_ledger and ledger[len(initial_ledger):] == rows,
|
||||||
|
'native ledger receipt delta')
|
||||||
|
require(rows[:len(previous_rows)] == previous_rows, 'native receipt history changed')
|
||||||
|
wallet_delta = sum(r['amount'] * ((r['debit_account'] == account) - (r['credit_account'] == account)) for r in rows)
|
||||||
|
require(observation['wallet'] == initial['wallet'] + wallet_delta, 'native wallet delta')
|
||||||
|
require(observation['wallet'] == sum(r['amount'] * ((r['debit_account'] == account) -
|
||||||
|
(r['credit_account'] == account)) for r in ledger), 'native ledger wallet balance')
|
||||||
|
if observation['guest']:
|
||||||
|
require(observation['guest']['wallet_gold'] == observation['wallet']
|
||||||
|
and observation['guest']['guest_id'] == guest_id, 'native guest wallet binding')
|
||||||
|
else:
|
||||||
|
require(point == 'next', 'native guest missing before completion')
|
||||||
|
sales = [r for r in rows if r['category'] == 'SERVICE_SALE']
|
||||||
|
paused = current['state'] == 'PAUSED'
|
||||||
|
if position in (1, 2):
|
||||||
|
require(paused and current['interruption_reason'] == 'RELATED_FACILITY_INACTIVE'
|
||||||
|
and current['elapsed_minutes'] == service['elapsed_minutes'] and not sales,
|
||||||
|
'native PAUSED sale/progress/reason')
|
||||||
|
elif position >= 3:
|
||||||
|
require(current['state'] == 'COMPLETED' and len(sales) == 1
|
||||||
|
and current['elapsed_minutes'] == current['duration_minutes'], 'native completion sale count')
|
||||||
|
sale = sales[0]
|
||||||
|
require(sale['transaction_id'] == transaction and sale['credit_account'] == account
|
||||||
|
and sale['source_entity_id'] == service['facility_id']
|
||||||
|
and sale['debit_account'] == 'FACILITY_REVENUE:' + service['facility_id']
|
||||||
|
and sale['amount'] == service['price_snapshot']
|
||||||
|
and sale['absolute_minute'] == current['completed_at'], 'native sale service join')
|
||||||
|
reservations = observation['reservations']['reservations']
|
||||||
|
require(len(reservations) == len(service['inventory_reservations']) and
|
||||||
|
{r['reservation_id'] for r in reservations} == set(service['inventory_reservations']),
|
||||||
|
'native orphan reservation membership')
|
||||||
|
requests = state['facilities']['requests']
|
||||||
|
matching_requests = [r for r in requests if r['request_id'] == service['request_id']]
|
||||||
|
require(len(matching_requests) == (1 if position < 3 else 0), 'native request series')
|
||||||
|
if matching_requests:
|
||||||
|
request = matching_requests[0]
|
||||||
|
require(request['facility_id'] == service['facility_id'] and request['guest_ids'] == service['guest_ids']
|
||||||
|
and request['quoted_price'] == service['price_snapshot']
|
||||||
|
and request['reserved_inventory_ids'] == service['inventory_reservations'], 'native request service join')
|
||||||
|
for reservation in reservations:
|
||||||
|
require(reservation['requester_id'] == service['request_id']
|
||||||
|
and reservation['owner_inventory_id'] == 'facility.inventory:' + service['facility_id']
|
||||||
|
and reservation['state'] == ('ACTIVE' if position < 3 else 'CONSUMED'),
|
||||||
|
'native orphan reservation relation')
|
||||||
|
previous_rows = rows
|
||||||
|
require(receipt_rows(observations['first_receipts']) == previous_rows
|
||||||
|
== receipt_rows(observations['replay_receipts']), 'native midnight replay receipts')
|
||||||
|
reader = load(safe(base, 'dependency-formal-native/restart-evidence/reader.json'))
|
||||||
|
require(len(reader['results']) == 1 and receipt_rows(reader['results'][0]['receipts']) == previous_rows,
|
||||||
|
'native reader receipt delta')
|
||||||
|
sweep = [r for r in previous_rows if r['category'] == 'SERVICE_CASH_SWEEP']
|
||||||
|
require(len(sweep) == 1 and sweep[0]['transaction_id'] == transaction
|
||||||
|
and sweep[0]['amount'] == service['price_snapshot']
|
||||||
|
and sweep[0]['credit_account'] == 'FACILITY_REVENUE:' + service['facility_id']
|
||||||
|
and sweep[0]['debit_account'] == 'PLAYER_CASH', 'native sale cash sweep')
|
||||||
|
maintenance = [r for r in previous_rows if r['category'].startswith('MAINTENANCE_')]
|
||||||
|
accruals = [r for r in maintenance if r['category'] == 'MAINTENANCE_ACCRUAL']
|
||||||
|
payments = [r for r in maintenance if r['category'] == 'MAINTENANCE_PAYMENT']
|
||||||
|
require(accruals and len(accruals) == len(payments), 'native maintenance pairs')
|
||||||
|
for accrual in accruals:
|
||||||
|
matches = [r for r in payments if r['transaction_id'] == accrual['transaction_id']
|
||||||
|
and r['source_entity_id'] == accrual['source_entity_id'] and r['amount'] == accrual['amount']
|
||||||
|
and r['absolute_minute'] == series[1]['minute'] == accrual['absolute_minute'] + 1]
|
||||||
|
require(len(matches) == 1, 'native maintenance accrual/payment join')
|
||||||
|
return {'service_id': service['service_id'], 'transaction_id': transaction,
|
||||||
|
'receipt_rows': len(previous_rows), 'formal_original_credit': 0}
|
||||||
|
|
||||||
|
|
||||||
|
def verify_native(base):
|
||||||
|
"""Finite C-layer check, never a replacement for the 15+EV whole gate."""
|
||||||
|
for row in load(safe(base, 'SOURCE_MANIFEST.json'))['files']:
|
||||||
|
native_file(base, row['copy'], row)
|
||||||
|
production = native_applicability(base)
|
||||||
|
pids = native_ownership(base)
|
||||||
|
native_saves(base)
|
||||||
|
result = native_ledger(base)
|
||||||
|
return dict(result, production_commit=production, owned_processes=len(pids),
|
||||||
|
verified_conditions=['dependency_stop', 'recovery_completion', 'paused_midnight'])
|
||||||
|
|
||||||
|
|
||||||
|
def verify(directory):
|
||||||
|
# Contract hashes the supplied spec string, while the checked-in JSON has
|
||||||
|
# one final LF. Accept only that exact representation difference.
|
||||||
|
spec_bytes = SPEC.read_bytes()
|
||||||
|
require(sha(SPEC) == SPEC_HASH or (spec_bytes.endswith(b'\n') and
|
||||||
|
hashlib.sha256(spec_bytes[:-1]).hexdigest() == SPEC_HASH), 'dispatch spec pin mismatch')
|
||||||
|
spec = load(SPEC)
|
||||||
|
evidence = directory / 'p04-evidence'
|
||||||
|
require(not evidence.is_symlink(), 'symlink evidence directory')
|
||||||
|
index = load(safe(evidence, 'index.json'))
|
||||||
|
require(type(index.get('schema_version')) is int and index['schema_version'] == 1, 'schema_version')
|
||||||
|
require(index.get('task_id') == 'P04-01' and index.get('formal_acceptance') is False, 'task/acceptance')
|
||||||
|
checker_commit = git_bytes('rev-parse', 'HEAD').decode().strip()
|
||||||
|
require(index.get('candidate_commit') == checker_commit, 'checker invocation commit mismatch')
|
||||||
|
head, target_result = verify_product_target(index, directory, checker_commit)
|
||||||
|
source = index.get('source_map')
|
||||||
|
applicability(index, spec, source, head)
|
||||||
|
oldmap = load(ROOT / 'docs/plans/original-completion-2026-10-02/execution/p04-01-input-r01/historical-source-map.json')
|
||||||
|
required = {p for p in oldmap if p.endswith('.gd') and p.startswith(('autoload/', 'scripts/'))}
|
||||||
|
require(required <= source.keys(), 'missing runtime/service dependency source pins')
|
||||||
|
environment = index.get('environment')
|
||||||
|
require(isinstance(environment, dict) and isinstance(environment.get('os'), str) and environment['os'], 'environment OS')
|
||||||
|
for kind in ('engine', 'configuration', 'inputs'):
|
||||||
|
pins(environment.get(kind), evidence)
|
||||||
|
artifacts = index.get('artifacts')
|
||||||
|
require(isinstance(artifacts, dict) and artifacts, 'missing artifacts')
|
||||||
|
paths, raw = set(), {}
|
||||||
|
for aid, item in artifacts.items():
|
||||||
|
require(isinstance(aid, str) and aid and isinstance(item, dict), 'invalid artifact')
|
||||||
|
path = safe(evidence, item['path'])
|
||||||
|
require(path not in paths, 'duplicate artifact path')
|
||||||
|
paths.add(path)
|
||||||
|
require(type(item.get('bytes')) is int and item['bytes'] == path.stat().st_size, 'artifact size: ' + aid)
|
||||||
|
require(sha(path) == item.get('sha256'), 'artifact hash: ' + aid)
|
||||||
|
require(item.get('type') in {'observation', 'state', 'receipt', 'stdout', 'native_log', 'save', 'authority'}, 'artifact type')
|
||||||
|
raw[aid] = path
|
||||||
|
authority = index.get('authority')
|
||||||
|
require(isinstance(authority, dict) and authority, 'missing authority')
|
||||||
|
domains = index.get('domains')
|
||||||
|
require(isinstance(domains, dict) and set(domains) == DOMAINS, 'required seven domains')
|
||||||
|
for name, domain in domains.items():
|
||||||
|
require(isinstance(domain, dict), 'invalid domain: ' + name)
|
||||||
|
for field in ('source_refs', 'authority_refs', 'cases', 'invariants'):
|
||||||
|
require(isinstance(domain.get(field), list) and domain[field], name + ': missing ' + field)
|
||||||
|
require(domain.get('applicability') == {'source_map': source, 'environment': environment}, name + ': applicability mismatch')
|
||||||
|
for ref in domain['source_refs']:
|
||||||
|
require(isinstance(ref, dict) and ref.get('path') in source and isinstance(ref.get('function'), str) and ref['function'], 'invalid source ref')
|
||||||
|
require(ref.get('sha256') == source[ref['path']], 'source ref hash')
|
||||||
|
for ref in domain['authority_refs']:
|
||||||
|
require(ref in authority and authority[ref].get('status') in {'original', 'approved'}, 'pending/missing authority')
|
||||||
|
require(authority[ref].get('artifact_id') in raw, 'missing raw authority')
|
||||||
|
case_ids = set()
|
||||||
|
for case in domain['cases']:
|
||||||
|
require(isinstance(case, dict) and isinstance(case.get('id'), str) and case['id'] not in case_ids, 'duplicate/invalid case')
|
||||||
|
case_ids.add(case['id'])
|
||||||
|
for field in ('input', 'before', 'operation', 'after', 'receipt'):
|
||||||
|
require(case.get(field) in raw, name + ': missing case artifact ' + field)
|
||||||
|
receipt = load(raw[case['receipt']])
|
||||||
|
pid = receipt.get('start_pid')
|
||||||
|
require(type(pid) is int and pid > 0 and receipt.get('end_pid') == pid and type(receipt.get('exit_code')) is int and receipt['exit_code'] == 0 and receipt.get('engine_ended') is True, 'unexecuted/incomplete receipt')
|
||||||
|
require(receipt.get('source_map') == source and receipt.get('environment') == environment, 'receipt applicability')
|
||||||
|
for field in ('stdout', 'native_log'):
|
||||||
|
require(receipt.get(field) in raw, 'receipt missing raw ' + field)
|
||||||
|
require(receipt.get('case_id') == case['id'], 'receipt case identity')
|
||||||
|
require(receipt.get('artifact_hashes') == {field: sha(raw[case[field]]) for field in ('input', 'before', 'operation', 'after')}, 'receipt artifact binding')
|
||||||
|
for pred in domain['invariants']:
|
||||||
|
require(isinstance(pred, dict) and pred.get('artifact_id') in raw, 'missing predicate artifact')
|
||||||
|
require(pred.get('authority_ref') in domain['authority_refs'], 'predicate authority')
|
||||||
|
require(pred.get('raw_origin') == 'engine', 'unsupported derivation/origin')
|
||||||
|
value = pointer(load(raw[pred['artifact_id']]), pred['json_pointer'])
|
||||||
|
op = pred.get('operator')
|
||||||
|
if op in {'same_literal', 'distinct'}:
|
||||||
|
require(pred.get('paired_artifact_id') in raw, 'missing paired artifact')
|
||||||
|
other = pointer(load(raw[pred['paired_artifact_id']]), pred['paired_json_pointer'])
|
||||||
|
result = literal(value, other) if op == 'same_literal' else not literal(value, other)
|
||||||
|
elif op == 'eq':
|
||||||
|
result = literal(value, pred['expected'])
|
||||||
|
elif op == 'nonnegative':
|
||||||
|
result = type(value) in (int, float) and value >= 0
|
||||||
|
elif op == 'count_eq':
|
||||||
|
result = isinstance(value, (list, dict)) and type(pred.get('expected')) is int and len(value) == pred['expected']
|
||||||
|
else:
|
||||||
|
raise ValueError('unsupported predicate: ' + str(op))
|
||||||
|
require(result, name + ': predicate failed')
|
||||||
|
require(index.get('review_status') in {'pending_review', 'approved'}, 'review status')
|
||||||
|
coverage(index, raw, source, head)
|
||||||
|
# Scalar conditions remain necessary declarations, never sufficient proof.
|
||||||
|
binding = index.get('native_bundle')
|
||||||
|
require(isinstance(binding, dict), 'native bundle required; self-reported scalar evidence is insufficient')
|
||||||
|
manifest = supplied_file(directory, binding)
|
||||||
|
require(manifest.name == 'OWNERSHIP_RAW_PROVENANCE.json' and sha(manifest) == binding['sha256'],
|
||||||
|
'native bundle provenance pin')
|
||||||
|
native = verify_native(manifest.parent)
|
||||||
|
require({safe(manifest.parent, run + '/runner-input.json') for run in NATIVE_RUNS}
|
||||||
|
<= target_result['runner_paths'], 'C runner inputs absent from complete product binding')
|
||||||
|
require(native['production_commit'] == target_result['tested_product'], 'native production role mismatch')
|
||||||
|
# This adapter covers only the supplied dependency/formal series. Never
|
||||||
|
# promote it to the missing original scenarios through scalar booleans.
|
||||||
|
missing = (set(REQUIRED) | {'EV'}) - set(native['verified_conditions'])
|
||||||
|
require(not missing, 'uncollected original boundaries: ' + ', '.join(sorted(missing)) +
|
||||||
|
'; formal_original_credit=0')
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument('--verify-only', action='store_true', required=True)
|
||||||
|
parser.parse_args()
|
||||||
|
try:
|
||||||
|
require(bool(os.environ.get('BUILD_OUTPUT_DIR')), 'BUILD_OUTPUT_DIR required')
|
||||||
|
with isolated_objects():
|
||||||
|
verify(Path(os.environ['BUILD_OUTPUT_DIR']).resolve())
|
||||||
|
except (ValueError, OSError, KeyError, TypeError, IndexError, AttributeError, subprocess.SubprocessError) as exc:
|
||||||
|
print(json.dumps({'task_id': 'P04-01', 'machine_verified': False, 'formal_acceptance': False, 'error': str(exc)}, ensure_ascii=False))
|
||||||
|
return 1
|
||||||
|
print(json.dumps({'task_id': 'P04-01', 'machine_verified': True, 'formal_acceptance': False}))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main())
|
||||||
Reference in New Issue
Block a user