diff --git a/docs/plans/original-completion-2026-10-02/execution/p04-01/s2/S2_ROOT_RUN.md b/docs/plans/original-completion-2026-10-02/execution/p04-01/s2/S2_ROOT_RUN.md index 05ce134..2648493 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p04-01/s2/S2_ROOT_RUN.md +++ b/docs/plans/original-completion-2026-10-02/execution/p04-01/s2/S2_ROOT_RUN.md @@ -1,5 +1,42 @@ # P04-01 S2a Root run(code-ready提出用・実測未実施) +repair12確認: worktreeのoperations_coordinator.gd SHA256は +`871a248ee101b6df543fd5cf0440b5d4b0f02c21fa145a99f0687888c1bc6f90`。 +製品パスの直接読取はOSが拒否したが、Root r01のsource_before.jsonに記録された +同ファイルの製品pin(130891 bytes)と一致した。 + +## repair12の判断と検証境界 + +ROOT_S2A_NATIVE_R01_RESULT.json、r01 source_before.json、execution.json、Reader stderr、 +raw Writer C1 join/C2 recall/C3 midnightとReader C3 branchのJSONを直接確認した。 +対応する型保持.varも全バイトを読みSHAを確認した。原本を保持し、workerはengineで復号・再実行しない。 +Readerの失敗はformal equality day_summaryの1件。実勤務はAkaneの1+236分、 +Bardoの361分(0勤務取消shiftも存在)、維持費は3行がledger_00000082を共有する。 + +受入JSON、既存S2条件、ORDER_AUDITは決算内容・決定的順序・重複防止・保存復元を要求し、 +getterのDictionary挿入順やStringNameキー型の保存前後不変を要求していない。 +OperationsCoordinator:577-580は内部duplicate、668-710と2943-3065は保存用のString化・sort、 +restore:734と_parse_historiesは保存形を内部へ採用する。したがって製品変更は不要と判断した。 +JSON正規化で一致を作らず、create_snapshot.day_summariesのday==1をそのまま取り出して +Writer/Readerのvar_to_bytes完全一致を必須とする。raw getterは保持し、再帰typeof・キー型・ +キー順fingerprint、rawバイト比較結果も保存する。rawとcanonicalは全キーをString(key)で +一対一照合し、全値を再帰的に型込みで検査する。StringName/Stringだけは明示した差分種別で +照合し、その他の型差・値差・欠落は失敗する。配列の順序も維持する。 + +paid.payment>0の各staffを同日の実worked/overtime>0のshiftへ結合し、各shiftの実deltaを +照合してからpayroll取引IDを集計する。Bardoは1439→1440でworked+1、Akaneは+0、 +同分recallは双方+0、overtime差分は0が必須。0勤務取消shiftは実勤務根拠にしない。 +維持費distinct取引数は1を維持し、売上時点・日帰属は変えない。 +Runtime全体、ledger/payroll、4member SHA、原本不変、recall typed不変は従来どおり必須。 + +Rootはfresh r02 plan/outputでimport/parse/Writer/別PID Readerを各1回だけ正式実行する。 +r01のFAILをr02の成功として扱わない。worker検証はpy_compileとwrapper --check-onlyのみ。 +engine0、default/private profile読取0、手動commit0、全回帰0。原30/88、部分点0を維持する。 + +repair12 worker検証: py_compile成功、--check-only成功(source1300、QA1、固定pin17、engine0)。 +終了済cache receiptはPermissionErrorをplanへ記録した。Rootは自身の権限で確認する必要がある。 +GDScript parseとnativeの成否は未検証で、正式なr02実行に委ねる。 + 原P04の完成加点は0。独立したwhole受入の前段の準備資料であり、D05、whole受入、独立レビュー、統合を確定しない。 Rootは新しいcode-readyの提出が出るまでengineを起動しない。 workerはGodot起動(parse、--check-only、--versionを含む)、S1再実行、全回帰、commitを行わない。 @@ -32,8 +69,8 @@ wrapperは毎回templateを直接読み、両ブロックのliteral一致を確 ```sh export BUILD_OUTPUT_DIR='/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build' export PYTHONDONTWRITEBYTECODE=1 -python3 docs/plans/original-completion-2026-10-02/execution/p04-01/s2/run_s2a_native.py --check-only --plan "$BUILD_OUTPUT_DIR/p04-s2a-root-plan-r01.json" --output "$BUILD_OUTPUT_DIR/p04-s2a-native-r01" -python3 docs/plans/original-completion-2026-10-02/execution/p04-01/s2/run_s2a_native.py --plan "$BUILD_OUTPUT_DIR/p04-s2a-root-plan-r01.json" --output "$BUILD_OUTPUT_DIR/p04-s2a-native-r01" +python3 docs/plans/original-completion-2026-10-02/execution/p04-01/s2/run_s2a_native.py --check-only --plan "$BUILD_OUTPUT_DIR/p04-s2a-root-plan-r02.json" --output "$BUILD_OUTPUT_DIR/p04-s2a-native-r02" +python3 docs/plans/original-completion-2026-10-02/execution/p04-01/s2/run_s2a_native.py --plan "$BUILD_OUTPUT_DIR/p04-s2a-root-plan-r02.json" --output "$BUILD_OUTPUT_DIR/p04-s2a-native-r02" ``` wrapperのパスはこの提出checkoutのものを使う。実行cwdに依存して製品sourceを選ばず、既定product rootを使用する。 @@ -47,13 +84,13 @@ Writerはseed424242の独立fresh Runtimeを3回作り、各branchを1回ずつ - C1 midnight_sale_payroll_upkeep:原のclock advanceで1440にserviceを完了し、同じadvanceでDay1決算。実sale transaction IDをservice.paymentのIdempotency記録でservice IDへjoinし、request ID・予約IDと照合する。WAGEのsource_entity_idを実payroll settlement IDへjoinし、各paid rowのstaff_idとsettlement.day(1始まり)をShiftInstance.staff_id/start_day(0始まり)へjoinする。取引IDをdistinctで数え、行数・勤務分数を取引数の代わりにしない。 - C2 same_minute_recall:別fresh stateから1440へ到達した直後、current_minute==1440で公開advance_to(1440)を1回呼ぶ。ok/code/payloadとCommandResult全体を記録し、全typed snapshot、ledger、payroll、day_summary、shifts不変を検査。request再送や1443からの過去呼出しで代用しない。 -- C3 formal_restore:別fresh stateからC1と同じ境界に到達し、公的SaveServiceで1slotを保存。別PID/profileのReaderが4member全コピーとSHA一致、公的load/restore、typed snapshot完全一致、ledger/payroll/day_summary一致を確認。復元直後の1440でadvance_to(1440)を再度呼び、実結果と再決算0を採取する。 +- C3 formal_restore:別fresh stateからC1と同じ境界に到達し、公的SaveServiceで1slotを保存。別PID/profileのReaderが4member全コピーとSHA一致、公的load/restore、typed snapshot完全一致、ledger/payroll/canonical day1完全一致とraw getter結合を確認。復元直後の1440でadvance_to(1440)を再度呼び、実結果と再決算0を採取する。 勤務分は全shiftのbefore/after、worked_minutesとovertime_minutes差分を保持する。 孤立予約0は対象serviceの予約と実予約行のrequester_id/purpose/CONSUMEDを照合し、ACTIVE facility_service予約は原requestのreserved_inventory_idsへ照合する。未知stateは失敗する。 sale/payroll/upkeep/dutyは全ledgerと正式snapshotに加えjoin行に保持する。停止条件はないため、1440の売上・給与・維持費ledgerを「停止中課金ledger0」と判定しない。「停止中課金0」は実停止条件を満たす別probeの受入条件として残る。 -出力は `BUILD_OUTPUT_DIR/p04-s2a-native-r01/`。rawの各観測はJSON(閲覧用)と.var(型保持)の対で保存する。FAILも原値のまま残す。 +出力は `BUILD_OUTPUT_DIR/p04-s2a-native-r02/`。rawの各観測はJSON(閲覧用)と.var(型保持)の対で保存する。FAILも原値のまま残す。 seed、入力、日、minute、実取引/service/request/予約ID、勤務差分、ledger全行、commands、全Runtime snapshot、operations/Idempotency、upkeep accrual/settlementを保持する。 Writer各branchのbefore/midnight/join/branch、C2 recall、Reader C3 branch/recall、phase receiptを保存する。 private profile内のWriter/Reader formal package(journal.log、manifest.json、thumbnail.png、world.bin)を保持する。 diff --git a/docs/plans/original-completion-2026-10-02/execution/p04-01/s2/p04_s2a_midnight_probe.gd b/docs/plans/original-completion-2026-10-02/execution/p04-01/s2/p04_s2a_midnight_probe.gd index 7545f0a..01801de 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p04-01/s2/p04_s2a_midnight_probe.gd +++ b/docs/plans/original-completion-2026-10-02/execution/p04-01/s2/p04_s2a_midnight_probe.gd @@ -42,7 +42,7 @@ func _writer_branch(branch: String) -> void: _expect(joined[&"sale_join_complete"], "sale transaction idempotency joins actual service and reservations") _expect(int(after[&"day_summary"].get(&"closed_at", -1)) == 1440, "same advance closes Day 1") _expect(joined[&"payroll_transaction_ids"].size() == 1 and joined[&"payroll_join_complete"], "actual payroll transaction joins paid Staff and day shifts") - _expect(not joined[&"upkeep_rows"].is_empty(), "actual upkeep observed") + _expect(joined[&"upkeep_transaction_ids"].size() == 1, "one distinct actual upkeep transaction") _expect(joined[&"orphans"] == 0, "reservation orphans zero") var row := {&"before": before, &"midnight": after, &"join": joined} if branch == "C2": row[&"recall"] = _recall() @@ -57,6 +57,16 @@ func _writer_branch(branch: String) -> void: _branches[branch] = row; _write_var("branch", row) func _capture() -> Dictionary: + var raw_day := _runtime.operations_coordinator.get_day_summary(1) + var canonical_day: Dictionary = {} + var day_count := 0 + for day: Dictionary in _runtime.operations_coordinator.create_snapshot()["day_summaries"]: + if int(day["day"]) == 1: + canonical_day = day + day_count += 1 + var day_differences: Array = [] + var day_join := _day_join(raw_day, canonical_day, "$", day_differences) + _expect(day_join and day_count == (0 if raw_day.is_empty() else 1), "raw getter joins every canonical day1 key and typed value") var ledger: Array = [] for entry: LedgerEntry in _runtime.economy_service.get_entries(): ledger.append(entry.to_dictionary()) var reservations: Array = [] @@ -78,12 +88,57 @@ func _capture() -> Dictionary: &"snapshot": _runtime.create_snapshot(), &"ledger": ledger, &"reservations": reservations, &"active_reservation_orphans": active_orphans, &"service": service.create_snapshot() if service != null else {}, &"staff": staff, &"payroll": staff[&"payroll_settlements"], &"shifts": staff[&"shifts"], - &"day_summary": _runtime.operations_coordinator.get_day_summary(1), + &"day_summary": raw_day, &"canonical_day1": canonical_day, + &"day_summary_diagnostic": {&"raw": _fingerprint(raw_day), &"canonical": _fingerprint(canonical_day), &"differences": day_differences, &"join_complete": day_join}, &"idempotency": {&"commands": _runtime.command_queue.create_snapshot(), &"economy": _runtime.economy_service.create_snapshot()[&"idempotency"]}, &"upkeep": {&"accrual": _runtime.facility_upkeep.create_snapshot(), &"settlement": _runtime.upkeep_settlement.create_snapshot()}, &"operations": _runtime.operations_coordinator.create_snapshot(), &"commands": _commands.duplicate(true), &"setup_observations": _observations.duplicate(true)} +func _fingerprint(value: Variant) -> Dictionary: + var result := {"typeof": typeof(value), "type": type_string(typeof(value))} + if value is Dictionary: + var entries: Array = [] + for key: Variant in value: + entries.append({"key": str(key), "key_typeof": typeof(key), "value": _fingerprint(value[key])}) + result["entries_in_order"] = entries + elif value is Array: + var children: Array = [] + for child: Variant in value: children.append(_fingerprint(child)) + result["elements"] = children + return result + +func _day_join(raw: Variant, saved: Variant, path: String, differences: Array) -> bool: + if typeof(raw) != typeof(saved): + var text_pair := typeof(raw) in [TYPE_STRING, TYPE_STRING_NAME] and typeof(saved) in [TYPE_STRING, TYPE_STRING_NAME] + differences.append({"path": path, "kind": "StringName_String" if text_pair else "type_mismatch", "raw_type": typeof(raw), "saved_type": typeof(saved)}) + return text_pair and String(raw) == String(saved) + if raw is Dictionary: + var matched: Dictionary = {} + var ok: bool = raw.size() == saved.size() + var raw_order: Array = [] + var saved_order: Array = [] + for key: Variant in saved: saved_order.append(String(key)) + for key: Variant in raw: + var name := String(key) + raw_order.append(name) + if matched.has(name) or not saved.has(name): + ok = false + continue + matched[name] = true + if typeof(key) != TYPE_STRING: + differences.append({"path": path + "/" + name, "kind": "key_StringName_String" if typeof(key) == TYPE_STRING_NAME else "invalid_key_type", "raw_type": typeof(key), "saved_type": TYPE_STRING}) + ok = typeof(key) == TYPE_STRING_NAME and ok + ok = _day_join(raw[key], saved[name], path + "/" + name, differences) and ok + if raw_order != saved_order: differences.append({"path": path, "kind": "key_order", "raw": raw_order, "saved": saved_order}) + return ok + if raw is Array: + if raw.size() != saved.size(): return false + var ok := true + for index: int in range(raw.size()): ok = _day_join(raw[index], saved[index], path + "/" + str(index), differences) and ok + return ok + return var_to_bytes(raw) == var_to_bytes(saved) + func _recall() -> Dictionary: var before := _capture() _expect(_runtime.operations_coordinator.current_minute == 1440, "recall starts at current minute 1440") @@ -103,6 +158,7 @@ func _join(before: Dictionary, after: Dictionary) -> Dictionary: var sales: Array = [] var upkeep: Array = [] var transactions: Dictionary = {} + var upkeep_transactions: Dictionary = {} var payroll_join: Array = [] var complete := true var sale_complete := true @@ -117,21 +173,40 @@ func _join(before: Dictionary, after: Dictionary) -> Dictionary: var payment_id: StringName = _runtime.economy_service.get_transaction_id_for_idempotency_key(StringName("service.payment.%s" % _service_id)) sale_complete = sale_complete and String(payment_id) == entry[&"transaction_id"] and int(entry[&"absolute_minute"]) == 1440 and int(entry[&"amount"]) == _original_price sales.append({&"ledger": entry, &"service_id": _service_id, &"reservation_ids": after[&"service"].get(&"inventory_reservations", [])}) - if entry[&"category"] == "MAINTENANCE_PAYMENT": upkeep.append(entry) + if entry[&"category"] == "MAINTENANCE_PAYMENT": + upkeep.append(entry) + upkeep_transactions[entry[&"transaction_id"]] = true if entry[&"category"] != "WAGE": continue var found := false for settlement: Dictionary in after[&"payroll"]: if settlement[&"settlement_id"] != entry[&"source_entity_id"]: continue found = true - transactions[entry[&"transaction_id"]] = true + var settlement_duty_complete := true + var paid_count := 0 for paid: Dictionary in settlement[&"rows"]: - if int(paid[&"daily_wage"]) <= 0: continue + if int(paid[&"payment"]) <= 0: continue + paid_count += 1 var shifts: Array = [] + var duty: Array = [] + var worked_delta := 0 + var overtime_delta := 0 # Settlement day is 1-based; ShiftInstance.start_day is 0-based. for shift: Dictionary in after[&"shifts"]: - if shift[&"staff_id"] == paid[&"staff_id"] and int(shift[&"start_day"]) + 1 == int(settlement[&"day"]): shifts.append(shift) - complete = complete and not shifts.is_empty() - payroll_join.append({&"transaction": entry, &"settlement": settlement, &"paid_staff": paid, &"day": settlement[&"day"], &"shifts": shifts, &"service_id": _service_id, &"sale_rows": sales, &"upkeep_rows": upkeep, &"duty_delta": shift_delta}) + if shift[&"staff_id"] != paid[&"staff_id"] or int(shift[&"start_day"]) + 1 != int(settlement[&"day"]): continue + if int(shift[&"worked_minutes"]) + int(shift[&"overtime_minutes"]) <= 0: continue + shifts.append(shift) + for delta: Dictionary in shift_delta: + if delta[&"after"][&"shift_instance_id"] != shift[&"shift_instance_id"]: continue + duty.append(delta) + worked_delta += int(delta[&"worked_minutes_delta"]) + overtime_delta += int(delta[&"overtime_minutes_delta"]) + var expected_delta := 1 if paid[&"staff_id"] == "staff_bardo" else 0 + if int(before[&"minute"]) == int(after[&"minute"]): expected_delta = 0 + var duty_ok := not shifts.is_empty() and duty.size() == shifts.size() and worked_delta == expected_delta and overtime_delta == 0 + settlement_duty_complete = settlement_duty_complete and duty_ok + payroll_join.append({&"transaction": entry, &"settlement": settlement, &"paid_staff": paid, &"day": settlement[&"day"], &"shifts": shifts, &"service_id": _service_id, &"sale_rows": sales, &"upkeep_rows": upkeep, &"duty_delta": duty, &"worked_delta": worked_delta, &"overtime_delta": overtime_delta, &"duty_join_complete": duty_ok}) + complete = complete and settlement_duty_complete and paid_count > 0 + if settlement_duty_complete and paid_count > 0: transactions[entry[&"transaction_id"]] = true complete = complete and found var orphans := 0 var service: Dictionary = after[&"service"] @@ -149,7 +224,7 @@ func _join(before: Dictionary, after: Dictionary) -> Dictionary: var request: ServiceRequest = _runtime.facility_service.get_request(StringName(reservation[&"requester_id"])) if request == null or not request.reserved_inventory_ids.has(StringName(reservation[&"reservation_id"])): orphans += 1 return {&"sales": sales, &"sale_join_complete": sale_complete and not sales.is_empty(), &"payroll_transaction_ids": transactions.keys(), &"payroll_join": payroll_join, - &"payroll_join_complete": complete and not payroll_join.is_empty(), &"upkeep_rows": upkeep, &"duty_delta": shift_delta, &"orphans": orphans} + &"payroll_join_complete": complete and not payroll_join.is_empty(), &"upkeep_rows": upkeep, &"upkeep_transaction_ids": upkeep_transactions.keys(), &"duty_delta": shift_delta, &"orphans": orphans} func _reader_branch() -> void: _case = "reader-C3" @@ -185,7 +260,12 @@ func _reader_branch() -> void: _original_price = int(expected[&"inputs"][&"original_price"]) var actual := _capture() _expect(var_to_bytes(actual[&"snapshot"]) == var_to_bytes(expected[&"snapshot"]), "formal entire typed snapshot equality") - for key: StringName in [&"ledger", &"payroll", &"day_summary"]: _expect(var_to_bytes(actual[key]) == var_to_bytes(expected[key]), "formal equality " + String(key)) + for key: StringName in [&"ledger", &"payroll", &"canonical_day1"]: _expect(var_to_bytes(actual[key]) == var_to_bytes(expected[key]), "formal equality " + String(key)) + var raw_equal := var_to_bytes(actual[&"day_summary"]) == var_to_bytes(expected[&"day_summary"]) + var raw_differences: Array = [] + var raw_join := _day_join(expected[&"day_summary"], actual[&"canonical_day1"], "$", raw_differences) + _expect(raw_join, "Writer raw getter joins Reader canonical day1") + _write_var("day-summary-comparison", {&"raw_bytes_equal": raw_equal, &"writer": expected[&"day_summary_diagnostic"], &"reader": actual[&"day_summary_diagnostic"], &"differences": raw_differences}) var recall := _recall() for member: String in members: hashes[member][&"original_after"] = FileAccess.get_sha256(original.path_join(member)) diff --git a/docs/plans/original-completion-2026-10-02/execution/p04-01/s2/run_s2a_native.py b/docs/plans/original-completion-2026-10-02/execution/p04-01/s2/run_s2a_native.py index 64f2991..1f2f682 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p04-01/s2/run_s2a_native.py +++ b/docs/plans/original-completion-2026-10-02/execution/p04-01/s2/run_s2a_native.py @@ -68,8 +68,8 @@ def pin(path): def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--project-root", type=Path, default=PRODUCT) - parser.add_argument("--output", type=Path, default=BUILD / "p04-s2a-native-r01") - parser.add_argument("--plan", type=Path, default=BUILD / "p04-s2a-plan-r01.json") + parser.add_argument("--output", type=Path, default=BUILD / "p04-s2a-native-r02") + parser.add_argument("--plan", type=Path, default=BUILD / "p04-s2a-plan-r02.json") parser.add_argument("--cache-run", type=Path, default=CACHE_RUN) parser.add_argument("--godot", type=Path, default=ENGINE) parser.add_argument("--check-only", action="store_true")