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

This commit is contained in:
Agent Control Runner
2026-10-16 03:46:10 +09:00
parent 3bba4f34f0
commit c0bc92a469
3 changed files with 133 additions and 16 deletions
@@ -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)を保持する。
@@ -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))
@@ -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")