P04-01: 同時発生の更新規約

This commit is contained in:
Agent Control Runner
2026-10-15 23:15:15 +09:00
parent a19737b160
commit 9767b0a28c
6 changed files with 664 additions and 38 deletions
@@ -2,7 +2,7 @@
`python3 tools/check_p04_order_evidence.py --verify-only` は環境変数 `BUILD_OUTPUT_DIR` の `p04-evidence/index.json` を読み、書込み・試験・Runner操作をしない。欠落時は非0。原30/88の受入・完了を確定しない。 `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や試験数があっても判定には使用しない。 内部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 のいずれか。 - `artifacts`: ID→`path`, `bytes`(整数), `sha256`, `type`。typeは observation/state/receipt/stdout/native_log/save/authority のいずれか。
- `authority`: ref→`status`(original/approved), `artifact_id`。raw文書の存在を確認するが内容の原規約適合は独立レビューを要する。pendingは失敗。 - `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`。 - `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は失敗。 - 各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/<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の他領域を一括解決したとは扱わない。"
}
@@ -1,6 +1,6 @@
# P04-01 監査状態 # 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が実行する。旧証跡参照は来歴であり、現版の生証跡・ソース・設定・入力・環境の適用性確認なしに合格として再利用しない。 正式試験は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不変は生証跡未確認。進捗・予約・帳簿・通知を現版で確認する必要がある。 | | 停電 | `_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を区別。失敗診断も保持し、原本適合と断定しない。 | | 破壊 | commandはAPPLY_COMMANDSで先行。`_advance_defense` は完了後のINCIDENTS。BROKENはサービス準備時に失敗。 | 先行破壊44と後段完了45を区別。失敗診断も保持し、原本適合と断定しない。 |
| 退出 | 公開queue leave → 完了 → INCIDENTS → queue patience解放 → AI再計画/予定退出。LEAVINGのdespawnは入状態分より後。 | 公開leave・自動patience・overdue退出の正式保存/別PID証跡は参照のみ。孤立0を現版で証明する必要がある。 | | 退出 | 公開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の存在や試験数だけで二重課金・二重生成なしとしない。 | | 課金 | `_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を非ゼロ給与証拠に数えない。 | | 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は試験未実行。 承認4文書は全てバイト数とSHA256を再計算し、SOURCE_MANIFESTと一致。必要な承認JSONのみ2073 bytesを同梱した。初期空き容量約1.8GiB、旧39 artifactの複製なし。39件の原パスは来歴であり、今回のミラーは承認4文書のみ。生証拠・入力・設定・環境が不足しているので、境界の適用性合格は宣言しない。
2. 生証跡・設定・入力・環境が揃う有限証拠だけ再利用する。Runtime/PathService変更に影響される到達性・勤務・退出境界を確認する。
3. D05承認後に閉店/18:00の依存境界だけ補完し、停止復電・先行後段破壊・公開/自動退出・00:00・再送/復元の不足分を絞る。A01を再確認しない。
4. semantic/native検証の不足を別契約で解消し、Runnerがp04-acceptanceと全条件の独立レビュー・統合を実施する。native Windows限定条件が必要なら個別保留とする。
試験・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。
@@ -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))
@@ -113,14 +113,29 @@
"missing_map_paths": [], "missing_map_paths": [],
"task_id": "P04-01", "task_id": "P04-01",
"base_commit": "deb1bcb2374eb378b840ca345db952bd8572cb6e", "base_commit": "deb1bcb2374eb378b840ca345db952bd8572cb6e",
"status": "blocked", "status": "submitted",
"original_unit_complete": false, "original_unit_complete": false,
"counted_units_added": 0, "counted_units_added": 0,
"tests_executed": false, "tests_executed": false,
"authority": { "authority": {
"original": "docs/traceability/original/BASELINE.txt:64,67,71,72", "owner_approved": {
"approved": "A01: ownership-time apportioned upkeep at midnight; retained without reconfirmation", "artifact": "D05_OWNER_APPROVAL.json",
"pending": "D05: started service after closure, final shift interval at 18:00, unresolved EV/transport stopping/cost/day crossing rules; no invented policy" "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.", "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": [ "domains": [
@@ -132,9 +147,9 @@
"simulation_runtime._release_closed_business_queues", "simulation_runtime._release_closed_business_queues",
"simulation_runtime._release_closed_business_request" "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.", "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": "停電", "domain": "停電",
@@ -192,9 +207,9 @@
"StaffService.advance_one_minute", "StaffService.advance_one_minute",
"simulation_runtime._sync_facility_staffing" "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.", "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決算", "domain": "00:00決算",
@@ -212,23 +227,24 @@
], ],
"old_evidence_applicability": { "old_evidence_applicability": {
"raw_available_for_reuse": false, "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.", "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.", "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.", "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." "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": [ "blockers": [
"D05 closure service and final shift interval remain unapproved.", "D05 dependency closure and 18:00 interval are approved; approval is not test success.",
"Raw historical boundary proofs/config/input/environment not supplied; summary pass claims cannot establish original acceptance.", "Raw 39 historical artifacts, settings/input/environment and engine receipts are not mirrored here. Their hashes in recheck are provenance, not verified current evidence.",
"Initial checker validates structural/hash predicates but intentionally rejects unsupported semantic coverage/native provenance; passing machine gate unavailable in pending-D05 dispatch." "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": [ "validation_plan": [
"Runner supplies exact-version raw boundary evidence using CHECKER_PROTOCOL.md; no worker engine/test execution.", "Runner runs p04-acceptance; worker only py_compile and synthetic negative fixture checks.",
"Confirm finite reachability/arrival/staff path impacts for affected Runtime and PathService; retain 14 raw differences and classify 7 metadata-only differences separately.", "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.",
"After D05 approval verify only affected closure/18:00 boundaries; retain failed diagnostics and approved A01.", "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.",
"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.", "Do not reopen A01 or invent cost/day attribution. Pending unrelated D05 does not fail the whole gate.",
"Runner executes p04-acceptance and independent all-condition review/integration; native Windows conditions individually pending only if applicable. No engine/all-regression run here." "Root rechecks production Runtime 4696df4a against immutable checkout d52a995, then independent review in separate session and normal integration. Formal credit remains zero."
], ],
"repair_support": { "repair_support": {
"hashes": { "hashes": {
@@ -240,5 +256,265 @@
"production_differences": 7, "production_differences": 7,
"managed_differences": 14, "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." "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."
} }
} }
+129 -8
View File
@@ -1,8 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Read-only P04 evidence preflight. Never grants original acceptance. """Read-only P04 evidence preflight. Never grants original acceptance.
This initial-phase verifier deliberately rejects unsupported semantic evidence: Required conditions and authority bindings are fixed here, never supplied by an index.
D05 is pending in the dispatch, irrespective of declarations in an index.
See execution/p04-01/CHECKER_PROTOCOL.md for the supported input subset. See execution/p04-01/CHECKER_PROTOCOL.md for the supported input subset.
""" """
import argparse import argparse
@@ -18,6 +17,35 @@ ROOT = Path(__file__).resolve().parents[1]
SPEC = ROOT / 'docs/spec/acceptance/P04-01.json' SPEC = ROOT / 'docs/spec/acceptance/P04-01.json'
SPEC_HASH = '9ec722e7dfb0bee178d291624a026420c08412574e238bba1fde012c7a6f7881' SPEC_HASH = '9ec722e7dfb0bee178d291624a026420c08412574e238bba1fde012c7a6f7881'
DOMAINS = {'閉店', '停電', '破壊', '退出', '課金', 'シフト交代', '00:00決算'} 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): 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) 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): 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) spec = load(SPEC)
evidence = directory / 'p04-evidence' evidence = directory / 'p04-evidence'
require(not evidence.is_symlink(), 'symlink evidence directory') 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') require(receipt.get('source_map') == source and receipt.get('environment') == environment, 'receipt applicability')
for field in ('stdout', 'native_log'): for field in ('stdout', 'native_log'):
require(receipt.get(field) in raw, 'receipt missing raw ' + field) 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']: for pred in domain['invariants']:
require(isinstance(pred, dict) and pred.get('artifact_id') in raw, 'missing predicate artifact') 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('authority_ref') in domain['authority_refs'], 'predicate authority')
@@ -161,9 +283,7 @@ def verify(directory):
raise ValueError('unsupported predicate: ' + str(op)) raise ValueError('unsupported predicate: ' + str(op))
require(result, name + ': predicate failed') require(result, name + ': predicate failed')
require(index.get('review_status') in {'pending_review', 'approved'}, 'review status') require(index.get('review_status') in {'pending_review', 'approved'}, 'review status')
# Raw observation claims cannot establish production provenance, mandatory coverage(index, raw, source)
# 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.')
def main(): def main():
@@ -173,10 +293,11 @@ def main():
try: try:
require(bool(os.environ.get('BUILD_OUTPUT_DIR')), 'BUILD_OUTPUT_DIR required') require(bool(os.environ.get('BUILD_OUTPUT_DIR')), 'BUILD_OUTPUT_DIR required')
verify(Path(os.environ['BUILD_OUTPUT_DIR'])) 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)) print(json.dumps({'task_id': 'P04-01', 'machine_verified': False, 'formal_acceptance': False, 'error': str(exc)}, ensure_ascii=False))
return 1 return 1
return 1 print(json.dumps({'task_id': 'P04-01', 'machine_verified': True, 'formal_acceptance': False}))
return 0
if __name__ == '__main__': if __name__ == '__main__':