diff --git a/docs/plans/original-completion-2026-10-02/execution/p04-01/CHECKER_PROTOCOL.md b/docs/plans/original-completion-2026-10-02/execution/p04-01/CHECKER_PROTOCOL.md index c0e025b..91c6259 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p04-01/CHECKER_PROTOCOL.md +++ b/docs/plans/original-completion-2026-10-02/execution/p04-01/CHECKER_PROTOCOL.md @@ -2,7 +2,7 @@ `python3 tools/check_p04_order_evidence.py --verify-only` は環境変数 `BUILD_OUTPUT_DIR` の `p04-evidence/index.json` を読み、書込み・試験・Runner操作をしない。欠落時は非0。原30/88の受入・完了を確定しない。 -このdispatchではD05が未承認であるため、検証器は常に非0で終了する。indexのapproved/pass宣言で解除できない。これは初期監査の制限であり、全条件を機械的に検証できる完成版gateではない。承認済み原規約と生証跡が供給された後、別契約でsemantic coverageとnative provenanceの検証を追加する必要がある。 +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や試験数があっても判定には使用しない。 @@ -12,9 +12,28 @@ - `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とrawログの意味的結合、別PID保存member一致は未対応であり合格扱いしない。 +- 各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は失敗。 -期待値の承認根拠、停止中帳簿不変、決算再送/再開一回、予約とlive targetの対応、payment identity重複なし、必須case coverageを構造チェックだけで証明しない。最後にD05とsemantic/native証明の不足を明示して失敗させる。 +## 固定必須条件 -Runner向け最小確認: index欠落、重複キー、symlink/traversal、hash/size不一致、stale source、未実行receipt、未知predicate、pending authority、7領域欠落が非0になること。その後、D05が未承認の完全形indexでも非0であること。正式境界試験は監査の有限計画に沿い、不適用の旧証拠だけを置き換える。workerはこれらを実行していない。 +checkerのREQUIREDが唯一の必須条件定義であり、indexから条件を受け取らない。conditionsはそのIDと完全一致するmap。各値はcase_id、固定順のauthority_refs、nativeWindows: pending。各条件は指定domainのengine caseと全metricのeq invariantを必要とする。観測はcase.afterの /conditions// に置き、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が生証拠由来と意味を独立確認する。 diff --git a/docs/plans/original-completion-2026-10-02/execution/p04-01/D05_OWNER_APPROVAL.json b/docs/plans/original-completion-2026-10-02/execution/p04-01/D05_OWNER_APPROVAL.json new file mode 100644 index 0000000..8408b27 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p04-01/D05_OWNER_APPROVAL.json @@ -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の他領域を一括解決したとは扱わない。" +} diff --git a/docs/plans/original-completion-2026-10-02/execution/p04-01/ORDER_AUDIT.md b/docs/plans/original-completion-2026-10-02/execution/p04-01/ORDER_AUDIT.md index 9aceedf..8619d7a 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p04-01/ORDER_AUDIT.md +++ b/docs/plans/original-completion-2026-10-02/execution/p04-01/ORDER_AUDIT.md @@ -1,6 +1,6 @@ # P04-01 監査状態 -原単位は未完了。D05の閉店後開始済サービスと18:00最終勤務区間は未承認であり、現実装の観測を承認済み規約に置き換えない。A01の00:00保有時間按分維持費は承認済み。 +原単位は未完了。D05-P04-DEPENDENCY と D05-P04-SHIFT-1800 は direct_user_reply により承認済み(同梱 D05_OWNER_APPROVAL.json、SHA256 4583ee9aad45c4db183d72df4420df48aa8e0897e3b5efda13016f0b5cdbd68e)。承認は試験合格を意味しない。A01の00:00保有時間按分維持費は承認済みで再確認しない。 正式試験はRunnerが実行する。旧証跡参照は来歴であり、現版の生証跡・ソース・設定・入力・環境の適用性確認なしに合格として再利用しない。 @@ -10,12 +10,12 @@ | 領域 | 現行呼出しと先後 | 原本・承認・不足 | | --- | --- | --- | -| 閉店 | `_advance_facilities` の後、`_advance_guest_queues` が `_release_closed_business_queues` を実行し、patienceより先に予約解放を試みる。 | 閉店後開始済サービスはD05保留。旧business試験は部分候補。open serviceの00:00証拠を代用しない。 | +| 閉店 | `_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保留。1080診断を承認規約に格上げしない。 | +| シフト交代 | 完了後の `_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を非ゼロ給与証拠に数えない。 | ## 再利用の制限 @@ -28,11 +28,25 @@ ## 提出状態と有限検証計画 -修正入力は内部index形式の設計を許可した。`tools/check_p04_order_evidence.py` と `CHECKER_PROTOCOL.md` を作成した。構造・pin・artifact hash・receipt項目・有限JSON比較を検証するが、native provenance、全必須境界coverage、原規約への意味的適合は未対応であり、このdispatchのD05未承認とともに必ず非0になる。初期監査をsubmittedとして提出し、machine gate利用可能・原成果完了とは扱わない。 +今回の承認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なら失敗する。 -1. RunnerがCHECKER_PROTOCOL.mdの負ケースでfailclosed動作を正式確認する。workerは試験未実行。 -2. 生証跡・設定・入力・環境が揃う有限証拠だけ再利用する。Runtime/PathService変更に影響される到達性・勤務・退出境界を確認する。 -3. D05承認後に閉店/18:00の依存境界だけ補完し、停止復電・先行後段破壊・公開/自動退出・00:00・再送/復元の不足分を絞る。A01を再確認しない。 -4. semantic/native検証の不足を別契約で解消し、Runnerがp04-acceptanceと全条件の独立レビュー・統合を実施する。native Windows限定条件が必要なら個別保留とする。 +承認4文書は全てバイト数とSHA256を再計算し、SOURCE_MANIFESTと一致。必要な承認JSONのみ2073 bytesを同梱した。初期空き容量約1.8GiB、旧39 artifactの複製なし。39件の原パスは来歴であり、今回のミラーは承認4文書のみ。生証拠・入力・設定・環境が不足しているので、境界の適用性合格は宣言しない。 -試験・Runner操作・Git書込み・他エージェント起動は行っていない。原30パッケージ/88成果の既存1単位内の準備であり、成果追加0、正式完了0。 +順序関数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。 diff --git a/docs/plans/original-completion-2026-10-02/execution/p04-01/checker_negative_fixtures.py b/docs/plans/original-completion-2026-10-02/execution/p04-01/checker_negative_fixtures.py new file mode 100644 index 0000000..e4de641 --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p04-01/checker_negative_fixtures.py @@ -0,0 +1,154 @@ +#!/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/'))} + 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], start=s, end=e, + text='\n'.join((ROOT / p).read_text().splitlines()[s-1:e])) for p,s,e 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') + 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_control', lambda i: None, None) + 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)) + diff --git a/docs/plans/original-completion-2026-10-02/execution/p04-01/order-audit.json b/docs/plans/original-completion-2026-10-02/execution/p04-01/order-audit.json index 5c8952e..aeff5aa 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p04-01/order-audit.json +++ b/docs/plans/original-completion-2026-10-02/execution/p04-01/order-audit.json @@ -113,14 +113,29 @@ "missing_map_paths": [], "task_id": "P04-01", "base_commit": "deb1bcb2374eb378b840ca345db952bd8572cb6e", - "status": "blocked", + "status": "submitted", "original_unit_complete": false, "counted_units_added": 0, "tests_executed": false, "authority": { - "original": "docs/traceability/original/BASELINE.txt:64,67,71,72", - "approved": "A01: ownership-time apportioned upkeep at midnight; retained without reconfirmation", - "pending": "D05: started service after closure, final shift interval at 18:00, unresolved EV/transport stopping/cost/day crossing rules; no invented policy" + "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": [ @@ -132,9 +147,9 @@ "simulation_runtime._release_closed_business_queues", "simulation_runtime._release_closed_business_request" ], - "order": "Facility completion precedes queue closure release; release occurs before patience spending. Started-service closure semantics remain D05 pending.", + "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": "Approved started-service closure rule and exact-version boundary raw proof." + "missing": "Exact-version direct/dependency closure evidence; approval is supplied, not a pass." }, { "domain": "停電", @@ -192,9 +207,9 @@ "StaffService.advance_one_minute", "simulation_runtime._sync_facility_staffing" ], - "order": "Facility completion precedes staff AI, commute and staffing projection. Final18:00 interval design meaning is D05 pending.", + "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": "Approved final-interval rule then exact-version1080 boundary including nonzero cost where required." + "missing": "Applicable 1080 completion/replay, 1081 WAITING_FOR_STAFF with no night staff, and existing overtime task evidence." }, { "domain": "00:00決算", @@ -212,23 +227,24 @@ ], "old_evidence_applicability": { "raw_available_for_reuse": false, - "assessment": "Only local summaries/maps/draft read; original artifact paths remain provenance. No approved reuse asserted.", + "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 closure service and final shift interval remain unapproved.", - "Raw historical boundary proofs/config/input/environment not supplied; summary pass claims cannot establish original acceptance.", - "Initial checker validates structural/hash predicates but intentionally rejects unsupported semantic coverage/native provenance; passing machine gate unavailable in pending-D05 dispatch." + "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 supplies exact-version raw boundary evidence using CHECKER_PROTOCOL.md; no worker engine/test execution.", - "Confirm finite reachability/arrival/staff path impacts for affected Runtime and PathService; retain 14 raw differences and classify 7 metadata-only differences separately.", - "After D05 approval verify only affected closure/18:00 boundaries; retain failed diagnostics and approved A01.", - "Verify applicable stop/recovery, early/late destruction, public/patience/overdue exit, midnight/replay/reload evidence with raw hashes and source/config/input/environment pins; request fresh finite proofs only where reuse fails.", - "Runner executes p04-acceptance and independent all-condition review/integration; native Windows conditions individually pending only if applicable. No engine/all-regression run here." + "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": { @@ -240,5 +256,265 @@ "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." } } diff --git a/tools/check_p04_order_evidence.py b/tools/check_p04_order_evidence.py index dbfbcce..fa44ebd 100644 --- a/tools/check_p04_order_evidence.py +++ b/tools/check_p04_order_evidence.py @@ -1,8 +1,7 @@ #!/usr/bin/env python3 """Read-only P04 evidence preflight. Never grants original acceptance. -This initial-phase verifier deliberately rejects unsupported semantic evidence: -D05 is pending in the dispatch, irrespective of declarations in an index. +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 @@ -18,6 +17,35 @@ ROOT = Path(__file__).resolve().parents[1] SPEC = ROOT / 'docs/spec/acceptance/P04-01.json' SPEC_HASH = '9ec722e7dfb0bee178d291624a026420c08412574e238bba1fde012c7a6f7881' DOMAINS = {'閉店', '停電', '破壊', '退出', '課金', 'シフト交代', '00:00決算'} +OWNER_HASH = '4583ee9aad45c4db183d72df4420df48aa8e0897e3b5efda13016f0b5cdbd68e' +# 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', 6733, 6742), + ('scripts/staff/task_dispatcher.gd', 256, 261), + ('autoload/simulation_runtime.gd', 9141, 9170), + ('autoload/simulation_runtime.gd', 8986, 8987), + ('scripts/transport/elevator_service.gd', 279, 284)], + 'transport': [('autoload/simulation_runtime.gd', 12023, 12030), + ('autoload/simulation_runtime.gd', 12976, 13050)], +} def require(condition, message): @@ -81,8 +109,100 @@ def literal(a, b): return type(a) is type(b) and json.dumps(a, sort_keys=True) == json.dumps(b, sort_keys=True) +def coverage(index, raw, source): + 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') + lines = safe(ROOT, ref['path']).read_text().splitlines() + start, end = ref.get('start'), ref.get('end') + require(type(start) is int and type(end) is int and 1 <= start <= end <= len(lines), 'reachability lines') + require(ref.get('text') == '\n'.join(lines[start - 1:end]), 'reachability source text') + require(set(REACH_REFS[name]) <= {(r['path'], r['start'], r['end']) 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') + + def verify(directory): - require(sha(SPEC) == SPEC_HASH, 'dispatch spec pin mismatch') + # 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') @@ -141,6 +261,8 @@ def verify(directory): 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') @@ -161,9 +283,7 @@ def verify(directory): raise ValueError('unsupported predicate: ' + str(op)) require(result, name + ': predicate failed') require(index.get('review_status') in {'pending_review', 'approved'}, 'review status') - # Raw observation claims cannot establish production provenance, mandatory - # scenario coverage or an approval absent from this immutable dispatch. - raise ValueError('D05 pending: closure service / final shift interval; semantic coverage and native provenance require independent raw-evidence review. Machine gate unavailable; original acceptance not granted.') + coverage(index, raw, source) def main(): @@ -173,10 +293,11 @@ def main(): try: require(bool(os.environ.get('BUILD_OUTPUT_DIR')), 'BUILD_OUTPUT_DIR required') verify(Path(os.environ['BUILD_OUTPUT_DIR'])) - except (ValueError, OSError, KeyError, TypeError, IndexError, subprocess.SubprocessError) as exc: + 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 - return 1 + print(json.dumps({'task_id': 'P04-01', 'machine_verified': True, 'formal_acceptance': False})) + return 0 if __name__ == '__main__':