Compare commits
90
Commits
e8b3dbe7a4
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fee479af83 | ||
|
|
50207e9e6e | ||
|
|
34b16de279 | ||
|
|
8a2ea74f44 | ||
|
|
4ca3bbef55 | ||
|
|
f5fe219674 | ||
|
|
a073ccb59b | ||
|
|
b06fec17c7 | ||
|
|
0b59d7c23c | ||
|
|
9b5b09b35e | ||
|
|
db87270972 | ||
|
|
f99c881bca | ||
|
|
c50136017b | ||
|
|
7ac2224e47 | ||
|
|
4654789b01 | ||
|
|
48d3b0023b | ||
|
|
cb183fd127 | ||
|
|
8954914564 | ||
|
|
d65e3da9f3 | ||
|
|
37deb6e877 | ||
|
|
7808ca76db | ||
|
|
613b9886da | ||
|
|
dda3eed47d | ||
|
|
a916841bfc | ||
|
|
44bce9177c | ||
|
|
4e34e52097 | ||
|
|
9adbbee334 | ||
|
|
a46418d064 | ||
|
|
b2dbda8c95 | ||
|
|
54e07ca980 | ||
|
|
6f75c9a051 | ||
|
|
ac1a115e3f | ||
|
|
e758d6873a | ||
|
|
f3840c9637 | ||
|
|
51d453040f | ||
|
|
f50e2752bc | ||
|
|
e29e743ab3 | ||
|
|
b0d2463a92 | ||
|
|
2549cf7d69 | ||
|
|
dfe07eba27 | ||
|
|
fdb309f3bc | ||
|
|
a25ee83441 | ||
|
|
d6ce6ddb95 | ||
|
|
ecbbb6bb16 | ||
|
|
89162734d4 | ||
|
|
8b5d0d46fa | ||
|
|
0fb102d497 | ||
|
|
8c06c3b442 | ||
|
|
413c8e3e51 | ||
|
|
ae9ae47646 | ||
|
|
ddde6f8ab5 | ||
|
|
0aee05e1d9 | ||
|
|
3ac2cedc8d | ||
|
|
3086af2ccd | ||
|
|
954528a7c5 | ||
|
|
464cc26fd3 | ||
|
|
e524ff2924 | ||
|
|
5e4d96d910 | ||
|
|
14764a2e0a | ||
|
|
ab4f40e71c | ||
|
|
bc703c9f6f | ||
|
|
f2a1249440 | ||
|
|
558fca6f8f | ||
|
|
2f77560013 | ||
|
|
971996f6d5 | ||
|
|
d908a2c4a2 | ||
|
|
bc582be820 | ||
|
|
815e86f589 | ||
|
|
71290054b7 | ||
|
|
131e9a599a | ||
|
|
2132bbe3d3 | ||
|
|
db9d6132a8 | ||
|
|
0f53a67210 | ||
|
|
5db97d78a1 | ||
|
|
25198e2924 | ||
|
|
061df6f120 | ||
|
|
c1d9ad51db | ||
|
|
90e3e312fe | ||
|
|
a09681e287 | ||
|
|
3ac3243dfb | ||
|
|
7bf44b072c | ||
|
|
11b7a24a52 | ||
|
|
990481709c | ||
|
|
86049b3819 | ||
|
|
8fe128c5a8 | ||
|
|
91676c6e16 | ||
|
|
9b4518cd48 | ||
|
|
8c36c100b2 | ||
|
|
b7f6168dc8 | ||
|
|
a5d2491204 |
+637
-21
@@ -15,6 +15,8 @@ const STARTER_MANA_OUTPUT := 100
|
||||
const STARTER_MANA_SOURCE := &"world_tree.mana_source"
|
||||
const STARTER_MANA_BUS := &"world_tree.mana_bus"
|
||||
const PLAYER_CASH_ACCOUNT := &"PLAYER_CASH"
|
||||
## The settlement.funds report definition (see _daily_fund_report).
|
||||
const FUND_REPORT_VERSION := 2
|
||||
const CONSTRUCTION_EXPENSE_ACCOUNT := &"CONSTRUCTION_EXPENSE"
|
||||
const OPERATING_EXPENSE_ACCOUNT := &"OPERATING_EXPENSE"
|
||||
const AUTHORITY_FINE_ACCOUNT := &"AUTHORITY_FINE_EXPENSE"
|
||||
@@ -35,6 +37,7 @@ const CleaningBridge = preload("res://scripts/environment/facility_cleaning_brid
|
||||
const CleaningPolicy = preload("res://scripts/environment/facility_cleaning_policy.gd")
|
||||
const UPKEEP_RUNTIME_SNAPSHOT_VERSION := 10
|
||||
const WeatherTimeline = preload("res://scripts/environment/weather_timeline.gd")
|
||||
const NaturalWeather = preload("res://scripts/environment/natural_weather_policy.gd")
|
||||
const STAIRS_RUNTIME_SNAPSHOT_VERSION := 9
|
||||
const TRANSPORT_RUNTIME_SNAPSHOT_VERSION := 8
|
||||
const SUPPLY_RUNTIME_SNAPSHOT_VERSION := 7
|
||||
@@ -83,6 +86,7 @@ const RNG_STREAM_IDS: Array[StringName] = [
|
||||
&"defense",
|
||||
&"event",
|
||||
&"loot",
|
||||
&"weather",
|
||||
]
|
||||
const DEFENSE_COMMAND_TYPES: Array[StringName] = [
|
||||
&"create_defense_raid",
|
||||
@@ -332,6 +336,7 @@ func initialize(clock: SimulationKernel, state: Variant, seed := DEFAULT_WORLD_S
|
||||
register_command_executor(&"decide_guest", Callable(self, "_execute_decide_guest"))
|
||||
register_command_executor(&"register_staff", Callable(self, "_execute_register_staff"))
|
||||
register_command_executor(&"hire_staff_candidate", Callable(self, "_execute_hire_staff_candidate"))
|
||||
register_command_executor(&"dismiss_staff", Callable(self, "_execute_dismiss_staff"))
|
||||
register_command_executor(&"create_staff_shift", Callable(self, "_execute_create_staff_shift"))
|
||||
register_command_executor(&"set_staff_schedule_override", Callable(self, "_execute_set_staff_schedule_override"))
|
||||
register_command_executor(&"set_staff_work_assignment", Callable(self, "_execute_set_staff_work_assignment"))
|
||||
@@ -461,6 +466,8 @@ func initialize(clock: SimulationKernel, state: Variant, seed := DEFAULT_WORLD_S
|
||||
if not report[&"valid"]:
|
||||
_clock.set_paused(true)
|
||||
definition_validation_failed.emit(report)
|
||||
# A13: a new game opening at 06:00 draws its first day once streams exist.
|
||||
_draw_natural_weather_if_due(_clock.absolute_minute)
|
||||
_publish_snapshot(_clock.absolute_minute)
|
||||
return report
|
||||
|
||||
@@ -909,6 +916,9 @@ func _restore_snapshot_internal(snapshot: Dictionary, recovery_mode: bool) -> bo
|
||||
return false
|
||||
if not FacilityUpkeepSettlement.prepare_accounts(restored_economy) or not restored_upkeep.start_at(restored_minute)[&"ok"]:
|
||||
return false
|
||||
# A12: a save made before the funds were split reads every Gold as front funds.
|
||||
if not FinancePolicy.prepare_accounts(restored_economy):
|
||||
return false
|
||||
var saved_resources: Variant = snapshot[&"game_state"].get(&"resources", null)
|
||||
if not saved_resources is Dictionary \
|
||||
or int(saved_resources.get(&"gold", saved_resources.get("gold", -1))) \
|
||||
@@ -1286,6 +1296,18 @@ func _restore_snapshot_internal(snapshot: Dictionary, recovery_mode: bool) -> bo
|
||||
return false
|
||||
elif runtime_data.has(&"weather") or not restored_weather.initialize(&"clear", restored_minute):
|
||||
return false
|
||||
# A13: the saved day's draw must be the draw rule's own result (SPEC section 5.3).
|
||||
if not NaturalWeather.is_consistent_draw(restored_weather.get_today_draw()):
|
||||
return false
|
||||
# A13 rain holds must still describe a present guest's extended plan.
|
||||
var rain_holds := restored_weather.get_rain_holds()
|
||||
for held_key: Variant in rain_holds:
|
||||
var held_guest := restored_guests.get_guest(StringName(held_key))
|
||||
if held_guest == null or held_guest.planned_exit_at != int(rain_holds[held_key]["extended_to"]):
|
||||
return false
|
||||
var saved_weather: Variant = runtime_data.get(&"weather", null)
|
||||
var weather_stream_required: bool = saved_weather is Dictionary \
|
||||
and int(saved_weather.get("version", 0)) >= WeatherTimeline.VERSION
|
||||
var restored_cleanliness := FacilityCleanliness.new()
|
||||
if runtime_schema_version >= CLEANING_RUNTIME_SNAPSHOT_VERSION:
|
||||
if not runtime_data.get(&"facility_cleanliness") is Dictionary or not restored_cleanliness.restore_snapshot(runtime_data[&"facility_cleanliness"]):
|
||||
@@ -1318,6 +1340,12 @@ func _restore_snapshot_internal(snapshot: Dictionary, recovery_mode: bool) -> bo
|
||||
if has_defense_snapshot:
|
||||
return false
|
||||
restored_streams[&"defense"] = DeterministicRng.from_root_seed(restored_world_seed, &"defense")
|
||||
# A13 migration: saves before natural weather start the weather stream from
|
||||
# the world seed, as defense did (SPEC section 5.2).
|
||||
if not restored_streams.has(&"weather"):
|
||||
if weather_stream_required:
|
||||
return false
|
||||
restored_streams[&"weather"] = DeterministicRng.from_root_seed(restored_world_seed, &"weather")
|
||||
if runtime_schema_version >= DEFENSE_RUNTIME_SNAPSHOT_VERSION:
|
||||
for required_stream_id: StringName in RNG_STREAM_IDS:
|
||||
if not restored_streams.has(required_stream_id):
|
||||
@@ -1935,11 +1963,14 @@ func _operations_closed_arrears_total() -> int:
|
||||
var last_close := 0
|
||||
if operations_coordinator != null and operations_coordinator.get_open_financial_day() > 1:
|
||||
last_close = operations_coordinator.get_daily_close_minute(operations_coordinator.get_open_financial_day() - 1)
|
||||
return _staff_payroll_arrears_total(staff_service) + upkeep_settlement.get_arrears_through(last_close)
|
||||
return _staff_payroll_arrears_total(staff_service) + upkeep_settlement.get_arrears_through(last_close) \
|
||||
+ _finance_arrears_through(economy_service, last_close)
|
||||
|
||||
|
||||
func _operations_total_arrears() -> int:
|
||||
return _staff_payroll_arrears_total(staff_service) + upkeep_settlement.get_arrears_total()
|
||||
return _staff_payroll_arrears_total(staff_service) + upkeep_settlement.get_arrears_total() \
|
||||
- economy_service.get_balance(FinancePolicy.RANK_TAX_PAYABLE) \
|
||||
- economy_service.get_balance(FinancePolicy.LOAN_INTEREST_PAYABLE)
|
||||
|
||||
|
||||
func _advance_operations_time(absolute_minute: int) -> void:
|
||||
@@ -2504,6 +2535,13 @@ func _operations_midnight_upkeep_boundary(payload: Dictionary, phase: StringName
|
||||
var prepared := _run_operations_test_boundary_hook(&"MIDNIGHT_UPKEEP", payload, OPERATIONS_TEST_PREPARE_PHASE, token)
|
||||
if not bool(prepared.get(&"ok", false)):
|
||||
return prepared
|
||||
var midnight_due := _upkeep_debt_through(int(minute))
|
||||
if midnight_due < 0:
|
||||
return {&"ok": false, &"reason": &"UPKEEP_DUE_PREVIEW_FAILED"}
|
||||
var midnight_top_up := top_up_front_fund(midnight_due, int(minute), StringName("finance.back_top_up.midnight.%d" % int(minute)))
|
||||
if not midnight_top_up.is_success() or (int(midnight_top_up.details.get(&"amount", 0)) > 0 \
|
||||
and not _stage_operations_economy_notification(midnight_top_up)):
|
||||
return {&"ok": false, &"reason": &"BACK_FUND_TOP_UP_FAILED"}
|
||||
var result := _settle_upkeep_through(int(minute))
|
||||
if not bool(result[&"ok"]):
|
||||
return result
|
||||
@@ -2524,10 +2562,29 @@ func _apply_operations_daily_close(payload: Dictionary, token: Variant) -> Dicti
|
||||
if not bool(prepare_hook.get(&"ok", false)):
|
||||
return prepare_hook
|
||||
var day := int(payload[&"day"])
|
||||
var boundary_minute := int(payload[&"boundary_minute"])
|
||||
var preview := staff_service.preview_daily_payroll(day)
|
||||
if not bool(preview.get(&"ok", false)) \
|
||||
or not preview.get(&"rows", null) is Array:
|
||||
return {&"ok": false, &"reason": &"PAYROLL_PREVIEW_FAILED"}
|
||||
# A09: the hidden fund first covers what the front fund cannot pay of the day's
|
||||
# wages, upkeep, rank tax and loan interest (arrears included).
|
||||
var finance_due := _daily_finance_due(economy_service, int(payload[&"period_start_minute"]), boundary_minute)
|
||||
if not bool(finance_due[&"ok"]):
|
||||
return finance_due
|
||||
var payroll_due := 0
|
||||
for raw_due: Variant in preview[&"rows"]:
|
||||
if raw_due is Dictionary and typeof((raw_due as Dictionary).get(&"total_due", null)) == TYPE_INT:
|
||||
payroll_due += maxi(int(raw_due[&"total_due"]), 0)
|
||||
var upkeep_due := _upkeep_debt_through(boundary_minute)
|
||||
if upkeep_due < 0:
|
||||
return {&"ok": false, &"reason": &"UPKEEP_DUE_PREVIEW_FAILED"}
|
||||
var top_up := top_up_front_fund(payroll_due + upkeep_due + int(finance_due[&"due_total"]), boundary_minute,
|
||||
StringName("finance.back_top_up.day.%08d" % day))
|
||||
if not top_up.is_success():
|
||||
return {&"ok": false, &"reason": &"BACK_FUND_TOP_UP_FAILED", &"result": top_up.to_dictionary()}
|
||||
if int(top_up.details.get(&"amount", 0)) > 0 and not _stage_operations_economy_notification(top_up):
|
||||
return {&"ok": false, &"reason": &"BACK_FUND_TOP_UP_NOTIFICATION_STAGE_FAILED"}
|
||||
var available_gold := economy_service.get_balance(PLAYER_CASH_ACCOUNT)
|
||||
var remaining_gold := available_gold
|
||||
var payments: Dictionary = {}
|
||||
@@ -2592,6 +2649,17 @@ func _apply_operations_daily_close(payload: Dictionary, token: Variant) -> Dicti
|
||||
if not bool(upkeep_result[&"ok"]):
|
||||
return upkeep_result
|
||||
var upkeep_paid := int(upkeep_result[&"paid_total"])
|
||||
var rank_tax := _post_daily_finance_charge(day, boundary_minute, int(finance_due[&"rank_tax"]),
|
||||
FinancePolicy.RANK_TAX_EXPENSE, FinancePolicy.RANK_TAX_PAYABLE,
|
||||
FinancePolicy.RANK_TAX_CATEGORY, FinancePolicy.RANK_TAX_PAYMENT_CATEGORY, "rank_tax")
|
||||
if not bool(rank_tax[&"ok"]):
|
||||
return rank_tax
|
||||
var loan_interest := _post_daily_finance_charge(day, boundary_minute, int(finance_due[&"loan_interest"]),
|
||||
FinancePolicy.LOAN_INTEREST_EXPENSE, FinancePolicy.LOAN_INTEREST_PAYABLE,
|
||||
FinancePolicy.LOAN_INTEREST_CATEGORY, FinancePolicy.LOAN_INTEREST_PAYMENT_CATEGORY, "loan_interest")
|
||||
if not bool(loan_interest[&"ok"]):
|
||||
return loan_interest
|
||||
var finance_paid := int(rank_tax[&"paid"]) + int(loan_interest[&"paid"])
|
||||
var financials := _build_operations_daily_financials(
|
||||
int(payload[&"period_start_minute"]),
|
||||
int(payload[&"period_end_minute_exclusive"]),
|
||||
@@ -2603,14 +2671,19 @@ func _apply_operations_daily_close(payload: Dictionary, token: Variant) -> Dicti
|
||||
var arrears_total := int(settlement.details.get(&"unpaid_after_total", -1))
|
||||
if arrears_total < 0 \
|
||||
or arrears_total != _staff_payroll_arrears_total(staff_service) \
|
||||
or economy_service.get_balance(PLAYER_CASH_ACCOUNT) != available_gold - expected_paid_total - upkeep_paid \
|
||||
or economy_service.get_balance(PLAYER_CASH_ACCOUNT) != available_gold - expected_paid_total - upkeep_paid - finance_paid \
|
||||
or int(_state.get_resource(&"gold")) != economy_service.get_balance(PLAYER_CASH_ACCOUNT) \
|
||||
or not bool(economy_service.validate_ledger().get(&"ok", false)) \
|
||||
or not bool(staff_service.validate_invariants().get(&"ok", false)):
|
||||
return {&"ok": false, &"reason": &"DAILY_CLOSE_POSTCONDITION_FAILED"}
|
||||
var funds := _daily_fund_report(economy_service, day, int(payload[&"period_start_minute"]), boundary_minute)
|
||||
if funds.is_empty():
|
||||
return {&"ok": false, &"reason": &"DAILY_FUND_REPORT_FAILED"}
|
||||
var settlement_report := settlement.details.duplicate(true)
|
||||
settlement_report[&"upkeep_arrears_total"] = upkeep_settlement.get_arrears_total()
|
||||
settlement_report[&"arrears_total"] = arrears_total + int(settlement_report[&"upkeep_arrears_total"])
|
||||
settlement_report[&"funds"] = funds
|
||||
settlement_report[&"arrears_total"] = arrears_total + int(settlement_report[&"upkeep_arrears_total"]) \
|
||||
+ int(funds[&"rank_tax_arrears"]) + int(funds[&"loan_interest_arrears"])
|
||||
return {
|
||||
&"ok": true,
|
||||
&"category_totals": financials[&"category_totals"],
|
||||
@@ -2647,6 +2720,8 @@ func _build_operations_daily_financials(
|
||||
&"EVENT_COST_RESERVED", &"EVENT_COST_RELEASED", &"INITIALIZATION",
|
||||
&"TRANSFER", &"GENERATION", &"DESTRUCTION", &"MIGRATION",
|
||||
&"SERVICE_CASH_SWEEP", &"WAGE", &"MAINTENANCE_PAYMENT",
|
||||
FinancePolicy.TOP_UP_CATEGORY, FinancePolicy.RANK_TAX_PAYMENT_CATEGORY,
|
||||
FinancePolicy.LOAN_INTEREST_PAYMENT_CATEGORY,
|
||||
]
|
||||
var category_mapping := {
|
||||
&"SERVICE_SALE": &"revenue",
|
||||
@@ -2656,10 +2731,13 @@ func _build_operations_daily_financials(
|
||||
&"AUTHORITY_FINE": &"fines",
|
||||
&"OPERATING_EXPENSE": &"cost_of_goods",
|
||||
&"STAFF_SIGNING_COST": &"cost_of_goods",
|
||||
FinancePolicy.SEVERANCE_CATEGORY: &"cost_of_goods",
|
||||
&"EVENT_ACTION": &"cost_of_goods",
|
||||
&"EVENT_COST_COMMITTED": &"cost_of_goods",
|
||||
&"DEFENSE_SKILL_COST": &"cost_of_goods",
|
||||
&"MAINTENANCE_ACCRUAL": &"maintenance",
|
||||
FinancePolicy.RANK_TAX_CATEGORY: &"taxes_and_interest",
|
||||
FinancePolicy.LOAN_INTEREST_CATEGORY: &"taxes_and_interest",
|
||||
&"LOAN_PRINCIPAL_BORROWED": &"financing_principal_inflows",
|
||||
&"LOAN_PRINCIPAL_REPAID": &"financing_principal_outflows",
|
||||
}
|
||||
@@ -2897,6 +2975,8 @@ func _operations_arrival_boundary(
|
||||
&"ok": not captured.is_empty(),
|
||||
&"token": captured,
|
||||
&"capacity_remaining": maxi(population_cap - guest_service.guest_count(), 0) if int(_state.get_resource(&"mana")) > 0 and weather.permits_new_arrivals() else 0,
|
||||
# A13: rain halves the planned new arrivals of this boundary.
|
||||
&"arrival_percent": NaturalWeather.arrival_percent(weather.get_kind()),
|
||||
}
|
||||
if phase == OperationsCoordinator.PHASE_VALIDATE:
|
||||
return {
|
||||
@@ -3332,11 +3412,15 @@ func _parse_operations_runtime_snapshot(
|
||||
var terminal_shape := deferred_runtime_minute == -1 \
|
||||
and not failure_latched and restored_coordinator.is_terminal() \
|
||||
and coordinator_current_matches and bool(restored_clock_paused)
|
||||
# The closed arrears are counted as the live check counts them
|
||||
# (_operations_closed_arrears_total): wages, upkeep and the unpaid rank tax and interest.
|
||||
var last_close := restored_coordinator.get_daily_close_minute(restored_coordinator.get_open_financial_day() - 1)
|
||||
if not bool(restored_coordinator.validate_invariants().get(&"ok", false)) \
|
||||
or restored_coordinator.get_origin_minute() != OPERATIONS_COORDINATOR_ORIGIN_MINUTE \
|
||||
or not (normal_shape or failure_shape or pause_shape or terminal_shape) \
|
||||
or restored_coordinator.get_population_count() != restored_guests.guest_count() \
|
||||
or restored_coordinator.get_arrears_total() != (_staff_payroll_arrears_total(restored_staff) + restored_upkeep_settlement.get_arrears_through(restored_coordinator.get_daily_close_minute(restored_coordinator.get_open_financial_day() - 1))) \
|
||||
or restored_coordinator.get_arrears_total() != (_staff_payroll_arrears_total(restored_staff) + restored_upkeep_settlement.get_arrears_through(last_close)
|
||||
+ _finance_arrears_through(restored_economy, last_close)) \
|
||||
or not _operations_payroll_crosslinks_are_valid(
|
||||
restored_coordinator, restored_staff, restored_economy, restored_upkeep_settlement
|
||||
) \
|
||||
@@ -3551,8 +3635,32 @@ func _operations_payroll_crosslinks_are_valid(
|
||||
summary_settlement.erase(&"upkeep_arrears_total")
|
||||
elif upkeep_debt != 0:
|
||||
return false
|
||||
var finance_arrears := 0
|
||||
# A saved report is checked by the definition it was written with: without
|
||||
# the mark it is version 1; any other mark is not a report this game wrote.
|
||||
var fund_version := FUND_REPORT_VERSION
|
||||
if summary_settlement.get(&"funds", null) is Dictionary:
|
||||
var saved_funds: Dictionary = summary_settlement[&"funds"]
|
||||
var mark: Variant = saved_funds.get(&"fund_report_version", saved_funds.get("fund_report_version", null))
|
||||
if mark == null:
|
||||
fund_version = 1
|
||||
elif typeof(mark) != TYPE_INT or int(mark) != FUND_REPORT_VERSION:
|
||||
return false
|
||||
var expected_funds := _daily_fund_report(economy, day, coordinator.get_financial_period_start(day),
|
||||
coordinator.get_daily_close_minute(day), fund_version)
|
||||
if expected_funds.is_empty():
|
||||
return false
|
||||
if summary_settlement.has(&"funds"):
|
||||
if not _strict_variants_equal(_operations_snapshot_comparison_value(summary_settlement[&"funds"]),
|
||||
_operations_snapshot_comparison_value(expected_funds)):
|
||||
return false
|
||||
finance_arrears = int(expected_funds[&"rank_tax_arrears"]) + int(expected_funds[&"loan_interest_arrears"])
|
||||
summary_settlement.erase(&"funds")
|
||||
elif bool(expected_funds[&"has_finance_postings"]):
|
||||
# Days closed before the funds were split carry no fund report and no postings.
|
||||
return false
|
||||
if int(summary_settlement.get(&"arrears_total", -1)) \
|
||||
!= int(staff_record.get(&"unpaid_after_total", -2)) + upkeep_debt \
|
||||
!= int(staff_record.get(&"unpaid_after_total", -2)) + upkeep_debt + finance_arrears \
|
||||
or summary_settlement.get(&"replayed", null) != false:
|
||||
return false
|
||||
summary_settlement.erase(&"arrears_total")
|
||||
@@ -3599,7 +3707,10 @@ func get_loan_principal_report() -> Dictionary:
|
||||
loans.sort_custom(func(left: Dictionary, right: Dictionary) -> bool: return String(left[&"loan_id"]) < String(right[&"loan_id"]))
|
||||
var valid: bool = _state.get_resource(&"gold") == cash and (loans.is_empty() or _loan_principal_ledger_is_valid(economy_service))
|
||||
var ended: bool = _operations_enabled and operations_coordinator != null and operations_coordinator.is_terminal()
|
||||
return {&"available": true, &"ledger_valid": valid, &"terminal": ended, &"cash": cash, &"next_command_id": _next_command_id, &"loans": loans}
|
||||
# A09: a repayment may use the hidden fund for what the front fund (cash) cannot pay.
|
||||
return {&"available": true, &"ledger_valid": valid, &"terminal": ended, &"cash": cash,
|
||||
&"back_cash": economy_service.get_balance(FinancePolicy.BACK_CASH), &"available_funds": FinancePolicy.available_funds(economy_service),
|
||||
&"next_command_id": _next_command_id, &"loans": loans}
|
||||
|
||||
|
||||
## Atomic principal payment against an already attested loan liability.
|
||||
@@ -3626,15 +3737,20 @@ func repay_loan_principal(
|
||||
return CommandResult.failed(CommandResult.Code.NOT_FOUND, 0, "The loan liability does not exist.")
|
||||
var remaining := -economy_service.get_balance(liability)
|
||||
var cash := economy_service.get_balance(PLAYER_CASH_ACCOUNT)
|
||||
if amount > remaining or amount > cash or _state.get_resource(&"gold") != cash:
|
||||
if amount > remaining or amount > FinancePolicy.available_funds(economy_service) or _state.get_resource(&"gold") != cash:
|
||||
return CommandResult.failed(CommandResult.Code.VALIDATION_FAILED, 0, "Principal payment exceeds the outstanding principal or available Gold, or its mirror is inconsistent.")
|
||||
# The single balanced posting is atomic. Defer receipts until the Gold
|
||||
# mirror is synchronized, also respecting any enclosing effect scope.
|
||||
# A09: what the front fund cannot pay comes from the hidden fund in the same
|
||||
# balanced posting, so the payment and its top-up are atomic. Defer receipts
|
||||
# until the Gold mirror is synchronized, also respecting any enclosing effect scope.
|
||||
var minute := _runtime_event_minute()
|
||||
var entries: Array = []
|
||||
if amount > cash:
|
||||
entries.append(LedgerEntry.new().configure(&"", minute, PLAYER_CASH_ACCOUNT, FinancePolicy.BACK_CASH,
|
||||
amount - cash, FinancePolicy.TOP_UP_CATEGORY, loan_id, &"", &"finance.back_fund.top_up"))
|
||||
entries.append(LedgerEntry.new().configure(&"", minute, liability, PLAYER_CASH_ACCOUNT,
|
||||
amount, &"LOAN_PRINCIPAL_REPAID", loan_id))
|
||||
economy_service.begin_notification_scope()
|
||||
var result := economy_service.transfer(
|
||||
PLAYER_CASH_ACCOUNT, liability, amount, _runtime_event_minute(),
|
||||
&"LOAN_PRINCIPAL_REPAID", loan_id, idempotency_key
|
||||
)
|
||||
var result := economy_service.post(entries, idempotency_key)
|
||||
if result.is_success():
|
||||
_state.set_resource(&"gold", economy_service.get_balance(PLAYER_CASH_ACCOUNT))
|
||||
result.details[&"loan_id"] = loan_id
|
||||
@@ -3644,6 +3760,144 @@ func repay_loan_principal(
|
||||
return result
|
||||
|
||||
|
||||
## A09: what the front fund cannot pay is covered from the hidden fund first. The move
|
||||
## is posted on its own (BACK_FUND_TOP_UP) and is neither income nor expense.
|
||||
func top_up_front_fund(due: int, minute: int, idempotency_key: StringName) -> CommandResult:
|
||||
var front := economy_service.get_balance(PLAYER_CASH_ACCOUNT)
|
||||
var amount := mini(maxi(due - front, 0), economy_service.get_balance(FinancePolicy.BACK_CASH))
|
||||
if amount <= 0:
|
||||
return CommandResult.succeeded(0, {&"amount": 0})
|
||||
var result := economy_service.transfer(FinancePolicy.BACK_CASH, PLAYER_CASH_ACCOUNT, amount, minute,
|
||||
FinancePolicy.TOP_UP_CATEGORY, &"", idempotency_key, &"finance.back_fund.top_up", &"", false)
|
||||
# Callers publish the notification, which also refreshes the Gold projection.
|
||||
if result.is_success():
|
||||
result.details[&"amount"] = amount
|
||||
return result
|
||||
|
||||
|
||||
## Whole Gold the closed upkeep periods through the minute would owe, arrears
|
||||
## included; -1 if a period cannot be planned. Nothing is changed.
|
||||
func _upkeep_debt_through(minute: int) -> int:
|
||||
var periods: Array = []
|
||||
for period: Dictionary in facility_upkeep.get_closed_periods_from(upkeep_settlement.get_settled_period_count()):
|
||||
if int(period[&"period_end"]) <= minute:
|
||||
periods.append(period)
|
||||
return upkeep_settlement.preview_debt(periods)
|
||||
|
||||
|
||||
## A10/A11: the closed day's rank tax and loan interest, with their older arrears.
|
||||
func _daily_finance_due(economy: EconomyService, period_start: int, boundary_minute: int) -> Dictionary:
|
||||
var totals: Variant = _operations_expected_category_totals(economy, period_start, boundary_minute, 0)
|
||||
if totals == null:
|
||||
return {&"ok": false, &"reason": &"DAILY_SALES_UNAVAILABLE"}
|
||||
var tax := FinancePolicy.rank_tax(int(totals[&"revenue"]))
|
||||
var interest := FinancePolicy.loan_interest(FinancePolicy.outstanding_principal(economy))
|
||||
return {&"ok": true, &"rank_tax": tax, &"loan_interest": interest,
|
||||
&"due_total": tax + interest - economy.get_balance(FinancePolicy.RANK_TAX_PAYABLE)
|
||||
- economy.get_balance(FinancePolicy.LOAN_INTEREST_PAYABLE)}
|
||||
|
||||
|
||||
## Accrues a daily charge at the end of the closed day and pays its older arrears
|
||||
## first, then the new amount, from the front fund; the rest stays payable.
|
||||
func _post_daily_finance_charge(day: int, boundary_minute: int, new_amount: int, expense: StringName,
|
||||
payable: StringName, category: StringName, payment_category: StringName, kind: String) -> Dictionary:
|
||||
var due := new_amount - economy_service.get_balance(payable)
|
||||
var paid := mini(due, economy_service.get_balance(PLAYER_CASH_ACCOUNT))
|
||||
var source := StringName("finance-day-%08d" % day)
|
||||
var entries: Array = []
|
||||
if new_amount > 0:
|
||||
entries.append(LedgerEntry.new().configure(&"", boundary_minute - 1, expense, payable, new_amount,
|
||||
category, source, &"", StringName("finance.%s.accrued" % kind)))
|
||||
if paid > 0:
|
||||
entries.append(LedgerEntry.new().configure(&"", boundary_minute, payable, PLAYER_CASH_ACCOUNT, paid,
|
||||
payment_category, source, &"", StringName("finance.%s.paid" % kind)))
|
||||
if not entries.is_empty():
|
||||
var posted := economy_service.post(entries, StringName("finance.%s.day.%08d" % [kind, day]), &"", false)
|
||||
if not posted.is_success():
|
||||
return {&"ok": false, &"reason": &"FINANCE_LEDGER_POST_FAILED", &"result": posted.to_dictionary()}
|
||||
if not _stage_operations_economy_notification(posted):
|
||||
return {&"ok": false, &"reason": &"FINANCE_NOTIFICATION_STAGE_FAILED"}
|
||||
return {&"ok": true, &"new": new_amount, &"paid": paid, &"arrears": due - paid}
|
||||
|
||||
|
||||
## The fund side of a closed day, derived only from the ledger, so the live close
|
||||
## and a restored save produce the same report. Version 2 (marked in the report):
|
||||
## back_top_up is every top-up of the business day, the operation-time purchases
|
||||
## and repayments and the legacy 00:00 upkeep as well as the close's own. A report
|
||||
## without the mark is version 1, written while back_top_up counted only the
|
||||
## close's own top-up; a restore checks it by that definition, not a looser one.
|
||||
func _daily_fund_report(economy: EconomyService, day: int, period_start: int, boundary_minute: int,
|
||||
version := FUND_REPORT_VERSION) -> Dictionary:
|
||||
if economy == null or day <= 0 or period_start < 0 or boundary_minute <= period_start \
|
||||
or version not in [1, FUND_REPORT_VERSION]:
|
||||
return {}
|
||||
var report := {
|
||||
&"revenue_front": 0, &"revenue_back": 0, &"back_top_up": 0,
|
||||
&"rank_tax": 0, &"rank_tax_paid": 0, &"loan_interest": 0, &"loan_interest_paid": 0,
|
||||
}
|
||||
for entry: LedgerEntry in economy.get_entries():
|
||||
if entry.absolute_minute >= period_start and entry.absolute_minute < boundary_minute \
|
||||
and entry.category == &"SERVICE_CASH_SWEEP":
|
||||
if entry.debit_account == FinancePolicy.BACK_CASH:
|
||||
report[&"revenue_back"] = int(report[&"revenue_back"]) + entry.amount
|
||||
elif entry.debit_account == PLAYER_CASH_ACCOUNT:
|
||||
report[&"revenue_front"] = int(report[&"revenue_front"]) + entry.amount
|
||||
var keyed := {
|
||||
&"back_top_up": StringName("finance.back_top_up.day.%08d" % day),
|
||||
&"rank_tax": StringName("finance.rank_tax.day.%08d" % day),
|
||||
&"loan_interest": StringName("finance.loan_interest.day.%08d" % day),
|
||||
}
|
||||
var postings := false
|
||||
for field: StringName in keyed:
|
||||
var transaction := economy.get_transaction_id_for_idempotency_key(keyed[field])
|
||||
if transaction.is_empty():
|
||||
continue
|
||||
postings = true
|
||||
for entry: LedgerEntry in economy.get_transaction_entries(transaction):
|
||||
if field == &"back_top_up" and entry.category == FinancePolicy.TOP_UP_CATEGORY:
|
||||
report[field] = int(report[field]) + entry.amount
|
||||
elif field != &"back_top_up" and entry.category in [FinancePolicy.RANK_TAX_CATEGORY, FinancePolicy.LOAN_INTEREST_CATEGORY]:
|
||||
report[field] = int(report[field]) + entry.amount
|
||||
elif field != &"back_top_up" and entry.category in [FinancePolicy.RANK_TAX_PAYMENT_CATEGORY, FinancePolicy.LOAN_INTEREST_PAYMENT_CATEGORY]:
|
||||
report[StringName(String(field) + "_paid")] = int(report[StringName(String(field) + "_paid")]) + entry.amount
|
||||
else:
|
||||
return {}
|
||||
if version == FUND_REPORT_VERSION:
|
||||
# The closes are keyed; the previous day's close is posted at period_start.
|
||||
var closes := {}
|
||||
for close_day: int in [day - 1, day]:
|
||||
var close := economy.get_transaction_id_for_idempotency_key(StringName("finance.back_top_up.day.%08d" % close_day))
|
||||
if not close.is_empty():
|
||||
closes[close] = true
|
||||
for entry: LedgerEntry in economy.get_entries():
|
||||
if entry.category == FinancePolicy.TOP_UP_CATEGORY and entry.absolute_minute >= period_start \
|
||||
and entry.absolute_minute < boundary_minute and not closes.has(entry.transaction_id):
|
||||
report[&"back_top_up"] = int(report[&"back_top_up"]) + entry.amount
|
||||
postings = true
|
||||
report[&"fund_report_version"] = FUND_REPORT_VERSION
|
||||
report[&"rank_tax_arrears"] = _payable_arrears_through(economy, FinancePolicy.RANK_TAX_PAYABLE, boundary_minute)
|
||||
report[&"loan_interest_arrears"] = _payable_arrears_through(economy, FinancePolicy.LOAN_INTEREST_PAYABLE, boundary_minute)
|
||||
report[&"has_finance_postings"] = postings
|
||||
return report
|
||||
|
||||
|
||||
func _payable_arrears_through(economy: EconomyService, payable: StringName, minute: int) -> int:
|
||||
var arrears := 0
|
||||
for entry: LedgerEntry in economy.get_entries():
|
||||
if entry.absolute_minute > minute:
|
||||
continue
|
||||
if entry.credit_account == payable:
|
||||
arrears += entry.amount
|
||||
elif entry.debit_account == payable:
|
||||
arrears -= entry.amount
|
||||
return arrears
|
||||
|
||||
|
||||
func _finance_arrears_through(economy: EconomyService, minute: int) -> int:
|
||||
return _payable_arrears_through(economy, FinancePolicy.RANK_TAX_PAYABLE, minute) \
|
||||
+ _payable_arrears_through(economy, FinancePolicy.LOAN_INTEREST_PAYABLE, minute)
|
||||
|
||||
|
||||
func _loan_principal_ledger_is_valid(economy: EconomyService) -> bool:
|
||||
if economy == null:
|
||||
return false
|
||||
@@ -3697,6 +3951,8 @@ func _operations_expected_category_totals(
|
||||
&"EVENT_COST_RESERVED", &"EVENT_COST_RELEASED", &"INITIALIZATION",
|
||||
&"TRANSFER", &"GENERATION", &"DESTRUCTION", &"MIGRATION",
|
||||
&"SERVICE_CASH_SWEEP", &"WAGE", &"MAINTENANCE_PAYMENT",
|
||||
FinancePolicy.TOP_UP_CATEGORY, FinancePolicy.RANK_TAX_PAYMENT_CATEGORY,
|
||||
FinancePolicy.LOAN_INTEREST_PAYMENT_CATEGORY,
|
||||
]
|
||||
var mapping := {
|
||||
&"SERVICE_SALE": &"revenue", &"REFUND": &"refunds",
|
||||
@@ -3704,9 +3960,12 @@ func _operations_expected_category_totals(
|
||||
&"CONSTRUCTION_REFUND": &"construction_cash_flow",
|
||||
&"AUTHORITY_FINE": &"fines", &"OPERATING_EXPENSE": &"cost_of_goods",
|
||||
&"STAFF_SIGNING_COST": &"cost_of_goods", &"EVENT_ACTION": &"cost_of_goods",
|
||||
FinancePolicy.SEVERANCE_CATEGORY: &"cost_of_goods",
|
||||
&"EVENT_COST_COMMITTED": &"cost_of_goods",
|
||||
&"DEFENSE_SKILL_COST": &"cost_of_goods",
|
||||
&"MAINTENANCE_ACCRUAL": &"maintenance",
|
||||
FinancePolicy.RANK_TAX_CATEGORY: &"taxes_and_interest",
|
||||
FinancePolicy.LOAN_INTEREST_CATEGORY: &"taxes_and_interest",
|
||||
&"LOAN_PRINCIPAL_BORROWED": &"financing_principal_inflows",
|
||||
&"LOAN_PRINCIPAL_REPAID": &"financing_principal_outflows",
|
||||
}
|
||||
@@ -5254,6 +5513,7 @@ func _initialize_economy() -> void:
|
||||
facility_upkeep = FacilityUpkeepAccrual.new()
|
||||
upkeep_settlement = FacilityUpkeepSettlement.new()
|
||||
FacilityUpkeepSettlement.prepare_accounts(economy_service)
|
||||
FinancePolicy.prepare_accounts(economy_service)
|
||||
facility_upkeep.start_at(_clock.absolute_minute)
|
||||
mana_reserve = ManaReserveCycle.new()
|
||||
mana_reserve.start_at(_clock.absolute_minute)
|
||||
@@ -6672,6 +6932,285 @@ func _rollback_staff_candidate_hire(snapshot: Dictionary) -> bool:
|
||||
_runtime_event_minute()
|
||||
)
|
||||
|
||||
|
||||
## P16-01-DISMISS: whether a hired employee can be dismissed now, with the severance
|
||||
## (A14), the Task the removal reopens and the rescue Tasks the command interrupts.
|
||||
## Nothing is changed.
|
||||
func get_staff_dismissal_preview(staff_id: StringName) -> Dictionary:
|
||||
var staff: StaffRuntime = staff_service.get_staff(staff_id) if staff_service != null else null
|
||||
var preview := {
|
||||
&"staff_id": staff_id, &"can_dismiss": false, &"reason": &"NOT_FOUND",
|
||||
&"severance": 0, &"unpaid_wages": 0, &"reopened_task_ids": [], &"reopened_task_types": [],
|
||||
&"interrupted_rescue_task_ids": [], &"interrupted_rescue_task_types": [], &"interrupted_rescue_roles": [],
|
||||
&"available_funds": FinancePolicy.available_funds(economy_service) if economy_service != null else 0,
|
||||
&"front_cash": economy_service.get_balance(PLAYER_CASH_ACCOUNT) if economy_service != null else 0,
|
||||
}
|
||||
if staff == null:
|
||||
return preview
|
||||
var severance := FinancePolicy.severance(staff.get_effective_daily_wage())
|
||||
preview[&"severance"] = severance
|
||||
preview[&"unpaid_wages"] = staff.unpaid_wages
|
||||
var work := _staff_dismissal_work(staff)
|
||||
for kind: StringName in [&"reopened", &"interrupted_rescue"]:
|
||||
for task: StaffTask in work[kind]:
|
||||
(preview[StringName(String(kind) + "_task_ids")] as Array).append(task.task_id)
|
||||
(preview[StringName(String(kind) + "_task_types")] as Array).append(task.task_type)
|
||||
for task: StaffTask in work[&"interrupted_rescue"]:
|
||||
(preview[&"interrupted_rescue_roles"] as Array).append(&"carrier" if task.task_id == staff.current_task_id else &"patient")
|
||||
# R5: after the game ends the command is refused before acceptance (submit_command).
|
||||
var ended := _operations_enabled and operations_coordinator != null and operations_coordinator.is_terminal()
|
||||
var reason: StringName = &"BANKRUPT" if ended else _staff_dismissal_block_reason(staff, severance)
|
||||
preview[&"reason"] = reason
|
||||
preview[&"can_dismiss"] = reason.is_empty()
|
||||
return preview
|
||||
|
||||
|
||||
## The work a dismissal changes: the held Task the removal contract reopens, and the
|
||||
## rescue Tasks the command interrupts (the held transport and every rescue of the
|
||||
## employee as the patient; SPEC 6.2.1).
|
||||
func _staff_dismissal_work(staff: StaffRuntime) -> Dictionary:
|
||||
var reopened: Array[StaffTask] = []
|
||||
var interrupted: Array[StaffTask] = []
|
||||
var held := staff_service.get_task(staff.current_task_id)
|
||||
if held != null and not held.is_terminal():
|
||||
if held.task_type == &"rescue_transport":
|
||||
interrupted.append(held)
|
||||
else:
|
||||
reopened.append(held)
|
||||
for task: StaffTask in staff_service.get_tasks():
|
||||
if task.task_type in [&"rescue_pickup", &"rescue_transport"] and not task.is_terminal() \
|
||||
and task.target_entity_id == staff.staff_id and not interrupted.has(task):
|
||||
interrupted.append(task)
|
||||
return {&"reopened": reopened, &"interrupted_rescue": interrupted}
|
||||
|
||||
|
||||
## Every refusal of SPEC 6.3 the executor decides (R1, R2, R3, R7, R8, R9).
|
||||
func _staff_dismissal_block_reason(staff: StaffRuntime, severance: int) -> StringName:
|
||||
if staff.employment_kind == StaffRuntime.EmploymentKind.UNIQUE:
|
||||
return &"UNIQUE"
|
||||
if staff.employment_kind != StaffRuntime.EmploymentKind.FREE_HIRE:
|
||||
return &"NOT_GENERAL_EMPLOYEE"
|
||||
if staff_service.is_externally_driven_task(staff.current_task_id):
|
||||
return &"EXTERNAL_TASK"
|
||||
if not _staff_task_reservations_releasable(staff):
|
||||
return &"RESERVATION_RELEASE_FAILED"
|
||||
# Root's temporary boundary R9: an unfinished raid's defender stays until the raid ends.
|
||||
if not _defense_lock_owner(staff.staff_id, &"DEFENDER").is_empty():
|
||||
return &"DEFENSE_LOCKED"
|
||||
if severance > FinancePolicy.available_funds(economy_service):
|
||||
return &"INSUFFICIENT_FUNDS"
|
||||
return &""
|
||||
|
||||
|
||||
## R3 decided before any change: the removal contract releases every Reservation of the
|
||||
## held Task, and InventoryService.release_reservation fails for a missing or a
|
||||
## consumed Reservation (a released or expired one is a replayed success).
|
||||
func _staff_task_reservations_releasable(staff: StaffRuntime) -> bool:
|
||||
var task := staff_service.get_task(staff.current_task_id)
|
||||
if task == null:
|
||||
return true
|
||||
for reservation_id: StringName in task.reservation_ids:
|
||||
var reservation := inventory_service.get_reservation(reservation_id)
|
||||
if reservation == null or reservation.state == InventoryReservation.State.CONSUMED:
|
||||
return false
|
||||
return true
|
||||
|
||||
|
||||
## Dismissal of a general employee (FREE_HIRE). All refusals are decided before any
|
||||
## change (SPEC 4.1 c). The employee's seat in a boarded rescue pair, the existing
|
||||
## removal contract, the severance paid from the front fund with the hidden fund
|
||||
## covering the rest (one posting, A14) and the cleanup of SPEC 6.2 then commit as one
|
||||
## operation and are rolled back together on a failure.
|
||||
func _execute_dismiss_staff(command: SimulationCommand) -> CommandResult:
|
||||
var raw_staff_id: Variant = command.payload.get(&"staff_id", command.payload.get("staff_id", null))
|
||||
if typeof(raw_staff_id) not in [TYPE_STRING, TYPE_STRING_NAME] or String(raw_staff_id).is_empty():
|
||||
return CommandResult.failed(
|
||||
CommandResult.Code.INVALID_PAYLOAD, command.command_id, "A Staff id is required."
|
||||
)
|
||||
var staff_id := StringName(raw_staff_id)
|
||||
var staff := staff_service.get_staff(staff_id)
|
||||
if staff == null:
|
||||
return CommandResult.failed(
|
||||
CommandResult.Code.NOT_FOUND, command.command_id,
|
||||
"Staff was not found: %s" % staff_id, {&"reason": &"NOT_FOUND", &"staff_id": staff_id}
|
||||
)
|
||||
var severance := FinancePolicy.severance(staff.get_effective_daily_wage())
|
||||
var reason := _staff_dismissal_block_reason(staff, severance)
|
||||
if not reason.is_empty():
|
||||
# R1, R2 and R3 keep the existing removal contract's code and text.
|
||||
var refusals := {
|
||||
&"UNIQUE": [CommandResult.Code.TRANSACTION_APPLY_FAILED, "Unique Staff cannot be dismissed, sold or permanently removed."],
|
||||
&"EXTERNAL_TASK": [CommandResult.Code.TRANSACTION_APPLY_FAILED, "Finish the employee external operation before removal."],
|
||||
&"RESERVATION_RELEASE_FAILED": [CommandResult.Code.TRANSACTION_APPLY_FAILED, "Staff removal could not release Task Reservations."],
|
||||
&"NOT_GENERAL_EMPLOYEE": [CommandResult.Code.VALIDATION_FAILED, "Only a general employee can be dismissed."],
|
||||
&"DEFENSE_LOCKED": [CommandResult.Code.VALIDATION_FAILED, "Staff is defending an unfinished raid."],
|
||||
&"INSUFFICIENT_FUNDS": [CommandResult.Code.VALIDATION_FAILED, "Front and hidden funds cannot pay the severance."],
|
||||
}
|
||||
return CommandResult.failed(refusals[reason][0], command.command_id, refusals[reason][1], {
|
||||
&"reason": reason, &"staff_id": staff_id, &"severance": severance,
|
||||
&"available_funds": FinancePolicy.available_funds(economy_service),
|
||||
})
|
||||
var minute := _runtime_event_minute()
|
||||
var before := {
|
||||
&"economy": economy_service.create_snapshot(),
|
||||
&"game_state": _state.create_snapshot(),
|
||||
&"staff": staff_service.create_snapshot(),
|
||||
&"inventory": inventory_service.create_snapshot(),
|
||||
&"path": path_service.create_snapshot(),
|
||||
&"elevators": elevators.create_snapshot(),
|
||||
&"movement_credits": _movement_credits.duplicate(true),
|
||||
&"domain_events": domain_events.create_snapshot(),
|
||||
&"id_generator": id_generator.create_snapshot(),
|
||||
}
|
||||
var held_task_id := staff.current_task_id
|
||||
var reopened: Array = _staff_dismissal_work(staff)[&"reopened"]
|
||||
if not _release_dismissed_rescue_seat(staff_id):
|
||||
_rollback_staff_dismissal(before)
|
||||
return CommandResult.failed(
|
||||
CommandResult.Code.TRANSACTION_APPLY_FAILED, command.command_id,
|
||||
"Staff dismissal could not release the employee's seat in a boarded rescue pair.",
|
||||
{&"reason": &"RESCUE_SEAT_RELEASE_FAILED", &"staff_id": staff_id, &"error": elevators.last_error}
|
||||
)
|
||||
# The existing contract still refuses an unreleasable Task Reservation (R3, decided
|
||||
# above) as the last guard; nothing it would change survives the rollback.
|
||||
var removal := staff_service.remove_staff(staff_id, &"DISMISSED", true, minute)
|
||||
if not removal.is_success():
|
||||
_rollback_staff_dismissal(before)
|
||||
removal.command_id = command.command_id
|
||||
removal.details[&"reason"] = &"RESERVATION_RELEASE_FAILED"
|
||||
removal.details[&"staff_id"] = staff_id
|
||||
return removal
|
||||
var cash := economy_service.get_balance(PLAYER_CASH_ACCOUNT)
|
||||
var top_up := maxi(severance - cash, 0)
|
||||
var transaction_id: StringName = &""
|
||||
if severance > 0:
|
||||
var entries: Array = []
|
||||
if top_up > 0:
|
||||
entries.append(LedgerEntry.new().configure(&"", minute, PLAYER_CASH_ACCOUNT, FinancePolicy.BACK_CASH,
|
||||
top_up, FinancePolicy.TOP_UP_CATEGORY, staff_id, &"", &"finance.back_fund.top_up"))
|
||||
entries.append(LedgerEntry.new().configure(&"", minute, FinancePolicy.SEVERANCE_EXPENSE, PLAYER_CASH_ACCOUNT,
|
||||
severance, FinancePolicy.SEVERANCE_CATEGORY, staff_id, &"", &"staff.severance"))
|
||||
economy_service.begin_notification_scope()
|
||||
var paid := economy_service.post(entries, StringName("staff.severance.%d" % command.command_id))
|
||||
if paid.is_success():
|
||||
_state.set_resource(&"gold", economy_service.get_balance(PLAYER_CASH_ACCOUNT))
|
||||
transaction_id = StringName(paid.details.get(&"transaction_id", &""))
|
||||
economy_service.end_notification_scope()
|
||||
if not paid.is_success():
|
||||
_rollback_staff_dismissal(before)
|
||||
return CommandResult.failed(
|
||||
CommandResult.Code.TRANSACTION_APPLY_FAILED, command.command_id,
|
||||
"Staff dismissal could not commit its severance with the removal.",
|
||||
{&"reason": &"SEVERANCE_POST_FAILED", &"staff_id": staff_id, &"post": paid.to_dictionary()}
|
||||
)
|
||||
var interrupted: Variant = _interrupt_dismissed_staff_rescue(staff_id, held_task_id, minute)
|
||||
if interrupted == null:
|
||||
_rollback_staff_dismissal(before)
|
||||
return CommandResult.failed(
|
||||
CommandResult.Code.TRANSACTION_APPLY_FAILED, command.command_id,
|
||||
"Staff dismissal could not interrupt the rescue it leaves.",
|
||||
{&"reason": &"RESCUE_CLEANUP_FAILED", &"staff_id": staff_id}
|
||||
)
|
||||
# SPEC 6.2 / 6.2.1: the facility projection and the Events' missing actors are
|
||||
# settled at the command boundary by the existing rules, not only at the next minute.
|
||||
_sync_facility_staffing(minute)
|
||||
event_service.audit_actor_availability(minute)
|
||||
var reopened_ids: Array[StringName] = []
|
||||
for task: StaffTask in reopened:
|
||||
if not task.is_terminal():
|
||||
reopened_ids.append(task.task_id)
|
||||
return CommandResult.succeeded(command.command_id, {
|
||||
&"staff_id": staff_id,
|
||||
&"severance": severance,
|
||||
&"severance_top_up": top_up,
|
||||
&"severance_transaction_id": transaction_id,
|
||||
&"unpaid_wages_preserved": int(removal.details.get(&"unpaid_wages_preserved", 0)),
|
||||
&"removed_shift_ids": removal.details.get(&"removed_shift_ids", []),
|
||||
&"removed_override_days": removal.details.get(&"removed_override_days", []),
|
||||
&"reopened_task_ids": reopened_ids,
|
||||
&"interrupted_rescue_task_ids": interrupted,
|
||||
&"player_cash": economy_service.get_balance(PLAYER_CASH_ACCOUNT),
|
||||
&"back_cash": economy_service.get_balance(FinancePolicy.BACK_CASH),
|
||||
})
|
||||
|
||||
|
||||
## SPEC 6.2.1 and 10: a dismissed member of a rescue pair aboard an elevator leaves the
|
||||
## cabin at the command, as the non-depicted removal; the other member stays aboard, as
|
||||
## the existing rule keeps a live passenger to a real landing. These are the steps of
|
||||
## ElevatorService's own cancellation of a single boarded rider (_cancel_single),
|
||||
## through the public Path and elevator snapshot interfaces: the Path request ends, and
|
||||
## the employee's trip, cabin seat and pairing are removed from the snapshot that
|
||||
## ElevatorService.restore_snapshot then checks against the Path state.
|
||||
func _release_dismissed_rescue_seat(staff_id: StringName) -> bool:
|
||||
var actor_id := _staff_path_actor_id(staff_id)
|
||||
var trip := elevators.get_actor_trip(actor_id)
|
||||
var partner := StringName(trip.get(&"companion_id", &""))
|
||||
if trip.get(&"state", &"") != PathService.ELEVATOR_REQUEST_BOARDED or partner.is_empty():
|
||||
return true
|
||||
if not path_service.complete_elevator_request(StringName(trip[&"connector_id"]), StringName(trip[&"request_id"])):
|
||||
return false
|
||||
var snapshot := elevators.create_snapshot()
|
||||
var trips: Array = []
|
||||
for value: Dictionary in snapshot[&"trips"]:
|
||||
var row := value.duplicate(true)
|
||||
if StringName(row[&"actor_id"]) == actor_id:
|
||||
continue
|
||||
if StringName(row[&"actor_id"]) == partner:
|
||||
row.erase(&"companion_id")
|
||||
trips.append(row)
|
||||
snapshot[&"trips"] = trips
|
||||
for car: Dictionary in snapshot[&"shafts"]:
|
||||
(car[&"boarded"] as Array).erase(actor_id)
|
||||
return elevators.restore_snapshot(snapshot)
|
||||
|
||||
|
||||
func _staff_aboard(staff_id: StringName) -> bool:
|
||||
return elevators.get_actor_trip(_staff_path_actor_id(staff_id)).get(&"state", &"") == PathService.ELEVATOR_REQUEST_BOARDED
|
||||
|
||||
|
||||
## SPEC 6.2.1: no live rescue Task keeps the dismissed employee as its patient or
|
||||
## carrier. The next minute's rule (_advance_staff_rescue) is applied to those Tasks
|
||||
## now: each is interrupted and its elevator requests are cancelled, except that a live
|
||||
## passenger already aboard rides on to a real landing. A pickup whose carrier was
|
||||
## dismissed was reopened by the removal and stays open for another medic, as that
|
||||
## rule keeps it. Returns null if a Task could not be interrupted.
|
||||
func _interrupt_dismissed_staff_rescue(staff_id: StringName, held_task_id: StringName, minute: int) -> Variant:
|
||||
var interrupted: Array[StringName] = []
|
||||
for task: StaffTask in staff_service.get_tasks():
|
||||
if task.task_type not in [&"rescue_pickup", &"rescue_transport"] or task.is_terminal():
|
||||
continue
|
||||
var carried := task.task_id == held_task_id and task.task_type == &"rescue_transport"
|
||||
if task.target_entity_id != staff_id and not carried:
|
||||
continue
|
||||
var patient := staff_service.get_staff(task.target_entity_id)
|
||||
var carrier := staff_service.get_staff(task.assigned_staff_id)
|
||||
if not staff_service.cancel_task(task.task_id, &"RESCUE_INTERRUPTED", minute).is_success():
|
||||
return null
|
||||
interrupted.append(task.task_id)
|
||||
for member: StaffRuntime in [patient, carrier]:
|
||||
if member != null and not _staff_aboard(member.staff_id):
|
||||
elevators.cancel_actor(_staff_path_actor_id(member.staff_id))
|
||||
return interrupted
|
||||
|
||||
|
||||
func _rollback_staff_dismissal(snapshot: Dictionary) -> bool:
|
||||
var restored := economy_service.restore_snapshot(snapshot[&"economy"])
|
||||
restored = _state.restore_snapshot(snapshot[&"game_state"]) and restored
|
||||
restored = staff_service.restore_snapshot(snapshot[&"staff"]) and restored
|
||||
restored = inventory_service.restore_snapshot(snapshot[&"inventory"]) and restored
|
||||
restored = path_service.restore_snapshot(snapshot[&"path"]) and restored
|
||||
restored = elevators.restore_snapshot(snapshot[&"elevators"]) and restored
|
||||
_movement_credits = (snapshot[&"movement_credits"] as Dictionary).duplicate(true)
|
||||
restored = domain_events.restore_snapshot(snapshot[&"domain_events"]) and restored
|
||||
restored = id_generator.restore_snapshot(snapshot[&"id_generator"]) and restored
|
||||
return restored and _validate_runtime_path_crosslinks(
|
||||
path_service,
|
||||
guest_service,
|
||||
staff_service,
|
||||
_movement_credits,
|
||||
_runtime_event_minute()
|
||||
)
|
||||
|
||||
func _staff_role_matches_facility(role_id: StringName, definition: FacilityDefinition) -> bool:
|
||||
if definition.required_roles.is_empty() or role_id in definition.required_roles:
|
||||
return true
|
||||
@@ -8555,6 +9094,7 @@ func _advance_guest_vitals(absolute_minute: int) -> void:
|
||||
&"is_night": minute_of_day < 360 or minute_of_day >= 1_080,
|
||||
&"intimacy_day": 3,
|
||||
&"intimacy_night": 7,
|
||||
&"mana_fog": weather.get_kind() == &"mana_fog",
|
||||
}
|
||||
})
|
||||
|
||||
@@ -8806,7 +9346,7 @@ func _advance_guest_ai_decisions(absolute_minute: int) -> void:
|
||||
GuestRuntime.State.ENTERING:
|
||||
guest_service.transition_guest(guest.guest_id, GuestRuntime.State.DECIDING, absolute_minute, &"ENTRY_COMPLETED")
|
||||
GuestRuntime.State.DECIDING:
|
||||
if guest.planned_exit_at <= absolute_minute:
|
||||
if guest.planned_exit_at <= absolute_minute and not _hold_guest_for_rain(guest, absolute_minute):
|
||||
guest_service.begin_leaving(guest.guest_id, absolute_minute, &"PLANNED_EXIT")
|
||||
continue
|
||||
guest.wallet_gold = economy_service.get_balance(_guest_wallet_account(guest.guest_id))
|
||||
@@ -9277,6 +9817,14 @@ func _reserve_staff_task_items(task: StaffTask, absolute_minute: int) -> Diction
|
||||
var before := inventory_service.create_snapshot()
|
||||
var reservation_ids: Array[StringName] = []
|
||||
for item_id: StringName in _sorted_name_keys(task.required_items):
|
||||
# One operation per reservation of the Task: InventoryService never removes a
|
||||
# Reservation, so the count of the Task's earlier ones for the item makes the next
|
||||
# reservation after a release (an interruption, a removal or a dismissal) a new
|
||||
# operation, also within the same minute.
|
||||
var earlier := 0
|
||||
for reservation: InventoryReservation in inventory_service.get_reservations():
|
||||
if reservation.requester_id == task.task_id and reservation.item_id == item_id:
|
||||
earlier += 1
|
||||
var result := inventory_service.reserve_item(
|
||||
inventory_id,
|
||||
item_id,
|
||||
@@ -9285,7 +9833,7 @@ func _reserve_staff_task_items(task: StaffTask, absolute_minute: int) -> Diction
|
||||
&"staff_task",
|
||||
absolute_minute,
|
||||
absolute_minute + 480,
|
||||
StringName("staff.task.reserve.%s.%s" % [task.task_id, item_id])
|
||||
StringName("staff.task.reserve.%s.%s.%d" % [task.task_id, item_id, earlier])
|
||||
)
|
||||
if not bool(result.get(&"ok", false)):
|
||||
inventory_service.restore_snapshot(before)
|
||||
@@ -10764,13 +11312,14 @@ func _reservation_references_are_valid(
|
||||
## HUD view of the A06 reserve: supply, consumption and storage per interval.
|
||||
func get_mana_reserve_report() -> Dictionary:
|
||||
var demand := _mana_reserve_demand()
|
||||
var generation := NaturalWeather.mana_yield(ManaReserveCycle.WORLD_TREE_GENERATION, weather.get_kind())
|
||||
return {
|
||||
&"amount": int(_state.get_resource(&"mana")),
|
||||
&"capacity": ManaReserveCycle.CAPACITY,
|
||||
&"generation": ManaReserveCycle.WORLD_TREE_GENERATION,
|
||||
&"generation": generation,
|
||||
&"interval_minutes": ManaReserveCycle.INTERVAL_MINUTES,
|
||||
&"demand": demand,
|
||||
&"net": ManaReserveCycle.WORLD_TREE_GENERATION - demand,
|
||||
&"net": generation - demand,
|
||||
&"last_flow": mana_reserve.get_last_flow(),
|
||||
}
|
||||
|
||||
@@ -10803,8 +11352,10 @@ func _advance_mana(absolute_minute: int) -> void:
|
||||
return
|
||||
# A06: generation and upkeep demand settle before local distribution and the
|
||||
# whole-tower gate, so the network and projection see the committed reserve.
|
||||
# A13: mana fog raises the world tree yield of this boundary to x1.2 (floor).
|
||||
var reserve_flow := mana_reserve.advance_to(
|
||||
absolute_minute, int(_state.get_resource(&"mana")), _mana_reserve_demand()
|
||||
absolute_minute, int(_state.get_resource(&"mana")), _mana_reserve_demand(),
|
||||
NaturalWeather.mana_yield(ManaReserveCycle.WORLD_TREE_GENERATION, weather.get_kind())
|
||||
)
|
||||
if not bool(reserve_flow.get(&"ok", false)):
|
||||
_latch_infrastructure_failure(
|
||||
@@ -11059,6 +11610,7 @@ func get_staff_management_report() -> Dictionary:
|
||||
&"rest_status": get_staff_rest_report(staff.staff_id).get(&"label", ""),
|
||||
&"can_edit": block_reason.is_empty(),
|
||||
&"assignment_block_reason": block_reason,
|
||||
&"dismissal": get_staff_dismissal_preview(staff.staff_id),
|
||||
&"unique_contract": _staff_unique_contract_report(staff),
|
||||
&"original_relationship": relationship_service.get_original_relationship(staff.staff_id),
|
||||
})
|
||||
@@ -11960,6 +12512,12 @@ func _apply_service_transaction(
|
||||
if service.price_snapshot > 0:
|
||||
var payer_account := _guest_wallet_account(request.guest_ids[0])
|
||||
var revenue_account := _facility_revenue_account(facility.instance_id)
|
||||
# Only a registered definition with a hidden-service tag routes the sale to the
|
||||
# hidden fund. A facility whose definition cannot be read keeps the existing
|
||||
# front-fund behaviour (disclosed in execution/p05-02/REPAIR_02.md).
|
||||
var sale_definition := definition_registry.get_facility(facility.definition_id)
|
||||
var sale_fund := FinancePolicy.BACK_CASH \
|
||||
if sale_definition != null and FinancePolicy.is_back_fund_service(sale_definition.tags) else PLAYER_CASH_ACCOUNT
|
||||
if not economy_service.has_account(payer_account) or not economy_service.has_account(revenue_account):
|
||||
inventory_service.restore_snapshot(inventory_before)
|
||||
return CommandResult.failed(CommandResult.Code.NOT_FOUND, 0, "A service settlement account is missing.")
|
||||
@@ -11969,7 +12527,7 @@ func _apply_service_transaction(
|
||||
service.price_snapshot, &"SERVICE_SALE", facility.instance_id
|
||||
),
|
||||
LedgerEntry.new().configure(
|
||||
&"", absolute_minute, PLAYER_CASH_ACCOUNT, revenue_account,
|
||||
&"", absolute_minute, sale_fund, revenue_account,
|
||||
service.price_snapshot, &"SERVICE_CASH_SWEEP", facility.instance_id
|
||||
),
|
||||
]
|
||||
@@ -12157,6 +12715,8 @@ func _on_guest_despawned(guest_id: StringName, persistent_record: Dictionary) ->
|
||||
elevators.cancel_actor(actor_id)
|
||||
var path_removed := path_service.despawn_actor(actor_id)
|
||||
_movement_credits.erase(actor_id)
|
||||
# A13: a rain hold describes a present guest, so it leaves with the guest.
|
||||
weather.release_rain_hold(guest_id)
|
||||
_synchronize_operations_population()
|
||||
var payload := persistent_record.duplicate(true)
|
||||
payload[&"path_actor_id"] = actor_id
|
||||
@@ -13337,10 +13897,66 @@ func get_staff_rest_report(staff_id: StringName) -> Dictionary:
|
||||
|
||||
func _advance_weather(absolute_minute: int) -> void:
|
||||
weather.advance_to(absolute_minute)
|
||||
_draw_natural_weather_if_due(absolute_minute)
|
||||
if not weather.get_rain_holds().is_empty():
|
||||
var present := {}
|
||||
for guest: GuestRuntime in guest_service.get_guests():
|
||||
present[guest.guest_id] = true
|
||||
weather.prune_rain_holds(present)
|
||||
|
||||
|
||||
## A13: one draw per day at 06:00 from the saved weather stream. The weather
|
||||
## boundary runs first, so the same minute's arrivals and closes see the result.
|
||||
func _draw_natural_weather_if_due(absolute_minute: int) -> void:
|
||||
if not NaturalWeather.is_draw_minute(absolute_minute) or weather.get_minute() != absolute_minute:
|
||||
return
|
||||
var day := NaturalWeather.day_of(absolute_minute)
|
||||
var stream := get_rng_stream(&"weather")
|
||||
if day <= weather.get_last_draw_day() or stream == null:
|
||||
return
|
||||
var draw := NaturalWeather.draw(stream, day)
|
||||
var applied := weather.apply_draw(draw)
|
||||
var no_entities: Array[StringName] = []
|
||||
_publish_actor_event(&"WEATHER_FORECAST_DRAWN", &"weather", no_entities, {
|
||||
&"day": day,
|
||||
&"kind": String(draw[&"kind"]),
|
||||
&"roll": int(draw[&"roll"]),
|
||||
&"slot": int(draw[&"slot"]),
|
||||
&"start": int(draw[&"start"]),
|
||||
&"end": int(draw[&"end"]),
|
||||
&"applied": bool(applied.get(&"ok", false)),
|
||||
&"code": String(applied.get(&"code", &"")),
|
||||
})
|
||||
|
||||
|
||||
## A13: a guest due to leave while it rains stays until the rain ends, at most
|
||||
## 240 minutes after the original plan, once per guest (SPEC section 4.3).
|
||||
func _hold_guest_for_rain(guest: GuestRuntime, absolute_minute: int) -> bool:
|
||||
if weather.get_kind() != &"rain" or not weather.get_rain_hold(guest.guest_id).is_empty():
|
||||
return false
|
||||
var original := guest.planned_exit_at
|
||||
var extended := original + NaturalWeather.RAIN_HOLD_MAX_MINUTES
|
||||
var rain_end := weather.rain_end_minute()
|
||||
if rain_end >= 0:
|
||||
extended = mini(extended, rain_end)
|
||||
if extended <= absolute_minute or not weather.add_rain_hold(guest.guest_id, original, extended):
|
||||
return false
|
||||
guest.planned_exit_at = extended
|
||||
return true
|
||||
|
||||
|
||||
func get_weather_report() -> Dictionary:
|
||||
return {&"kind": weather.get_kind(), &"minute": weather.get_minute()}
|
||||
var kind := weather.get_kind()
|
||||
return {
|
||||
&"kind": kind,
|
||||
&"minute": weather.get_minute(),
|
||||
&"since": weather.get_since(),
|
||||
&"next_change": weather.next_change(),
|
||||
&"today_draw": weather.get_today_draw(),
|
||||
&"effect_text": NaturalWeather.effect_text(kind),
|
||||
&"arrival_percent": NaturalWeather.arrival_percent(kind),
|
||||
&"rain_holds": weather.get_rain_holds().size(),
|
||||
}
|
||||
|
||||
|
||||
## Read-only migration preview; applying dimensions remains a separate transaction.
|
||||
|
||||
@@ -1,3 +1,47 @@
|
||||
## 現在の対象(2026-10-25・正式24成果/機能実装を優先)
|
||||
|
||||
元30パッケージ・88成果単位で、部分点はありません。正式完了は24成果、完了パッケージは4(P00、P04、P06、P14)です。
|
||||
|
||||
**2026-10-20 の後に正式完了した成果**
|
||||
- P28-01「現HEADの性能基準」(`413c8e3`)。性能の不足は保持しています。
|
||||
- P05-02「全費目と資金区分」(`48d3b00`)。P05-03 と P05 の残件は保持しています。
|
||||
|
||||
**原本への加算 0 の補助の作業として正式完了したもの**(原単位は閉じません)
|
||||
- P28-02-DIAG(実測上位の特定)と P28-02-EQ(同等性の先行固定)。
|
||||
- P07-01-WX(天候、Q `9b5b09b`)。原 P07-01 は、霧の境界のマナ炉の産出 120 と `AR.weather.fog_magic` が残り、in_progress です。
|
||||
- P16-01-DISMISS(一般従業員の通常操作での解雇と退職金、Q `4ca3bbe`)。原 P16-01 は、求人募集・市場と借金での獲得・雇用区分ごとの費用が残り、in_progress です。受入記録は `execution/p16-01-dismiss/DISMISS_ACCEPTANCE_4CA3BBE.md` です。
|
||||
|
||||
**優先の順**(ユーザーの指示、2026-10-23)
|
||||
- まずゲームとしての機能実装の完了を優先します。統合試験の動作速度の調整(P28)と見た目の調整は後に回します。
|
||||
- 承認済みの決定: A13(天候の初期仕様)、A14(退職金の初期仕様)。再質問しません。
|
||||
- 回答待ち: P05-03 の3件(推定しません)。
|
||||
- P24 は上流の条件が揃うまで保留です。P28-02-OPT の記録は保持し、後段にします。
|
||||
|
||||
**次の対象**: 機能の残件の次の候補を、同じ実 Opus が、原条文・上流の条件・既存の証拠・通常の登録案とともに Root へ渡し、Root が通常の登録をします。新しい実装は登録の後です。
|
||||
|
||||
**解消扱いしない未達**: 同じ Windows の実機・IME・OS の倍率・同じ候補での実機の受入、長い通常のプレイ、全ゲームの最終の受入。
|
||||
|
||||
以下の「現在」は各記録時点の履歴です。上記と現在の台帳を優先してください。
|
||||
|
||||
## 現在の対象(2026-10-20・正式22成果/P14-03正式完了、P14パッケージ完了)
|
||||
|
||||
元30パッケージ・88成果単位で、部分点はありません。正式完了は22成果、完了パッケージは4(P00、P04、P06、P14)です。
|
||||
|
||||
**P14-03「混雑と経路選択」は Runner で正式完了しました。**
|
||||
- Runner の実receipt: 提出 `a5d2491`、統合・final `b7f6168`、main-base `e8b3dbe`。登録9条件すべて完了、high/medium/critical の指摘は0です。
|
||||
- 受入記録は `execution/p14-03-route-wait/ACCEPTANCE.json` です。条件表と、結合した証拠(正式全回帰192/192、Root別PID pair 5/5、代表性能r02、独立Opusレビュー PASS)を記載しています。
|
||||
- これで P14-01・02・03 の3成果がそろい、P14パッケージが完了しました。
|
||||
|
||||
**解消扱いしない別の未達**(P14の完了に含めません)
|
||||
- Runner review の low 4件
|
||||
- x1 1分tick は平均約827 msで、4 ms の予算に届いていません
|
||||
- x3 frame、stall、描画は未測定、Windows は未検証です
|
||||
- 代表負荷で、実際の職員が業務用shaftを使う場面は実施していません
|
||||
|
||||
**過去の回帰範囲の補足**: 46 suite の件は P14-03 の受入記録と各単位の訂正注記に残しました。p06-01・p14-02 の pinned ACCEPTANCE.json は改変していません。
|
||||
|
||||
**次の対象**: P28-01「現HEADの性能基準」です。現sourceの r02 実測(headless・Operations OFF・x1)を再利用し、windowed描画・x3・Operations ON と記録項目の不足分だけを埋めます。
|
||||
|
||||
## 現在の対象(2026-10-20・正式21成果/P14-03はRunnerゲート待ち)
|
||||
|
||||
元30パッケージ・88成果単位で、部分点はありません。正式完了は21成果、完了パッケージは3(P00、P04、P06)です。
|
||||
|
||||
@@ -56,6 +56,74 @@
|
||||
"origin": "user_reply_in_conversation「従業員用を裏層に別で作って」",
|
||||
"approved_at_jst": "2026-10-20",
|
||||
"values_note": "業務用の数値はユーザー指定がなくA07と同値を適用。"
|
||||
},
|
||||
{
|
||||
"id": "A09",
|
||||
"title": "費用の支払元",
|
||||
"decision": "維持費・仕入れ・人件費・返済・利息・税は表金から払い、不足を裏金で補う。",
|
||||
"origin": "user_selected_answer_label_verified_in_claude_desktop",
|
||||
"approved_at_jst": "2026-10-23",
|
||||
"selected_label": "Front, hidden fills gap (Recommended)",
|
||||
"reference": "execution/p05-02/SPEC.md",
|
||||
"provenance_reference": "execution/p05-02/PROVENANCE.json",
|
||||
"implementation_note": "日次義務の不足は滞納繰越、資金不足の操作時仕入れ/返済は拒否する区別は、既存挙動とOpusの解釈。全費目の繰越を追加の人間承認とは主張しない。SPEC §3.3。"
|
||||
},
|
||||
{
|
||||
"id": "A10",
|
||||
"title": "世界樹ランク税の初期試験値",
|
||||
"decision": "世界樹ランク税は、その日の売上の10%。初期試験値として適用する。",
|
||||
"origin": "user_selected_answer_label_verified_in_claude_desktop",
|
||||
"approved_at_jst": "2026-10-23",
|
||||
"selected_label": "10% of daily sales (Recommended)",
|
||||
"reference": "execution/p05-02/SPEC.md",
|
||||
"provenance_reference": "execution/p05-02/PROVENANCE.json",
|
||||
"implementation_note": "表裏売上合計・00:00計上は提示された説明と原要件、整数切り捨てはOpusの仕様選択。SPEC §3.4。"
|
||||
},
|
||||
{
|
||||
"id": "A11",
|
||||
"title": "闇銀行利息の初期試験値",
|
||||
"decision": "借入残高に対して日利1%。元本は売上に含めない。",
|
||||
"origin": "user_selected_answer_label_verified_in_claude_desktop",
|
||||
"approved_at_jst": "2026-10-23",
|
||||
"selected_label": "1% per day on balance (Recommended)",
|
||||
"reference": "execution/p05-02/SPEC.md",
|
||||
"provenance_reference": "execution/p05-02/PROVENANCE.json",
|
||||
"implementation_note": "00:00計上と整数切り捨てはOpusが記録した選択肢説明。Rootが直接確認したのは質問と選択ラベル。SPEC §3.5。"
|
||||
},
|
||||
{
|
||||
"id": "A12",
|
||||
"title": "旧セーブの資金区分移行",
|
||||
"decision": "旧セーブの所持金は全額を表金とし、裏金は0から始める。",
|
||||
"origin": "user_selected_answer_label_verified_in_claude_desktop",
|
||||
"approved_at_jst": "2026-10-23",
|
||||
"selected_label": "All to front funds (Recommended)",
|
||||
"reference": "execution/p05-02/SPEC.md",
|
||||
"provenance_reference": "execution/p05-02/PROVENANCE.json",
|
||||
"implementation_note": "帳簿履歴を保持。口座欠如による移行判定と5空口座追加はOpusの実装仕様。F8は他の帳簿フィールドbyte不変を必須とする。SPEC §3.8。"
|
||||
},
|
||||
{
|
||||
"id": "A13",
|
||||
"title": "天候の初期仕様",
|
||||
"decision": "毎朝6時に晴れ60%・雨25%・マナ霧10%・嵐5%で抽選し、雨6時間・霧4時間・嵐2時間の後は晴れ(開始は6〜20時の30分刻み)。雨の間は新規来客を半分にし、帰宅予定の客は雨が止むまで滞在を延長(最大4時間)。マナ霧の魔法効率上昇は世界樹・マナ炉の産出1.2倍、興奮度上昇は成人確認済みの客の性欲の増加1.5倍。",
|
||||
"origin": "user_answer_recorded_by_root_in_codex_conversation",
|
||||
"approved_at_jst": null,
|
||||
"answer_verbatim": "この初期仕様で進める",
|
||||
"reference": "execution/p07-01/SPEC.md",
|
||||
"provenance_reference": "execution/p07-01/PROVENANCE.json",
|
||||
"implementation_note": "切り捨て・抽選の手順・保存される系列 weather・延長の記録・旧セーブの移行・拒否の規則は実装担当の選択で、人の新しい承認としない。SPEC §2・§4・§5。",
|
||||
"recorded_at_utc": "2026-10-23T18:07:36Z (Root record time; the answer time is not recorded)"
|
||||
},
|
||||
{
|
||||
"id": "A14",
|
||||
"title": "一般従業員の解雇の退職金",
|
||||
"decision": "退職金はその職員の日給の3日分。表金から払い、不足分を裏金で補い、合計でも足りなければ解雇しない。未払い給与は退職金と分けて保持し次の午前0時に精算する。退職金は帳簿で区別し、日次決算では運営費に計上する。",
|
||||
"origin": "user_answer_recorded_by_root_in_codex_conversation",
|
||||
"approved_at_jst": null,
|
||||
"answer_verbatim": "この初期仕様で進める",
|
||||
"reference": "execution/p16-01-dismiss/SPEC.md",
|
||||
"provenance_reference": "execution/p16-01-dismiss/PROVENANCE.json",
|
||||
"implementation_note": "日給×3の整数、補填と退職金の1つの取引、区分 STAFF_SEVERANCE と口座、旧セーブへの空の口座の追加、命令ごとに1回の記帳は実装担当の選択。進行中の襲撃の防衛者の扱い(R9)は Root の一時の境界で人の承認ではない。SPEC §3.5・§3.6・§6.3。",
|
||||
"recorded_at_utc": "2026-10-23T18:07:36Z (Root record time; the answer time is not recorded)"
|
||||
}
|
||||
],
|
||||
"pending": [
|
||||
@@ -86,7 +154,7 @@
|
||||
{
|
||||
"id": "D04",
|
||||
"title": "未指定の経済と行動係数",
|
||||
"details": "税・利息・マナ・天候・環境・役割・成長等の必須値。A01〜A05は決定済み。",
|
||||
"details": "税・利息・マナ・天候・環境・役割・成長等の必須値のうち未確定部分。A01〜A14の決定範囲はconfirmedと各仕様を参照。A10・A11は初期試験値であり、その他の未確定値を補完しない。",
|
||||
"needed_at": "各機能の本番値固定",
|
||||
"independent_work": "原本明示値、契約、候補比較を先行する。",
|
||||
"kind": "required_per_feature"
|
||||
|
||||
@@ -3925,6 +3925,105 @@
|
||||
"text": "\tprint(\"%s: %d checks\" % [\"PASS\" if _failures == 0 else \"FAIL\", _checks])"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"test": "tests/economy/test_p05_funds_runtime.gd",
|
||||
"template": "PASS: <checks> checks",
|
||||
"required": true,
|
||||
"fixed_check_count_used": false,
|
||||
"source_sha256_at_schema_audit": "4c8f929b0de76198182dfaaef4be4bea7961faa9c31c01889497d6675e0c5def",
|
||||
"authority": {
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p05-02/SPEC.md",
|
||||
"scope": "new P05-02 suite marker; native execution pending"
|
||||
}
|
||||
},
|
||||
{
|
||||
"test": "tests/economy/test_p05_funds_restart_1_write.gd",
|
||||
"template": "PASS: <checks> checks",
|
||||
"required": true,
|
||||
"fixed_check_count_used": false,
|
||||
"source_sha256_at_schema_audit": "3b28e94f1a1f577d74c5322d532b32854339dd4731532ac0b21b4fbc7ffb12f3",
|
||||
"authority": {
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p05-02/SPEC.md",
|
||||
"scope": "new P05-02 F6 restart writer marker; native execution pending"
|
||||
}
|
||||
},
|
||||
{
|
||||
"test": "tests/economy/test_p05_funds_restart_2_read.gd",
|
||||
"template": "PASS: <checks> checks",
|
||||
"required": true,
|
||||
"fixed_check_count_used": false,
|
||||
"source_sha256_at_schema_audit": "1b5bbf0f5c58df39ec28cc867c18a1a14d293fd36459343a118a3e97bc31d33a",
|
||||
"authority": {
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p05-02/SPEC.md",
|
||||
"scope": "new P05-02 F6 restart reader marker; native execution pending"
|
||||
}
|
||||
},
|
||||
{
|
||||
"test": "tests/environment/test_p07_weather_runtime.gd",
|
||||
"template": "PASS: <checks> checks",
|
||||
"required": true,
|
||||
"fixed_check_count_used": false,
|
||||
"source_sha256_at_schema_audit": "e34b52ba3511f7391de1435c2f907997dd10235742ebc029831808af969c68e0",
|
||||
"authority": {
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p07-01/SPEC.md",
|
||||
"scope": "new P07-01 suite marker; native execution pending"
|
||||
}
|
||||
},
|
||||
{
|
||||
"test": "tests/environment/test_p07_weather_restart_1_write.gd",
|
||||
"template": "PASS: <checks> checks",
|
||||
"required": true,
|
||||
"fixed_check_count_used": false,
|
||||
"source_sha256_at_schema_audit": "d85e56dcf75535b19b1af615f8b75e010f4f3906a0e71afdf97102e1bfbffc65",
|
||||
"authority": {
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p07-01/SPEC.md",
|
||||
"scope": "new P07-01 F7 restart writer marker; native execution pending"
|
||||
}
|
||||
},
|
||||
{
|
||||
"test": "tests/environment/test_p07_weather_restart_2_read.gd",
|
||||
"template": "PASS: <checks> checks",
|
||||
"required": true,
|
||||
"fixed_check_count_used": false,
|
||||
"source_sha256_at_schema_audit": "e5c2376e9cb3197d6a0a8d99c9dea020cad2648328178949cd188d999e72071a",
|
||||
"authority": {
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p07-01/SPEC.md",
|
||||
"scope": "new P07-01 F7 restart reader marker; native execution pending"
|
||||
}
|
||||
},
|
||||
{
|
||||
"test": "tests/staff/test_general_dismissal.gd",
|
||||
"template": "PASS: <checks> checks",
|
||||
"required": true,
|
||||
"fixed_check_count_used": false,
|
||||
"source_sha256_at_schema_audit": "f1f77bfd6320a3c8e0851915b150e95fc40f4a352876358393572a6d3f7c79ce",
|
||||
"authority": {
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p16-01-dismiss/SPEC.md",
|
||||
"scope": "new P16-01-DISMISS suite marker; native execution pending"
|
||||
}
|
||||
},
|
||||
{
|
||||
"test": "tests/staff/test_general_dismissal_restart_1_write.gd",
|
||||
"template": "PASS: <checks> checks",
|
||||
"required": true,
|
||||
"fixed_check_count_used": false,
|
||||
"source_sha256_at_schema_audit": "b38745eccb4e51ddf4790051795e2f64faf10f875edc7b754ac2d04db2917a5b",
|
||||
"authority": {
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p16-01-dismiss/SPEC.md",
|
||||
"scope": "new P16-01-DISMISS F8 restart writer marker; native execution pending"
|
||||
}
|
||||
},
|
||||
{
|
||||
"test": "tests/staff/test_general_dismissal_restart_2_read.gd",
|
||||
"template": "PASS: <checks> checks",
|
||||
"required": true,
|
||||
"fixed_check_count_used": false,
|
||||
"source_sha256_at_schema_audit": "4e13de2594096fab648fc8c418a1a4aa764ac72fc13fbd68cba1c6f05767fc83",
|
||||
"authority": {
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p16-01-dismiss/SPEC.md",
|
||||
"scope": "new P16-01-DISMISS F8 restart reader marker; native execution pending"
|
||||
}
|
||||
}
|
||||
],
|
||||
"unknown_roster_suite_policy": "refuse, explicitly review its source/schema before accepting",
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"schema": "p05-02-expectation-changes-v1",
|
||||
"rule": "SPEC §4 F8: an existing test expectation changes only where the difference is explained by A09, A10, A11 or A12 alone",
|
||||
"changes": [
|
||||
{
|
||||
"test": "tests/business/test_required_kitchen.gd",
|
||||
"function": "_approved_runtime_migrations",
|
||||
"decision": "A12",
|
||||
"before": "a legacy v14 save loaded back equals the written state plus the approved mana_reserve and elevators migrations",
|
||||
"after": "the same, plus the five empty fund accounts A12 creates on load (BACK_CASH and RANK_TAX_EXPENSE and LOAN_INTEREST_EXPENSE non-negative; RANK_TAX_PAYABLE and LOAN_INTEREST_PAYABLE allowing negative; none system); every other economy field must stay byte-identical (checked)",
|
||||
"results_local_diagnostic": {
|
||||
"base_dda3eed": "163 checks, 0 failures",
|
||||
"draft_before_change": "163 checks, 2 failures (legacy migration control; whole typed loaded state)",
|
||||
"draft_after_change": "163 checks, 0 failures"
|
||||
}
|
||||
}
|
||||
],
|
||||
"not_changed_by_this_unit": "the other suites that end nonzero in a direct headless run (Main/audio teardown asks for the guarded runner proof; the export pack test needs a built pack; tests/s0/test_kernel_and_save.gd times out at 600 s) end the same way on the unchanged base dda3eed"
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
# P05-02 の F6(別の process での再開)と F7(通常の操作で開く帳簿)の作り
|
||||
|
||||
SPEC(`SPEC.md` §6)の roster 8本は変えない。F6 は SPEC §6 のとおり `tools/run_p05_funds_tests.py` と `tests/**` の範囲で、書き手と読み手の2つの段として roster の後に足した。F7 は SPEC §6 のとおり新しい suite(`tests/economy/test_p05_funds_runtime.gd`)の中で確かめ、帳簿を Main の通常の操作で開く形にした。
|
||||
|
||||
## F6: 別の process での再開
|
||||
|
||||
- 書き手 `tests/economy/test_p05_funds_restart_1_write.gd` と読み手 `tests/economy/test_p05_funds_restart_2_read.gd`。共通の fixture と日の筋書きは `tests/helpers/p05_funds_restart_fixture.gd`(新しい suite と同じ帳簿の fixture と 30 分ごとの境界の進め方)。
|
||||
- 日の筋書き: 1日目に借入 1000・裏の売上 700(税 70・利息 10)。2日目に経費で表金が 5 になり、利息 10 のうち 5 を裏金から補填(A09)。3日目に表の売上 50(税 5・利息 10)。最後は表金 35・裏金 695。
|
||||
- 書き手は、保存しない通しの実行と、2日目の途中(2400)・2日目の 00:00 の締めの直前(2850)・直後(2880)に通常のセーブファイル(`save_to_path`)を書く実行をする。セーブの包み(`manifest.json`・`world.bin`)の bytes を、通しの実行の帳簿の SHA-256 と日次の締めの値と一緒に、宣言した生成ファイル `res://artifacts/p05-02/restart-handoff.json` に書き、`P05_RESTART_WRITER pid=<書き手の PID> handoff_sha256=<SHA>` を1行だけ出す。
|
||||
- 読み手は次の段の別の engine の process で、handoff を読み(書き手の PID が自分と違うこと、書き手の source の SHA が今のものと同じことを確かめる)、`P05_RESTART_READER pid=<読み手の PID> writer_pid=<書き手の PID> handoff_sha256=<SHA>` を1行だけ出す。自分の profile に包みの bytes をそのまま置き(SHA を確かめる)、新しい Runtime で `load_from_path` で読み、残りの筋書きを進める。3つの保存点のどれから再開しても、帳簿の bytes が通しの実行(自分の process と書き手の process の両方)と同じで、日次の締め・資金の報告・滞納が同じで、税と利息の計上が5回、補填が1回だけ(二重にならない)であることを確かめる。`world.bin` を1 byte 変えた包みは読込を拒否され、Gold も時刻も変わらない。
|
||||
- 段をまたいで bytes を保つ共有の仕組みは、`tools/regression/suite-policy.json` の `fixed_generated_report_routes` だけである(共有の `declared_reports` が、後の全ての段の前に、それまでの生成ファイルの bytes の一致を確かめる)。そこで書き手の1つだけの生成ファイルとして handoff を宣言した。狭い roster の8本には生成ファイルはなく、`tests/mvp/test_runtime_upkeep.gd` を roster に入れない扱い(SPEC §6)は変えていない。
|
||||
- `tools/run_p05_funds_tests.py` の照合で足したこと: 段は import・parse・roster 8・再開 2 の 12。書き手の段の生成ファイルは handoff の1つだけで、残した stage の bytes・書き手の PID・書き手の source の SHA と一致する。書き手が出した行の PID と SHA、handoff に書かれた PID が書き手の段の実際の PID と一致する。読み手が出した行の PID が読み手の段の実際の PID、書き手の PID が書き手の段の実際の PID と一致し(両者は違う)、SHA が handoff と一致する。他の段は生成ファイルを持たない。残した stage は source と handoff だけ。外の報告の経路(`outside_routes`)は使わない。
|
||||
- 全体の roster(`tools/run_all_tests.py`)は名前の順に走るので、書き手が読み手より先に走る(`tools/run_p05_funds_tests.py` が起動の前に確かめる)。全体の照合(`tools/regression/review_protocol.py`)は宣言した生成ファイルを同じ規則で確かめる。読み手だけを単独で直接走らせると、handoff がないので失敗する(書き手を先に走らせる必要がある)。
|
||||
|
||||
## F7: 通常の操作で開く帳簿
|
||||
|
||||
- 新しい suite の最後に、ゲームの本物の `SimulationRuntime`・`GameClock`・`GameState` と `scenes/main/main.tscn` を使い、1日目に借入 1000・表の売上 300・裏の売上 500 を置いて、通常の時計で 00:00 の締めまで進める。Main の左の「帳簿」のボタン(`navigation_buttons[&"ledger"]`)を押して帳簿を開き、表金 6210・裏金 500、本日(00:00 に始まる2日目)の税 80・利息 10 の支払い、1日目の締めの「うち表の売上 300G / 裏の売上 500G」「世界樹ランク税 80G(支払 80G)/ 利息 10G(支払 10G)」「裏金から表金へ補填 0G / 未払い 税 0G・利息 0G」を読む。
|
||||
- Main を使うので、新しい suite の方針を通常の Main の音声の後始末の証明つき(`ordinary_cleanup_prepared`、札 `single`)にした(`tests/ui/test_upkeep_closed_ui.gd` と同じ)。最後に `tests/helpers/ui_audio_fixture_cleanup.gd` で Main を片付け、その証明を guarded の runner が照合する。
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
# P05-02 正式受入記録
|
||||
|
||||
P05-02「全費目と資金区分」を、受入 8 件(F1〜F8)の別の fresh な Desktop Opus 5.5 の独立レビューの PASS(高・中 0、low 3)、通常の validation・review・integration・final の全 PASS、実 DB と traceability の結合で正式に完了した。S 8954914 → Q cb183fd。正式 24/88・4/30、部分点なし。P05 パッケージ(P05-03)、通常の操作での裏金(原子要件 back_gold)、P09・P11 の残り、UI の low 2 件、Windows・全ゲームの受入は未達のまま。
|
||||
|
||||
提出 S: `895491456477b42a74e59fa239bea86c4fb9aa33`(修正 3)
|
||||
統合・最終検証 Q: `cb183fd12715bd0edc5f3b8a7fdff76bf1a8ec83`(Q の tree は S の tree と同じ `46cdbfa7…`)
|
||||
基準 M: `613b9886dae31b2578f6aa13b81712b68ac694bf`
|
||||
|
||||
正式証拠: `P05_02_FORMAL_COMPLETION_CB183FD.json`
|
||||
独立レビューの結果(実結果の bytes の写し): `P05_02_INDEPENDENT_REVIEW_8954914.json` SHA256 `fb3e5be2a15214a449da41b8a496de1b2f18ecbb1e8482c68ecd45999b9ad9a5`
|
||||
|
||||
## 原本の条件
|
||||
|
||||
- 原 work: 表金・裏金、維持費未払い、仕入れ、人件費、元本、利息、税を帳簿へ接続する。
|
||||
- 原 acceptance: 3営業日で資金増減と台帳が一致し、借入元本を売上に含めない。
|
||||
- 横断 6 条件(P05): 原本条件とUI操作が一致、Runtimeの実利用・会計・行動へ接続、途中保存・再起動で再現、異常/境界/中断を検証、関連回帰PASS、要件別ログとソースハッシュを保存。
|
||||
- 受入 8 件(F1〜F8)は登録の `acceptance_baseline.json` の本文のとおり(証明の `acceptance_ids`)。決定 A09〜A12 の出典は `artifacts/original-completion-2026-10-02/feature-priority-r01/DECISIONS_P05_02.json`。
|
||||
|
||||
## 4つの通常のゲート(Runner の the-tree-p05-02、task P05-02、completed 8/8)
|
||||
|
||||
| ゲート | commit | Runner の証跡 | 結果 |
|
||||
|---|---|---|---|
|
||||
| validation | S | evidence 6 | p05-funds exit 0、verified true、10 段・867 checks、engines_started 0 |
|
||||
| review | S | evidence 7 | 実結果 `fb3e5be2…`、pass、F1〜F8、low 3 |
|
||||
| integration | Q | evidence 8 | p05-funds exit 0、verified true、engines_started 0 |
|
||||
| final | Q | evidence 9 | `ec32bfcd…-final.json` SHA `f8d25e28…`、verified true、engines_started 0 |
|
||||
|
||||
- owner の承認: Root が、The Tree の main の統合と push についての既存の人の許可を、正確な Q `cb183fd` と main_base `613b988` に正直に当てはめた(新しい人の発言は作っていない)。Runner の approvals の行と `ROOT_OWNER_APPROVAL_CB183FD_R01.json`。integrate の job `2509b410d93e49d998624da6bb163053`。
|
||||
- 測定は全て S で行った(native 10 段・867 checks、最終の S の全 roster 195 本・22,539 checks)。Q での integration と final は、S で測った証拠を、中身が同じ Q の新しい checkout で中身ベースの verifier が照合したもので、Q で測ったとは書かない。
|
||||
|
||||
## 独立レビュー
|
||||
|
||||
- 別の fresh な Claude Desktop Opus 5.5 の session(実装担当と別。前の S の 2 回のレビューとも別)。凍結の入力(packet・schema・material 7,055 ファイル)の SHA を確かめ、読むだけで判定した。会話の記録の全体を配送の前に確かめた(Bash 80 件は全て読むだけ。結果は `open(..., 'xb')` で 1 回だけ書いた。engine・試験・Runner のコマンド・git の書き換えは無い)。
|
||||
- low 3 件(原文は独立レビューの結果のファイル):
|
||||
- 仕入れの画面(allowed_paths の外)が「手持ち」に表金だけ、「購入後」に表金+裏金の残りを出す。操作は A09 どおり。後の作業として開示(ユーザーの機能を先にする指示)。
|
||||
- 帳簿の画面の資金の欄が表金を「手持ち」と出し、借入が無い時は表金か合計かが読み取りにくい。後の作業として開示(UI は後で入れ替える予定)。
|
||||
- 修正 2・3 の局所の確認の生ログが scratchpad にだけあった。→ 32 件を管理用のフォルダへ写し、記録の SHA・bytes と一致を確かめた(`P05_02_LOCAL_CHECK_RAW_LOGS_RETAINED_R01.json`)。Root も 32 件の元と写しを読み直し、違いは無かった。
|
||||
|
||||
## 保持した失敗
|
||||
|
||||
- S `7808ca7` の validation の失敗、S `37deb6e`(r02)と S `d65e3da`(r01)のレビューの changes_requested、修正 2・3 の局所の確認と試作の失敗。どれも元の記録のまま残し、新しい S の証拠に流用していない。
|
||||
|
||||
## 未達として残すもの
|
||||
|
||||
- P05 パッケージは in_progress のまま。P05-03「金庫と銀行の通常営業」は planned で、人への問い 3 件は回答待ち(推定しない)。
|
||||
- 通常の操作での裏金(原子要件 back_gold): F1 の裏の施設の場面は、通常の Main では雇えない役割(service_attendant)を register_staff の command で登録した。P09・P11 の原の残件(remaining_paid_role、2026-10-08)で、ここでは閉じない。
|
||||
- カジノ・脅迫金・ユニット売却の売上の発生源は無い(SPEC §7 の開示)。罰金・出来事は表金だけで払う(A09 の範囲。専用の試験は無いが開示済み)。
|
||||
- 退職金(AR.staff.dismissal_severance、P05、D04)はこの単位に入らない。人の回答待ち。
|
||||
- 同一の Windows 配布版の実機での受入、長い通常のプレイ、全ゲームの最終受入。
|
||||
|
||||
実装・native・全 roster は登録の実装担当(Claude Opus 5.5 Desktop)が行い、commit は Runner の submit が作った。契約の worker 欄の codex は互換のためだけで、Codex・Astra の run や dispatch は無い。原 88 の分母は増やさない。
|
||||
+526
@@ -0,0 +1,526 @@
|
||||
{
|
||||
"at_jst": "2026-10-23T22:50:18.074400+09:00",
|
||||
"original_unit": "P05-02",
|
||||
"project": "the-tree-p05-02",
|
||||
"task": "P05-02",
|
||||
"state": "completed",
|
||||
"S": "895491456477b42a74e59fa239bea86c4fb9aa33",
|
||||
"Q": "cb183fd12715bd0edc5f3b8a7fdff76bf1a8ec83",
|
||||
"M": "613b9886dae31b2578f6aa13b81712b68ac694bf",
|
||||
"repairs": 3,
|
||||
"Q_tree_equals_S_tree": "46cdbfa7629384fa53a83196efff274abb0dfd9b",
|
||||
"all4_normal_gates_passed": true,
|
||||
"original_work": "表金・裏金、維持費未払い、仕入れ、人件費、元本、利息、税を帳簿へ接続する。",
|
||||
"original_acceptance": "3営業日で資金増減と台帳が一致し、借入元本を売上に含めない。",
|
||||
"original_package_work": "表/裏収入区分、維持費、返済元本/利息、税、未払いを帳簿に接続。金庫上限・購買・銀行のサービス契約を先に作る。決算損益と現金収支を区別。",
|
||||
"original_package_acceptance": "3営業日の費目別総額が台帳・施設別・資金と一致。借入元本を売上にしない。支払い失敗/再送/復元/3赤字を検証。",
|
||||
"original_cross_cutting_acceptance": [
|
||||
"原本条件とUI操作が一致",
|
||||
"Runtimeの実利用・会計・行動へ接続",
|
||||
"途中保存・再起動で再現",
|
||||
"異常/境界/中断を検証",
|
||||
"関連回帰PASS",
|
||||
"要件別ログとソースハッシュを保存"
|
||||
],
|
||||
"acceptance_ids": [
|
||||
{
|
||||
"id": "P05-02-F1",
|
||||
"text": "表金(PLAYER_CASH)と裏金(BACK_CASH)が別の口座で、施設の定義に restricted_service か adult_only の札がある施設の売上だけが裏金に入り、それ以外(飲食・宿泊など)は表金に入る。表・裏の施設を各1つ以上で確かめる。"
|
||||
},
|
||||
{
|
||||
"id": "P05-02-F2",
|
||||
"text": "A09: 00:00 の精算(維持費・人件費・税・利息)は表金から先に払い、足りない分を裏金で補い、両方で足りない分を滞納として翌日へ繰り越す。操作の時の支払(仕入れの手動・自動、借入の返済)は表金+裏金が足りれば表金から先に払い、足りなければ拒否して何も変えない。対象外の支払(建設・罰金など)は今までどおり表金だけ。境界: 表金だけで足りる、裏金で補う、両方で足りず滞納、滞納の翌日の支払。"
|
||||
},
|
||||
{
|
||||
"id": "P05-02-F3",
|
||||
"text": "A10: 第 d 日の 00:00 に、税 = floor(第 d 日の売上(表+裏)× 10 / 100) を RANK_TAX として計上し、決算の taxes_and_interest に入れる。0 は記帳しない。第 d+1 日に二重に数えない。不足は滞納として繰り越し、次の精算で新しい額より先に払う。"
|
||||
},
|
||||
{
|
||||
"id": "P05-02-F4",
|
||||
"text": "A11: 毎日 00:00 に、利息 = floor(借入残高 × 1 / 100) を LOAN_INTEREST として計上し、決算の taxes_and_interest に入れる。借入・返済の後の残高で正しく変わる。借入の元本は売上にも経費にも入らない。"
|
||||
},
|
||||
{
|
||||
"id": "P05-02-F5",
|
||||
"text": "3営業日の照合: 表金・裏金それぞれの日ごとの増減が帳簿の記帳の合計と一致し、日次決算の全ての区分(表裏の売上・仕入れ・人件費・維持費・税と利息・罰金など)が帳簿と一致し、借入の元本が売上に入らない。"
|
||||
},
|
||||
{
|
||||
"id": "P05-02-F6",
|
||||
"text": "途中保存・00:00 の前後の保存・別の process での再開で、税・利息・振り分け・支払が二重にならず、同じ結果になる。旧 snapshot(裏金の口座が無い版)は PLAYER_CASH の全額を表金、裏金 0 として読み(A12)、帳簿の履歴は変えない。不正な値は拒否し、読込の前の状態を保つ。"
|
||||
},
|
||||
{
|
||||
"id": "P05-02-F7",
|
||||
"text": "帳簿の画面と日次のレポートを通常の操作で開き、表金・裏金の残高、表裏の売上、税、利息、滞納が読める(UI は今の部品のまま。完成の見た目は求めない)。"
|
||||
},
|
||||
{
|
||||
"id": "P05-02-F8",
|
||||
"text": "関連の回帰と最終Sの全 roster が PASS。既存試験の期待値変更はA09・A10・A11の承認済み差、または§3.8のA12旧セーブ移行の差だけに限定し、全件を EXPECTATION_CHANGES.json に変更前後の値・計算または構造差・根拠つきで一覧化する。A12は旧セーブへの5口座(BACK_CASH、LOAN_INTEREST_EXPENSE、LOAN_INTEREST_PAYABLE、RANK_TAX_EXPENSE、RANK_TAX_PAYABLE)の追加だけとし、残高・既存口座・取引履歴・その他の帳簿フィールドの不変をbyte比較で確認する。元の失敗を保存し、未解決の試験を除外してPASSにしない。"
|
||||
}
|
||||
],
|
||||
"acceptance_baseline": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p05-02/acceptance_baseline.json",
|
||||
"bytes": 3987,
|
||||
"sha256": "31d682e5cf57637edbfe4cc1d57dff23d646f98efa86001a61368303b0f74f2e"
|
||||
},
|
||||
"spec": {
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p05-02/SPEC.md",
|
||||
"bytes": 17246,
|
||||
"sha256": "3ba7af53d6a2b005cdf16b7957fbb7cc902339d9626dfb9dcd4d4c792f54e44f",
|
||||
"contract_spec_sha256": "b3339ce4cbc5400a70962159f5c2c193b2132e76b7dcbb477dfbad4e0f51b6b2"
|
||||
},
|
||||
"decisions_A09_A12": {
|
||||
"path": "artifacts/original-completion-2026-10-02/feature-priority-r01/DECISIONS_P05_02.json",
|
||||
"bytes": 2135,
|
||||
"sha256": "87828430f5f5a9c3ceed4916e56addc79c984a9aa2563e537d160c5fcfcaddc8"
|
||||
},
|
||||
"unit_disclosures": [
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p05-02/PROVENANCE.json",
|
||||
"bytes": 6855,
|
||||
"sha256": "adeab21581e539875169acbec1ee48feba814d0b1c445f6c5ab172d009257389"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p05-02/EXPECTATION_CHANGES.json",
|
||||
"bytes": 1313,
|
||||
"sha256": "6e6eb9e32d4cfaab86c163902b116bff98dffbb5af7cfbdfc633558e86eeec51"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p05-02/F6_F7_IMPLEMENTATION.md",
|
||||
"bytes": 5380,
|
||||
"sha256": "a578e0a60ac3e22946ce3b495e6d927310e2e14b2c00c77f17fe9b511fca64ae"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p05-02/REPAIR_01.md",
|
||||
"bytes": 1340,
|
||||
"sha256": "53a3beefb7d8c0e34f60bdc2f8fb38f354b464d33a07d16471e812d7a31b46db"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p05-02/REPAIR_02.md",
|
||||
"bytes": 7948,
|
||||
"sha256": "c656f4362260cf613ef8e193b65309fa0d075076d88f0b311361873e86a701ae"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p05-02/REPAIR_03.md",
|
||||
"bytes": 5770,
|
||||
"sha256": "7fd80675ccff48fa2018383e69646f2b8101b454cdc197133a3f42482824a9d1"
|
||||
}
|
||||
],
|
||||
"runner_task": {
|
||||
"id": "P05-02",
|
||||
"state": "completed",
|
||||
"submitted": "895491456477b42a74e59fa239bea86c4fb9aa33",
|
||||
"candidate": "cb183fd12715bd0edc5f3b8a7fdff76bf1a8ec83",
|
||||
"main_base": "613b9886dae31b2578f6aa13b81712b68ac694bf",
|
||||
"reason": null,
|
||||
"repairs": 3
|
||||
},
|
||||
"runner_events_from_review": [
|
||||
{
|
||||
"id": 35,
|
||||
"event": "review_pending",
|
||||
"created": 1792762636.352181
|
||||
},
|
||||
{
|
||||
"id": 36,
|
||||
"event": "owner_actual_independent_Desktop_review_output",
|
||||
"created": 1792762644.6540768
|
||||
},
|
||||
{
|
||||
"id": 37,
|
||||
"event": "reviewed",
|
||||
"created": 1792762644.6552699
|
||||
},
|
||||
{
|
||||
"id": 38,
|
||||
"event": "approval_pending",
|
||||
"created": 1792762742.227736
|
||||
},
|
||||
{
|
||||
"id": 39,
|
||||
"event": "approved",
|
||||
"created": 1792762829.3107798
|
||||
},
|
||||
{
|
||||
"id": 40,
|
||||
"event": "completed",
|
||||
"created": 1792762841.389877
|
||||
}
|
||||
],
|
||||
"normal_evidence_join": [
|
||||
{
|
||||
"id": 6,
|
||||
"task_id": "P05-02",
|
||||
"kind": "validation",
|
||||
"commit_sha": "895491456477b42a74e59fa239bea86c4fb9aa33",
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p05-02/runs/d661df3aacae40f7a32db1b5d0c6ea5d-validation.json",
|
||||
"passed": 1,
|
||||
"artifact_sha256": "363329adca9b3ba135d09004be3a8e371f336311db1e4798f457d97b6b08eae4",
|
||||
"checks": [
|
||||
{
|
||||
"id": "p05-funds",
|
||||
"exit_code": 0,
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p05-02/runs/f6dee1bbc95849c6af9d40108c6bf045",
|
||||
"cwd": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p05-02/P05-02-validation-efe3b38b",
|
||||
"stdout": {
|
||||
"verified": true,
|
||||
"actual_suites": 10,
|
||||
"parse_checks": 345,
|
||||
"actual_checks": 867,
|
||||
"engines_started": 0,
|
||||
"formal_acceptance": false,
|
||||
"original_unit_complete": false
|
||||
},
|
||||
"raw": {
|
||||
"command.json": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p05-02/runs/f6dee1bbc95849c6af9d40108c6bf045/command.json",
|
||||
"bytes": 719,
|
||||
"sha256": "4e2ef2b7a79995d1277a24970e61013e9fcb64a9ca6a9b1febec37eb52d5a9e3"
|
||||
},
|
||||
"stdout.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p05-02/runs/f6dee1bbc95849c6af9d40108c6bf045/stdout.log",
|
||||
"bytes": 166,
|
||||
"sha256": "c6cef78e638b60c51f9e014ecd374158cd6f138915678d73b7c874e845849844"
|
||||
},
|
||||
"stderr.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p05-02/runs/f6dee1bbc95849c6af9d40108c6bf045/stderr.log",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
"sandbox.sb": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p05-02/runs/f6dee1bbc95849c6af9d40108c6bf045/sandbox.sb",
|
||||
"bytes": 1920,
|
||||
"sha256": "b6f48231243d4131c3030a122cf99d0bf19dcb93363f5dcd0d14e7f09d93213c"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"task_id": "P05-02",
|
||||
"kind": "review",
|
||||
"commit_sha": "895491456477b42a74e59fa239bea86c4fb9aa33",
|
||||
"artifact": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/p05-02-control-r01/p05-02-independent-review-8954914-r01/actual-independent-desktop-output/result.json",
|
||||
"passed": 1,
|
||||
"artifact_sha256": "fb3e5be2a15214a449da41b8a496de1b2f18ecbb1e8482c68ecd45999b9ad9a5",
|
||||
"checks": []
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"task_id": "P05-02",
|
||||
"kind": "integration",
|
||||
"commit_sha": "cb183fd12715bd0edc5f3b8a7fdff76bf1a8ec83",
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p05-02/runs/8496f92e04474d4d814406e185964210-integration.json",
|
||||
"passed": 1,
|
||||
"artifact_sha256": "7174dd77cf513fa350d34c4aa861d4c22b054ca29b59a869a4f0ccfe44d43628",
|
||||
"checks": [
|
||||
{
|
||||
"id": "p05-funds",
|
||||
"exit_code": 0,
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p05-02/runs/aab161fd1f65485c8efed780e27b1ecd",
|
||||
"cwd": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p05-02/P05-02-integration-77b0fddc",
|
||||
"stdout": {
|
||||
"verified": true,
|
||||
"actual_suites": 10,
|
||||
"parse_checks": 345,
|
||||
"actual_checks": 867,
|
||||
"engines_started": 0,
|
||||
"formal_acceptance": false,
|
||||
"original_unit_complete": false
|
||||
},
|
||||
"raw": {
|
||||
"command.json": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p05-02/runs/aab161fd1f65485c8efed780e27b1ecd/command.json",
|
||||
"bytes": 720,
|
||||
"sha256": "0ffbbd87dc9cbe3736b0b922685cc8028313df071cb07e77a4b6e4821e183041"
|
||||
},
|
||||
"stdout.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p05-02/runs/aab161fd1f65485c8efed780e27b1ecd/stdout.log",
|
||||
"bytes": 166,
|
||||
"sha256": "c6cef78e638b60c51f9e014ecd374158cd6f138915678d73b7c874e845849844"
|
||||
},
|
||||
"stderr.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p05-02/runs/aab161fd1f65485c8efed780e27b1ecd/stderr.log",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
"sandbox.sb": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p05-02/runs/aab161fd1f65485c8efed780e27b1ecd/sandbox.sb",
|
||||
"bytes": 1921,
|
||||
"sha256": "ed7a1f2c41212537bcdb79548f03d055b6637b6ad4bc1d1975cadbb4a15ebb83"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"task_id": "P05-02",
|
||||
"kind": "final",
|
||||
"commit_sha": "cb183fd12715bd0edc5f3b8a7fdff76bf1a8ec83",
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p05-02/runs/ec32bfcdd72e44bdaa43f5aa69dec5c1-final.json",
|
||||
"passed": 1,
|
||||
"artifact_sha256": "f8d25e287894735b87b6179a730e0db34c112200bd7f87cff1ca936a5a20ece0",
|
||||
"checks": [
|
||||
{
|
||||
"id": "p05-funds",
|
||||
"exit_code": 0,
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p05-02/runs/6f7443a82356484997255ed18f06c951",
|
||||
"cwd": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p05-02/P05-02-final-9d2e7098",
|
||||
"stdout": {
|
||||
"verified": true,
|
||||
"actual_suites": 10,
|
||||
"parse_checks": 345,
|
||||
"actual_checks": 867,
|
||||
"engines_started": 0,
|
||||
"formal_acceptance": false,
|
||||
"original_unit_complete": false
|
||||
},
|
||||
"raw": {
|
||||
"command.json": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p05-02/runs/6f7443a82356484997255ed18f06c951/command.json",
|
||||
"bytes": 726,
|
||||
"sha256": "4eeb6b0523e20cbaa40ebcd8bc2a2e21ef5109a30b9f5bf01a606c1ccfb2f60c"
|
||||
},
|
||||
"stdout.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p05-02/runs/6f7443a82356484997255ed18f06c951/stdout.log",
|
||||
"bytes": 166,
|
||||
"sha256": "c6cef78e638b60c51f9e014ecd374158cd6f138915678d73b7c874e845849844"
|
||||
},
|
||||
"stderr.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p05-02/runs/6f7443a82356484997255ed18f06c951/stderr.log",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
"sandbox.sb": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p05-02/runs/6f7443a82356484997255ed18f06c951/sandbox.sb",
|
||||
"bytes": 1915,
|
||||
"sha256": "fbdb8fbc586d379e664d05d3009173ee54498af477222eb767ef06a8c43fdc87"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"approval": {
|
||||
"task_id": "P05-02",
|
||||
"candidate": "cb183fd12715bd0edc5f3b8a7fdff76bf1a8ec83",
|
||||
"main_base": "613b9886dae31b2578f6aa13b81712b68ac694bf",
|
||||
"owner": "Root applying existing human authorization for The Tree main integration and push, delegated continuation; exact candidate cb183fd12715bd0edc5f3b8a7fdff76bf1a8ec83 at main-base 613b9886dae31b2578f6aa13b81712b68ac694bf; no new human utterance claimed. Review S8954914 pass F1-F8 low3; diagnostic retention resolved, two UI lows disclosed and deferred per feature-first instruction.",
|
||||
"created": 1792762829.309799
|
||||
},
|
||||
"owner_records": [
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/p05-02-control-r01/ROOT_OWNER_APPROVAL_CB183FD_R01.json",
|
||||
"bytes": 1306,
|
||||
"sha256": "475c9c3e59336f1de978c45c3b6f59af00d42743cae81de090313c8b85507c01"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/p05-02-control-r01/ROOT_P05_02_COMPLETED_DB_JOIN_R01.json",
|
||||
"bytes": 3368,
|
||||
"sha256": "5b163d1da07b1d6c4851aa8e41aa02db4a8a64bd2631d6c0839f019ffd88b882"
|
||||
}
|
||||
],
|
||||
"integrate_job_id": "2509b410d93e49d998624da6bb163053",
|
||||
"traceability": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p05-02/traceability/P05-02.json",
|
||||
"bytes": 5158,
|
||||
"sha256": "7a37ceb8c5eb58c61d884a80b44d3f6f6774baee8ff8c40cbf1de0236b55211d"
|
||||
},
|
||||
"independent_reviewer": {
|
||||
"task_id": "P05-02",
|
||||
"commit": "895491456477b42a74e59fa239bea86c4fb9aa33",
|
||||
"session_id": "local_cbdd640f-21ad-4f21-8d71-12b8debccc21",
|
||||
"model": "claude-opus-5-5",
|
||||
"context": "fresh",
|
||||
"read_only": true,
|
||||
"read_only_meaning": "the governor read every tool call of the full reviewer transcript (792 rows, one user prompt carrying the brief SHA e5817d74..., no sidechain, model claude-opus-5-5 only): the 80 Bash commands are reads, SHA computations, git diff/log/show/status in the frozen worktree, read-only sqlite (mode=ro) of the Runner state and one temporary diff in the reviewer's own TMPDIR; Write was used only for the reviewer's own scratchpad draft; DESKTOP_RESULT.json was written once with open(..., 'xb'); no Godot, test, validation script, Runner command, git mutation or sandbox bypass; the frozen worktree HEAD is S with tracked files clean and READY/packet/schema/manifest bytes unchanged; the harness left an empty directory .claude/.cc-writes in the review folder at 22:20 (no files); the 3 pattern matches are a packet read, a grep of tools/run_all_tests.py and a packet read (transcript 71851c8e-607d-4969-8a6c-13b2dc7fb54d.jsonl; tool calls {\"mcp__ccd_turn__update_plan\": 5, \"Bash\": 80, \"Read\": 4, \"Write\": 1, \"mcp__ccd_turn__wrap_up\": 1}; edit tools [\"Write\"]; pattern matches read by the governor: 3)",
|
||||
"tests_or_engines_started": false,
|
||||
"packet_sha256": "2ca871ef9ae7291b542f215aec2d3c0c1f524d4c8c01589d3134913dd4ee33cd",
|
||||
"normal_packet_sha256": "ae8ce2ca51f6912b828ffb8a3d5e40642709d5e4c090e3d529521f2b5a11d681",
|
||||
"schema_sha256": "4420e0a2f8b81ec2f0fcdddc668f5abf1a3b0d42c66171153d21eedb3b61c3e5",
|
||||
"material_manifest_sha256": "2b8f91431bb16e698d52b8755311d409006da7305b713ee1a53fddbd790260a2",
|
||||
"result_file": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/p05-02-control-r01/p05-02-independent-review-8954914-r01/DESKTOP_RESULT.json",
|
||||
"result_sha256": "fb3e5be2a15214a449da41b8a496de1b2f18ecbb1e8482c68ecd45999b9ad9a5",
|
||||
"result_sha256_source": "computed by the governor from the actual DESKTOP_RESULT.json (not edited)",
|
||||
"status_reported": "pass",
|
||||
"delivered_by": "Desktop governor session local_a7f85421-3eed-4482-8030-c0dca669573a (implementer, commander and native executor of P05-02; did not review or edit the result)",
|
||||
"session_started_by": "Root (Codex) pressed the normal Start card task_e05a97cc once under existing user delegation (Root's message to the commander, 2026-10-23); the app reported the proposal started as session local_cbdd640f-21ad-4f21-8d71-12b8debccc21 (model claude-opus-5-5, effort xhigh, parent local_a7f85421); not a new human action",
|
||||
"cli_authentication_recovered": false,
|
||||
"cli_review_succeeded": false,
|
||||
"recorded_at": 1792762619.547446
|
||||
},
|
||||
"independent_result": {
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p05-02/P05_02_INDEPENDENT_REVIEW_8954914.json",
|
||||
"bytes": 10653,
|
||||
"sha256": "fb3e5be2a15214a449da41b8a496de1b2f18ecbb1e8482c68ecd45999b9ad9a5",
|
||||
"copy_of": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/p05-02-control-r01/p05-02-independent-review-8954914-r01/DESKTOP_RESULT.json"
|
||||
},
|
||||
"review_findings": [
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "仕入れの画面(scripts/ui/supply_management_panel.gd。allowed_paths の外)は今も「手持ち %dG」に表金だけを出し、見積の「購入後 %dG」は表金+裏金からの残りを出す。そのため表金 2・裏金 100 で 15 の仕入れでは「手持ち 2G」と「購入後 87G」が並ぶ。操作は A09 どおりで、REPAIR_03.md に差分の案と正規の扱い(範囲に含む別の登録で直す)が記録されているので、P05-02 の合否は下げない。P05-03 か仕入れ・UI の単位の登録で、この画面を allowed_paths に入れて直すこと。"
|
||||
},
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "帳簿の画面の資金の欄(scripts/ui/operating_ledger_panel.gd の _cash_label)は、表金の残高を「手持ち %dG」と出し、その下に「裏金 %dG」を出す。「表金」の語は借入がある時の返済の案内にしか出ないので、借入が無い時は「手持ち」が表金だけなのか合計なのかが画面から読み取りにくい。Main から開く試験も report の cash(6210)を見るだけで、画面の文字で表金の残高を確かめていない。「表金 %dG」に揃え、その文字を試験で確かめるとよい(UI は後で入れ替える予定なので low)。"
|
||||
},
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "局所の確認の生のログ(修正3の最初の失敗の p05-r3-funds-runtime-r01.log・p05-debug-funds-r02.log、修正2の 27 の pin の全て)は /private/tmp/claude-501/… の scratchpad にだけある。記録(P05_02_REPAIR2_LOCAL_CHECKS_R01.json・P05_02_REPAIR3_LOCAL_CHECKS_R01.json)は SHA を持ち今は全て一致するが、ログ自体は再起動などで消えうる。正式の証拠ではないが、元の失敗の保存のため、管理用のフォルダか artifacts の下に写しておくとよい。"
|
||||
}
|
||||
],
|
||||
"review_high_medium": 0,
|
||||
"review_low": 3,
|
||||
"review_provenance_records": [
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/p05-02-control-r01/REVIEW_CARD_PROPOSED_8954914.json",
|
||||
"bytes": 1855,
|
||||
"sha256": "ee3b6d40800f0bda0afdac1ca1acf138ef3546c11c274e549a363abf0c43ce6e"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/p05-02-control-r01/REVIEW_STARTED_8954914_COMMANDER_RECORD.json",
|
||||
"bytes": 1449,
|
||||
"sha256": "6a73f3e240a548b0a44f38492ac02c14d5e9727d28472fb2e96a92e5950445c5"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/p05-02-control-r01/DELIVERY_INSPECT_8954914.json",
|
||||
"bytes": 43791,
|
||||
"sha256": "b1779f03b110e5663a8b55e05115f60a9cd09109e125093ba553e8dbabeb7d2c"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/p05-02-control-r01/P05_02_REVIEW_BRIEF_8954914.md",
|
||||
"bytes": 14679,
|
||||
"sha256": "e5817d7468eb98029974a16a5aa5c51b09c128eea704554a9c32a98be3d30c7a"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/p05-02-control-r01/p05-02-independent-review-8954914-r01/READY_FOR_DESKTOP_REVIEW.json",
|
||||
"bytes": 2693,
|
||||
"sha256": "78477e7e229a195d8e0408f7eee6f022b0f99b5d7f1be5e8fabbe5d3ea881cb6"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/p05-02-control-r01/p05-02-independent-review-8954914-r01/DESKTOP_DELIVERY.json",
|
||||
"bytes": 2835,
|
||||
"sha256": "472ecbc74423c3cf982750d1918701025b46317229ca20db8e9f136557a37de3"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/p05-02-control-r01/p05-02-independent-review-8954914-r01/INTAKE_STARTED.json",
|
||||
"bytes": 217,
|
||||
"sha256": "d5db3ae269c619bb809bf788845e47f4fa9c3d59b34995751ae91ed21b561ec6"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/p05-02-control-r01/p05-02-independent-review-8954914-r01/INTAKE_FINISHED.json",
|
||||
"bytes": 288,
|
||||
"sha256": "a8ca9d2a819a8c58dbf31b934241f5baeb5bc7289d36d59766439ec78194b1c9"
|
||||
}
|
||||
],
|
||||
"measurement": {
|
||||
"measured_on": "895491456477b42a74e59fa239bea86c4fb9aa33",
|
||||
"native": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/p05-02-control-r01/NATIVE_8954914_START.json",
|
||||
"bytes": 801,
|
||||
"sha256": "0d6c683c9a2794bbd357a76b9bf9f0db35df05a0b4ac33925bf0aa8fceccdf8a"
|
||||
},
|
||||
"native_bundle_transfer": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/p05-02-control-r01/NATIVE_8954914_BUNDLE_TRANSFER.json",
|
||||
"bytes": 1335,
|
||||
"sha256": "5d7dd8951c54f855487fc381360730b0f54675bf693e8c9e872c8aef5ce1592b"
|
||||
},
|
||||
"formal_validation_summary": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/p05-02-control-r01/FORMAL_VALIDATE_8954914_SUMMARY.json",
|
||||
"bytes": 1964,
|
||||
"sha256": "1fc9754fa31331aad0c47e8fca6660765f06b5e0afa7c5f72adea99cb0094311"
|
||||
},
|
||||
"full_roster_summary": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/p05-02-control-r01/FULL_ROSTER_8954914_R01_SUMMARY.json",
|
||||
"bytes": 3151,
|
||||
"sha256": "31fca47ded8d0a5c685ecc15a8a76a2a5e72e3aec14d0958485c8900f784d832"
|
||||
},
|
||||
"full_roster_placement": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/p05-02-control-r01/FULL_ROSTER_8954914_PLACEMENT.json",
|
||||
"bytes": 958,
|
||||
"sha256": "230caae320ffa4ca194111b67571c8e4e4cd77a0a6100d0aaee541cde1f8d43d"
|
||||
},
|
||||
"native_phases": 10,
|
||||
"native_checks": 867,
|
||||
"full_roster_suites": 195,
|
||||
"full_roster_checks": 22539,
|
||||
"measurement_not_relabelled_as_gate_Q": true,
|
||||
"Q_meaning": "the evidence measured on S was checked by the registered content-based verifier in fresh checkouts of Q (integration and final); nothing was measured on Q and no engine started"
|
||||
},
|
||||
"capacity_records_content_preserving": [
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/p05-02-control-r01/P05_02_NATIVE_8954914_ALIGN_R16.json",
|
||||
"bytes": 319201,
|
||||
"sha256": "9a3c2b531c1fea5aeb6b6ad45d4560fbc1a34e0321289a7c72d20a406ac0ecb6"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/p05-02-control-r01/P05_02_VALIDATION_CHECKOUT_8954914_ALIGN_R17.json",
|
||||
"bytes": 1496745,
|
||||
"sha256": "1f4d4109bce6ba0d3b54239d0e09d420d8e7597a2233692e98170fb5299113e3"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/p05-02-control-r01/P05_02_FULL_ROSTER_8954914_PHASE061_PROFILE_COMPRESSION_R12B.json",
|
||||
"bytes": 664474,
|
||||
"sha256": "0ae3d93e7d130ccb3a698773f1f232a47869195622abb96a88c3c9469d94f07c"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/p05-02-control-r01/P05_02_FULL_ROSTER_RAW_COMPRESSION_8954914_R18.json",
|
||||
"bytes": 2368991,
|
||||
"sha256": "56af5091e7545b65bfe9310658de7b6068a485843564067103e617a6dcbb32c2"
|
||||
}
|
||||
],
|
||||
"evidence_retention_low_resolved": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/p05-02-control-r01/P05_02_LOCAL_CHECK_RAW_LOGS_RETAINED_R01.json",
|
||||
"bytes": 23020,
|
||||
"sha256": "cc7c7b0a995a2378c46219f94b9d3f9a2caa7b0d693eaa71d2077b289b4cc664",
|
||||
"files": 32,
|
||||
"Root_rehash": "Root rehashed all 32 original and copied diagnostic logs: no differences (Root message 2026-10-23)"
|
||||
},
|
||||
"historical_evidence_retained": [
|
||||
{
|
||||
"S": "7808ca76db5f997096cdfbb0edfad0c45479e238",
|
||||
"gate": "validation",
|
||||
"passed": false,
|
||||
"record": "Runner evidence 1"
|
||||
},
|
||||
{
|
||||
"S": "37deb6e877e60e8f19ae4fbc64382880cf7f75bb",
|
||||
"gate": "review",
|
||||
"passed": false,
|
||||
"status": "changes_requested",
|
||||
"record": "Runner evidence 3; p05-02-independent-review-37deb6e-r02/"
|
||||
},
|
||||
{
|
||||
"S": "d65e3da9f33d399c598ad88296e4a9d88d847a7b",
|
||||
"gate": "review",
|
||||
"passed": false,
|
||||
"status": "changes_requested",
|
||||
"record": "Runner evidence 5; p05-02-independent-review-d65e3da-r01/"
|
||||
},
|
||||
{
|
||||
"record": "repair 2 and repair 3 local check first-run failures and prototype failures",
|
||||
"manifest": "P05_02_LOCAL_CHECK_RAW_LOGS_RETAINED_R01.json"
|
||||
}
|
||||
],
|
||||
"remaining_not_claimed": [
|
||||
"P05 package stays in_progress: P05-03 (vault and bank normal operation) is planned; its three human questions are pending and no answer is inferred",
|
||||
"Atomic back_gold through ordinary play is not achieved: the F1 back-facility scene registers its service_attendant by the register_staff command because normal Main cannot hire that role (P09/P11 remaining_paid_role, 2026-10-08); not closed here",
|
||||
"Casino, extortion money and unit-sale revenue sources do not exist yet (disclosed in SPEC section 7)",
|
||||
"Fines and events still pay from front funds only (A09 scope), disclosed without a dedicated test",
|
||||
"UI lows deferred under the feature-first instruction: supply panel shows front-only on-hand next to a front+back after-purchase figure; ledger panel labels front funds as on-hand; the ledger UI is the current mock",
|
||||
"AR.staff.dismissal_severance (P05, D04) is not addressed; the human severance answer is pending",
|
||||
"Same Windows distribution device acceptance, long normal play and whole-game final acceptance remain"
|
||||
],
|
||||
"implementation_provenance": "Contract worker field codex is compatibility only; no Codex or Astra run or dispatch. Implementation, natives and full roster by the registered Claude Opus 5.5 Desktop implementer; commits made by Runner submit; independent review by a separate fresh Opus 5.5 Desktop session",
|
||||
"formal_original_credit_after_ledger": 1,
|
||||
"original_completed_before": 23,
|
||||
"original_completed_after_ledger": 24,
|
||||
"original_delivery_total": 88,
|
||||
"packages_completed": 4,
|
||||
"packages_total": 30,
|
||||
"partial_credit": false,
|
||||
"new_engine_or_full_roster_runs_for_ledger": 0,
|
||||
"all_prior_failures_retained": true,
|
||||
"CLI_auth_recovered": false,
|
||||
"no_new_human_utterance": true
|
||||
}
|
||||
+30
File diff suppressed because one or more lines are too long
@@ -0,0 +1,114 @@
|
||||
{
|
||||
"at": 1792736492.9881642,
|
||||
"by": "Claude Opus 5.5 Desktop (local_a7f85421-3eed-4482-8030-c0dca669573a), the commander that asked the questions",
|
||||
"feature_priority_user_message": {
|
||||
"text_verbatim": "開発作業止めないで。\n\n\nそれと、動作速度の試験と軽量化の作業は後ろ回しにして。理由はUIやアセット含めて入れ替える予定なので、現状のモック版を計画目標に置いてないので。\n\nまずゲームとしての機能実装完了を優先して",
|
||||
"position": "the user turn right after the assistant reply that listed the disk-space candidates (OPT_94F9344_SPACE_CANDIDATES.md, user request 「空きを作る候補(保持している証拠を消さない範囲)を、大きさと一緒に一覧にしてください。削除はまだしないでください。」); before the AskUserQuestion below",
|
||||
"applied_as": "P28 deferred (commander_proposal event 23, action wait); features first (feature-priority-r01/PLAN.md). The reading that UI and asset polish packages also move later is the commander interpretation of the stated reason, not a separate user statement."
|
||||
},
|
||||
"questions_as_presented": {
|
||||
"tool": "AskUserQuestion, one call with four questions, in the assistant turn that followed the feature-priority message",
|
||||
"language": "English (the user then asked 「日本で回答してよ」 as a mid-turn message, right after the answers)",
|
||||
"items": [
|
||||
{
|
||||
"id": "A09",
|
||||
"header": "Payment",
|
||||
"question": "P05-02: Which fund should expenses (upkeep, supplies, wages, repayment, interest, tax) be paid from?",
|
||||
"options": [
|
||||
[
|
||||
"Front, hidden fills gap (Recommended)",
|
||||
"Pay from front funds first, and cover any shortfall from hidden funds. If both are short, the unpaid amount carries over to the next day, the same as upkeep (A01)."
|
||||
],
|
||||
[
|
||||
"Front funds only",
|
||||
"Hidden funds are never used to pay expenses. Any shortfall carries over to the next day as unpaid."
|
||||
],
|
||||
[
|
||||
"Split by expense",
|
||||
"Loan repayment and interest come from hidden funds; all other expenses come from front funds."
|
||||
]
|
||||
],
|
||||
"selected_label": "Front, hidden fills gap (Recommended)"
|
||||
},
|
||||
{
|
||||
"id": "A10",
|
||||
"header": "Rank tax",
|
||||
"question": "P05-02: How should the World Tree rank tax be calculated?",
|
||||
"options": [
|
||||
[
|
||||
"10% of daily sales (Recommended)",
|
||||
"Tax is 10% of that day's sales (front + hidden) and is recorded at 00:00. Initial test value, to be adjusted later."
|
||||
],
|
||||
[
|
||||
"Flat amount per rank",
|
||||
"A flat amount per rank, recorded at 00:00. The amounts are decided in a separate step."
|
||||
],
|
||||
[
|
||||
"Mechanism only for now (0)",
|
||||
"Build the tax ledger entry and its display, with the value left at 0 (unset) for now."
|
||||
]
|
||||
],
|
||||
"selected_label": "10% of daily sales (Recommended)"
|
||||
},
|
||||
{
|
||||
"id": "A11",
|
||||
"header": "Interest",
|
||||
"question": "P05-02: What interest should the dark bank's loans carry?",
|
||||
"options": [
|
||||
[
|
||||
"1% per day on balance (Recommended)",
|
||||
"1% of the outstanding loan balance, recorded at 00:00 each day (whole numbers, rounded down). Initial test value. Principal is not counted as sales."
|
||||
],
|
||||
[
|
||||
"10% fee when borrowing",
|
||||
"10% of the loan amount is charged once, at the time of borrowing, with no daily interest."
|
||||
],
|
||||
[
|
||||
"Mechanism only for now (0)",
|
||||
"Build the interest ledger entry and its display, with the value left at 0 (unset) for now."
|
||||
]
|
||||
],
|
||||
"selected_label": "1% per day on balance (Recommended)"
|
||||
},
|
||||
{
|
||||
"id": "A12",
|
||||
"header": "Old saves",
|
||||
"question": "P05-02: How should the gold held in old saves be handled?",
|
||||
"options": [
|
||||
[
|
||||
"All to front funds (Recommended)",
|
||||
"When an old save is loaded, all of its cash becomes front funds and hidden funds start from 0. Ledger history is kept as is."
|
||||
],
|
||||
[
|
||||
"Do not load old saves",
|
||||
"Old saves made before the funds were split are rejected, leaving their contents unchanged; only new games are supported."
|
||||
]
|
||||
],
|
||||
"selected_label": "All to front funds (Recommended)"
|
||||
}
|
||||
],
|
||||
"answer_returned_by_the_tool": "all four selected labels as above (the tool result lists question text and selected label only)"
|
||||
},
|
||||
"what_counts_as_user_decision": {
|
||||
"root_verified": "the four question texts and the selected labels (rendered in the app)",
|
||||
"presented_but_not_root_verified": "the option descriptions above, copied from the tool call. The user selected an option that carried this description, but the description is not raised to a new or wider human approval beyond the label and the core of the option (A09 front first, hidden covers the gap; A10 10% of daily sales at 00:00; A11 1% per day on the outstanding balance; A12 all cash to front funds).",
|
||||
"opus_spec_choices": [
|
||||
"A10 rounding down (the description does not say it; chosen to match the existing integer handling and A11)",
|
||||
"A09 carry-over applied to the 00:00 obligations (wages and upkeep already have arrears; tax and interest get payable accounts)",
|
||||
"A09 operation-time payments (manual and automatic purchases, repayment) keep the existing refusal when front + hidden cannot pay; nothing is carried for a refused command",
|
||||
"A09 not applied to construction, fines, events, staff signing and defense skills (front funds only, as before)",
|
||||
"A12 detection by the absence of BACK_CASH, without a Runtime snapshot version change"
|
||||
]
|
||||
},
|
||||
"a09_split": {
|
||||
"user_answer_scope": "Front, hidden fills gap (Recommended): pay from front funds first and cover the shortfall from hidden funds",
|
||||
"presented_option_text_about_shortfall": "If both are short, the unpaid amount carries over to the next day, the same as upkeep (A01).",
|
||||
"existing_code_at_base_dda3eed": [
|
||||
"autoload/simulation_runtime.gd repay_loan_principal rejects an amount above the available Gold before posting",
|
||||
"scripts/supply/supply_management_service.gd purchase quotes INSUFFICIENT_FUNDS and refuses the purchase before stock or posting",
|
||||
"payroll and upkeep settlement pay what the cash allows and keep the rest as arrears (A01 for upkeep)"
|
||||
],
|
||||
"commander_interpretation": "the carry-over is for the 00:00 obligations; a user command that front + hidden cannot pay is refused as before (SPEC §3.3)"
|
||||
},
|
||||
"original_credit": 0
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
# P05-02 修正1: 登録の validation の出力先
|
||||
|
||||
- S `7808ca76db5f997096cdfbb0edfad0c45479e238` の正式の validation(`p05-funds`)は、照合の入口で失敗した(生の stderr: `AssertionError: regular final receipt required`)。native の収集(10 suites・765 checks・verify true)と build の場所への写しは成功していた。
|
||||
- 原因: 登録の argv は `--output {build_dir}/p05-funds-evidence`。今の Runner は validation の argv の `{python}` だけを置き換え、`{build_dir}` は文字どおり渡し、build の場所を環境変数 `BUILD_OUTPUT_DIR` で渡す。`tools/run_p05_funds_tests.py` は P18 の runner の写しで、置き換えを Runner に任せていたため、作業ツリーの中の存在しない path を照合した。
|
||||
- 修正: `tools/run_p05_funds_tests.py` は、`--output` が `{build_dir}/` で始まるとき、`BUILD_OUTPUT_DIR`(絶対 path で、symlink でない既存のディレクトリ)で置き換える。それ以外の値の扱い、収集と照合の中身、登録の argv、試験、製品のコードは変えない。
|
||||
- 新しい S では native の収集をやり直す(runner の SHA が変わり、S `7808ca7` の証拠の source map と合わないため)。S `7808ca7` の収集と失敗の validation の記録は保持する。
|
||||
@@ -0,0 +1,32 @@
|
||||
# P05-02 修正2: 実際の経路での表裏の資金・補填・利息・税の滞納と、日次の補填の定義
|
||||
|
||||
- S `37deb6e877e60e8f19ae4fbc64382880cf7f75bb` の whole-unit の独立レビュー(r02)は changes_requested(指摘 1 high・2 medium・3 low)。正規の受付で `repair_pending`、commander の修正2(`COMMANDER_REPAIR_PLAN_P05_02_R02.md`、訂正の追記 `..._CORRECTION_01.md`)。S `37deb6e` までの native・全 roster・validation・review の記録と失敗は全て保持し、新しい S に流用しない。原本は 23/88 のまま。
|
||||
|
||||
## 試験(`tests/economy/test_p05_funds_runtime.gd` に足した場面)
|
||||
|
||||
- 指摘1(F1・F5)`_test_actual_service_funds`: Runtime で、表の施設(`massage_room`、通常の雇用 `staff_petra` を DAY の配置)と、登録済みの裏の札の施設(`adult_private_room`、地下。rank 2・掘削・客用の通路と階段・職員用の通路と階段を通常の command で作る)を建てて開ける。自然に来る客が自分でサービスを選び、地下の個室へは地上から歩いて降りて、完了させる(客を置く fixture は使わない)。00:00 の締めで、massage_room の `SERVICE_CASH_SWEEP` は全て `PLAYER_CASH`、adult_private_room の分は全て `BACK_CASH`(他の行き先 0)、個室の支払が自然に来た客の財布からであること、`funds.revenue_front`・`revenue_back` がその日の行き先ごとの合計と一致すること、売上が表+裏であること、世界樹ランク税が両方の売上の合計の 10%(切り捨て)で払われたこと、裏金の残高と増減がその日の裏の売上と一致することを確かめる。行き先の選択を元に戻すとこの場面は失敗する。
|
||||
- 指摘2(F2):
|
||||
- 人件費: 同じ場面の2日目、締めの前に表金を 5 に下げ(台帳の fixture。下げるだけ)、本物の締めが実際の雇用の給与・維持費・税を払う。`funds.back_top_up` が(給与+維持費+税−5)、給与の未払い 0、表金 0、裏金がその分減ることを確かめる。
|
||||
- 自動の仕入れ `_test_operation_top_ups_and_day_total`: 倉庫に自動の仕入れ(10 個)を設定し、表金 2・裏金 1,000 で時計を進める。`advance_auto_purchases` の本物の注文が 30 で買い、領収(`SupplyAutoPurchasePlans.receipt_from_ledger`)が検証され、28 が裏金からの補填(鍵 `finance.back_top_up.purchase.<注文の鍵>`)であることを確かめる。
|
||||
- 旧暦の 00:00 の維持費 `_test_legacy_midnight_top_up`: `tests/mvp/test_upkeep_legacy_midnight.gd` と同じ作り方の旧暦の save(締めが 06:00)を読み、表金 0・裏金 500 で 00:00 を越える。00:00 の維持費 1 が裏金から補填され(鍵 `finance.back_top_up.midnight.1440`)、滞納 0、06:00 の締めの report がその補填を数えることを確かめる。
|
||||
- 指摘3(F4): 同じ `_test_operation_top_ups_and_day_total` で、1,000 を借り(台帳の fixture。借入の発生源は無い)、返済の command(`repay_loan_principal`)で 200 を返す(表金 0 なので裏金で払う)。その日の締めの利息が返済の後の残高 800 の 1% の 8 で、払われることを確かめる。
|
||||
- 指摘4(F3)`_test_tax_arrears_next_day`: 売上 200 の日に表裏の資金が無く、税 20 が未払いのまま繰り越す(日次の滞納と Operations の report に出る)。翌日の締めで古い 20 と新しい 10 を払い、税の未払いが 0 になることを確かめる。
|
||||
- 各場面の最後に save の写しから戻し、そのまま一致することを確かめる。
|
||||
|
||||
## 日次の補填の定義(指摘5)と、保存済みの report の互換
|
||||
|
||||
- `funds.back_top_up` は、その営業日に記帳された全ての補填(操作の時の仕入れ・自動の仕入れ・返済、旧暦の 00:00 の維持費、締めの補填)の合計にした(`autoload/simulation_runtime.gd` の `_daily_fund_report`)。営業日の区切りは売上の集計と同じ(period_start 以上・締めの分より前)。締めの補填は鍵で数え、前日の締めの補填(period_start の分に記帳)は数えない。帳簿の画面の締めの欄「裏金から表金へ補填」はこの値を出す。
|
||||
- report に定義の版 `fund_report_version: 2` を入れた。版の無い report は、S `7808ca7`・`37deb6e`(統合されていない)で保存された、締めの補填だけを数えた定義(版1)。読込の照合(`settlement.funds` と台帳から作り直した report の比較)は、保存された report が書かれた定義で比べる。版の無い report は版1の式でだけ受け、新しい式の値を持つ版の無い report、版2の印で締めの分だけの値の report、知らない版の report は拒む(緩めない)。A12(資金を分ける前の save)は report を持たないので、今までどおり(帳簿の記帳が無いことを確かめる)。版1の report は書かれた時のまま残し、画面は「(締めの時の分のみ)」と出す。
|
||||
- 試験: 同じ日に注文・返済・締めの3つの補填がある save から、版1の形の report(印を外し、締めの分の値)を作って読めること、その値と表示が残ること、上の3つの不正な形が拒まれ何も変えないことを確かめる。
|
||||
|
||||
## 開示(指摘6 と、修正2で分かったこと)
|
||||
|
||||
- 今のゲームで裏金に入る売上の発生源は、登録済みの施設では `adult_private_room` だけ。`black_market`(trade。原子要件 income_trade のユニット売却に当たる)と `vip_private_salon` は拡張の定義で既定では登録されない。カジノ・脅迫金・ユニット売却の発生源は無い。原子要件 income_casino・income_blackmail・income_trade は未達のままで、この開示で完了にはしない。
|
||||
- 定義が引けない施設の売上は、既存の挙動どおり表金に入る(裏の札の判定ができないため。`_apply_service_transaction` の注記)。定義の無い施設を扱う既存の経路は変えない。
|
||||
- F1 の実際の売上の経路の試験で、`adult_private_room` の担当(役割 `service_attendant`)は Runtime の `register_staff` の command で登録した職員(fixture の職員。給与 0 の FREE_HIRE)。通常の雇用の候補にこの役割は無く、`_staff_role_matches_facility` にも代わりの役割は無いので、通常の Main(雇用と配置の画面)ではこの施設に職員を置けず、裏の売上を得られない。これは原 work の P09・P11 の `current_paid_role_compatibility_checkpoint.remaining_paid_role`(「adult_private_room/service_attendant has no canonical paid hire candidate」、2026-10-08、`work_packages.json`)に残っている原残件で、通常の Main での全施設の営業は未達。P05-02 では職員の候補・役割・給与・定義を作らず、P09・P11 の残件はこの開示で閉じない。
|
||||
- `adult_private_room` の定義の `unlock_rank` 3・`research_requirements`(discreet_hospitality)・`opening_schedule`(夜だけ)は、今の Runtime では建設や営業の条件として使われていない(定義の検査だけ)。試験は rank 2(地下の開放)で建て、夜に営業させた。P05-02 では変えない。
|
||||
- 補填の元にした表金の減少(台帳の fixture)と借入(台帳の fixture)は、残高を作るだけで、確かめる支払・補填・利息・税は本物の経路。
|
||||
|
||||
## 変えていないもの
|
||||
|
||||
- 登録の argv、`tools/run_p05_funds_tests.py`、狭い roster(8 suites+再開の2段、実 10 段)、試験の既存の期待値(`EXPECTATION_CHANGES.json` に足すものは無い)。
|
||||
@@ -0,0 +1,24 @@
|
||||
# P05-02 修正3: 通常の帳簿の画面の返済(A09)と、残りの試験と開示
|
||||
|
||||
- S `d65e3da9f33d399c598ad88296e4a9d88d847a7b` の whole-unit の独立レビュー(fresh、local_92520fb7)は changes_requested(medium 1・low 4)。F1〜F8 は証拠の上で満たすとされ、残りは下の指摘。正規の受付で `repair_pending`、commander の修正3(`COMMANDER_REPAIR_PLAN_P05_02_R03.md`、event 28、repairs 3)。S `d65e3da` までの native・全 roster・validation・review・容量の記録と失敗は全て保持し、新しい S に流用しない。原本は 23/88 のまま。
|
||||
|
||||
## 直したもの
|
||||
|
||||
- 指摘1(medium、F2 と横断条件「原本条件と UI 操作の一致」): 通常の帳簿の画面(`scripts/ui/operating_ledger_panel.gd`)の返済の上限・案内・拒否を、表金だけ(`cash`)から表金+裏金(`FinancePolicy.available_funds`)にした。`SimulationRuntime.get_loan_principal_report` に `back_cash` と `available_funds` を足した(`cash` は今までどおり表金)。案内は「表金 xG・裏金 yG(計 zG)から元本を返済します。表金で足りない分は裏金から補填します。」と出し、完済に足りない時は「完済には表金と裏金の合計が不足しています。」と出す。返済の command は今までどおり Runtime の `repay_loan_principal` で、表金から先に払い、残りを裏金からの補填と同じ1つの取引で記帳する(A09)。既存の試験が確かめる文言(「借入はありません」「受理済み」「実行できません」「…Gを返済」)は残した。
|
||||
- 試験 `_test_ledger_repayment_uses_hidden_funds`: 通常の帳簿の画面の部品で、表金 20・裏金 300 の時に 200 の返済が押せ、1つの取引(裏金からの補填 180 → 元本 200)になること、残元本 800 の完済は表金+裏金 120 では押せず、500 の指定も押せず、押しても状態が変わらないこと、裏金が 820 になると完済が押せ、1つの取引(補填 800 → 元本 800)で完済し、表金 0・裏金 20・Gold が表金を写すこと、save から戻してそのまま一致すること。
|
||||
- 指摘2(low): 税と利息の滞納も §3.8 の `arrears_total` に入るので、建設と投資の制限にも入る(開示)。締めの日次の記録の `arrears_total` に税・利息の滞納が入り、Operations の調整役の判定(`can_start_construction_or_investment`)が `ARREARS_REMAIN` で止める。その後の Runtime の判定(`_operations_construction_investment_gate`。`_operations_total_arrears` に `RANK_TAX_PAYABLE` と `LOAN_INTEREST_PAYABLE` の残高が入る)も、残高が残る間は `UNPAID_EXPENSES` で止める。試験 `_test_tax_arrears_next_day`: 税の滞納 20 がある日に建設の command が `ARREARS_REMAIN`(arrears 20)で拒まれ、翌日の締めで払った後に建てられる(表金から 20)こと(局所の確認で実際の理由が `ARREARS_REMAIN` だったので、レビューの指摘の `UNPAID_EXPENSES` ではなくそれを確かめる)。
|
||||
- 指摘3(low): 試験を足した。表金 0・裏金 663 の時に建設(`guest_corridor`、20)が拒まれ、裏金が減らず、補填が増えないこと(建設は A09 の対象外。`_test_operation_top_ups_and_day_total`)。税の滞納が残る日に、帳簿の画面の締めの欄が「未払い 税 20G・利息 0G」、今日の欄が「(未払い 20G)」と出すこと(`_test_tax_arrears_next_day`)。罰金・event の支払など他の対象外の支払は、コードで表金だけから払うことを確かめたが試験は無い(開示)。
|
||||
|
||||
## 文言の補足(指摘4、low)
|
||||
|
||||
- 凍結の SPEC(契約の `spec_sha256`)と DB の契約は変えない。SPEC §3.5 の「元本の出入り(financing_principal_*)は今までどおり売上と経費に入れない」は、原本どおり次の意味で読む: 借入の元本の入金は売上に入れない(F4)。元本の返済は原本どおり日次の経費(`financing_principal_outflows`。`scripts/operations/operations_coordinator.gd` の `_net_profit` が日次の利益から引き、帳簿の画面は「元本返済(決算経費)」と出す。原子要件の借金返済の経費)。この単位はこの挙動を変えていない。
|
||||
|
||||
## 範囲の外で直していないもの(指摘5、low)
|
||||
|
||||
- 仕入れの画面 `scripts/ui/supply_management_panel.gd` は契約の allowed_paths の外なので、この単位では直さない。今の画面は「手持ち %dG」に表金だけを出し、見積の「購入後 %dG」は preview の `cash_after`(A09 で表金+裏金からの残り)を出すので、例えば表金 2・裏金 100 で 15 の仕入れでは「手持ち 2G」と「購入後 87G」が並ぶ。操作は A09 どおりに通る。
|
||||
- 具体的な差分の案: 「手持ち %dG」を「表金 %dG・裏金 %dG」に分けて出し(`economy_service.get_balance(FinancePolicy.BACK_CASH)`)、「購入後 %dG」を「購入後(表金+裏金)%dG」とし、表金で足りない分を裏金から補うことを一言出す。
|
||||
- 正規の扱い: この画面を範囲に含む別の登録(仕入れ・UI の単位、または P05-03 の登録で allowed_paths に入れる)で直す。それまでは未修正で、P05-02 の完了に数えない。
|
||||
|
||||
## 変えていないもの
|
||||
|
||||
- 登録の argv、`tools/run_p05_funds_tests.py`、狭い roster(8 suites+再開の2段、実 10 段)、試験の既存の期待値(`EXPECTATION_CHANGES.json` に足すものは無い)、凍結の SPEC、DB の契約。
|
||||
@@ -0,0 +1,111 @@
|
||||
# P05-02 全費目と資金区分 仕様(登録版 r01)
|
||||
|
||||
- 状態: RootがOpusの登録候補r02を採用し、登録資料としてmainに固定する。登録・下書き試験は実装受入ではない。原本の加算はwhole-unitのfinalの後だけ。出典は同フォルダのPROVENANCE.json。
|
||||
- 前提: P04 は正式完了(b1810ff4)、P05-01 は正式受入済み。ユーザーの発言(2026-10-23)「開発作業止めないで。」「動作速度の試験と軽量化の作業は後ろ回しにして。理由はUIやアセット含めて入れ替える予定なので、現状のモック版を計画目標に置いてないので。」「まずゲームとしての機能実装完了を優先して」により、機能の実装を性能(P28)より先にする(`feature-priority-r01/PLAN.md`。会話の位置と原文は同じフォルダの `PROVENANCE.json`)。
|
||||
|
||||
## 1. 原本の条件(変えない)
|
||||
|
||||
- work: 表金・裏金、維持費未払い、仕入れ、人件費、元本、利息、税を帳簿へ接続する。
|
||||
- acceptance: 3営業日で資金増減と台帳が一致し、借入元本を売上に含めない。
|
||||
- 原子要件(`atomic_requirements.json`、P05): 表金と裏金を分けて保持する(`AR.economy.front_gold_account`・`back_gold_account`)、表のサービスで表資金・裏のサービスで裏資金を得る(`front_gold`・`back_gold`)、飲食・宿泊は表の収益(`income_food`・`income_lodging`)、成人施設・カジノ・脅迫金・ユニット売却は裏の収益(`income_adult`・`income_casino`・`income_blackmail`・`income_trade`)、仕入れ・人件費・維持費・借金返済・世界樹ランク税を経費に記帳(`expense_*`)、日次決算で表裏の売上から全対象経費を引く(`settlement_calculation`)、00:00 の決算レポート(`midnight_report`)、維持費の A01・A02(`upkeep.*`)。
|
||||
- P28 の横断条件6件と同じ形の横断条件(原本条件と UI 操作の一致 / Runtime の実利用・会計・行動へ接続 / 途中保存・再起動で再現 / 異常・境界・中断 / 関連回帰 PASS / 要件別ログとソースハッシュ)を、この単位の範囲で適用する。
|
||||
|
||||
## 2. 決定(捏造しない)
|
||||
|
||||
- 既存: A01(維持費は保有時間で日額按分、00:00 精算、不足は翌日へ繰越)、A02(維持費の初期試験値)。
|
||||
- 今回のユーザーの決定(2026-10-23、`feature-priority-r01/DECISIONS_P05_02.json`):
|
||||
- A09: 維持費・仕入れ・人件費・借入返済・利息・税は表金から払い、足りない分を裏金で補う。Rootが直接確認した回答は「Front, hidden fills gap (Recommended)」。この回答だけから、両資金が不足した場合の全費目の繰越までをユーザー決定とはしない。§3.3 の日次滞納と操作時拒否の区別は、既存挙動とOpusの仕様案に由来する。
|
||||
- A10: 世界樹ランク税は、その日の売上(表+裏)の10%を00:00に経費として計上する(初期試験値)。
|
||||
- A11: 闇銀行の利息は、借入残高の日利1%を毎日00:00に計上する(整数・切り捨て、初期試験値)。元本は売上に含めない。
|
||||
- A12: 表裏に分ける前の旧セーブは、所持金の全額を表金にし、裏金は0から始める。帳簿の履歴はそのまま。
|
||||
- 出典区分: Rootが直接確認したのは4質問と選択回答のラベル。A10の「10% of daily sales (Recommended)」、A11の「1% per day on balance (Recommended)」、A12の「All to front funds (Recommended)」。選択肢の説明文は、質問をした Opus の道具の呼出しから写して `PROVENANCE.json` に置く(Root は未確認)。説明文は、ラベルと選択肢の中心(A09 表金から先・不足を裏金で補う、A10 その日の売上の10%を00:00、A11 借入残高の日利1%、A12 所持金の全額を表金)を超える新しい人間の承認としない。精算時点・整数切り捨て・帳簿履歴保持などの詳細はOpusの登録仕様案および原要件に由来し、未確認の人間発言として追加しない。根拠は Root の `p05-02-owner-registration-draft-r01/SOURCE_RECONCILIATION.json` と、このフォルダの `PROVENANCE.json`。
|
||||
- まだ決めていないもの(この単位では作らない): ランクごとの税の差、借入の上限・期限・滞納から脅威(P05-03)、カジノ・脅迫金の発生源(後の単位)、D05 のうち下の §3.6 以外の同時発生。
|
||||
|
||||
## 3. 範囲
|
||||
|
||||
### 3.1 口座
|
||||
|
||||
- 表金は既存の `PLAYER_CASH`(意味を変えない。`gold` の資源は今までどおり表金を映す)。裏金は新しい口座 `BACK_CASH`(負を許さない)。
|
||||
- 税と利息は、維持費と同じく「計上(経費 → 未払金)」と「支払(未払金 → 資金)」の2つの記帳にする。未払金の口座に残った額が滞納。
|
||||
|
||||
### 3.2 収入の振り分け
|
||||
|
||||
- 施設の売上は今までどおり施設の売上の口座に入り、その後の移し替え(`SERVICE_CASH_SWEEP`)の行き先を、施設の定義の札で決める: `restricted_service` か `adult_only` があれば `BACK_CASH`、それ以外は `PLAYER_CASH`。今の定義では `adult_private_room`・`black_market`(`trade`)・`vip_private_salon` が裏。
|
||||
- カジノと脅迫金は今のゲームに発生源が無い。同じ札の規則で裏に入ることを試験で固定し、発生源は後の単位で作る(開示)。
|
||||
|
||||
### 3.3 支払の規則(A09)
|
||||
|
||||
- 00:00 の精算の支払(維持費、人件費、税、利息): その日に払う額の合計(滞納を含む)のうち表金で足りない分だけを、精算の前に裏金から表金へ補填する(区分 `BACK_FUND_TOP_UP`、売上でも経費でもない記帳)。その後の支払は今までどおり表金から行う。両方で足りない分は、それぞれの滞納として翌日へ繰り越す(維持費と人件費は既存の滞納の仕組み、税と利息は未払金の口座)。締めが 06:00 の旧暦の 00:00 の維持費の精算にも同じ補填を行う。
|
||||
- 操作の時の支払(仕入れの手動と自動、借入の返済): 表金+裏金が足りれば、表金で足りない分を裏金から補填して表金から払う。返済は補填と支払を1つの取引にする。仕入れは、記帳が1つの領収の形を保つため、補填を直前の別の取引にし、仕入れが失敗した場合は両方を取り消す。足りなければ今までどおり拒否し、Gold と在庫を変えない。見積の「払える額」は表金+裏金。
|
||||
- それ以外の支払(建設、罰金、event、雇用の契約金、防衛の技能、event の預り金)は今までどおり表金だけ(A09 の対象外。開示)。
|
||||
- 出どころの区分: 「表金から先に払い、足りない分を裏金で補う」はユーザーの回答(A09 のラベル)。両方で足りない場合に、00:00 の義務は滞納として繰り越し、操作の時の支払は拒否する、という区別は、ユーザーの決定ではなく、既存のコード(base `dda3eed` の返済と仕入れは払えない額を拒否する。人件費と維持費は払える分を払い残りを滞納にする)と、それに合わせた Opus の解釈(拒否した操作には繰り越すものが無い)による。
|
||||
|
||||
### 3.4 世界樹ランク税(A10)
|
||||
|
||||
- 第 d 日の 00:00 の精算で、税 = floor(売上_d × 10 / 100)。売上_d は日次決算の売上(表+裏、既存の返金の相殺の後)。0 なら記帳しない。
|
||||
- 区分は `RANK_TAX`、決算の `taxes_and_interest` に入れる。第 d 日の決算に入り、第 d+1 日に二重に数えない。
|
||||
|
||||
### 3.5 闇銀行の利息(A11)
|
||||
|
||||
- 毎日 00:00 に、利息 = floor(借入残高 × 1 / 100)。借入残高は、その時点の全ての借入の元本の合計(`LOAN_PRINCIPAL_BORROWED` − `LOAN_PRINCIPAL_REPAID`)。0 なら記帳しない。
|
||||
- 区分は `LOAN_INTEREST`、決算の `taxes_and_interest`。元本の出入り(`financing_principal_*`)は今までどおり売上と経費に入れない。
|
||||
|
||||
### 3.6 00:00 の順序
|
||||
|
||||
- 00:00 の日次の締めの中で、補填 → 人件費 → 維持費(既存の順序)→ 税 → 利息。税と利息は、それぞれの未払金の口座に古い滞納が残っていれば、新しい額より先にそれを払う。
|
||||
- 途中保存・再開・同じ分の再試行で、税と利息を二重に計上・支払しない(日ごとの冪等の key)。
|
||||
|
||||
### 3.7 決算とレポート
|
||||
|
||||
- 日次決算に、売上の内訳(表・裏)、税・利息、税と利息の滞納を足す。利益の式(売上 − 全対象経費)と、3回連続の赤字で終了の規則は変えない。
|
||||
|
||||
### 3.8 保存(A12)
|
||||
|
||||
- 資金の口座(`BACK_CASH`、税と利息の経費と未払金)は帳簿の snapshot の中にあるので、Runtime の snapshot の版は上げない(版 15 はミミの購入の履歴と一致させて検査しているため、版の組を増やさない)。旧セーブは `BACK_CASH` が無いことで判定し、他の資金の口座も無いことを確かめてから、それらを 0 で作る(A12。`PLAYER_CASH` と帳簿の履歴はそのまま)。一部だけがある、または性質の違う口座は拒否し、読込の前の状態を保つ。
|
||||
- 日次の決算の精算の報告(`settlement`)に、帳簿だけから求める資金の報告 `funds`(表裏の売上、補填、税・利息とその支払、税・利息の滞納)を足す。決算の区分(`category_totals`)の形は変えず、税と利息は既存の `taxes_and_interest` に入れる。読込の時は `funds` を帳簿から求め直して一致を確かめ、滞納の合計(`arrears_total`)は人件費・維持費・税・利息の滞納の和と照合する。分ける前に確定した日は `funds` を持たない(その日に資金の記帳が無いことを確かめる)。
|
||||
|
||||
### 3.9 表示
|
||||
|
||||
- 帳簿の画面と日次のレポートに、表金・裏金の残高、表裏の売上、税、利息、滞納を出す。今の UI は後で入れ替えるので、見た目は今の部品のまま、通常の操作で読めることだけを求める。
|
||||
|
||||
## 4. 受入条件(全て必須。部分点なし)
|
||||
|
||||
| ID | 内容 |
|
||||
|---|---|
|
||||
| P05-02-F1 | 表金と裏金が別の口座で、裏の札の施設の売上だけが裏金に入る(表・裏の施設を各1つ以上で確かめる) |
|
||||
| P05-02-F2 | A09 の支払の規則(00:00 の精算、操作の時、対象外の支払が今までどおり)。境界: 表金だけで足りる、裏金で補う、両方で足りず滞納、滞納の翌日の支払 |
|
||||
| P05-02-F3 | A10 の税の値・切り捨て・時点・決算の区分・滞納 |
|
||||
| P05-02-F4 | A11 の利息の値・時点・借入の後と返済の後の残高・元本が売上に入らない |
|
||||
| P05-02-F5 | 3営業日の照合: 表金・裏金それぞれの日ごとの増減が帳簿の合計と一致し、決算の全ての区分が帳簿と一致し、元本が売上に入らない |
|
||||
| P05-02-F6 | 途中保存・00:00 の前後の保存・別の process での再開で、税・利息・振り分けが二重にならず同じ結果。旧 snapshot の移行(A12)と不正値の拒否 |
|
||||
| P05-02-F7 | 帳簿の画面と日次のレポートで、表金・裏金・表裏の売上・税・利息・滞納が読める(通常の操作で開く) |
|
||||
| P05-02-F8 | 関連の回帰と最終Sの全 roster が PASS。既存試験の期待値変更はA09・A10・A11の承認済み差、または§3.8のA12旧セーブ移行の差だけに限定し、全件を EXPECTATION_CHANGES.json に変更前後の値・計算または構造差・根拠つきで一覧化する。A12は旧セーブへの5口座(BACK_CASH、LOAN_INTEREST_EXPENSE、LOAN_INTEREST_PAYABLE、RANK_TAX_EXPENSE、RANK_TAX_PAYABLE)の追加だけとし、残高・既存口座・取引履歴・その他の帳簿フィールドの不変をbyte比較で確認する。元の失敗を保存し、未解決の試験を除外してPASSにしない |
|
||||
|
||||
## 5. 変更してよいパス(候補)
|
||||
|
||||
- `autoload/simulation_runtime.gd`
|
||||
- `scripts/economy/facility_upkeep_settlement.gd`(払う額の見込みの純粋な関数)、`scripts/economy/finance_policy.gd`(新規、口座・税率・利率・裏の札)と `.uid`
|
||||
- `scripts/supply/supply_management_service.gd`(見積の払える額と、仕入れの前の補填)
|
||||
- `scripts/ui/operating_ledger_panel.gd`
|
||||
- `tests/economy/test_p05_funds_runtime.gd`(新規)と `.uid`
|
||||
- `tests/**` のうち既存の試験の期待値の更新(§4 F8 の規則の範囲だけ。一覧 `execution/p05-02/EXPECTATION_CHANGES.json` が必須)
|
||||
- `tools/run_p05_funds_tests.py`(新規、既存の guarded runner の protocol を使う)、`tools/regression/suite-policy.json`
|
||||
- `docs/plans/original-completion-2026-10-02/execution/p05-02/**`、`docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json`
|
||||
|
||||
## 6. 検証
|
||||
|
||||
- 登録の validation `p05-funds`: `tools/run_p05_funds_tests.py --output {build_dir}/p05-funds-evidence`(検証だけ。収集は Root の役割の native で `--collect`)。roster は8本: 新しい `tests/economy/test_p05_funds_runtime.gd` と、関連の `tests/mvp/test_original_midnight_settlement.gd`、`tests/mvp/test_runtime_operations_integration.gd`、`tests/s2/test_runtime_operations_cycle.gd`、`tests/s6/test_runtime_save_lifecycle.gd`、`tests/s6/test_runtime_snapshot_serialization.gd`、`tests/supply/test_supply_management_service.gd`、`tests/ui/test_upkeep_closed_ui.gd`。runner は P18 の guarded runner と同じ protocol(roster・名前・数を roster から求める点だけが違う)。
|
||||
- `tests/mvp/test_runtime_upkeep.gd` は source の中に報告のファイルを生成する suite(`suite-policy.json` の `fixed_generated_report_routes`)で、狭い roster の規則(生成物なし)に合わないので入れない。維持費を裏金で補う場面は新しい suite に入れ、`test_runtime_upkeep.gd` は最終の全 roster で確かめる。`tests/ui/test_business_management.gd` は古い有界の音声の後始末(`old_bounded_teardown`)の suite で、その証明は置き直した証拠では照合できない(証明が元の絶対パスを持つ)ので、狭い roster に入れず最終の全 roster で確かめる。帳簿の画面の新しい表示(F7)は、新しい suite が実際の `OperatingLedgerPanel` に4日分の帳簿を読ませて確かめる。
|
||||
- F6 の別の process での再開: 下書きの新しい suite は、同じ process での snapshot の保存と読み直し、セーブファイルへの書き出しと読み直し(`save_to_path`・`load_from_path`)までを確かめている。別の process での再開(書き手の process が書いたセーブファイルを、別の process の読み手が読んで同じ結果になること)は、まだ実装していない。登録の後に `tools/run_p05_funds_tests.py` と `tests/**` の範囲で、書き手と読み手の2つの段として足す。実装できない場合は F6 を満たさないものとして扱い、範囲を狭めない。
|
||||
- final の前に、最終の S で全 roster を1回(重複の全回帰はしない)。
|
||||
- 独立レビュー: 実装担当とは別の新しい Opus が、原 work・原 acceptance・横断条件・U1/U2 を whole-unit で判定する。
|
||||
|
||||
## 7. 開示(U1)
|
||||
|
||||
- カジノと脅迫金の発生源は無い(規則だけを固定)。ランクごとの税の差は作らない(A10 は一律10%の試験値)。借入の上限・期限・滞納から脅威、金庫の上限は P05-03。建設・罰金などは表金だけで払う(A09 の対象外)。UI は今の部品のままで、完成の見た目ではない。Windows 実機は後。
|
||||
|
||||
## 8. 登録の前の下書き(正式の証拠ではない)
|
||||
|
||||
- 下書きの作業ツリーと状態: `p05-02-registration-proposal-r01/DRAFT_STATUS.md`、全試験の直接の実行の診断 `p05-02-registration-proposal-r01/DIAGNOSTIC_SWEEP_R01.json`(今回の変更で期待値が変わったのは `tests/business/test_required_kitchen.gd` の A12 だけ。他の 0 以外は base `dda3eed` でも同じ)。
|
||||
- 下書きでの guarded の収集(`local-collection-failures/`): r01 は import の段で停止(新しい suite の `.uid` が無かった)。r02 と r03 は 10 本の収集は成功したが、runner の自己検証が失敗した(r02 は P18 の数 7 が残っていた、r03 は報告を生成する suite が roster にあった)。いずれも正式の合格ではなく、失敗の出力を保持する。
|
||||
- 登録の後は、下書きの成果を SHA の一致で W に引き継ぎ、W で局所の確認と通常の owner submit を行う。下書きでの実行は数えない。
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"task": "P07-01",
|
||||
"spec": "docs/plans/original-completion-2026-10-02/execution/p07-01/SPEC.md",
|
||||
"rule": "SPEC F8: every changed expectation of an existing suite is listed with the value before and after and the reason. Only the natural-weather contract (A13, SPEC sections 4 and 5) causes these changes.",
|
||||
"changes": [
|
||||
{
|
||||
"suite": "tests/environment/test_runtime_weather.gd",
|
||||
"check": "old neutral save migration preserves other state",
|
||||
"before": "runtime.create_snapshot() == original (the whole snapshot after migrating a version 10 save equals the original new game)",
|
||||
"after": "the whole snapshot without systems.runtime.weather equals the original without it, and the migrated weather has last_draw_day -1 and no drawn day",
|
||||
"reason": "A13: a new game opening at 06:00 draws its first day (SPEC 4.1), and a pre-weather save has no draw and must not draw for the past (SPEC 5.2). Only the weather section can differ; every other system stays byte-equal."
|
||||
},
|
||||
{
|
||||
"suite": "tests/environment/test_runtime_weather.gd",
|
||||
"check": "future weather clock rejects atomically",
|
||||
"before": "the refused restore leaves runtime.create_snapshot() == original",
|
||||
"after": "the refused restore leaves runtime.create_snapshot() == the migrated snapshot taken just before",
|
||||
"reason": "The state before this refusal is the migrated state of the previous check, which now differs from the original only in the weather section; the check still proves the refusal is atomic."
|
||||
},
|
||||
{
|
||||
"suite": "tests/environment/test_weather_timeline.gd",
|
||||
"check": "malformed or contradictory state rejects atomically (unknown version case)",
|
||||
"before": "{\"version\": 2} is an unknown version",
|
||||
"after": "{\"version\": 3} is an unknown version",
|
||||
"reason": "Version 2 is now the current weather snapshot (natural state and rain holds, SPEC 5.1); version 1 is read as the legacy format (SPEC 5.2)."
|
||||
},
|
||||
{
|
||||
"suite": "tests/environment/test_weather_recovery.gd",
|
||||
"check": "disk-restored recovery occurs once at scheduled minute",
|
||||
"before": "after the recovery the pending weather list is empty",
|
||||
"after": "after the recovery the pending weather list equals only the opening day's natural draw (rain 16:30-22:30 for seed 424242)",
|
||||
"reason": "A13: the opening day's natural draw schedules its own start and return to clear (SPEC 4.1); the manually scheduled storm and recovery are still consumed exactly once."
|
||||
}
|
||||
],
|
||||
"unchanged_related_suites_passing_locally": [
|
||||
"tests/mvp/test_runtime_operations_integration.gd",
|
||||
"tests/s2/test_runtime_operations_cycle.gd",
|
||||
"tests/s2/test_runtime_mana_reserve.gd",
|
||||
"tests/s2/test_mana_reserve_cycle.gd",
|
||||
"tests/s3/test_guest_service.gd",
|
||||
"tests/s6/test_runtime_save_lifecycle.gd",
|
||||
"tests/s6/test_runtime_snapshot_serialization.gd",
|
||||
"tests/ui/test_main_dashboard.gd"
|
||||
],
|
||||
"local_run_scope": "diagnostic direct runs on a scratch stage; the formal evidence is the submitted S native collection, the registered validation and the final full roster"
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
# P07-01 実装の記録(実 Opus の実装担当、2026-10-24)
|
||||
|
||||
- 仕様: 同じフォルダの `SPEC.md`(固定の commit `4654789`、Runner の `spec_sha256` `2626d70b…7956`)。決定 A13(`PROVENANCE.json`)。
|
||||
- この記録は、仕様の §4・§5 を実装した場所と、実装担当として選んだ詳細、開示を書く。人の新しい承認は主張しない。
|
||||
|
||||
## 1. 実装した場所
|
||||
|
||||
- `scripts/environment/natural_weather_policy.gd`(新しい): A13 の値(確率の境 60・85・95、続く時間 360・240・120 分、開始の 29 個の 30 分刻み、雨の来客 50%、雨の延長の上限 240 分、霧のマナ ×1.2・性欲 ×1.5)と、1日1回の抽選の手順。天候の仕組みは確率を持たない方針のままにするため分けた。
|
||||
- `scripts/environment/weather_timeline.gd`(VERSION 2): 自然天候の状態(最後に抽選した日、その日の抽選)と雨の延長の記録を持つ。予定の規則(同じ分の別の種類・過去の時刻・上限)は変えない。その日の抽選の当てはめ(`apply_draw`)は、その日の 06:00 にだけ、1回だけ受け付ける。予定と重なれば `SCHEDULE_CONFLICT` で拒む。VERSION 1 の保存は、過去の抽選をさかのぼらない形で読む。
|
||||
- `autoload/simulation_runtime.gd`:
|
||||
- 系列 `weather` を足した。
|
||||
- 06:00 の抽選と出来事 `WEATHER_FORECAST_DRAWN`。新しいゲームが 06:00 に始まる時は、初期化の最後に開始の日を抽選する。
|
||||
- 来客の受付の境界は、雨の割合(`arrival_percent`)を渡す。
|
||||
- 帰りの判定は、雨の延長(`_hold_guest_for_rain`)を通す。
|
||||
- マナの境界は、霧の産出を渡す。
|
||||
- 客の生理の文脈に `mana_fog` を足した。
|
||||
- 予報を含む `get_weather_report`。
|
||||
- 復元では、延長の記録と客の予定を照合し、旧保存の系列を `world_seed` から作る。
|
||||
- `scripts/operations/operations_coordinator.gd`: 来客の計画を2段にした(行ごとの受け入れと持ち越しを決め、雨なら合計を floor(×50/100) まで後ろの行から減らし、減らした分は持ち越さない)。雨でない時の計画と客の ID は今と同じ。
|
||||
- `scripts/mana/mana_reserve_cycle.gd`: 境界の世界樹の産出を引数で受ける(既定 20、霧 24)。炉の受け入れの判定と `plan` も霧の量(24・120)を受ける。保存の照合は産出 20 と 24 だけを許す。上限 300(A06)は変えない。
|
||||
- `scripts/guest/guest_service.gd`: 文脈の真偽値 `mana_fog` を検査し、成人確認済みの客の正の性欲の増分 n を n + floor(n / 2)(= floor(1.5n))にする。
|
||||
- `scenes/main/main.gd`: 上部の天候の名前の表示はそのままにし、隣に予報(`WeatherForecastLabel`、「次: 晴れ 12:00」「次: 予定なし」)を足した。天候の名前の案内(tooltip)に効果の案内を出す。
|
||||
|
||||
## 2. 実装担当として選んだ詳細(SPEC §2 のとおり人の承認ではない)
|
||||
|
||||
- 抽選の乱数: 0〜99 の整数を1つ、晴れ以外は開始の枠(0〜28)を1つ引く。
|
||||
- 日の数え方: 絶対分 ÷ 1440 の切り捨て。06:00 ちょうどは、まだ抽選していない日として数える(新しいゲームの開始の日のため)。旧保存の移行では、06:00 を過ぎていればその日は抽選済みとして数える。
|
||||
- 当てはめに失敗した抽選(手で置いた予定と同じ分になる時など)は、系列を進めたまま `applied: false` と理由を出来事に残し、その日の予定は変えない。次の日の 06:00 には抽選する。
|
||||
- 雨の延長は、帰りの判定(客が「決める」状態の時)で行う。延長の後の時刻には天候に関わらず今の規則で帰る。帰った客の記録は、天候の境界で片づける。
|
||||
- 霧の性欲は、その分の正の増分だけに掛ける(減少と、成人確認の無い客は変えない)。
|
||||
|
||||
## 3. 開示
|
||||
|
||||
- マナ炉の施設と産出の経路は今のゲームに無い(P06 の未決定)。霧の炉の ×1.2(120)は、マナの貯蔵の規則(受け入れの判定と `plan`)にだけ入り、通常の営業で観測できるのは世界樹の産出(20→24)だけ。
|
||||
- 今の客の種類で性欲が増えるのは、成人確認済みの客(`adventurer` など)に Runtime が渡す昼 3・夜 7 だけなので、霧の効果は昼 4・夜 10 として表れる。
|
||||
- 予報はその日の予定だけを出す(翌日は 06:00 に決まる)。UI は今の部品のままで、完成の見た目ではない。Windows の実機は後。
|
||||
- 性能の調整はしていない。横断条件の性能の回帰は、最終の S の全 roster の既存の性能の suite の結果で見る。
|
||||
- 新しい出来事 `WEATHER_FORECAST_DRAWN` が毎日 1 件増える。出来事の番号は、その分だけ後ろへずれる。
|
||||
|
||||
## 4. 確かめ(局所。正式の証拠ではない)
|
||||
|
||||
- 既存の天候の 3 本(期待値の変更は `EXPECTATION_CHANGES.json`)、関係する既存の 8 本、新しい suite(88 checks)、再開の書き手(28 checks)と読み手(47 checks、別の process)が、scratch の stage で全て合格した。正式の証拠は、提出の S の native の収集、登録の validation、最終の S の全 roster。
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"by": "Claude Opus 5.5 Desktop (local_a7f85421-3eed-4482-8030-c0dca669573a), the commander that wrote the proposal; the question was asked by Root (Codex)",
|
||||
"decision_record": {
|
||||
"path": "artifacts/original-completion-2026-10-02/USER_DECISIONS_WEATHER_SEVERANCE_R01.json",
|
||||
"sha256": "c4a08ad4369ceec1738543540611a8c8921bd263d9fbe6b3e2621698f2957390"
|
||||
},
|
||||
"human_decision": {
|
||||
"id": "A13",
|
||||
"questionItemId": "[\"request_user_input_async\",\"call_d8c3f7424f574a0abd751ed8050687f0\",0]",
|
||||
"question_verbatim": "天候機能は原本に効果量の指定がないため、次の初期仕様を採用して進めてよいですか。\n\n1. 毎朝6時に晴れ60%・雨25%・マナ霧10%・嵐5%で抽選。雨6時間・霧4時間・嵐2時間で、その後は晴れ。開始は6〜20時の30分刻み。\n2. 雨の間は新規来客を半分にし、帰宅予定の客は雨が止むまで滞在を延長(最大4時間)。\n3. マナ霧の「魔法効率上昇」は世界樹・マナ炉の産出1.2倍、「興奮度上昇」は成人確認済みの客の性欲の増加1.5倍として実装。\n\nこの数値と対応づけは実装担当の提案で、まだ確定していません。詳細は[天候機能の実装案](/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p07-01-registration-proposal-r01/PROPOSAL.md)です。資金管理の確認は回答を待つ間も継続します。",
|
||||
"answer_verbatim": "この初期仕様で進める",
|
||||
"source": "Actual user send_user_message_question_reply in this Codex conversation; recorded time is not the original answer time.",
|
||||
"recorded_at_utc": "2026-10-23T18:07:36Z",
|
||||
"proposal": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-registration-proposal-r01/PROPOSAL.md",
|
||||
"sha256": "0772743ac11c466e4e9e283f5df2a8e652255c6db2964e83aab81d95f480a532",
|
||||
"sha256_now": "0772743ac11c466e4e9e283f5df2a8e652255c6db2964e83aab81d95f480a532"
|
||||
}
|
||||
},
|
||||
"interpretation_boundary": [
|
||||
"Human-approved: the three items of the question and the linked proposal as presented.",
|
||||
"Implementer choices (not a new human approval): floor rounding as written in the proposal, the draw procedure (integer 0-99 and the 29 start slots), the saved weather stream, the once-per-guest extension record, the furnace acceptance rule at the fog yield, the effect on the positive intimacy increment only, the old-save migration and the rejection rules (SPEC sections 4 and 5).",
|
||||
"Not approved here: P07-02 environment, P07-03 news, other D04 values, P05-03 answers (still pending)."
|
||||
],
|
||||
"limits": [
|
||||
"These answers supersede the stale heartbeat statements that weather and severance are awaiting user answers.",
|
||||
"P05-03 questions remain unanswered.",
|
||||
"No approval of additional combat dismissal rules, acceptance waivers, deletion of models/runtime/personal data, or exact future integration candidates is asserted.",
|
||||
"This record is a decision record, not evidence of implementation or acceptance. Original completion remains 24/88 and 4/30 packages."
|
||||
],
|
||||
"original_completed": 24
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
# P07-01 雨と霧と自然天候 仕様(登録版 r01)
|
||||
|
||||
- 状態: 登録の前の仕様。Root が main の `docs/plans/original-completion-2026-10-02/execution/p07-01/SPEC.md` に固定し、その commit を `base_commit` にして通常の登録をする。登録・下書きは実装の受入ではない。原本への加算は whole-unit の final の後だけ。出典は同じフォルダの `PROVENANCE.json`。
|
||||
- 前提: P04 は正式完了。ユーザーの指示(2026-10-23)「まずゲームとしての機能実装完了を優先して」「…統合試験の動作速度のチューニングを、今やるよりも機能実装の完了を優先して」により、機能の単位を先にする。P07-01 は P05-03 の人への問い(未回答)とは独立している。
|
||||
|
||||
## 1. 原本の条件(変えない)
|
||||
|
||||
- 原本 `docs/traceability/original/BASELINE.txt` 74 行(原文): `* **天候:** 雨(客足減・滞在延長)、嵐(新規客ゼロ)、マナの霧(魔法効率UP・興奮度UP)。` 214 行(原文): `* **上部:** 資金・マナ・資材リソース、時間・天候、世界樹ランク。`
|
||||
- P07-01 の work: 雨の来客減・滞在延長、マナ霧、自然変化を実営業へ接続する。acceptance: 天候切替直前後、途中保存、自然来客の差を同条件で説明できる。
|
||||
- P07 の acceptance のうちこの単位に当たる部分: 嵐で新規入館0、雨で来客減/滞在延長。天候変化の瞬間と保存再開が一致。
|
||||
- 原子要件(P07): `AR.weather.rain_arrivals`(雨で客足が減る)、`AR.weather.rain_stay`(雨で滞在が延長する)、`AR.weather.fog_magic`(マナの霧で魔法効率が上がる)、`AR.weather.fog_excitation`(マナの霧で興奮度が上がる)、`AR.weather.storm_no_arrivals`(嵐で新規客がゼロになる)、`AR.ui.hud_weather`(上部に天候を表示する)。
|
||||
- 横断条件(P07): 原本条件とUI操作が一致、Runtimeの実利用・会計・行動へ接続、途中保存・再起動で再現、異常/境界/中断を検証、関連回帰PASS、要件別ログとソースハッシュを保存、既存代表負荷の性能回帰を計測。
|
||||
|
||||
## 2. 決定(捏造しない)
|
||||
|
||||
- **A13(ユーザーの決定。回答の時刻は記録に無く、Root の記録は 2026-10-23T18:07:36Z)**: 天候の初期仕様。問い(原文は `PROVENANCE.json`)の3項目に、ユーザーが「この初期仕様で進める」と答えた。
|
||||
1. 毎朝 6 時に、晴れ 60%・雨 25%・マナ霧 10%・嵐 5% で抽選する。雨は 6 時間、霧は 4 時間、嵐は 2 時間続き、その後は晴れ。開始は 6〜20 時の 30 分刻み。
|
||||
2. 雨の間は新規来客を半分にする。帰宅予定の客は、雨が止むまで滞在を延長する(最大 4 時間)。
|
||||
3. マナ霧の「魔法効率上昇」は、世界樹とマナ炉の産出を 1.2 倍にする。「興奮度上昇」は、成人確認済みの客の性欲の増加を 1.5 倍にする。
|
||||
- 出典の区分: 人が承認したのは、上の問いの3項目と、問いが詳細として示した実装案 `p07-01-registration-proposal-r01/PROPOSAL.md`(SHA-256 `0772743a…a532`)。実装案にある「切り捨て」と、下の §4 の抽選の手順・保存・移行・同時の扱いの詳細は、実装担当(Opus)の仕様の選択で、人の新しい承認として扱わない。
|
||||
- 既存の決定のまま: 嵐の新規入館 0(原本の明示。今の実装を保つ)。A06(マナの貯蔵の上限 300 と 10 分の境界)は変えない。
|
||||
- この単位で作らないもの: P07-02 の環境(採光・遮音・隠蔽・景観・清潔・騒音)、P07-03 の流行ニュース、天候の魔法以外への効果、D04 の他の値。
|
||||
|
||||
## 3. 今の実装(main `48d3b00`)
|
||||
|
||||
- `scripts/environment/weather_timeline.gd`: 種類 `clear`・`rain`・`storm`・`mana_fog`、予定の切り替え(`schedule`、`MAX_PENDING` 4096)、時刻の前進、保存と復元(VERSION 1)。確率と効果は持たない。
|
||||
- `autoload/simulation_runtime.gd`: 時刻の境界の最初(`simulation_runtime.0_weather` → `_advance_weather`)で天候を進める。来客の計画の受付(`_operations_arrival_boundary`)は、嵐の間 `capacity_remaining` を 0 にする。新しい客の帰りの予定は一律 `planned_exit_at = 今 + 720`(3109 行と `guest_service.gd` 131 行)、帰りの判定は 9038 行。保存される乱数の系列は `RNG_STREAM_IDS`(78 行〜。`world`・`guest_spawn` など9本)で、旧 save に `defense` の系列が無い時は `world_seed` から作る前例がある(1322〜1324 行)。`get_weather_report` は今の種類と分だけを返す。
|
||||
- `scripts/mana/mana_reserve_cycle.gd`: 10 分ごとの境界で、世界樹の産出 `WORLD_TREE_GENERATION` 20、マナ炉の産出 `FURNACE_YIELD` 100、上限 300(A06)。
|
||||
- `scripts/guest/guest_service.gd` 330〜367 行: 性欲(`intimacy`)の増減の計算。成人確認は `guest_definition.gd` の `adult_verified`。
|
||||
- `scenes/main/main.gd`: 上部の天候の表示(晴れ・雨・嵐・マナの霧)。予報は無い。
|
||||
- ゲームの中で天候を予定する経路(自然天候)は無く、試験だけが `schedule` を呼ぶ。雨・霧の効果は無い。
|
||||
|
||||
## 4. 挙動
|
||||
|
||||
- 4.1 自然天候(A13-1):
|
||||
- 毎日 06:00 の時刻の境界で1回だけ抽選する。新しいゲームでは、開始の日の 06:00 にも抽選する。
|
||||
- 保存される乱数の系列 `weather` を `RNG_STREAM_IDS` に足して使う。
|
||||
- 種類: 0〜99 の整数を1つ引き、0〜59 晴れ、60〜84 雨、85〜94 マナの霧、95〜99 嵐。
|
||||
- 晴れ以外では、開始の時刻を 06:00〜20:00 の 30 分刻みの 29 個から1つ引く。開始の時刻に切り替え、続く時間(雨 360 分・霧 240 分・嵐 120 分)の後に晴れへ切り替える。予定は `weather_timeline.schedule` で行い、保存される。
|
||||
- 最も遅い終わり(20:00 開始の雨で翌 02:00)は、次の 06:00 の抽選より前なので、日をまたいで予定が重ならない。開始が 06:00 の時は、同じ境界で切り替える。
|
||||
- 抽選の結果(日、種類、開始、終わり)を出来事 `WEATHER_FORECAST_DRAWN` として記録する。
|
||||
- 4.2 雨の来客(A13-2): 雨の間の来客の計画の境界では、計画の新規の人数を半分(切り捨て)にする。嵐の 0 と、マナや定員の今の上限は先に効く。晴れと霧では変えない。
|
||||
- 4.3 雨の滞在(A13-2):
|
||||
- 雨の間に帰りの時刻(`planned_exit_at`)になった客は、帰りを、その雨の終わりの時刻か、元の帰りの予定 + 240 分の早い方まで延ばす。
|
||||
- 延長は客ごとに1回だけで、元の予定と延長の後の予定を保存する。延長の後の時刻には、天候に関わらず今の規則で帰る。
|
||||
- 嵐と霧では延ばさない。
|
||||
- 4.4 マナの霧(A13-3):
|
||||
- 魔法効率: 霧の間のマナの 10 分の境界では、世界樹の産出を floor(20 × 1.2) = 24、マナ炉の産出を floor(100 × 1.2) = 120 にする。炉の受け入れの判定(空きが産出以上)も、その境界の産出の量で行う。上限 300 は変えない。
|
||||
- 興奮度: 霧の間、`adult_verified` の客の性欲の増加(その計算の正の増分)を floor(増分 × 1.5) にする。減少と、成人確認の無い客は変えない。
|
||||
- 霧の外では元に戻る。
|
||||
- 4.5 嵐(原本): 今の新規入館 0 を保つ。自然天候の嵐でも同じ。
|
||||
- 4.6 上部の表示と予報(`AR.ui.hud_weather`):
|
||||
- 今の天候に加えて、その日の次の切り替え(種類と時刻)を出す(`get_weather_report` に足す)。
|
||||
- 雨・嵐・霧の時は効果を短く案内する(雨: 来客が半分・帰りを最大 4 時間延ばす、嵐: 新規来客なし、霧: マナの産出と成人客の性欲が増える)。
|
||||
- UI は今の部品のままにし、完成の見た目は求めない。
|
||||
- 4.7 同時の扱い(D05 の範囲の実装の契約):
|
||||
- 天候は今どおり時刻の境界の最初に進めるので、同じ分の来客の計画・帰りの判定・マナの境界・締めより先に効く。
|
||||
- 予定の重複・過去の時刻・上限を超えた予定は、今の `weather_timeline` の規則で拒む。
|
||||
|
||||
## 5. 保存と移行
|
||||
|
||||
- 5.1 自然天候の状態(最後に抽選した日と、その日の予定)、系列 `weather`、客の延長の記録を保存する。同じ process の保存の往復と別の process の再開で同じ状態になり、抽選・延長・霧の効果を二重に行わない。
|
||||
- 5.2 旧 save(自然天候の状態と系列 `weather` が無い):
|
||||
- 今の天候と予定をそのまま保つ。
|
||||
- 系列 `weather` は `world_seed` から作る(`defense` の系列の既存の移行と同じ形)。
|
||||
- 読み込みの後の最初の 06:00 から抽選を始める。過去の日の抽選はさかのぼらない。客の延長の記録は無しとして読む。
|
||||
- 5.3 不正な天候の状態(知らない種類、範囲の外の時刻、重なる予定、延長の上限を超えた記録)は拒否し、読込の前の状態を保つ。
|
||||
|
||||
## 6. 受入条件(acceptance_baseline の本文と同じ)
|
||||
|
||||
- P07-01-F1 自然天候: 毎日 06:00 に1回だけ、§4.1 の確率・開始の時刻・続く時間で天候が自然に予定され、同じ world_seed と同じ操作で同じ天候の列になる。新しいゲームの開始の日も抽選され、旧 save は §5.2 のとおり移行する。
|
||||
- P07-01-F2 雨の来客: 雨の間の来客の計画の新規の人数が、同じ条件の晴れの半分(切り捨て)になり、嵐の 0 と定員・マナの上限を壊さない。
|
||||
- P07-01-F3 雨の滞在: 雨の間に帰りの時刻になった客が、雨の終わりか元の予定 + 240 分の早い方まで1回だけ残り、その後は今の規則で帰る。嵐と霧では延びない。
|
||||
- P07-01-F4 嵐: 自然の嵐を含め、嵐の間の新規入館が 0。
|
||||
- P07-01-F5 マナの霧: 霧の間のマナの境界で世界樹 24・マナ炉 120(上限 300 のまま)、成人確認済みの客の性欲の増加が floor(×1.5) になり、霧の外と成人確認の無い客は変わらない。
|
||||
- P07-01-F6 表示と予報: ゲームの本物の Runtime と Main の上部の表示で、今の天候・次の切り替え(種類と時刻)・効果の案内が Runtime の値と一致する。
|
||||
- P07-01-F7 保存と再開: 天候の切り替えの直前・直後・途中の保存を、同じ process と別の process で読み、天候・予定・系列・来客・滞在の延長・霧の効果が同じになり、二重に効かない。旧 save は §5.2 で読め、不正な状態は §5.3 で拒否して状態を保つ。
|
||||
- P07-01-F8 回帰: 関連の回帰(狭い roster と再開の2段)と最終の S の全 roster(既存の代表負荷の性能の suite を含む)が PASS。既存の試験の期待値の変更は `EXPECTATION_CHANGES.json` に変更前後と根拠を一覧にする。元の失敗を保存し、未解決の試験を除いて PASS にしない。
|
||||
|
||||
## 7. 開示
|
||||
|
||||
- 実装案の「切り捨て」と §4・§5 の詳細は、実装担当の選択(§2)。
|
||||
- 興奮度は性欲(`intimacy`)の増加に当てた。今の客の種類の多くは性欲の増加が 0 なので、霧の効果は増加が正の成人確認済みの客にだけ表れる。
|
||||
- 予報はその日の予定だけを出し、翌日以降は出さない(翌日の抽選は翌朝 06:00)。UI は今の部品のまま。Windows の実機は後。
|
||||
- 性能の調整はしない(横断条件の性能回帰の計測は、全 roster の既存の性能の suite の結果で見る)。
|
||||
|
||||
## 8. 検証
|
||||
|
||||
- validation `p07-weather`: `tools/run_p07_weather_tests.py --output {build_dir}/p07-weather-evidence`。P05-02 の `tools/run_p05_funds_tests.py` と同じ guarded の段の手順を、この作業の中で作る。
|
||||
- 狭い roster:
|
||||
- 新しい suite `tests/environment/test_p07_weather_runtime.gd`(F1〜F7)。
|
||||
- 既存の `tests/environment/test_runtime_weather.gd`・`test_weather_timeline.gd`・`test_weather_recovery.gd`・`tests/mvp/test_runtime_operations_integration.gd`・`tests/s2/test_runtime_operations_cycle.gd`・`tests/s2/test_runtime_mana_reserve.gd`・`tests/s2/test_mana_reserve_cycle.gd`・`tests/s3/test_guest_service.gd`・`tests/s6/test_runtime_save_lifecycle.gd`・`tests/s6/test_runtime_snapshot_serialization.gd`・`tests/ui/test_main_dashboard.gd`。
|
||||
- 再開の2段: `tests/environment/test_p07_weather_restart_1_write.gd`(天候の途中で保存。生成ファイルは handoff だけ)と `..._2_read.gd`(別の process で読む)。
|
||||
- その後: 正式の validation → 最終の S の全 roster を1回 → fresh な独立レビュー(whole-unit)。速度の調整の作業と、微小な変更ごとの全回帰はしない。
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"task": "P07-01-WX",
|
||||
"spec": "docs/plans/original-completion-2026-10-02/execution/p07-01/wx/SPEC.md",
|
||||
"rule": "SPEC WX8: every changed expectation of an existing suite is listed with the value before and after and the reason. The four changes carried from the original task stay listed in ../EXPECTATION_CHANGES.json (byte for byte); this file lists the changes made in P07-01-WX.",
|
||||
"changes": [
|
||||
{
|
||||
"suite": "tests/business/test_required_kitchen.gd",
|
||||
"check": "whole typed loaded state unchanged except complete identity/saveID-bound IO1: legacy14-load (and the byte-identical-copy legacy migration control, which uses the same mapped expectation)",
|
||||
"before": "_approved_runtime_migrations maps A06 (mana reserve fresh start), P14-03 (elevators v1 to v2) and P05-02 A12 (empty fund accounts) into the old checkpoint before the whole typed comparison",
|
||||
"after": "it also maps the P07-01 A13 migration, each part checked against its own contract before it is mapped: the weather snapshot v1 to v2 gains only the natural state (last_draw_day counts every day whose 06:00 has passed, today empty) and empty rain_holds, with minute, kind, since and pending unchanged; rng_streams gains only the weather stream from the world seed, every other stream byte-identical",
|
||||
"reason": "A13 (SPEC section 5.2) migrates a save from before natural weather on load. The genuine legacy14 fixture is such a save, so its loaded state differs from the old checkpoint exactly in these two fields. Found by the S c501360 full roster (phase 003, 163 checks, 2 failures) and the independent review finding 1."
|
||||
}
|
||||
],
|
||||
"found_by": {
|
||||
"full_roster": "FULL_ROSTER_C501360_FAILURE_R01.json",
|
||||
"review": "p07-01-wx-independent-review-c501360-r01/DESKTOP_RESULT.json (sha256 aea0cc4f2c00a6a093ca256154c62812a4d83749d075c6a4a9d36c1270026da7)"
|
||||
}
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
# P07-01-WX 実装の記録(実 Opus の実装担当、2026-10-24)
|
||||
|
||||
- 仕様: 同じフォルダの `SPEC.md`(main `7ac2224` に固定。Runner の `spec_sha256` は `f1cd4262…fbd783`)。出典は `PROVENANCE.json`。
|
||||
- 原本への加算は 0。原 P07-01 の F1〜F8・炉 120・`AR.weather.fog_magic`・原 88 の分母は未達のまま。
|
||||
- 管理の記録は `artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/` にある(git の外)。
|
||||
|
||||
## 1. 持ち越し(SPEC §3)
|
||||
|
||||
- 原 task の提出 S `dfacc83` の差分 24 ファイルを、`CARRY_OVER_DFACC83.patch`(SHA-256 `a880ea89…58f9`)で W に1回だけ当てた。
|
||||
- 当てた後、24 ファイル全部が manifest(`CARRY_OVER_DFACC83.json`)の bytes・SHA-256 と、S `dfacc83` の blob の両方に一致した(`CARRY_OVER_APPLY_R01.json`)。
|
||||
- 1回目は sandbox が W への書き込みを拒み、何も当たらなかった(W は clean のまま)。その記録を残してから当て直した。
|
||||
- 製品と試験は持ち越しのまま。変えたのは下の照合の道具だけ。
|
||||
|
||||
## 2. 照合の直し(`tools/run_p07_weather_tests.py` の `verify`)
|
||||
|
||||
- 原因:
|
||||
- S `dfacc83` の native の収集では、14 本の suite が全て PASS した。その後の照合が「exact controlled Guard/proof environment」で止まった。
|
||||
- 照合は `ordinary_cleanup_prepared` の suite の環境しか期待していなかった。`old_bounded_teardown` の suite(`tests/environment/test_p07_weather_runtime.gd` と `tests/ui/test_main_dashboard.gd`)には、共通の収集が `THE_TREE_AUDIO_TEARDOWN_PROOF_DIR` を渡しているのに、それを期待していなかった。
|
||||
- 直し(`VERIFY_OLD_BOUNDED_FIX_DRAFT_R01.md` のとおり):
|
||||
1. 段ごとの分類(ordinary・old_bounded)を、共通の収集(`tools/run_all_tests.py`)と同じ `phase_policy` から決める。
|
||||
2. 期待する環境:
|
||||
- ordinary の時は今のまま(`<段>-teardown/owned-audio` と `control.json`)。
|
||||
- old_bounded の時は `<段>-teardown`(共通の収集と同じ)。
|
||||
- どちらでもない時は無し。
|
||||
3. old_bounded の suite は、共通の `teardown_gate` で証明を照合し直し、収集の記録(`old_bounded_proof`)と一致することを求める。
|
||||
4. 照合を build の場所の写しにかけた時のために、`teardown_gate` が読む証明の path を読み替える。方法は ordinary の照合(`owners.read`)と同じ。照合の道具の中で作った module の写しだけを変え、共通の gate は変えていない。
|
||||
5. ordinary の証明・old_bounded の証明・`<段>-teardown` のファイルが、分類された suite にだけあることを求める(import と parse の段を含む)。
|
||||
- 消した検査は無い。任意の環境は許さない。
|
||||
|
||||
## 3. 訂正
|
||||
|
||||
- 持ち越した `../IMPLEMENTATION_NOTES.md` §3 の「(P06 の未決定)」は誤り。
|
||||
- 炉の産出を通常の営業で示すのに要るものは、次の3つ(SPEC §6):
|
||||
- P13 の炉の施設
|
||||
- D04 のユニットの消費
|
||||
- A06 の別承認
|
||||
- 持ち越しのファイルは bytes のまま残し、訂正はここに書く。
|
||||
|
||||
## 4. 確かめ(局所。正式の証拠ではない)
|
||||
|
||||
- 直した判定を、旧 S の native の生の出力(読むだけ)に当てた(`VERIFIER_FIX_LOCAL_CHECK_R01.json`)。照合の全体は、受領が旧い道具の bytes を固定しているので、旧い出力にはかけられない。
|
||||
- 16 段の環境と分類が記録と一致した。
|
||||
- bounded の 2 本の証明が、元の場所と別の場所の写しの両方で記録と一致した。
|
||||
- 直す前の期待は、元の失敗と同じ 000・011 の段で外れる。
|
||||
- 読み替えの無い写しと、1 byte 変えた写しは拒まれる。
|
||||
- 直した道具の `--prepare-only` は exit 0(engine 0)。
|
||||
- 製品は S `dfacc83` と同じ bytes なので、engine の局所の再実行はしていない。
|
||||
- 正式の証拠は、新しい S の native の収集、登録の validation、全 roster、独立レビュー。旧 S の結果は流用しない。
|
||||
|
||||
## 5. 開示
|
||||
|
||||
- 炉 120 と `AR.weather.fog_magic` は受け入れない(SPEC §6)。
|
||||
- 他の開示は、持ち越した `../IMPLEMENTATION_NOTES.md` §2・§3(P06 の句を除く)と `../EXPECTATION_CHANGES.json` のとおり。
|
||||
- 出来事の一覧の表示: 毎日 06:00 の出来事 `WEATHER_FORECAST_DRAWN` には、`scripts/ui/event_log_dialog.gd` の `EVENT_NAMES` に日本語の名前が無い。そのため、プレイヤーが見る出来事の一覧では「未識別の記録」と出る。
|
||||
- このファイルは契約の `allowed_paths` の外なので、この作業では直さない。
|
||||
- 名前を足す作業は、別の単位として提案する。
|
||||
- 照合の ordinary の分岐: 狭い roster に `ordinary_cleanup_prepared` の suite が無い。そのため、照合の ordinary の分岐は、この validation では実地には動かない。
|
||||
- 実地に動くのは、old_bounded(000・011)と、後片付けの無い段だけ。
|
||||
- ordinary の分岐は、共通の収集と同じ環境と証明の照合を、コードの上で求めている。
|
||||
|
||||
## 6. 直し 1(S `c5013601` の独立レビューと全 roster の後。repair 1)
|
||||
|
||||
- 契機:
|
||||
- S `c5013601` の全 roster が、段 003 の `tests/business/test_required_kitchen.gd` で止まった(`FULL_ROSTER_C501360_FAILURE_R01.json`)。
|
||||
- 独立レビュー(`local_21295ab0`)は changes_requested で、high 1・medium 1・low 4。
|
||||
- 管理の記録は `COMMANDER_REPAIR_PLAN_P07_01_WX_R01.md`。
|
||||
- 直したもの:
|
||||
1. high: kitchen の試験の `_approved_runtime_migrations` に、A13 の移行を足した。2つの部分を、それぞれの契約で照合してから写す(`./EXPECTATION_CHANGES.json`)。
|
||||
- 天候 v1→v2 では、`natural` と空の `rain_holds` だけが足される。
|
||||
- `rng_streams` には、`world_seed` から作った `weather` だけが足される。
|
||||
2. medium: 再開の台本に霧の場面を足した(`tests/helpers/p07_weather_restart_fixture.gd`、writer、reader)。
|
||||
- seed 1592(0 日目 06:00〜10:00 がマナ霧)を使う。
|
||||
- 06:58 と 09:58 に成人確認済みの客が着く。この世界には施設が無いので、客は数分で帰る。そのため、保存の時刻に館内に居るように着く時刻を選んだ。
|
||||
- 保存は 419・420・421・599・600・601 分。
|
||||
- 比べる状態に、客の `needs`(性欲)と、マナの貯蔵の snapshot を足した。
|
||||
- writer は、straight の進行で次を確かめる。
|
||||
- 成人の客の性欲が、霧の間は1分に4、霧の後は3上がる。
|
||||
- 10 分ごとのマナの境界が、霧の間は 24、霧の後は 20。
|
||||
- writer は、同じ process で各保存ファイルを読み直す。読んだ直後と1分後、最後の状態が、straight と bytes で一致することを確かめる。
|
||||
- reader は、別の process で同じことを確かめる。
|
||||
- これで、霧の産出と性欲の効果が二重に効かないことを示す。
|
||||
3. low(試験の不足):
|
||||
- (a) 霧の間に帰りの時刻になった客は延びない。
|
||||
- (b) 延びた客は、10:02 の決定(`_advance_guest_ai_decisions`)で PLANNED_EXIT で帰り始める。施設の無いこの世界では、客は延長の前に次の決定で施設が無いので帰る。そのため writer の雨の場面では、hold の記録の後も今の規則どおり(`NO_SUITABLE_FACILITY`)帰ることを、厳密に確かめた。元の「or has left since」は外した。
|
||||
- (c) 上部の表示で、嵐とマナ霧の名前・予報・効果の案内を確かめる。霧の案内が炉を主張しないことも確かめる。
|
||||
4. low(§5.3 の細部): 製品の `scripts/environment/natural_weather_policy.gd` に `is_consistent_draw` を足した。
|
||||
- 保存されたその日の抽選について、roll と種類、slot と開始、終わりと続く時間の対応を照合する。
|
||||
- Runtime の復元(`autoload/simulation_runtime.gd`)は、矛盾した記録を拒否し、状態を保つ。
|
||||
- 拒否の試験を3つ足した。
|
||||
5. 開示: 上の §5 の2つ。
|
||||
- 比べる状態の `needs` は、名前を文字の順に並べる。読み直した客は needs のキーの順が違うことがあり、`StringName` の並べ替えは文字の順ではなく、process ごとに違うため。
|
||||
- 固定の値: 変えた3本(`test_p07_weather_runtime.gd`・writer・reader)の source の SHA を、`tools/regression/suite-policy.json` と `completion_marker_contracts.json` で更新した。
|
||||
- 確かめ(局所。正式の証拠ではない): scratch の stage で、変えた suite と狭い roster の 12 本、kitchen を直接動かして合格した(`P07_01_WX_REPAIR1_LOCAL_CHECKS_R01.json`)。正式の証拠は、新しい S の native の収集、登録の validation、全 roster、独立レビュー。
|
||||
|
||||
## 7. 直し 2(S `f99c881b` の最終の独立レビューの後。repair 2)
|
||||
|
||||
- 契機:
|
||||
- S `f99c881b` の最終の独立レビュー(`local_c4c1a8d6`)は changes_requested(medium 1・low 1)。前のレビューの high と medium は解消したと判定された。
|
||||
- S `f99c881b` の全 roster は合格した(198 本・22,857 checks)。
|
||||
- 管理の記録は `COMMANDER_REPAIR_PLAN_P07_01_WX_R02.md`。
|
||||
- 診断(scratch の stage)で分かったこと:
|
||||
- 施設の無い試験の世界では、延びた客は 06:03 に帰り始め(`NO_SUITABLE_FACILITY`)、06:04 に居なくなる。必要(needs)の値や客の種類を変えても同じ。
|
||||
- そのため、再開の保存には延長の記録が一度も乗っていなかった。
|
||||
- 製品の不具合(持ち越したコード): 延長の記録は、次の tick の天候の境界まで片づかなかった。
|
||||
- そのため、客が居なくなった分の保存は、保存は成功するが読み込めなかった(`RESTORE_FAILED`)。
|
||||
- 原因は、復元の照合「延長は館内の客の予定と一致すること」。
|
||||
- 直したもの:
|
||||
1. 製品: 客が居なくなった時(`autoload/simulation_runtime.gd` の `_on_guest_despawned`)に、その客の延長の記録をすぐ片づける。
|
||||
- `scripts/environment/weather_timeline.gd` に `release_rain_hold` を足した。
|
||||
- 天候の境界での片づけは今のまま残した。
|
||||
2. medium(WX7): 再開の台本の雨の場面に、2つの保存を足した。
|
||||
- 363 分: 延びた客が帰る途中で館内に居て、延長の記録がある。
|
||||
- 364 分: 客が居なくなった直後で、延長の記録が無い。上の不具合の回帰の確かめ。
|
||||
- writer は、雨の全ての保存を同じ process で読み直す。読んだ直後・1分後・最後の状態が、straight と bytes で一致することを確かめる。
|
||||
- reader は、別の process で同じことを確かめる。
|
||||
- 363 分の保存では、延長の記録が1つで、`planned_exit_at` が 602 であることを厳密に確かめる。1分後の一致で、二度目の延長が無いことを示す。
|
||||
- 500 分の確かめから、誤っていた「with the hold of a present guest」を外した。
|
||||
3. 回帰: `test_p07_weather_runtime.gd` で、通常の進行で延びた客が帰った分に、延長の記録が片づいていて、その分の snapshot が復元できることを確かめる。
|
||||
- 開示(U1): 施設の無い試験の世界では、延びた客は延長の時刻より前に、施設が無いので帰る。
|
||||
- そのため、通常の時刻の進行で延長の時刻(10:02)に着いて帰る場面は、この試験では作れない。施設を建てて客が使う場面が要る。
|
||||
- 延長の時刻の帰りは、決定の処理を延長の時刻でじかに呼んで確かめている(`_test_rain_hold_exit`)。延長の時刻に、二度目の延長をせず、PLANNED_EXIT で帰り始める。
|
||||
- 固定の値: 変えた3本の source の SHA を、`tools/regression/suite-policy.json` と `completion_marker_contracts.json` で更新した。
|
||||
- 確かめ(局所。正式の証拠ではない): scratch の stage で、狭い roster の 12 本・再開の2段・kitchen を直接動かして合格した(`P07_01_WX_REPAIR2_LOCAL_CHECKS_R01.json`)。
|
||||
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"task": "P07-01-WX",
|
||||
"by": "Claude Opus 5.5 Desktop (local_a7f85421-3eed-4482-8030-c0dca669573a), the actual commander; registration is Root's",
|
||||
"kind": "auxiliary successor of the original P07-01 (Root work sequencing and scope decomposition)",
|
||||
"root_decision": {
|
||||
"record": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-control-r01/ROOT_AUXILIARY_SCOPE_ORDER_R01.json",
|
||||
"sha256": "bd54c728301de9318faa9c077995c945172be1074ee8e95718a9f1e5ab1d659b"
|
||||
},
|
||||
"at_utc": "2026-10-23T19:36:14.185700+00:00",
|
||||
"relayed_message_verbatim_parts": [
|
||||
"提案B「原加算0の補助後継」を進めます。これはRootの作業範囲の分割であり、新たな人のゲーム仕様承認でも原P07-01の受入削除でもありません。原P07全F1〜F8/炉120/原88分母は未達のまま保全し、P13炉施設・D04消費・A06別承認を残要件と明示してください。",
|
||||
"現mainから、完成済みの自然天候/雨/世界樹霧/成人客霧/HUD/保存移行を受け入れる補助SPEC・原全8条件との対応と炉の残件・contract/baseline/policy/validation/登録手順を用意してRootへ渡してください。既存製品差分を再実装しない。後継も別Sで正式native/全roster/fresh別Opus wholeunit/通常統合が必要で、旧成功を新S実測として流用しない。",
|
||||
"照合不具合はVERIFY_OLD_BOUNDED_FIX_DRAFT_R01.mdに沿う実修正を後継の許可範囲に含め、old_bounded/ordinary双方の厳密な環境とteardown証明・hash/proofを照合し、検査削除や任意環境容認にしない。後継登録後に通常開始判断・委任実装で修正してください。",
|
||||
"P16はこの補助統合後のmainで続行可、原P07完了待ちを新しい循環待ちにしない。"
|
||||
],
|
||||
"authority": "Root's existing owner authority under the existing human instruction that prioritizes functional implementation. Not a new human gameplay rule or approval, and not a deletion of the original P07-01 acceptance."
|
||||
},
|
||||
"human_decision_unchanged": {
|
||||
"id": "A13",
|
||||
"record": {
|
||||
"path": "artifacts/original-completion-2026-10-02/USER_DECISIONS_WEATHER_SEVERANCE_R01.json",
|
||||
"sha256": "c4a08ad4369ceec1738543540611a8c8921bd263d9fbe6b3e2621698f2957390"
|
||||
},
|
||||
"answer_verbatim": "この初期仕様で進める",
|
||||
"original_spec_provenance": "docs/plans/original-completion-2026-10-02/execution/p07-01/PROVENANCE.json"
|
||||
},
|
||||
"original_task": {
|
||||
"project": "the-tree-p07-01",
|
||||
"id": "P07-01",
|
||||
"state": "submitted (held by commander decision wait, event 5)",
|
||||
"submitted": "dfacc83d0afdd322a6389e131b87d3205e5ac9c8",
|
||||
"base_commit": "4654789b01369a0d43915060db1f425e18a5e4a9",
|
||||
"spec_sha256_runner": "2626d70bd6d4b68dbffe2470d9e0726177d6704132da2f75282affa803287956"
|
||||
},
|
||||
"inputs": {
|
||||
"carry_over_patch": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-wx-registration-r01/CARRY_OVER_DFACC83.patch",
|
||||
"sha256": "a880ea89680a03f2e41f2c7118935a02c16a39995471a2b971a901513ade58f9"
|
||||
},
|
||||
"carry_over_manifest": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-wx-registration-r01/CARRY_OVER_DFACC83.json",
|
||||
"sha256": "b13aacebc6f445c57be33dd548e2d08526e4c04853cfd7b9ce829534fc17c9a6"
|
||||
},
|
||||
"verifier_fix_draft": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-control-r01/VERIFY_OLD_BOUNDED_FIX_DRAFT_R01.md",
|
||||
"sha256": "ee88ec3460c54c09778981965128e6b37aebd294cdd594f7759697d9b2085693"
|
||||
},
|
||||
"furnace_scope_decision": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-control-r01/P07_01_F5_FURNACE_SCOPE_DECISION_R01.md",
|
||||
"sha256": "f496aa8159e0d7324fb6517163327a0d21b438a379dfe1c2aa547f846ceccf35"
|
||||
},
|
||||
"commander_hold_decision": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-control-r01/COMMANDER_DECISION_P07_01_R02.json",
|
||||
"sha256": "7c0f67469772c053e27c3992cc7cfd49bf8c528a2ecc2f278a6a82bea4085f99"
|
||||
},
|
||||
"native_failure_record": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-control-r01/NATIVE_DFACC83_FAILURE_R01.json",
|
||||
"sha256": "af53fe3eeb58ff9a17274e8c17644407062aa75f1534a661d0dbdc1f7a4b4db5"
|
||||
}
|
||||
},
|
||||
"native_raw_kept": "artifacts/original-completion-2026-10-02/p07-01-control-r01/p07-01-native-dfacc83-r01 (and NATIVE_DFACC83.stdout/.stderr/.exit; SHA-256 in the Root record above). Not evidence for the successor.",
|
||||
"interpretation_boundary": [
|
||||
"Accepted here: the finished natural weather, rain arrivals and stay, storm, the world tree fog yield 24, the adult guest fog intimacy floor(x1.5), the HUD forecast, and save/migration/rejection, as implemented in S dfacc83 and carried byte-for-byte.",
|
||||
"Not accepted here: the furnace yield 120 at fog boundaries and AR.weather.fog_magic. They stay original residuals that need the P13 furnace facility, the D04 unit consumption and the A06 separate approval.",
|
||||
"In scope as a real fix: the registered verifier of tools/run_p07_weather_tests.py, strict for both ordinary_cleanup_prepared and old_bounded_teardown suites (environment, teardown proof re-check, hashes). No check is removed and no arbitrary environment is accepted.",
|
||||
"Implementer details of the carried code remain implementer choices, not human approvals (original SPEC section 2)."
|
||||
],
|
||||
"limits": [
|
||||
"Original credit 0. Original P07-01 F1-F8, the furnace 120 and the 88 denominator stay unmet. Original completion remains 24/88 and 4/30 packages.",
|
||||
"The successor needs its own S, native collection, registered validation, full roster, fresh separate Opus whole-unit review and normal integration. Old S dfacc83 results are not new S measurements.",
|
||||
"P05-03 questions remain unanswered and are not inferred."
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
# P07-01-WX 仕上がった天候の機能の受入(マナ炉の産出を除く) 仕様(登録版 r01。P07-01 の補助の作業)
|
||||
|
||||
- 状態: 登録の前の仕様。Root が main の `docs/plans/original-completion-2026-10-02/execution/p07-01/wx/SPEC.md` に固定し、その commit を `base_commit` にして通常の登録をする。出典は同じフォルダの `PROVENANCE.json`。
|
||||
- 位置づけ: Root の通常の owner の順序づけ(Root の記録 2026-10-23T19:36:14Z)による、原 P07-01 の作業の範囲の分割。人の新しいゲームの仕様の承認ではなく、原 P07-01 の受入の削除でもない。
|
||||
- 原 P07-01(仕様 `../SPEC.md`。決定 A13、`../PROVENANCE.json`)の F1〜F8、霧の境界のマナ炉 120、原 88 の分母は、どれも未達のまま保つ。
|
||||
- この補助の作業の原本への加算は 0。
|
||||
- 前提: ユーザーの言葉(原文。Root が確かめたもの)「開発作業を進めて。」「統合試験の動作速度のチューニングを、今やるよりも機能実装の完了を優先して」。発言の日時は、元の答えを特定できないので書かない。原 P07-01 の task(project `the-tree-p07-01`)は、commander の判断 wait(event 5)で保持している。
|
||||
|
||||
## 1. 原本の条件と決定(変えない)
|
||||
|
||||
- 原 P07-01 の work・acceptance・原子要件・横断条件・決定 A13 は、原の仕様 `../SPEC.md` §1・§2 のとおり。
|
||||
- 原の受入 F1〜F8 は `../SPEC.md` §6 のとおりで、この補助の作業はそれを書き換えない。
|
||||
|
||||
## 2. この補助の作業が受け入れるもの
|
||||
|
||||
原の仕様 §4・§5 の挙動のうち、次のものを受け入れる。§3 の持ち越しで、もう実装されている。
|
||||
|
||||
- 自然天候(原 §4.1): 毎日 06:00 の1回の抽選、保存される系列 `weather`、出来事 `WEATHER_FORECAST_DRAWN`。
|
||||
- 雨の来客(原 §4.2)、雨の滞在(原 §4.3)、嵐(原 §4.5)。
|
||||
- マナ霧のうち、世界樹の産出(20→24)と、成人確認済みの客の性欲の増加(floor(×1.5))(原 §4.4)。
|
||||
- 上部の表示と予報(原 §4.6)、同時の扱い(原 §4.7)、保存と移行と拒否(原 §5)。
|
||||
|
||||
受け入れないもの(§6 の残件): 霧の境界のマナ炉の産出 120(原 §4.4 の炉の部分、原 F5 の炉の句)。
|
||||
|
||||
- この補助の作業の要件 ID: `P07-01-WX`(受入 WX1〜WX8 の要件)。
|
||||
- 契約に並べる原子要件(原 §1。この補助の範囲で全部を満たすもの):
|
||||
- `AR.weather.rain_arrivals`
|
||||
- `AR.weather.rain_stay`
|
||||
- `AR.weather.fog_excitation`
|
||||
- `AR.weather.storm_no_arrivals`
|
||||
- `AR.ui.hud_weather`
|
||||
- 並べない原子要件: `AR.weather.fog_magic`(マナの霧で魔法効率が上がる)。A13 の問いの3項目め(原文は `../PROVENANCE.json`)は「マナ霧の「魔法効率上昇」は世界樹・マナ炉の産出1.2倍」なので、炉の部分の無いこの作業では閉じない。
|
||||
- 原 §1 の横断条件(P07)は、この補助の範囲でもそのまま求める。
|
||||
|
||||
## 3. 持ち越しと、この作業で直すもの
|
||||
|
||||
- 持ち越し: 原の task の提出 S `dfacc83d0afdd322a6389e131b87d3205e5ac9c8`(基準 `4654789`)の差分 24 ファイルを、bytes のまま後継の W に当てる。
|
||||
- 当て方は `CARRY_OVER_DFACC83.patch`(`git apply`)で、当てた後に全ての path を `CARRY_OVER_DFACC83.json` の SHA-256 と照合する。2つのファイルの path と SHA-256 は `PROVENANCE.json` の `inputs`。
|
||||
- 製品を作り直さない。持ち越した 24 ファイルは、下の照合の直し(`tools/run_p07_weather_tests.py`)を除いて変えない。
|
||||
- この作業の記録は `./IMPLEMENTATION_NOTES.md`(この `wx/` のフォルダ)に新しく書く(持ち越しの方法、照合の直し、§6 の訂正)。
|
||||
- S `dfacc83` の局所の確認と native の収集(照合で exit 1)は、原の task の記録のままにし、後継の証拠に流用しない。
|
||||
- 持ち越す `../IMPLEMENTATION_NOTES.md` §3 の「(P06 の未決定)」は誤り。正しい前提は §6 のとおりで、訂正は `./IMPLEMENTATION_NOTES.md` に書く(持ち越しのファイルは bytes のまま)。
|
||||
- 直すもの: 登録の照合の道具 `tools/run_p07_weather_tests.py` の照合(`verify`)。S `dfacc83` の native の収集では 14 本の suite が全て PASS したが、照合が `old_bounded_teardown` の suite の制御の環境を知らずに止まった。
|
||||
- 直し方は `VERIFY_OLD_BOUNDED_FIX_DRAFT_R01.md`(原の task の管理の記録。path と SHA-256 は `PROVENANCE.json`)のとおり。
|
||||
- `ordinary_cleanup_prepared` と `old_bounded_teardown` の両方の suite について、次を厳密に照合する。
|
||||
- 共通の収集(main `4654789` の `tools/run_all_tests.py` 172〜176・207〜208 行)と同じ制御の環境
|
||||
- 後片付けの証明(`ordinary_gate`・`teardown_gate`)の再照合
|
||||
- hash と証明
|
||||
- 検査を消したり、任意の環境を許したりしない。
|
||||
- 後継の検証で別の失敗が出た時: 失敗の原文を保存し、`./IMPLEMENTATION_NOTES.md` に原因と直しを書いてから、契約の `allowed_paths` の中で直す。直した後の確かめは、その失敗に関係する範囲だけにする。
|
||||
- 変えないもの: 固定された `../SPEC.md`・`../PROVENANCE.json`・`./SPEC.md`(この文書)・`./PROVENANCE.json`。
|
||||
|
||||
## 4. 受入条件(acceptance_baseline の本文と同じ)
|
||||
|
||||
- P07-01-WX1 自然天候: 毎日 06:00 に1回だけ、原 SPEC §4.1 の確率・開始の時刻・続く時間で天候が自然に予定され、同じ world_seed と同じ操作で同じ天候の列になる。新しいゲームの開始の日も抽選され、旧 save は原 SPEC §5.2 のとおり移行する。
|
||||
- P07-01-WX2 雨の来客: 雨の間の来客の計画の新規の人数が、同じ条件の晴れの半分(切り捨て)になり、嵐の 0 と定員・マナの上限を壊さない。
|
||||
- P07-01-WX3 雨の滞在: 雨の間に帰りの時刻になった客が、雨の終わりか元の予定 + 240 分の早い方まで1回だけ残り、その後は今の規則で帰る。嵐と霧では延びない。
|
||||
- P07-01-WX4 嵐: 自然の嵐を含め、嵐の間の新規入館が 0。
|
||||
- P07-01-WX5 マナ霧(炉を除く): 霧の間のマナの境界で世界樹 24(上限 300 のまま)、成人確認済みの客の性欲の増加が floor(×1.5) になり、霧の外と成人確認の無い客は変わらない。マナ炉の産出 120 はこの補助の作業では受け入れず、通常の営業での達成を主張しない(炉の産出の経路が無いので、貯蔵の規則の単体の確かめは開示として扱う)。
|
||||
- P07-01-WX6 表示と予報: ゲームの本物の Runtime と Main の上部の表示で、今の天候・次の切り替え(種類と時刻)・効果の案内が Runtime の値と一致する。
|
||||
- P07-01-WX7 保存と再開: 天候の切り替えの直前・直後・途中の保存を、同じ process と別の process で読み、天候・予定・系列・来客・滞在の延長・霧の効果が同じになり、二重に効かない。旧 save は原 SPEC §5.2 で読め、不正な状態は原 SPEC §5.3 で拒否して状態を保つ。
|
||||
- P07-01-WX8 回帰と照合: 関連の回帰(狭い roster と再開の2段)と後継の S の全 roster(既存の代表負荷の性能の suite を含む)が PASS。登録の照合が、`ordinary_cleanup_prepared` と `old_bounded_teardown` の両方の suite について、制御の環境と後片付けの証明・hash を厳密に照合する。既存の試験の期待値の変更は `EXPECTATION_CHANGES.json` に変更前後と根拠を一覧にする。元の失敗(S `dfacc83` の native の照合の失敗を含む)を保存し、未解決の試験を除いて PASS にしない。
|
||||
|
||||
## 5. 原 F1〜F8 との対応
|
||||
|
||||
| 原の受入 | この補助 | 扱い |
|
||||
|---|---|---|
|
||||
| F1 自然天候 | WX1 | 同じ本文 |
|
||||
| F2 雨の来客 | WX2 | 同じ本文 |
|
||||
| F3 雨の滞在 | WX3 | 同じ本文 |
|
||||
| F4 嵐 | WX4 | 同じ本文 |
|
||||
| F5 マナ霧 | WX5 | 世界樹 24 と性欲 ×1.5 だけ。炉 120 は §6 の残件 |
|
||||
| F6 表示と予報 | WX6 | 同じ本文 |
|
||||
| F7 保存と再開 | WX7 | 同じ本文 |
|
||||
| F8 回帰 | WX8 | 同じ要求に、照合の厳密さ(両方の後片付けの suite)を足す |
|
||||
|
||||
- この補助の作業が合格しても、原 F1〜F8 の whole-unit の受入にはならない。原 P07-01 の完了は、§6 の残件を満たした後に、原の全8条件の独立レビューと通常の統合・final を経てだけ数える。
|
||||
|
||||
## 6. 残件(原 P07-01 の未達として保つもの)
|
||||
|
||||
- 霧の境界のマナ炉の産出 120(原 §4.4・F5 の炉の句)と、マナの霧で魔法効率が上がる原子要件。今のゲームには、炉の施設も、炉が産出を入れる経路も無い(持ち越しの `../IMPLEMENTATION_NOTES.md` §3、原の task の `P07_01_F5_FURNACE_SCOPE_DECISION_R01.md`。path と SHA-256 は `PROVENANCE.json`)。満たすのに要るもの:
|
||||
- P13 の炉の施設(`AR.facility.mana_furnace.exists`・`footprint`(D06)・`zone`・`layer` など)
|
||||
- D04 のユニットの消費(`AR.facility.mana_furnace.unit_consumption`)
|
||||
- A06 の別承認(炉の人物の適格性と収率の実接続。`docs/plans/original-completion-2026-10-02/decisions.json` の A06 の原文「炉の人物適格性・収率の実接続は別承認。」)
|
||||
- 原 P07-01 の whole-unit の独立レビュー・通常の統合・final。
|
||||
|
||||
## 7. 検証
|
||||
|
||||
- validation `p07-weather`(`tools/run_p07_weather_tests.py --output {build_dir}/p07-weather-evidence`。持ち越した道具に §3 の直しを入れたもの)。
|
||||
- 狭い roster は原 SPEC §8 と同じ 12 本と再開の2段(実 16 段)。
|
||||
- 手順: 持ち越しと照合の直し → 局所の確認(直した道具の `--prepare-only` を含む)→ 通常の submit → 後継の S で native の収集 → 正式の validation → 空きを 2.6 GB 以上と実測してから全 roster を1回 → 後の圧縮 → 配置 → 凍結 → 別の新しい Opus の独立レビュー(この補助の範囲で whole-unit。炉の産出を主張していないことの確かめを含む)→ 通常の統合と final。
|
||||
- 速度の調整の作業と、微小な変更ごとの全回帰はしない。
|
||||
|
||||
## 8. 開示
|
||||
|
||||
- 持ち越した `IMPLEMENTATION_NOTES.md` §2・§3 のとおり。
|
||||
- 実装担当の詳細は人の承認ではない。
|
||||
- 霧の性欲は正の増分だけに効く。
|
||||
- 予報はその日の分だけを出す。
|
||||
- UI は今の部品のまま。
|
||||
- Windows の実機は後。
|
||||
- 毎日の出来事が1件増える。
|
||||
- 原本の完了数は 24/88 のまま。
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
# P07-01-WX 正式受入記録(補助の作業。原本への加算 0)
|
||||
|
||||
- P07-01-WX は、原 P07-01 の補助の作業である。対象は、仕上がった天候の機能(自然天候・雨・嵐・世界樹と成人客のマナ霧・上部の表示と予報・保存と移行)。
|
||||
- 次の3つをそろえて、正式に完了した。
|
||||
- 受入 8 件(WX1〜WX8)についての、別の fresh な Desktop Opus 5.5 の独立レビューの PASS(高・中 0、low 2)
|
||||
- 通常の validation・review・integration・final の全 PASS
|
||||
- 実 DB と traceability の結合
|
||||
- S `db87270` → Q `9b5b09b`。
|
||||
- 原本への加算は 0 で、正式の数は 24/88・4/30 のまま。部分点なし。
|
||||
- 原 P07-01 は未達のまま。霧の境界のマナ炉の産出 120 と `AR.weather.fog_magic` が残っている。
|
||||
|
||||
| 名前 | commit | 備考 |
|
||||
|---|---|---|
|
||||
| 提出 S | `db87270972c9b243e3a1fccdf2c1fa96975dffc1` | 修正 2 |
|
||||
| 統合・最終検証 Q | `9b5b09b35e888e5ac1a49174b8a7f73e51c502d2` | Q の tree は S の tree と同じ(`86fa8fdd…`) |
|
||||
| 基準 M | `7ac2224e4705d06aab28803592a68b31d60841ed` | |
|
||||
|
||||
- 正式証拠: `WX_FORMAL_COMPLETION_9B5B09B.json`
|
||||
- 独立レビューの結果(実結果の bytes の写し): `WX_INDEPENDENT_REVIEW_DB87270.json`(SHA256 `1fd2c8bbe0f9d6b165d5f993e86002b94ead97674c27ed9270a223c929c1aaca`)
|
||||
|
||||
## 位置づけと原本の条件
|
||||
|
||||
- この補助の作業は、Root の作業の範囲の分割(提案 B、`ROOT_AUXILIARY_SCOPE_ORDER_R01.json`)である。
|
||||
- 人の新しいゲームの仕様の承認ではない。
|
||||
- 原 P07-01 の受入を消したものでもない。
|
||||
- 人の決定 A13(天候の初期仕様、答え「この初期仕様で進める」)は、`../PROVENANCE.json` のとおり。
|
||||
- 原 work: 雨の来客減・滞在延長、マナ霧、自然変化を実営業へ接続する。
|
||||
- 原 acceptance: 天候切替直前後、途中保存、自然来客の差を同条件で説明できる。
|
||||
- 原の横断条件(P07、7 項目):
|
||||
- 原本条件とUI操作が一致
|
||||
- Runtimeの実利用・会計・行動へ接続
|
||||
- 途中保存・再起動で再現
|
||||
- 異常/境界/中断を検証
|
||||
- 関連回帰PASS
|
||||
- 要件別ログとソースハッシュを保存
|
||||
- 既存代表負荷の性能回帰を計測
|
||||
- 受入 8 件(WX1〜WX8)は `SPEC.md` §4 と、登録の `acceptance_baseline.json` の本文のとおり。原 F1〜F8 との対応は `SPEC.md` §5 にある。
|
||||
|
||||
## 4つの通常のゲート(Runner の the-tree-p07-01-wx、task P07-01-WX、completed、修正 2)
|
||||
|
||||
| ゲート | commit | Runner の証跡 | 結果 |
|
||||
|---|---|---|---|
|
||||
| validation | S | evidence 5 | `p07-weather` exit 0、verified true、14 本(16 段)・1,159 checks、engines_started 0 |
|
||||
| review | S | evidence 6 | 実結果 `1fd2c8bb…`、pass、WX1〜WX8、low 2 |
|
||||
| integration | Q | evidence 7 | `p07-weather` exit 0、verified true、engines_started 0 |
|
||||
| final | Q | evidence 8 | `9145ecb2…-final.json` SHA `61d87d35…`、verified true、14 本・1,159 checks・parse 350、engines_started 0 |
|
||||
|
||||
- owner の承認:
|
||||
- Root が、main の統合と push についての既存の人の許可を、正確な Q `9b5b09b` と main_base `7ac2224` に正直に当てはめた。新しい人の発言は作っていない。
|
||||
- 記録は、Runner の approvals の行と `ROOT_OWNER_APPROVAL_9B5B09B_R01.json`。
|
||||
- integrate の job は `d166ed1445074b71bdae495edf5a6880`。
|
||||
- 測定は全て S で行った。
|
||||
- native: 14 本・1,159 checks
|
||||
- S の全 roster: 198 本・22,959 checks。既存の代表負荷の性能の suite 10 本を含む。
|
||||
- Q での integration と final について:
|
||||
- S で測った証拠を、中身が同じ Q の新しい checkout で、中身ベースの verifier が照合したものである。
|
||||
- Q で測ったとは書かない。
|
||||
|
||||
## 独立レビュー(S db87270)
|
||||
|
||||
- レビュー担当: 別の fresh な Claude Desktop Opus 5.5 の session(`local_fc6760c6`)。実装担当とも、前の S の2回のレビューとも別である。
|
||||
- 凍結の入力の SHA を確かめ、読むだけで判定した。凍結の入力は packet・schema・材料 7,174 ファイル。
|
||||
- 判定の範囲:
|
||||
- 受入 WX1〜WX8 の全部
|
||||
- 原の横断条件の7項目の全部
|
||||
- U1(未達の開示)と U2(証拠の適用)
|
||||
- 前の2つのレビューの findings の解消
|
||||
- 配送の前に、会話の記録の全体を確かめた。
|
||||
- tool の呼び出しは 77 件(重複を除く)。
|
||||
- 書き込みは結果のファイルだけ(作成1回と、同じファイルの書き直し3回)。
|
||||
- 補助のファイルは、その session の scratchpad だけにあった。
|
||||
- engine・試験・Runner のコマンド・git の書き換えは無い。
|
||||
- low 2 件。原文は独立レビューの結果のファイルにあり、どちらも試験の不足で、製品は正しいと判定された。
|
||||
1. 延長が二重に効かないことと延長の時刻の帰りを、まだ判断中の延長された客で動かして確かめていない。
|
||||
- 施設の無い試験の世界では、延びた客は延長の時刻より前に帰る。
|
||||
- 延長の時刻の帰りは、決定の処理を直接呼んで確かめている。
|
||||
- この点は `IMPLEMENTATION_NOTES.md` §7 で開示した。
|
||||
- 後の提案: 施設を使う世界で、延長を持つ保存を読み直して確かめる。または check の文言を実態に合わせる。
|
||||
2. 移行した save で、「読み込みの後の最初の 06:00 から抽選を始める」ことを直接確かめていない。
|
||||
- 製品の経路は、新しいゲームの毎日の抽選と同じで、そちらは確かめている。
|
||||
- 後の提案: 移行の試験で、次の 06:00 まで進めて抽選が1回出ることを確かめる。
|
||||
|
||||
## 修正の経過と保持した失敗
|
||||
|
||||
1. 原 P07-01 の S `dfacc83`
|
||||
- native では 14 本が PASS した。
|
||||
- しかし、写した照合の道具が old_bounded の後片付けの環境を知らず、exit 1 になった(`NATIVE_DFACC83_FAILURE_R01.json`)。
|
||||
- WX で照合を直した(ordinary と old_bounded の両方を厳密に照合する)。
|
||||
2. S `c5013601`
|
||||
- 全 roster が `test_required_kitchen.gd` で止まった。旧 save の比べが A13 の移行を知らなかったため(`FULL_ROSTER_C501360_FAILURE_R01.json`)。
|
||||
- レビューは changes_requested(high 1・medium 1・low 4)。
|
||||
- 修正 1 で、kitchen の試験への A13 の写し、霧の再開、low の試験、保存した抽選の照合、開示を入れた。
|
||||
3. S `f99c881b`
|
||||
- 全 roster は合格した。
|
||||
- レビューは changes_requested(medium 1・low 1)。別の process で雨の延長の保存が確かめられていなかった。
|
||||
- 診断で、製品の不具合も見つかった。延長の記録が客と一緒に片づかず、客が居なくなった分の保存が読めなかった(`RESTORE_FAILED`)。
|
||||
- 修正 2 で、記録を客と一緒に片づけるようにし、再開の雨の場面に 06:03・06:04 の保存を足した。
|
||||
- 全ての失敗の生の出力、診断の記録、前のレビューの結果は保持した。新しい S の証拠に流用していない。
|
||||
|
||||
## 未達として保つもの(主張しない)
|
||||
|
||||
- 原 P07-01(F1〜F8 の whole-unit の受入)は未達のまま。
|
||||
- 霧の境界のマナ炉の産出 120 と `AR.weather.fog_magic` が残っている。
|
||||
- 満たすには、P13 の炉の施設、D04 のユニットの消費、A06 の別承認が要る(`SPEC.md` §6)。
|
||||
- 出来事の一覧では、毎日の `WEATHER_FORECAST_DRAWN` が「未識別の記録」と出る。
|
||||
- `scripts/ui/event_log_dialog.gd` は範囲の外なので、別の単位として提案する。
|
||||
- 照合の ordinary の分岐は、狭い roster では実地に動かない。
|
||||
- 予報はその日の分だけを出す。
|
||||
- UI は今の部品のままで、完成の見た目ではない。
|
||||
- 同じ Windows の実機の受入、長い通常のプレイ、全ゲームの最終の受入は残っている。
|
||||
+744
@@ -0,0 +1,744 @@
|
||||
{
|
||||
"at_jst": "2026-10-24T10:43:31.780899+09:00",
|
||||
"auxiliary_unit": "P07-01-WX",
|
||||
"original_unit": "P07-01",
|
||||
"project": "the-tree-p07-01-wx",
|
||||
"task": "P07-01-WX",
|
||||
"state": "completed",
|
||||
"S": "db87270972c9b243e3a1fccdf2c1fa96975dffc1",
|
||||
"Q": "9b5b09b35e888e5ac1a49174b8a7f73e51c502d2",
|
||||
"M": "7ac2224e4705d06aab28803592a68b31d60841ed",
|
||||
"repairs": 2,
|
||||
"Q_tree_equals_S_tree": "86fa8fdd300f9146c6afaac9fddd2405a433718c",
|
||||
"all4_normal_gates_passed": true,
|
||||
"scope": "Auxiliary successor of the original P07-01 by Root work sequencing (option B, ROOT_AUXILIARY_SCOPE_ORDER_R01.json): the finished natural weather, rain arrivals and stay, storm, the world tree fog yield 24 and the adult fog intimacy, the HUD forecast and save/migration/rejection. Not a new human gameplay approval and not a deletion of the original P07-01 acceptance.",
|
||||
"original_work": "雨の来客減・滞在延長、マナ霧、自然変化を実営業へ接続する。",
|
||||
"original_acceptance": "天候切替直前後、途中保存、自然来客の差を同条件で説明できる。",
|
||||
"original_package_work": "雨/嵐/マナ霧、流行ニュース、ゾーンの採光/遮音/隠蔽、景観/清潔/騒音を段階導入。係数と乱数系列を保存しHUDと予測へ表示。",
|
||||
"original_package_acceptance": "嵐で新規入館0、雨で来客減/滞在延長。天候変化の瞬間と保存再開が一致。環境変更に応じて満足度と対象売上が変わる。",
|
||||
"original_cross_cutting_acceptance": [
|
||||
"原本条件とUI操作が一致",
|
||||
"Runtimeの実利用・会計・行動へ接続",
|
||||
"途中保存・再起動で再現",
|
||||
"異常/境界/中断を検証",
|
||||
"関連回帰PASS",
|
||||
"要件別ログとソースハッシュを保存",
|
||||
"既存代表負荷の性能回帰を計測"
|
||||
],
|
||||
"acceptance_ids": [
|
||||
{
|
||||
"id": "P07-01-WX1",
|
||||
"text": "自然天候: 毎日 06:00 に1回だけ、原 SPEC §4.1 の確率・開始の時刻・続く時間で天候が自然に予定され、同じ world_seed と同じ操作で同じ天候の列になる。新しいゲームの開始の日も抽選され、旧 save は原 SPEC §5.2 のとおり移行する。"
|
||||
},
|
||||
{
|
||||
"id": "P07-01-WX2",
|
||||
"text": "雨の来客: 雨の間の来客の計画の新規の人数が、同じ条件の晴れの半分(切り捨て)になり、嵐の 0 と定員・マナの上限を壊さない。"
|
||||
},
|
||||
{
|
||||
"id": "P07-01-WX3",
|
||||
"text": "雨の滞在: 雨の間に帰りの時刻になった客が、雨の終わりか元の予定 + 240 分の早い方まで1回だけ残り、その後は今の規則で帰る。嵐と霧では延びない。"
|
||||
},
|
||||
{
|
||||
"id": "P07-01-WX4",
|
||||
"text": "嵐: 自然の嵐を含め、嵐の間の新規入館が 0。"
|
||||
},
|
||||
{
|
||||
"id": "P07-01-WX5",
|
||||
"text": "マナ霧(炉を除く): 霧の間のマナの境界で世界樹 24(上限 300 のまま)、成人確認済みの客の性欲の増加が floor(×1.5) になり、霧の外と成人確認の無い客は変わらない。マナ炉の産出 120 はこの補助の作業では受け入れず、通常の営業での達成を主張しない(炉の産出の経路が無いので、貯蔵の規則の単体の確かめは開示として扱う)。"
|
||||
},
|
||||
{
|
||||
"id": "P07-01-WX6",
|
||||
"text": "表示と予報: ゲームの本物の Runtime と Main の上部の表示で、今の天候・次の切り替え(種類と時刻)・効果の案内が Runtime の値と一致する。"
|
||||
},
|
||||
{
|
||||
"id": "P07-01-WX7",
|
||||
"text": "保存と再開: 天候の切り替えの直前・直後・途中の保存を、同じ process と別の process で読み、天候・予定・系列・来客・滞在の延長・霧の効果が同じになり、二重に効かない。旧 save は原 SPEC §5.2 で読め、不正な状態は原 SPEC §5.3 で拒否して状態を保つ。"
|
||||
},
|
||||
{
|
||||
"id": "P07-01-WX8",
|
||||
"text": "回帰と照合: 関連の回帰(狭い roster と再開の2段)と後継の S の全 roster(既存の代表負荷の性能の suite を含む)が PASS。登録の照合が、`ordinary_cleanup_prepared` と `old_bounded_teardown` の両方の suite について、制御の環境と後片付けの証明・hash を厳密に照合する。既存の試験の期待値の変更は `EXPECTATION_CHANGES.json` に変更前後と根拠を一覧にする。元の失敗(S `dfacc83` の native の照合の失敗を含む)を保存し、未解決の試験を除いて PASS にしない。"
|
||||
}
|
||||
],
|
||||
"acceptance_baseline": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p07-01-wx/acceptance_baseline.json",
|
||||
"bytes": 3400,
|
||||
"sha256": "5207d0ac39538c7e639e04c66d73e6a316de17bf872f42eed718d3e0fc7bde7a"
|
||||
},
|
||||
"spec": {
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p07-01/wx/SPEC.md",
|
||||
"bytes": 11023,
|
||||
"sha256": "d9bdb25710faf18f306107328e3277fe621ef060cd6e93f580889672f899e0ec",
|
||||
"contract_spec_sha256": "f1cd426202c605cc81727649283900438c6adb5d473670f5e9e6b01858fbd783"
|
||||
},
|
||||
"provenance": {
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p07-01/wx/PROVENANCE.json",
|
||||
"bytes": 5834,
|
||||
"sha256": "84dc1d4bece6a06e95d4230eae050bd004b2f22a466b9bb595c2ee5d4f56cbd8"
|
||||
},
|
||||
"original_spec": {
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p07-01/SPEC.md",
|
||||
"bytes": 13238,
|
||||
"sha256": "7c715a1a4576a37e40f354d2e3c929f5be95c581713c7500984d2d073fe587dd"
|
||||
},
|
||||
"original_provenance_A13": {
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p07-01/PROVENANCE.json",
|
||||
"bytes": 3164,
|
||||
"sha256": "d9852735789d50ee57f6a35268d1fd9e30d4016082b219119def902abe20091c"
|
||||
},
|
||||
"unit_disclosures": [
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p07-01/wx/IMPLEMENTATION_NOTES.md",
|
||||
"bytes": 12625,
|
||||
"sha256": "194979ada4fd195ef5b29824dc6164123a9bed96609bb2e704ccaac397bd892e"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p07-01/wx/EXPECTATION_CHANGES.json",
|
||||
"bytes": 1871,
|
||||
"sha256": "28d5b13b011562771a8772f36c8c9fc1024ad551259b375bbf70b9cbb6ade0c3"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p07-01/IMPLEMENTATION_NOTES.md",
|
||||
"bytes": 5463,
|
||||
"sha256": "d6876514ed93bf1d0c4db1b51c9af7d416a7c80cd24406a4523fdcf51e436908"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p07-01/EXPECTATION_CHANGES.json",
|
||||
"bytes": 3131,
|
||||
"sha256": "51cff9ad1c1e8ef694fd1940fd1c242559ae981522fe8e1c48c87e5770d4a5ef"
|
||||
}
|
||||
],
|
||||
"runner_task": {
|
||||
"id": "P07-01-WX",
|
||||
"state": "completed",
|
||||
"submitted": "db87270972c9b243e3a1fccdf2c1fa96975dffc1",
|
||||
"candidate": "9b5b09b35e888e5ac1a49174b8a7f73e51c502d2",
|
||||
"main_base": "7ac2224e4705d06aab28803592a68b31d60841ed",
|
||||
"reason": null,
|
||||
"repairs": 2
|
||||
},
|
||||
"runner_events": [
|
||||
{
|
||||
"id": 1,
|
||||
"event": "registered",
|
||||
"created": 1792784865.483346
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"event": "owner_delegated_implementer",
|
||||
"created": 1792785023.35983
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"event": "commander_proposal",
|
||||
"created": 1792785130.728936
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"event": "submitted",
|
||||
"created": 1792785888.967114
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"event": "validating",
|
||||
"created": 1792787216.747516
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"event": "review_pending",
|
||||
"created": 1792787222.722386
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"event": "owner_prepared_actual_desktop_independent_review_input",
|
||||
"created": 1792790128.160841
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"event": "review_pending",
|
||||
"created": 1792791990.31691
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"event": "owner_actual_independent_Desktop_review_output",
|
||||
"created": 1792791999.8852632
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"event": "repair_pending",
|
||||
"created": 1792791999.887711
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"event": "commander_proposal",
|
||||
"created": 1792792080.606424
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"event": "repair_ready",
|
||||
"created": 1792792080.609395
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"event": "submitted",
|
||||
"created": 1792793303.897059
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"event": "validating",
|
||||
"created": 1792793918.329674
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"event": "review_pending",
|
||||
"created": 1792793925.8383539
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"event": "owner_prepared_actual_desktop_independent_review_input",
|
||||
"created": 1792797378.184551
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"event": "review_pending",
|
||||
"created": 1792799568.977567
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"event": "owner_actual_independent_Desktop_review_output",
|
||||
"created": 1792799578.633156
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"event": "repair_pending",
|
||||
"created": 1792799578.636506
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"event": "commander_proposal",
|
||||
"created": 1792799615.441633
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"event": "repair_ready",
|
||||
"created": 1792799615.4451442
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"event": "submitted",
|
||||
"created": 1792799976.661932
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"event": "validating",
|
||||
"created": 1792800668.8961751
|
||||
},
|
||||
{
|
||||
"id": 24,
|
||||
"event": "review_pending",
|
||||
"created": 1792800675.885521
|
||||
},
|
||||
{
|
||||
"id": 25,
|
||||
"event": "owner_prepared_actual_desktop_independent_review_input",
|
||||
"created": 1792804081.5048692
|
||||
},
|
||||
{
|
||||
"id": 26,
|
||||
"event": "review_pending",
|
||||
"created": 1792805107.4343512
|
||||
},
|
||||
{
|
||||
"id": 27,
|
||||
"event": "owner_actual_independent_Desktop_review_output",
|
||||
"created": 1792805117.083816
|
||||
},
|
||||
{
|
||||
"id": 28,
|
||||
"event": "reviewed",
|
||||
"created": 1792805117.0873659
|
||||
},
|
||||
{
|
||||
"id": 29,
|
||||
"event": "approval_pending",
|
||||
"created": 1792805138.5482361
|
||||
},
|
||||
{
|
||||
"id": 30,
|
||||
"event": "approved",
|
||||
"created": 1792805852.1386409
|
||||
},
|
||||
{
|
||||
"id": 31,
|
||||
"event": "completed",
|
||||
"created": 1792805862.032322
|
||||
}
|
||||
],
|
||||
"normal_evidence_join": [
|
||||
{
|
||||
"id": 5,
|
||||
"task_id": "P07-01-WX",
|
||||
"kind": "validation",
|
||||
"commit_sha": "db87270972c9b243e3a1fccdf2c1fa96975dffc1",
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p07-01-wx/runs/f60b5a77b01a40fbbd4cdec52d7b3419-validation.json",
|
||||
"passed": 1,
|
||||
"artifact_sha256": "ed62d295182c53bc661cfeaa578e69af96d1e89e1ef5e5959a666700a5f7518c",
|
||||
"checks": [
|
||||
{
|
||||
"id": "p07-weather",
|
||||
"exit_code": 0,
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p07-01-wx/runs/b6dad51a630149839962f304dc73df52",
|
||||
"cwd": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p07-01-wx/P07-01-WX-validation-704968e9",
|
||||
"stdout": {
|
||||
"verified": true,
|
||||
"actual_suites": 14,
|
||||
"parse_checks": 350,
|
||||
"actual_checks": 1159,
|
||||
"engines_started": 0,
|
||||
"formal_acceptance": false,
|
||||
"original_unit_complete": false
|
||||
},
|
||||
"raw": {
|
||||
"command.json": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p07-01-wx/runs/b6dad51a630149839962f304dc73df52/command.json",
|
||||
"bytes": 729,
|
||||
"sha256": "f64b75d0614d9dc38b77ee6635bdf656c69f6415a1e60dc714eb5e7fac7bb15c"
|
||||
},
|
||||
"stdout.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p07-01-wx/runs/b6dad51a630149839962f304dc73df52/stdout.log",
|
||||
"bytes": 167,
|
||||
"sha256": "7784c06ca2e44363e79c585aac0a01d01aeac250d5dd8b81fe60eae3961e60e1"
|
||||
},
|
||||
"stderr.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p07-01-wx/runs/b6dad51a630149839962f304dc73df52/stderr.log",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
"sandbox.sb": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p07-01-wx/runs/b6dad51a630149839962f304dc73df52/sandbox.sb",
|
||||
"bytes": 1944,
|
||||
"sha256": "07b5dcb0f19dcfbf238a9f4b7d9876c2eebf3e8d1ed3dc87dfa1488d9d3dca79"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"task_id": "P07-01-WX",
|
||||
"kind": "review",
|
||||
"commit_sha": "db87270972c9b243e3a1fccdf2c1fa96975dffc1",
|
||||
"artifact": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/p07-01-wx-independent-review-db87270-r01/actual-independent-desktop-output/result.json",
|
||||
"passed": 1,
|
||||
"artifact_sha256": "1fd2c8bbe0f9d6b165d5f993e86002b94ead97674c27ed9270a223c929c1aaca",
|
||||
"checks": []
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"task_id": "P07-01-WX",
|
||||
"kind": "integration",
|
||||
"commit_sha": "9b5b09b35e888e5ac1a49174b8a7f73e51c502d2",
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p07-01-wx/runs/ba5835b26b554b20a5bc7ddb5a5351af-integration.json",
|
||||
"passed": 1,
|
||||
"artifact_sha256": "848717653b817734a9a6a66f3ae40534dc991c4a60bbdea097df69e73256aabb",
|
||||
"checks": [
|
||||
{
|
||||
"id": "p07-weather",
|
||||
"exit_code": 0,
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p07-01-wx/runs/e44512c3202e4ef4bd22f7a8f601792b",
|
||||
"cwd": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p07-01-wx/P07-01-WX-integration-ed82b987",
|
||||
"stdout": {
|
||||
"verified": true,
|
||||
"actual_suites": 14,
|
||||
"parse_checks": 350,
|
||||
"actual_checks": 1159,
|
||||
"engines_started": 0,
|
||||
"formal_acceptance": false,
|
||||
"original_unit_complete": false
|
||||
},
|
||||
"raw": {
|
||||
"command.json": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p07-01-wx/runs/e44512c3202e4ef4bd22f7a8f601792b/command.json",
|
||||
"bytes": 730,
|
||||
"sha256": "c808e064d90d60dc84e96e6cf1072263ad233f5bfb97997e92e221019fcd764f"
|
||||
},
|
||||
"stdout.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p07-01-wx/runs/e44512c3202e4ef4bd22f7a8f601792b/stdout.log",
|
||||
"bytes": 167,
|
||||
"sha256": "7784c06ca2e44363e79c585aac0a01d01aeac250d5dd8b81fe60eae3961e60e1"
|
||||
},
|
||||
"stderr.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p07-01-wx/runs/e44512c3202e4ef4bd22f7a8f601792b/stderr.log",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
"sandbox.sb": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p07-01-wx/runs/e44512c3202e4ef4bd22f7a8f601792b/sandbox.sb",
|
||||
"bytes": 1945,
|
||||
"sha256": "a3494c5bdbb499f7304f635fba7e561b9a85590480a1eb1c8cffc49049637746"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"task_id": "P07-01-WX",
|
||||
"kind": "final",
|
||||
"commit_sha": "9b5b09b35e888e5ac1a49174b8a7f73e51c502d2",
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p07-01-wx/runs/9145ecb2c8ed4ac5b224c6609181ad88-final.json",
|
||||
"passed": 1,
|
||||
"artifact_sha256": "61d87d352a67d43ed3c52f290a657d5d0f2c7531267fac40133cf44d631b28d2",
|
||||
"checks": [
|
||||
{
|
||||
"id": "p07-weather",
|
||||
"exit_code": 0,
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p07-01-wx/runs/b8a8642f84df46a5b9b1a66a7c272350",
|
||||
"cwd": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p07-01-wx/P07-01-WX-final-9b2565c9",
|
||||
"stdout": {
|
||||
"verified": true,
|
||||
"actual_suites": 14,
|
||||
"parse_checks": 350,
|
||||
"actual_checks": 1159,
|
||||
"engines_started": 0,
|
||||
"formal_acceptance": false,
|
||||
"original_unit_complete": false
|
||||
},
|
||||
"raw": {
|
||||
"command.json": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p07-01-wx/runs/b8a8642f84df46a5b9b1a66a7c272350/command.json",
|
||||
"bytes": 736,
|
||||
"sha256": "23902a90ad03f64994a05874d69ba00b3de5e7b6224a8eaf16cad07556720c73"
|
||||
},
|
||||
"stdout.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p07-01-wx/runs/b8a8642f84df46a5b9b1a66a7c272350/stdout.log",
|
||||
"bytes": 167,
|
||||
"sha256": "7784c06ca2e44363e79c585aac0a01d01aeac250d5dd8b81fe60eae3961e60e1"
|
||||
},
|
||||
"stderr.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p07-01-wx/runs/b8a8642f84df46a5b9b1a66a7c272350/stderr.log",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
"sandbox.sb": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p07-01-wx/runs/b8a8642f84df46a5b9b1a66a7c272350/sandbox.sb",
|
||||
"bytes": 1939,
|
||||
"sha256": "2e408ff831d67ac2703239e6212f8db86f45839ee8d685cd3f491b4cb1da897e"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"earlier_evidence_retained": [
|
||||
{
|
||||
"id": 1,
|
||||
"kind": "validation",
|
||||
"commit_sha": "c50136017b686994c79bdb3ecf49a9df5a465052",
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p07-01-wx/runs/10a64c693929436d9ea374f1a520c965-validation.json",
|
||||
"passed": 1
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"kind": "review",
|
||||
"commit_sha": "c50136017b686994c79bdb3ecf49a9df5a465052",
|
||||
"artifact": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/p07-01-wx-independent-review-c501360-r01/actual-independent-desktop-output/result.json",
|
||||
"passed": 0
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"kind": "validation",
|
||||
"commit_sha": "f99c881bca0399fec7a75a8715265fbcf7ff5b4c",
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p07-01-wx/runs/d456a391f0334ef4b02dcaf3e80de182-validation.json",
|
||||
"passed": 1
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"kind": "review",
|
||||
"commit_sha": "f99c881bca0399fec7a75a8715265fbcf7ff5b4c",
|
||||
"artifact": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/p07-01-wx-independent-review-f99c881-r01/actual-independent-desktop-output/result.json",
|
||||
"passed": 0
|
||||
}
|
||||
],
|
||||
"approval": {
|
||||
"task_id": "P07-01-WX",
|
||||
"candidate": "9b5b09b35e888e5ac1a49174b8a7f73e51c502d2",
|
||||
"main_base": "7ac2224e4705d06aab28803592a68b31d60841ed",
|
||||
"owner": "Root/Codex applying existing human authorization for exact-candidate main integration and push; no new human utterance",
|
||||
"created": 1792805852.13765
|
||||
},
|
||||
"owner_records": [
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/ROOT_OWNER_APPROVAL_9B5B09B_R01.json",
|
||||
"bytes": 5946,
|
||||
"sha256": "9b8547780b75a4ecfa645fc63c1d5334e50e297fb8fc8034a1bde6ff362cb6b0"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/ROOT_WX_COMPLETED_DB_JOIN_9B5B09B_R01.json",
|
||||
"bytes": 8582,
|
||||
"sha256": "aeef2ac361363b23938992f36d58de12714620fea669f9ed2672ddcef764fe1c"
|
||||
}
|
||||
],
|
||||
"integrate_job_id": "d166ed1445074b71bdae495edf5a6880",
|
||||
"traceability": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p07-01-wx/traceability/P07-01-WX.json",
|
||||
"bytes": 4746,
|
||||
"sha256": "68964ef40bb6dd033974e9721d6572f6056d80e1300e4721d1735ed7c3daaffc"
|
||||
},
|
||||
"independent_reviewer": {
|
||||
"task_id": "P07-01-WX",
|
||||
"commit": "db87270972c9b243e3a1fccdf2c1fa96975dffc1",
|
||||
"session_id": "local_fc6760c6-9acd-4614-950e-632b91d53f54",
|
||||
"model": "claude-opus-5-5",
|
||||
"context": "fresh",
|
||||
"read_only": true,
|
||||
"read_only_meaning": "The governor read every unique tool call of the reviewer transcript (77 unique ids): Bash read files, git objects and packet data (cat, sed, grep, find, ls, shasum, cmp, diff, git diff/log/show/ls-tree, python3 -I reading JSON, git blobs and the handoff); Write created DESKTOP_RESULT.json and three Edit calls revised that same result file before delivery; no test, engine, Runner or git-mutating command ran and no project, source or evidence file was changed (the frozen material shows no new file). Outside the project the reviewer wrote helper files in its own session scratchpad only (packet.json, diff.txt and gitdiff.txt extracted for comparison, lstree.txt, parse.py and gvar.py, read-only decoders, and __pycache__), as the brief allowed. It also read the previous review results, read-only (transcript 68151603-2f8e-40d1-aa98-3e3c77d33652.jsonl; tool calls {\"mcp__ccd_turn__update_plan\": 5, \"Bash\": 66, \"Read\": 1, \"Write\": 1, \"Edit\": 3, \"mcp__ccd_turn__wrap_up\": 1}; edit tools [\"Edit\", \"Write\"]; pattern matches read by the governor: 5)",
|
||||
"tests_or_engines_started": false,
|
||||
"packet_sha256": "7e0fe0882371d6b74b9a8a2c1c68df5ad1bbd6486bc288bf8ffdc8900d4bee19",
|
||||
"normal_packet_sha256": "6e0f8d299a195d02b5f0b3579b5b4e49a6ecd772c9bdab6985ef31ab3c9f910e",
|
||||
"schema_sha256": "a25f262ed5d1a5d36b5f8fca747d28621f39c11e664e058cdaf5f92297857d49",
|
||||
"material_manifest_sha256": "c3ef98337585388c4a624ad56d1cc4dbfe209cc7d73c2c68865b34e5865c8d54",
|
||||
"result_file": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/p07-01-wx-independent-review-db87270-r01/DESKTOP_RESULT.json",
|
||||
"result_sha256": "1fd2c8bbe0f9d6b165d5f993e86002b94ead97674c27ed9270a223c929c1aaca",
|
||||
"result_sha256_source": "computed by the governor from the actual DESKTOP_RESULT.json (not edited)",
|
||||
"status_reported": "pass",
|
||||
"delivered_by": "Desktop governor session local_a7f85421-3eed-4482-8030-c0dca669573a (implementer, commander and native executor of P07-01-WX; did not review or edit the result)",
|
||||
"session_started_by": "Root started the own_initiative card task_2fb30bba in the Claude Desktop UI (Root relay: actual UI confirmed); app session local_fc6760c6-9acd-4614-950e-632b91d53f54, fresh context, parent local_a7f85421 only as the card's origin",
|
||||
"cli_authentication_recovered": false,
|
||||
"cli_review_succeeded": false,
|
||||
"recorded_at": 1792805101.216085
|
||||
},
|
||||
"independent_result": {
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p07-01/wx/WX_INDEPENDENT_REVIEW_DB87270.json",
|
||||
"bytes": 15057,
|
||||
"sha256": "1fd2c8bbe0f9d6b165d5f993e86002b94ead97674c27ed9270a223c929c1aaca",
|
||||
"copy_of": "artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/p07-01-wx-independent-review-db87270-r01/DESKTOP_RESULT.json"
|
||||
},
|
||||
"review_findings": [
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "WX3・WX7 の試験の不足(S f99c881 の low の残り。一部は開示済み)。再読み込みの後に延長が二重に効かないことと、延長の時刻の帰りを、まだ判断中の延長された客で動かして確かめていない。\n\n根拠:\n- 363 分の保存では、延びた客はもう LEAVING である(tests/environment/test_p07_weather_restart_1_write.gd 36〜37・55〜56 行)。\n- _advance_guest_ai_decisions は、DECIDING の時だけ _hold_guest_for_rain を通す(autoload/simulation_runtime.gd 9062〜9063 行)。\n- そのため、「(no second extension)」と書いた1分後の check(writer 76〜78 行、test_p07_weather_restart_2_read.gd 77〜78 行)と、wx/IMPLEMENTATION_NOTES.md 116 行の「1分後の一致で、二度目の延長が無いことを示す」は、延長の記録が失われても二度目の延長を検出できない。\n\n二重に効かないことの実際の根拠は、次の3つで、間接である:\n- 延長の記録と plan 602 が、両方の process で正確に戻ること(確かめ済み。handoff の復号でも一致)\n- _hold_guest_for_rain の記録の判定(13640 行)\n- メモリの上の直接の呼び出し(test_p07_weather_runtime.gd 201 行、256〜258 行)\n\n延長の時刻の PLANNED_EXIT も、直接の呼び出しだけ。こちらは wx §7 119〜121 行で開示されている。\n\nsource を読んだ限りでは製品は正しく、合格を妨げない。後の提案: 施設を使って延長の時刻まで DECIDING で残る客の世界で、延長を持つ保存を再読み込みし、10:02 に新しい延長が無く PLANNED_EXIT で帰ることを確かめる。または、check の message と記録の文言を実態に合わせる。"
|
||||
},
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "WX1・WX7 の試験の不足(§5.2 の4つの規則のうち1つ)。「読み込みの後の最初の 06:00 から抽選を始める」を、移行した save で直接確かめていない。\n\n根拠:\n- tests/environment/test_p07_weather_runtime.gd 318〜331 行は、v1 の save を 06:05 に読む。今の天候を保つこと、last_draw_day 0、その日の抽選が無いこと、系列 weather が world_seed から作られること、延長の記録が無いことは確かめる。しかし、移行した Runtime を次の 06:00(1800 分)まで進めて、day 1 の抽選が1回出ることは確かめていない。\n- tests/environment/test_runtime_weather.gd 22〜28 行(v10 の移行)と、kitchen の legacy14 の照合も、読んだ状態を比べるだけである。\n\nsource では、移行の last_draw_day(scripts/environment/weather_timeline.gd 216〜218 行の _days_before)と抽選の判定 day > last_draw_day(autoload/simulation_runtime.gd 13615〜13621 行)が、新しいゲームの毎日の抽選と同じ経路である。新しいゲームの毎日の抽選は、test_p07_weather_runtime.gd 146〜154 行で確かめている。そのため製品は正しいと判断し、合格を妨げない。後の提案: 移行の試験で、次の 06:00 まで進めて抽選が1回出ることの check を足す。"
|
||||
}
|
||||
],
|
||||
"review_high_medium": 0,
|
||||
"review_low": 2,
|
||||
"review_provenance_records": [
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/p07-01-wx-independent-review-db87270-r01/READY_FOR_DESKTOP_REVIEW.json",
|
||||
"bytes": 2785,
|
||||
"sha256": "903ad75cae4b685b57b8d06c70ae1cc6cd5cd40752fa742b245e6f0a2abe19f3"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/p07-01-wx-independent-review-db87270-r01/DESKTOP_DELIVERY.json",
|
||||
"bytes": 2692,
|
||||
"sha256": "3e0960d863904ddb01c62d5a04f53161ebc8304676fd8b68e6ee9d4e0741322a"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/p07-01-wx-independent-review-db87270-r01/INTAKE_STARTED.json",
|
||||
"bytes": 217,
|
||||
"sha256": "70c04498573812960b85f98efafa14cac5b79b29074ca2e02b09e677664c6b93"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/p07-01-wx-independent-review-db87270-r01/INTAKE_FINISHED.json",
|
||||
"bytes": 288,
|
||||
"sha256": "ecc0d3c9462f2459ad0988ac54fe97279e0fdf277aafb02d0ebaf434f1032f24"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/DESKTOP_DELIVERY_WRITE_DB87270.stdout",
|
||||
"bytes": 192,
|
||||
"sha256": "e7c8bab3ca2a266ec25dc63f0d5681d32e80e77163ab931c8c4556260dccf28a"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/RECEIVE_REVIEW_DB87270.stdout",
|
||||
"bytes": 179,
|
||||
"sha256": "743e2c703ea9702e30e60a1592c28d2c5066b6bf1486e91c60352af7ecfdf748"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/PREPARE_INTEGRATION_DB87270_SUMMARY.json",
|
||||
"bytes": 3177,
|
||||
"sha256": "7909b4f608d635168f648c8b6b9b6b4bd30b21291c3e3f0b8707df2e50bca926"
|
||||
}
|
||||
],
|
||||
"earlier_reviews_retained": [
|
||||
{
|
||||
"S": "c50136017b686994c79bdb3ecf49a9df5a465052",
|
||||
"status": "changes_requested",
|
||||
"result": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/p07-01-wx-independent-review-c501360-r01/DESKTOP_RESULT.json",
|
||||
"bytes": 12489,
|
||||
"sha256": "aea0cc4f2c00a6a093ca256154c62812a4d83749d075c6a4a9d36c1270026da7"
|
||||
},
|
||||
"repair": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/COMMANDER_REPAIR_PLAN_P07_01_WX_R01.md",
|
||||
"bytes": 4315,
|
||||
"sha256": "73fc02ad93ef1f000738d041abcd1b78a1aedb92d6beedda2621bd78aca33232"
|
||||
}
|
||||
},
|
||||
{
|
||||
"S": "f99c881bca0399fec7a75a8715265fbcf7ff5b4c",
|
||||
"status": "changes_requested",
|
||||
"result": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/p07-01-wx-independent-review-f99c881-r01/DESKTOP_RESULT.json",
|
||||
"bytes": 12831,
|
||||
"sha256": "59595592ad55a5e71c77ffc1fa5a4e5203bf8381af5dd72f3c506a3977c60269"
|
||||
},
|
||||
"repair": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/COMMANDER_REPAIR_PLAN_P07_01_WX_R02.md",
|
||||
"bytes": 3986,
|
||||
"sha256": "af76464dbafa880b4b803bb16e2be93ab73000e92507e62a2d5bf908ca0c6dc2"
|
||||
}
|
||||
}
|
||||
],
|
||||
"measurement": {
|
||||
"measured_on": "db87270972c9b243e3a1fccdf2c1fa96975dffc1",
|
||||
"native": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/NATIVE_DB87270_START.json",
|
||||
"bytes": 937,
|
||||
"sha256": "c6c6b34d6c81b4af6c1bfcbe8d490e755e82c98532490c8c0b4e94ec7434cceb"
|
||||
},
|
||||
"native_bundle_transfer": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/NATIVE_DB87270_BUNDLE_TRANSFER.json",
|
||||
"bytes": 1333,
|
||||
"sha256": "3e8c1fe02567452eb2a5250b55cd464542cacb64e1db4e4c17a04e96ef650b32"
|
||||
},
|
||||
"formal_validation_summary": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/FORMAL_VALIDATE_DB87270_SUMMARY.json",
|
||||
"bytes": 3273,
|
||||
"sha256": "2db2d9cace4e26358557fc81d5c7631ec420c14df9a289f7498d752a94e2201b"
|
||||
},
|
||||
"full_roster_summary": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/FULL_ROSTER_DB87270_R01_SUMMARY.json",
|
||||
"bytes": 3815,
|
||||
"sha256": "7f9a042d39291de5730ae2747f4de2c96282cd6a9124268fa0020580e0fb14e5"
|
||||
},
|
||||
"full_roster_placement": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/FULL_ROSTER_DB87270_PLACEMENT.json",
|
||||
"bytes": 951,
|
||||
"sha256": "0a647732ac0689640eabb0bd21f732b3d7b7a74d6166be074cbf6c00c387b495"
|
||||
},
|
||||
"native_phases": 16,
|
||||
"native_suites": 14,
|
||||
"native_checks": 1159,
|
||||
"full_roster_suites": 198,
|
||||
"full_roster_checks": 22959,
|
||||
"measurement_not_relabelled_as_gate_Q": true,
|
||||
"Q_meaning": "the evidence measured on S was checked by the registered content-based verifier in fresh checkouts of Q (integration and final); nothing was measured on Q and no engine started"
|
||||
},
|
||||
"capacity_records_content_preserving": [
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/CLOSED_COPIES_ALIGN_R21.json",
|
||||
"bytes": 31543021,
|
||||
"sha256": "cfa4f6139cf6e7427896f1f057dcdf2ec920218e0b0a78cd99d27811a89fc035"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/SCRATCH_STAGES_ALIGN_R23A.json",
|
||||
"bytes": 4544129,
|
||||
"sha256": "d9b762af5fc7bd7760a75592ef0bb5c3e41f9b34bd9cfa3825ac423c22a0468b"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/C501360_CLOSED_MOVE_ALIGN_R23B.json",
|
||||
"bytes": 3046949,
|
||||
"sha256": "0c33d06226315af930c9d134ef80cc05b5c6b5f2d7d093bb0e29dec3591d9647"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/F99C881_CLOSED_ALIGN_R25.json",
|
||||
"bytes": 1515211,
|
||||
"sha256": "a553519e47d45d518ce8dc1e9c241d53f7cd9ef69db0ba3bad808dfefab515bb"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/F99C881_CLOSED_MOVE_ALIGN_R26.json",
|
||||
"bytes": 1531974,
|
||||
"sha256": "de5519ec42b29d9a08c9d86155f43c5e8d0b5fcd61cfc5040c443ec8d68d4e94"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/DB87270_CLOSED_ALIGN_R27.json",
|
||||
"bytes": 1515187,
|
||||
"sha256": "f326f9d14f608a0a2eeef2212557ab0026403f6d73a268c5d4eb82df720d30be"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/P07_01_WX_FULL_ROSTER_RAW_COMPRESSION_F99C881_R24.json",
|
||||
"bytes": 3076779,
|
||||
"sha256": "7dbb9b918829a2861648cfc1d8d25ee0236a1b83fee46f0e276d60943204a89e"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/P07_01_WX_FULL_ROSTER_RAW_COMPRESSION_DB87270_R28.json",
|
||||
"bytes": 3113055,
|
||||
"sha256": "0622c636b62b222ddc6070cc623694210f1781f78a13d42c48067605bdf347db"
|
||||
}
|
||||
],
|
||||
"historical_evidence_retained": [
|
||||
{
|
||||
"S": "dfacc83d0afdd322a6389e131b87d3205e5ac9c8",
|
||||
"record": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-control-r01/NATIVE_DFACC83_FAILURE_R01.json",
|
||||
"bytes": 2582,
|
||||
"sha256": "af53fe3eeb58ff9a17274e8c17644407062aa75f1534a661d0dbdc1f7a4b4db5"
|
||||
},
|
||||
"meaning": "original P07-01 S native: 14 suites passed, the copied verifier exited 1 (old_bounded environment gap); fixed in WX"
|
||||
},
|
||||
{
|
||||
"S": "c50136017b686994c79bdb3ecf49a9df5a465052",
|
||||
"record": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/FULL_ROSTER_C501360_FAILURE_R01.json",
|
||||
"bytes": 3515,
|
||||
"sha256": "2b07e78e014aea37bad0fefb024d050e36fd4c0c94c8f4bcfa23499c73e7be26"
|
||||
},
|
||||
"meaning": "full roster stopped at test_required_kitchen.gd (A13 migration not mapped); fixed in repair 1"
|
||||
},
|
||||
{
|
||||
"record": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/P07_01_WX_DIAGNOSTIC_SWEEP_R01.json",
|
||||
"bytes": 39672,
|
||||
"sha256": "bd5efda6c32549a4a35829aa2e4d5c4f4d039f01202aea0f378781386da07c6e"
|
||||
},
|
||||
"meaning": "diagnostic sweep of all 198 suites on a scratch stage; not formal evidence"
|
||||
},
|
||||
{
|
||||
"record": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/P07_01_WX_REPAIR1_LOCAL_CHECKS_R01.json",
|
||||
"bytes": 10176,
|
||||
"sha256": "380a80c877d5992ae3f145a5fb41192ac25ee2504ad7fa4f1d33b5e07371bd6b"
|
||||
},
|
||||
"meaning": "repair 1 local checks with their failed first runs"
|
||||
},
|
||||
{
|
||||
"record": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/P07_01_WX_REPAIR2_LOCAL_CHECKS_R01.json",
|
||||
"bytes": 4959,
|
||||
"sha256": "9650d5050533edd9551d522d2288343dc01a78deadd474f0f9553affb4824eef"
|
||||
},
|
||||
"meaning": "repair 2 local checks; diag-4.log shows that before the fix a save in the minute a held guest left could not be loaded"
|
||||
},
|
||||
{
|
||||
"record": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p07-01-wx-control-r01/FULL_ROSTER_F99C881_R01_START_CORRECTION_01.json",
|
||||
"bytes": 1463,
|
||||
"sha256": "748dc4b8efb81eb16efe84b3574029de3ded6d8dd247bce2770397cbb1aa29fd"
|
||||
},
|
||||
"meaning": "start-record reference name correction (bytes of the start record kept)"
|
||||
}
|
||||
],
|
||||
"remaining_not_claimed": [
|
||||
"Original P07-01 stays unmet: the furnace yield 120 at fog boundaries (original SPEC section 4.4, F5) and AR.weather.fog_magic need the P13 furnace facility, the D04 unit consumption and the A06 separate approval; the original F1-F8 whole-unit acceptance is not claimed",
|
||||
"The event log shows the daily WEATHER_FORECAST_DRAWN as 未識別の記録 because scripts/ui/event_log_dialog.gd has no Japanese name for it (outside allowed_paths; proposed as a separate unit)",
|
||||
"In the test worlds without a usable facility a held guest leaves before its extended time; the exit at the extended time is checked at the decision step only (review low 1, disclosed)",
|
||||
"A migrated save drawing again at the next 06:00 is not tested directly (review low 2); the product path is the daily draw path that is tested",
|
||||
"The ordinary-cleanup branch of the registered verifier is not exercised by the narrow roster (no ordinary suite); old_bounded and no-teardown branches ran",
|
||||
"Forecast shows only the current day; the UI uses the current parts and is not the finished look; Windows device acceptance, long normal play and whole-game final acceptance remain"
|
||||
],
|
||||
"implementation_provenance": "Contract worker field codex is compatibility only; no Codex or Astra run or dispatch. Carry-over of the original S dfacc83 (24 files byte for byte), the verifier fix, repairs 1 and 2, natives and full rosters by the registered Claude Opus 5.5 Desktop implementer and commander; commits made by Runner submit; independent reviews by separate fresh Opus 5.5 Desktop sessions",
|
||||
"formal_original_credit": 0,
|
||||
"original_completed_before": 24,
|
||||
"original_completed_after_ledger": 24,
|
||||
"original_delivery_total": 88,
|
||||
"packages_completed": 4,
|
||||
"packages_total": 30,
|
||||
"partial_credit": false,
|
||||
"new_engine_or_full_roster_runs_for_ledger": 0,
|
||||
"all_prior_failures_retained": true,
|
||||
"CLI_auth_recovered": false,
|
||||
"no_new_human_utterance": true
|
||||
}
|
||||
+26
File diff suppressed because one or more lines are too long
+250
@@ -0,0 +1,250 @@
|
||||
{
|
||||
"unit": "P14-03",
|
||||
"title": "混雑と経路選択",
|
||||
"state": "completed",
|
||||
"work": "客用・業務用EV、階段、ワープの待ち時間をA*費用へ反映する。",
|
||||
"acceptance": "ラッシュ、相乗り、50%減速、救護同伴、経路喪失を実位置と人数で検証する。",
|
||||
"product_candidate": "83b170a4c7176761b0b18d8e9292017d990e80a9",
|
||||
"runner": {
|
||||
"project": "the-tree-p14-03-route-wait",
|
||||
"task_state": "completed",
|
||||
"submitted": "a5d2491204f0604d07bfd1f1c41344a08e6f4835",
|
||||
"candidate_and_integrated": "b7f6168dc8a31cdbdfb9771d583536cd6663c0dd",
|
||||
"main_base": "e8b3dbe7a40a5fafb4b7e760a59536919c8c9f5a",
|
||||
"registered_conditions_completed": 9,
|
||||
"source_map_files": 1407,
|
||||
"source_map_sha256": "146ea57fae34217239cde6d5e202cd67e1bfec70b20bccec14e0913f170c7c0f",
|
||||
"source_map_equals_formal_full_run": true,
|
||||
"high_medium_critical_findings": 0,
|
||||
"receipts": {
|
||||
"root_db_join": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-formal-closure-b7f6168d/ROOT_DB_JOIN.json",
|
||||
"sha256": "07e855612796732b4701a0384d81c61a29918d3acd6149b63afa834991a68179"
|
||||
},
|
||||
"validation": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-formal-closure-b7f6168d/normal-validation.json",
|
||||
"sha256": "74cd08637c1ec00b3d2a2ab4fff004b622191498b902b5676c1b531dde38eda0"
|
||||
},
|
||||
"review": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-formal-closure-b7f6168d/normal-review.json",
|
||||
"sha256": "f7816f703457089a6d2bf760fc86f6bb24aba097ca53b9b53bdf33821e1f33c2"
|
||||
},
|
||||
"integration": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-formal-closure-b7f6168d/normal-integration.json",
|
||||
"sha256": "324bd3bdd94aa942c8e0a424d244d71e9c6c19470342f5100220e9683d628a6e"
|
||||
},
|
||||
"final": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-formal-closure-b7f6168d/normal-final.json",
|
||||
"sha256": "892c29f456d01d1bf3cef98106387dfc94d9a1378efe0f08761525f5327e05b7"
|
||||
},
|
||||
"traceability": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-formal-closure-b7f6168d/normal-traceability.json",
|
||||
"sha256": "08bdc3647eee15b2a17d3a2fbe7d7981d518bcfcd0bf03655ceb1a824175d1e7"
|
||||
},
|
||||
"registered_acceptance_baseline": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-formal-closure-b7f6168d/registered-acceptance-baseline.json",
|
||||
"sha256": "cd2b4f4df25a5d2a8e52df08d97be452b6bd00baa561f8a862d4cc2a5e90af7c"
|
||||
},
|
||||
"owner_input_binding": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-formal-closure-b7f6168d/owner-input-binding.json",
|
||||
"sha256": "5b57672a42bccca559dcc010d57bf12bfdff595df30cd1856d5db413e8075788"
|
||||
},
|
||||
"owner_review_finished": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-formal-closure-b7f6168d/owner-review-finished.json",
|
||||
"sha256": "fa5f2c650af54e3321f3bddb76f9c3ea49ba9b77398ae80dd45f6eeb8d60e71c"
|
||||
}
|
||||
},
|
||||
"gate_payload": {
|
||||
"verify_p14_03_evidence.py": {
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p14-03-runner-gate/verify_p14_03_evidence.py",
|
||||
"sha256": "0cb5f2b1c129aeb765241db3f41569d4bd515d583e55aa2e40723cd46bce7a6a"
|
||||
},
|
||||
"EVIDENCE_BUNDLE_MANIFEST.json": {
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p14-03-runner-gate/EVIDENCE_BUNDLE_MANIFEST.json",
|
||||
"sha256": "651c9932b0389b302026d7008e03dab247558b4db07f583a7208e1db4f2fdd41"
|
||||
},
|
||||
"PROVENANCE.md": {
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p14-03-runner-gate/PROVENANCE.md",
|
||||
"sha256": "d8a6c3ea3a70572ab4fc385d3e03600cc0174018a366f9bf82088dfa0c024835"
|
||||
}
|
||||
},
|
||||
"note": "Runnerの実receiptを引用。新規engine実行・実装実行は本記録で行っていない。"
|
||||
},
|
||||
"bound_evidence": {
|
||||
"formal_full_regression": {
|
||||
"run_id": "7203a11962b14036be60830b657fafd3",
|
||||
"suites": "192/192",
|
||||
"checks": 22198,
|
||||
"failures": 0,
|
||||
"runner_input": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-formal-regression-83b170a-r01/runner-input.json",
|
||||
"sha256": "5bc9760c0c114a80a3c9e93634e733fc8fa93d4eeea5d8bcfda85d8e4eebd094"
|
||||
},
|
||||
"runner_ended": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-formal-regression-83b170a-r01/runner-ended.json",
|
||||
"sha256": "705fb2c12007bab125548742dc8d9b07918e30cae3a63c7d89e965c43b8b5f7b"
|
||||
},
|
||||
"all_tests": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-formal-regression-83b170a-r01/all-tests.json",
|
||||
"sha256": "31da4c4672f4321469183996e12fb23064b3772a69e8330052ab3e2ea66044f1"
|
||||
}
|
||||
},
|
||||
"root_owned_pairs": {
|
||||
"run_id": "9bb640c7a8354023aab27ef408805e19",
|
||||
"pairs": "5/5",
|
||||
"distinct_pids": 10,
|
||||
"runner_ended": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-root-owned-pairs-83b170a-r01/runner-ended.json",
|
||||
"sha256": "8e23cb61679e35d6ed92e312c8f2b860237170935f1d0ff010196f10b6e5eb62"
|
||||
}
|
||||
},
|
||||
"representative_perf_r02": {
|
||||
"execution": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/execution.json",
|
||||
"sha256": "553971f55635e529b48634ef86ad7d4198c8bafe04d5a6a5ef778a4de0439fc1"
|
||||
},
|
||||
"readiness": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/readiness.json",
|
||||
"sha256": "da93aecdd2c1648952e2a80382acb7e3f30cfe48a7e5df982315791161fedde2"
|
||||
},
|
||||
"before_report": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/before/evidence/report.json",
|
||||
"sha256": "a070c061ca783aaec37152bf66ad6b6e6dc69b80d9c4c024eceab916d4c5d4e9"
|
||||
},
|
||||
"after_report": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/after/evidence/report.json",
|
||||
"sha256": "f1d447937158a884a1b287c3b5be38b6e73b73c5d748f9576401877402dabe1b"
|
||||
},
|
||||
"verdict": "収集・機能検証合格、版間比較成立。絶対予算の合格ではない。"
|
||||
},
|
||||
"independent_whole_unit_review": {
|
||||
"verdict": "PASS",
|
||||
"request": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-whole-unit-review-r01/REVIEW_REQUEST.md",
|
||||
"sha256": "0e53d1c1868caa165cd3df8f56d04821798c2bf2d8cb93d9d3793c5171ba77cd"
|
||||
},
|
||||
"output": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-whole-unit-review-r01/review-output.md",
|
||||
"sha256": "15046de363106ece8fdbba2f58e2b701fd6102200e2c22505bddc82d20c70794"
|
||||
}
|
||||
},
|
||||
"evidence_matrix": "docs/plans/original-completion-2026-10-02/execution/p14-03-route-wait/EVIDENCE_MATRIX.md"
|
||||
},
|
||||
"condition_table": [
|
||||
{
|
||||
"id": "P14-03",
|
||||
"condition": "待ち時間のA*費用反映、ラッシュ・相乗り・50%減速・救護同伴・経路喪失を実位置と人数で検証",
|
||||
"result": "達成",
|
||||
"basis": "test_route_wait_choice 119 checks(実客12人ラッシュ迂回、実客6人の相乗り定員4、籠B撤去後の経路切替)、test_runtime_path_traffic:146-158(実客5人で半速)、test_rescue_elevator 30/0",
|
||||
"limit": "経路喪失は出発前の喪失のみ。移動中・待機中の再計画は未試験(low)"
|
||||
},
|
||||
{
|
||||
"id": "P14-X1",
|
||||
"condition": "原本条件とUI操作が一致",
|
||||
"result": "達成",
|
||||
"basis": "建設・撤去はplayer command経由。新規UI面なし"
|
||||
},
|
||||
{
|
||||
"id": "P14-X2",
|
||||
"condition": "Runtimeの実利用・会計・行動へ接続",
|
||||
"result": "達成",
|
||||
"basis": "実際に歩く従業員が迂回、相乗り客はRuntime自身が乗車要求。会計は本単位で新規なし(課金はP14-02)",
|
||||
"limit": "ラッシュ12人と業務用EV4人はrequest_tripで注入(low)"
|
||||
},
|
||||
{
|
||||
"id": "P14-X3",
|
||||
"condition": "途中保存・再起動で再現",
|
||||
"result": "達成",
|
||||
"basis": "保存契約v2往復、旧50ceca0 v1保存の移行、Root別PID pair 5/5・10PIDのdigest一致"
|
||||
},
|
||||
{
|
||||
"id": "P14-X4",
|
||||
"condition": "異常/境界/中断を検証",
|
||||
"result": "達成",
|
||||
"basis": "改ざん値0/300の原子拒否、v1非0拒否、取消、乗車中の退館指示"
|
||||
},
|
||||
{
|
||||
"id": "P14-X5",
|
||||
"condition": "関連回帰PASS",
|
||||
"result": "達成",
|
||||
"basis": "正式全roster 192/192・22,198 checks・失敗0"
|
||||
},
|
||||
{
|
||||
"id": "P14-X6",
|
||||
"condition": "要件別ログとソースハッシュを保存",
|
||||
"result": "達成",
|
||||
"basis": "192行すべてでsource SHAとraw log SHAが一致、source map 1407件が正式全回帰と同一"
|
||||
},
|
||||
{
|
||||
"id": "P14-X7",
|
||||
"condition": "既存代表負荷の性能回帰を計測",
|
||||
"result": "達成(計測として)",
|
||||
"basis": "r02 before/after比較成立。path P95<1ms、manual約300ms、autosave約327ms、load約3.19s",
|
||||
"limit": "x1 1分tickは両版約827msで4ms予算超過(未達、P28で扱う)"
|
||||
},
|
||||
{
|
||||
"id": "P14-X8",
|
||||
"condition": "Windows部分実機確認。不可なら未検証を残す",
|
||||
"result": "達成(未検証を保持)",
|
||||
"basis": "Windows実機は未検証として残す"
|
||||
}
|
||||
],
|
||||
"preserved_low_findings": [
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "Route loss is proven only for loss before departure: shaft B is demolished, then a new walker goes via shaft A. Replanning by an actor that loses its route mid-walk or while queued is not exercised in test_route_wait_choice. There is partial adjacent coverage: test_elevator_service releases waiting riders on outage 'for replan', and test_guest_service covers REPLANNING. The independent review also noted this. A future suite should demolish or stop the shaft while the walker is en route or queued."
|
||||
},
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "The rush (12 guests) and the service-EV queue (4 workers) are injected with elevators.request_trip directly; only the walker and the shared-cabin riders use Runtime-driven travel. The representative perf input does not make real staff use the service shaft. This is disclosed in PROVENANCE and in the verifier's not_measured_or_unmet list."
|
||||
},
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "Verifier robustness: a missing JSON key or malformed evidence makes it fail with a Python traceback (non-zero exit) instead of a structured failed check with --json output. It still fails closed, but a diagnostic is lost. The manifest itself is pinned only by git tracking, not by an internal SHA constant; this is acceptable because the commit is what gets validated."
|
||||
},
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "These are known, explicitly unaccepted items, not resolved by this unit: x1 one-minute tick ≈827 ms against the 4 ms budget in both versions, and x3 frame, main-thread stall, rendering and Windows not measured. They must stay open in the ledger and must not be counted as achieved."
|
||||
}
|
||||
],
|
||||
"unresolved_separate_unmet": [
|
||||
"low4件(Runner normal review)は解消扱いしない",
|
||||
"x1 1分tick 平均約827ms(両版)で4ms予算未達",
|
||||
"x3 frame 未測定",
|
||||
"main-thread stall 未測定",
|
||||
"描画 未測定",
|
||||
"Windows 未検証",
|
||||
"代表負荷で実職員が業務用shaftを使う場面は未実施",
|
||||
"移動中・待機中に経路を失ったactorの再計画は未試験"
|
||||
],
|
||||
"regression_scope_supplement": {
|
||||
"affected_units": [
|
||||
"P06-01",
|
||||
"P06-03",
|
||||
"P14-02",
|
||||
"P06-02"
|
||||
],
|
||||
"statement": "当時の回帰はMakefile経由で、collector rosterのみの46 suiteを含まなかった。その範囲の回帰(v15 mana_reserve保存拒否、test_required_kitchen旧14比較、defense autonomyのマナ前提、P08 palette数)は83b170aで修正し、正式192/192・22,198 checks・失敗0で全roster合格を確認した。各単位の既存ACCEPTANCE.json(p06-01-a06、p14-02-warp等のpinned記録)は改変せず、本記録とwork_packagesの訂正注記で補足する。",
|
||||
"pinned_unchanged": {
|
||||
"execution/p06-01-a06/ACCEPTANCE.json": "6f471e3f32460758b8a617051a507f780d70b33f254a7910dfada87a8c5c3484",
|
||||
"execution/p14-02-warp/ACCEPTANCE.json": "ac7bfd6fb88cb8ac8c195c8051ebe22ba95a6a6393342de089aae88fdcc73307"
|
||||
}
|
||||
},
|
||||
"decisions": [
|
||||
"A06",
|
||||
"A07",
|
||||
"A08"
|
||||
],
|
||||
"approval": "Runner normal final completed。owner判断はユーザー委任「承認するかどうかの判断はそちらでやって、いちいち作業止めないで」に基づくRoot候補審査(ROOT_DB_JOIN)。",
|
||||
"original_completed": 22,
|
||||
"original_total": 88,
|
||||
"whole_package_completed": "P14(P14-01/02/03全完了)",
|
||||
"not_claimed": [
|
||||
"全ゲーム完成",
|
||||
"Windows実機",
|
||||
"性能予算達成",
|
||||
"完成素材・描画目視",
|
||||
"P28"
|
||||
],
|
||||
"recorded_at_jst": "2026-10-20T20:00:00+09:00"
|
||||
}
|
||||
+12051
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,48 @@
|
||||
# P14-03 Runner ゲート:来歴と前提
|
||||
|
||||
この文書は、Runner の task contract に入れられない来歴を記録します(contract は `additionalProperties=false` のため)。
|
||||
|
||||
## 誰が実装したか
|
||||
|
||||
- P14-03 の製品実装は、2026-10-20 に Claude Desktop セッションが `main` 上で直接行いました。
|
||||
- これは、ユーザーが更新した体制の下での作業です。ユーザーは承認判断も委任しています。
|
||||
- 該当する commit は次のとおりで、すべて `83b170a4c7176761b0b18d8e9292017d990e80a9` の祖先です。
|
||||
- `0754f41`: shaft 全体の待ち時間を経路費用に反映
|
||||
- `a8edd3c`: 保存契約 v2、乗車中の客を籠内で退館させない修正
|
||||
- `4036434`: 新PID pair suite、UID、policy pin
|
||||
- `83b170a`: roster のみの suite で見つかった回帰の修正
|
||||
- この実装は Runner の worker(codex / opencode / agy)が行ったものではありません。
|
||||
- task contract の `worker: "codex"` は、Runner が今後修正を求めた場合の担当を予約する欄です。過去の実装者を示すものではありません。
|
||||
|
||||
## base と、今回の提出差分
|
||||
|
||||
- base commit は `e8b3dbe7a40a5fafb4b7e760a59536919c8c9f5a` です。この時点で、`83b170a` の製品 source をすべて含んでいます。base 以降の commit は記録だけで、source は変えていません。
|
||||
- 今回 Runner に提出する差分は、このディレクトリの3ファイルだけです。製品 source には触れません。
|
||||
- `verify_p14_03_evidence.py`
|
||||
- `EVIDENCE_BUNDLE_MANIFEST.json`
|
||||
- `PROVENANCE.md`
|
||||
|
||||
## 外部の前提
|
||||
|
||||
前提は Runner 登録済みの task ではないため、`depends_on` には入れていません。代わりに、tracked な記録として verifier で SHA を固定しています。
|
||||
- P06-01 の受入記録: `execution/p06-01-a06/ACCEPTANCE.json`(`6f471e3f…3484`)
|
||||
- P14-02 の受入記録: `execution/p14-02-warp/ACCEPTANCE.json`(`ac7bfd6f…7307`)
|
||||
- 決定 A06・A07・A08: `decisions.json`(`8b7dba98…a068c`)
|
||||
|
||||
## 再実行しないもの
|
||||
|
||||
次の4つは実行済みで、Runner で再実行はしません。verifier は、既知の SHA に固定したうえで、それぞれの結合を確認します。
|
||||
|
||||
| 証拠 | 主な内容 | 固定している SHA |
|
||||
|---|---|---|
|
||||
| 正式全回帰 | run `7203a119…`、192/192 suite、22,198 checks | ended `705fb2c1…5f7b` |
|
||||
| Root の別PID pair | run `9bb640c7…`、5/5 pair | ended `8e23cb61…eb62` |
|
||||
| 代表性能 r02 | 収集と機能検証は合格、版間比較が成立 | execution `553971f5…9fc1` |
|
||||
| 独立 Opus の whole-unit レビュー | PASS | 依頼 `0e53d1c1…77cd`、出力 `15046de3…0794` |
|
||||
|
||||
## 主張しないこと
|
||||
|
||||
- 絶対性能予算は満たしていません。x1 の1分 tick は両版とも約 827 ms で、4 ms の予算を超えています。
|
||||
- 次のものは未測定です: x3 frame、stall、描画、Windows。
|
||||
- 代表負荷で、実際の職員が業務用 shaft を使う場面は実施していません。
|
||||
- 原本88成果のうち完了数は、Runner の final が出るまで21件のままにします。
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
#!/usr/bin/env python3
|
||||
"""P14-03 evidence binding verifier (Runner validation; read-only, no engine).
|
||||
|
||||
Binds the already-collected P14-03 evidence to the exact source under
|
||||
validation without re-running it. Evidence is read from an evidence root
|
||||
(default: $BUILD_OUTPUT_DIR/p14-03-evidence) that Root populates byte-for-byte
|
||||
from artifacts/original-completion-2026-10-02/. Every evidence file must match
|
||||
the tracked EVIDENCE_BUNDLE_MANIFEST.json next to this script, and the key
|
||||
receipts are pinned to known immutable SHA256 values, so a replaced or
|
||||
regenerated file is detected even if it carries passed/PASS fields.
|
||||
|
||||
Checks:
|
||||
manifest every listed evidence file present, size and SHA equal
|
||||
formal pinned input/ended/all-tests SHAs; run passed completely; the
|
||||
source under validation (tools/run_all_tests.source_refs) equals
|
||||
the run's selected source map; roster equals the standard roster
|
||||
of that source; every row passed, its source SHA equals the map,
|
||||
its raw log SHA equals the bundled log; marker contract and engine
|
||||
pinned and equal to the tracked contract
|
||||
pairs pinned ended SHA; protocol, commit, 5/5, 10 distinct PIDs; bound
|
||||
to the same formal run
|
||||
perf pinned execution SHA; passed and comparison established; bound to
|
||||
formal and pair runs; pinned engine/input/probe; each report equals
|
||||
its execution record and its raw stdout line
|
||||
review pinned request and output SHAs; output names the request SHA and
|
||||
states PASS on the candidate
|
||||
prereqs tracked P06-01 / P14-02 acceptance records and decisions (A06,
|
||||
A07, A08) pinned
|
||||
Absolute performance budgets are not asserted (x1 exceeds 4 ms in both
|
||||
versions; x3/stall/render/Windows unmeasured). Exit 0 only if all hold.
|
||||
"""
|
||||
import argparse, hashlib, importlib.util, json, os, sys
|
||||
from pathlib import Path
|
||||
sys.dont_write_bytecode = True
|
||||
HERE = Path(__file__).resolve().parent
|
||||
CANDIDATE = '83b170a4c7176761b0b18d8e9292017d990e80a9'
|
||||
FULL, PAIR = 'p14-03-formal-regression-83b170a-r01', 'p14-03-root-owned-pairs-83b170a-r01'
|
||||
PERF, REVIEW = 'p14-03-representative-perf-r01/run-r02', 'p14-03-whole-unit-review-r01'
|
||||
PIN = {
|
||||
'full_input': '5bc9760c0c114a80a3c9e93634e733fc8fa93d4eeea5d8bcfda85d8e4eebd094',
|
||||
'full_ended': '705fb2c12007bab125548742dc8d9b07918e30cae3a63c7d89e965c43b8b5f7b',
|
||||
'full_all_tests': '31da4c4672f4321469183996e12fb23064b3772a69e8330052ab3e2ea66044f1',
|
||||
'pair_ended': '8e23cb61679e35d6ed92e312c8f2b860237170935f1d0ff010196f10b6e5eb62',
|
||||
'perf_execution': '553971f55635e529b48634ef86ad7d4198c8bafe04d5a6a5ef778a4de0439fc1',
|
||||
'review_request': '0e53d1c1868caa165cd3df8f56d04821798c2bf2d8cb93d9d3793c5171ba77cd',
|
||||
'review_output': '15046de363106ece8fdbba2f58e2b701fd6102200e2c22505bddc82d20c70794',
|
||||
'engine': 'c7cccbf8fb143e34e02fd6521e09be2c2b974f0d5db080b19071c9c570718ccf',
|
||||
'fixture': 'eb4f770071eb07cd7e136fe54c3f78768cc8de7a4264e62a9af1f06a8f90f9bd',
|
||||
'probe': '95e5967835cb05ad96e8e6f257689d0bd831792aa9c228c022e3af81448ff76a',
|
||||
'p06_01_acceptance': '6f471e3f32460758b8a617051a507f780d70b33f254a7910dfada87a8c5c3484',
|
||||
'p14_02_acceptance': 'ac7bfd6fb88cb8ac8c195c8051ebe22ba95a6a6393342de089aae88fdcc73307',
|
||||
'decisions': '8b7dba984d5b1cc0091e8faf8fddb2ab357dd79f1bd093eba46c23c2f62a068c',
|
||||
}
|
||||
|
||||
|
||||
def sha(path):
|
||||
return hashlib.sha256(Path(path).read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def read(path):
|
||||
return json.loads(Path(path).read_bytes())
|
||||
|
||||
|
||||
def main():
|
||||
p = argparse.ArgumentParser(description=__doc__)
|
||||
p.add_argument('--project-root', type=Path, default=Path('.'))
|
||||
p.add_argument('--evidence-root', type=Path)
|
||||
p.add_argument('--json', type=Path)
|
||||
args = p.parse_args()
|
||||
root = args.project_root.resolve()
|
||||
ev = args.evidence_root or (Path(os.environ['BUILD_OUTPUT_DIR']) / 'p14-03-evidence' if os.environ.get('BUILD_OUTPUT_DIR') else None)
|
||||
checks = []
|
||||
|
||||
def check(name, ok, detail=None):
|
||||
checks.append({'name': name, 'passed': bool(ok), 'detail': detail})
|
||||
return bool(ok)
|
||||
|
||||
def finish():
|
||||
result = {'candidate': CANDIDATE, 'passed': bool(checks) and all(c['passed'] for c in checks), 'checks': checks,
|
||||
'absolute_performance_budget_met': False,
|
||||
'not_measured_or_unmet': ['x1 one-minute wall time exceeds 4 ms in both versions', 'x3 frame', 'main-thread stall',
|
||||
'rendering', 'Windows', 'real staff use of the service shaft in the representative input']}
|
||||
text = json.dumps(result, ensure_ascii=False, indent=2)
|
||||
if args.json:
|
||||
args.json.write_text(text + '\n')
|
||||
print(text)
|
||||
return 0 if result['passed'] else 1
|
||||
|
||||
if not check('evidence root provided', ev is not None and Path(ev).is_dir(), str(ev)):
|
||||
return finish()
|
||||
ev = Path(ev).resolve()
|
||||
manifest = read(HERE / 'EVIDENCE_BUNDLE_MANIFEST.json')
|
||||
bad = [n for n, q in manifest['files'].items() if not (ev / n).is_file() or (ev / n).stat().st_size != q['bytes'] or sha(ev / n) != q['sha256']]
|
||||
check('every manifest file present and byte-identical', not bad, {'files': len(manifest['files']), 'mismatched': bad[:20]})
|
||||
for key, rel in [('full_input', FULL + '/runner-input.json'), ('full_ended', FULL + '/runner-ended.json'),
|
||||
('full_all_tests', FULL + '/all-tests.json'), ('pair_ended', PAIR + '/runner-ended.json'),
|
||||
('perf_execution', PERF + '/execution.json'), ('review_request', REVIEW + '/REVIEW_REQUEST.md'),
|
||||
('review_output', REVIEW + '/review-output.md')]:
|
||||
check('pinned ' + key, (ev / rel).is_file() and sha(ev / rel) == PIN[key] and manifest['files'].get(rel, {}).get('sha256') == PIN[key])
|
||||
if bad:
|
||||
return finish()
|
||||
|
||||
spec = importlib.util.spec_from_file_location('p1403gate_std', root / 'tools/run_all_tests.py')
|
||||
std = importlib.util.module_from_spec(spec); spec.loader.exec_module(std)
|
||||
refs = std.source_refs(root)
|
||||
source_map = {n: q['sha256'] for n, q in refs.items()}
|
||||
roster = ['tests/run_tests.gd'] + sorted(n for n in refs if n.startswith('tests/') and n.endswith('.gd') and Path(n).name.startswith('test')
|
||||
and 'export' not in Path(n).parts and n != 'tests/s0/test_kernel_and_save.gd')
|
||||
fi, fe, rows = read(ev / FULL / 'runner-input.json'), read(ev / FULL / 'runner-ended.json'), read(ev / FULL / 'all-tests.json')
|
||||
check('formal run passed completely', fe['passed'] is True and fe['engine_ended'] is True and fe['failed_suites'] == 0
|
||||
and not fe['remaining_unexecuted_tests'] and not fe['missing_marker_paths'] and not fe['guard_errors']
|
||||
and not fe['cleanup_errors'] and fi['run_id'] == fe['run_id'] and fe['all_tests_sha256'] == PIN['full_all_tests'],
|
||||
{'suites': fe['actual_suites'], 'checks': fe['actual_all_roster_checks'], 'run_id': fe['run_id']})
|
||||
check('source under validation equals the formal selected source map', fi['selected_source_map'] == source_map, {'files': len(source_map)})
|
||||
check('formal roster equals the standard roster of this source', fi['roster'] == roster and [r['test'] for r in rows] == roster, {'suites': len(roster)})
|
||||
row_bad = [r['test'] for r in rows if not (r['passed'] and r['source_sha256'] == source_map.get(r['test'])
|
||||
and (ev / FULL / r['log']).is_file() and sha(ev / FULL / r['log']) == r['log_sha256'])]
|
||||
check('every formal row passed with source and raw log bound', not row_bad, {'mismatched': row_bad[:20]})
|
||||
contract = root / 'docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json'
|
||||
check('formal marker contract equals the tracked contract', fi['marker_contract_sha256'] == sha(contract))
|
||||
check('formal run used the pinned engine', fi['engine_sha256'] == PIN['engine'])
|
||||
|
||||
pi, pe = read(ev / PAIR / 'runner-input.json'), read(ev / PAIR / 'runner-ended.json')
|
||||
pids = pe.get('actual_captured_PIDs', [])
|
||||
check('Root pair run passed 5/5 with 10 distinct owned PIDs', pe.get('protocol') == 'p14-root-owned-pairs-v1' and pe['passed'] is True
|
||||
and pe['commit'] == CANDIDATE and len(pe['pairs']) == 5 and all(x['passed'] for x in pe['pairs'])
|
||||
and len(pids) == 10 and len(set(pids)) == 10 and not pe['guard_errors'] and not pe['cleanup_errors'], {'run_id': pe['run_id']})
|
||||
check('pair run bound to the same formal run', pi['full_run_end_sha256'] == PIN['full_ended'] and pi['full_run_input_sha256'] == PIN['full_input'])
|
||||
|
||||
xe, xr = read(ev / PERF / 'execution.json'), read(ev / PERF / 'readiness.json')
|
||||
check('perf r02 passed with comparison established', xe['passed'] is True and xe['comparison_established'] is True
|
||||
and xe['source_and_default_player_unchanged'] is True and not xe['cleanup_errors'] and xe['actual_native'] == 4)
|
||||
check('perf bound to the same formal and pair runs', xr['full_run_binding']['runner_ended_sha256'] == PIN['full_ended']
|
||||
and xr['pair_run_binding']['runner_ended_sha256'] == PIN['pair_ended'] and xr['after'] == CANDIDATE)
|
||||
check('perf used pinned engine, input and probe', xr['engine_sha256'] == PIN['engine'] and xr['fixture_sha256'] == PIN['fixture']
|
||||
and xr['probe_sha256'] == PIN['probe'])
|
||||
for phase in [x for x in xe['phases'] if x['phase'] == 'measure']:
|
||||
variant = phase['variant']
|
||||
report_path = ev / PERF / variant / 'evidence/report.json'
|
||||
report = read(report_path)
|
||||
raw = (ev / PERF / variant / 'measure.stdout.log').read_text()
|
||||
printed = [l[len('P14_03_PERF_REPORT='):] for l in raw.splitlines() if l.startswith('P14_03_PERF_REPORT=')]
|
||||
check(variant + ' perf report bound to execution and raw stdout', sha(report_path) == phase['report_sha256']
|
||||
and len(printed) == 1 and json.loads(printed[0]) == report and report['pid'] == phase['actual_pid']
|
||||
and report['checks'] > 0 and report['failures'] == 0 and report['input_sha256'] == PIN['fixture'])
|
||||
|
||||
review = (ev / REVIEW / 'review-output.md').read_text()
|
||||
check('independent review names the exact request and states PASS on the candidate',
|
||||
PIN['review_request'] in review and ('判定: PASS(正確候補 ' + CANDIDATE) in review)
|
||||
|
||||
base = root / 'docs/plans/original-completion-2026-10-02'
|
||||
check('P06-01 acceptance record pinned', sha(base / 'execution/p06-01-a06/ACCEPTANCE.json') == PIN['p06_01_acceptance'])
|
||||
check('P14-02 acceptance record pinned', sha(base / 'execution/p14-02-warp/ACCEPTANCE.json') == PIN['p14_02_acceptance'])
|
||||
decisions = read(base / 'decisions.json')
|
||||
check('decisions A06/A07/A08 present and file pinned', sha(base / 'decisions.json') == PIN['decisions']
|
||||
and {'A06', 'A07', 'A08'} <= {d['id'] for d in decisions['confirmed']})
|
||||
return finish()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
# P16-01-DISMISS 正式受入記録(補助の作業。原本への加算 0)
|
||||
|
||||
- P16-01-DISMISS は、原 P16-01 の補助の作業である。対象は、一般従業員(ギルドで雇った `FREE_HIRE`)の通常の操作での解雇で、退職金を含む。
|
||||
- 次の3つをそろえて、正式に完了した。
|
||||
- 受入 10 件(F1〜F10)についての、別の fresh な Desktop Opus 5.5 の独立レビューの PASS(高・中 0、low 5)
|
||||
- 通常の validation・review・integration・final の全 PASS
|
||||
- 実 DB と traceability の結合
|
||||
- S `f5fe219` → Q `4ca3bbe`。
|
||||
- 原本への加算は 0 で、正式の数は 24/88・4/30 のまま。部分点なし。
|
||||
- 原 P16-01・P16-03 は未達のまま。`AR.staff.dismissal_severance` の package P05 の原 work・原 AC も、この作業では閉じない。
|
||||
|
||||
| 名前 | commit | 備考 |
|
||||
|---|---|---|
|
||||
| 提出 S | `f5fe2196740466a673879cb2950d96446fd99d6e` | 修正 2 |
|
||||
| 統合・最終検証 Q | `4ca3bbef553259e111b35856785ae83d34380287` | Q の tree は S の tree と同じ(`2cebcc3e…`) |
|
||||
| 基準 M | `0b59d7c23c301cf014343e217827cb2d837cc11c` | |
|
||||
|
||||
- 正式証拠: `DISMISS_FORMAL_COMPLETION_4CA3BBE.json`
|
||||
- 独立レビューの結果(実結果の bytes の写し): `DISMISS_INDEPENDENT_REVIEW_F5FE219.json`(SHA256 `1f530e6c4e6158a469271d287763fc8a573295f44f04dd26bf9b5e6a93f8b737`)
|
||||
|
||||
## 位置づけと原本の条件
|
||||
|
||||
- 仕様は同じフォルダの `SPEC.md`(登録版 r04、ファイルの SHA256 `b4cc31c0…5f77`)。
|
||||
- 人の決定 A14(退職金の初期仕様、答え「この初期仕様で進める」)は、`PROVENANCE.json` のとおり。
|
||||
- 退職金は、その職員の日給の3日分。
|
||||
- 表金から払い、不足分を裏金で補う。合計でも足りなければ解雇しない。
|
||||
- 未払いの給与は退職金と分けて保持し、次の 00:00 に精算する。
|
||||
- 退職金は帳簿で区別し、日次決算では運営費に入れる。
|
||||
- R9(進行中の襲撃の防衛者は解雇しない、`DEFENSE_LOCKED`)は、Root が既存の範囲の権限で採った一時の境界で、人の承認ではない。
|
||||
- 原単位の文:
|
||||
- P16-01 の acceptance: 求人から配置・精算・退職まで通常操作でき、ユニーク禁止操作を維持する。
|
||||
- P16-03 の work: 勤務、休息、給与、気絶、救護、退職時参照除去を横断検証する。
|
||||
- 原子要件: `AR.staff.general_dismissal`(P16)、`AR.staff.dismissal_severance`(P05、D04)、`AR.unique.no_dismissal`(P17)。
|
||||
- 原の横断条件(P16、6 項目):
|
||||
- 原本条件とUI操作が一致
|
||||
- Runtimeの実利用・会計・行動へ接続
|
||||
- 途中保存・再起動で再現
|
||||
- 異常/境界/中断を検証
|
||||
- 関連回帰PASS
|
||||
- 要件別ログとソースハッシュを保存
|
||||
- 受入 10 件(F1〜F10)は `SPEC.md` §7 と、登録の `acceptance_baseline.json` の本文のとおり。
|
||||
|
||||
## 4つの通常のゲート(Runner の the-tree-p16-01-dismiss、task P16-01-DISMISS、completed、修正 2)
|
||||
|
||||
| ゲート | commit | Runner の証跡 | 結果 |
|
||||
|---|---|---|---|
|
||||
| validation | S | evidence 6 | `p16-dismiss` exit 0、verified true、12 本(14 段)・1,888 checks・parse 354、engines_started 0 |
|
||||
| review | S | evidence 7 | 実結果 `1f530e6c…`、pass、F1〜F10、low 5 |
|
||||
| integration | Q | evidence 8 | `p16-dismiss` exit 0、verified true、engines_started 0 |
|
||||
| final | Q | evidence 9 | `32d1d379…-final.json` SHA `91b6d220…`、verified true、12 本・1,888 checks・parse 354、engines_started 0 |
|
||||
|
||||
- owner の承認:
|
||||
- Root が、正確な Q `4ca3bbe` の資格を確かめた(`ROOT_P16_Q4CA3BBE_QUALIFICATION_20261025_094225_R01.json`)。
|
||||
- main の統合と push についての既存の人の許可を、正確な Q と main_base `0b59d7c` に当てはめた。新しい人の発言は作っていない。
|
||||
- 記録は、Runner の approvals の行と `ROOT_OWNER_APPROVAL_4CA3BBE_R01.json`、統合の開始と終わりの記録、`ROOT_P16_DISMISS_COMPLETED_DB_JOIN_4CA3BBE_R01.json`。
|
||||
- 測定は全て S で行った。
|
||||
- native: 12 本・1,888 checks(新しい suite 721、再開の書き手 120・読み手 273 を含む)
|
||||
- S の全 roster: 201 本・24,073 checks・失敗 0。既存の代表負荷の性能の suite 10 本を含む。
|
||||
- Q での integration と final について:
|
||||
- S で測った証拠を、中身が同じ Q の新しい checkout で、中身ベースの verifier が照合したものである。
|
||||
- Q で測ったとは書かない。
|
||||
|
||||
## 何ができるようになったか
|
||||
|
||||
- 通常の命令 `dismiss_staff` と、Main の職員の管理の画面の「解雇する…」「解雇を確定」で、一般従業員を解雇できる。
|
||||
- 押す前に、退職金・裏金からの補填・未払いの給与・開き直す作業・命令の中で取り消す救護を出す。結果も出す。
|
||||
- 拒否の行(ユニーク・成人契約・物流の途中・予約を解けない・運営の終了・資金不足・襲撃の防衛中)では、ボタンが無効で理由が出る。
|
||||
- 解雇の命令の境目で、勤務・配置・経路・昇降機・救護・出来事・施設の人員の参照を片づける。乗ったままの救護の組でも解雇でき、残る人は本物の階まで乗る。
|
||||
- 開き直した予約つきの作業を、別の職員が取り直して終えられる(在庫の予約の操作 ID を直した)。
|
||||
- 締めの後に税・利息の未払いが残った保存が読めなかった P05-02 からの不具合を直した。
|
||||
|
||||
## 独立レビュー(S f5fe219)
|
||||
|
||||
- レビュー担当: 別の fresh な Claude Desktop Opus 5.5 の session(`local_4e746265`)。実装担当とも、前の S の2人のレビュー担当とも別である。
|
||||
- 凍結の入力の SHA を確かめ、読むだけで判定した。凍結の入力は packet・schema・材料 7,292 ファイル。
|
||||
- 判定の範囲:
|
||||
- 受入 F1〜F10 の全部
|
||||
- SPEC §4・§5・§6 の全ての行
|
||||
- 原の横断条件と、U1(未達の開示)・U2(証拠の適用)
|
||||
- 依頼文の項目 1〜11。F9 の通常の入力(viewport の入力の経路と、時計が動いている時の押し方を分けて判定)、§4.1 (d) の4つの巻き戻しの枝、画面の試験の実際の範囲、来歴の欠落を含む。
|
||||
- 配送の前に、会話の記録の全体を確かめた。
|
||||
- tool の呼び出しは Bash 74・Read 5・Write 1・Edit 1 など。
|
||||
- 書き込みは、その session の scratchpad と TMPDIR の補助と、結果のファイル(作成1回)だけ。
|
||||
- engine・試験・Runner のコマンド・git の書き換えは無い。
|
||||
- low 5 件。原文は独立レビューの結果のファイルにある。どれも受入を止めないと判定された。
|
||||
1. 命令の中で取り消す救護の、患者の側の結果の文を、画面で確かめていない。
|
||||
2. 画面の中の守り(確認を開いた後に拒否に変わった時・古い確認の時に送らない)の分岐を、試験で通していない。
|
||||
3. 出来事の欠けた actor の方針のうち REPLACE_WITH_ROLE と BRANCH_FAILURE を、解雇の境目で試験していない。
|
||||
4. `_rollback_staff_dismissal` の戻り値を使っていない(既存の雇用の巻き戻しと同じ形)。
|
||||
5. 来歴の欠落(下の「保持した記録の欠落」)は受入の判断に影響しないが、証拠の保持の欠落としては残る。
|
||||
|
||||
## 修正の経過と保持した失敗
|
||||
|
||||
1. S `b06fec1`
|
||||
- native・validation・全 roster(201 本・23,659 checks)は合格した。
|
||||
- 1回目のレビューは pass。同じレビュー担当の補足(改訂)の判定は changes_requested(high 1・medium 6・low 4)。
|
||||
- 乗ったままの救護の組の解雇を、実装の判断で拒んでいた(SPEC §6.2 の「直し、拒否に変えない」に合わない)。開き直した予約つきの作業を別の職員が取れなかった。ほかに、説明の文と確かめ方の代用。
|
||||
- 修正 1 で、組を解く後始末、予約の操作 ID、説明の分け方、条文どおりの試験を入れた。
|
||||
2. S `a073ccb`
|
||||
- native・validation・全 roster(201 本・24,017 checks)は合格した。
|
||||
- レビューは changes_requested(medium 1・low 3)。画面の試験がボタンの信号を直接出していて、F9 の通常の入力を確かめていなかった。
|
||||
- 修正 2 で、画面の試験を viewport の入力で操作する形にし、巻き戻しの枝と画面の文の試験を足した(製品のファイルは変えていない)。
|
||||
- 正式の試験の生の出力と、前のレビューの結果は保持した。新しい S の受入に、前の S の証拠を流用していない。
|
||||
- 後続のスクリプトを作る1回目の実行と、診断用の写しなどの元の bytes の欠落は、次の節に明示する。
|
||||
|
||||
## 保持した記録の欠落(承認でも免除でもない)
|
||||
|
||||
- S `a073ccb` の後続のスクリプトを作る1回目の実行の失敗。1回目の2つの出力、stdout、1回目の script の bytes は残っていない。失敗の全文は会話から書き写した(`DERIVE_A073CCB_SCRIPTS_FIRST_RUN_FAILURE.md`)。
|
||||
- 修正 2 の画面の場面の診断用の写し2つ(stage の上だけ)は、ファイルとして残っていない。log・生成の命令の写し・元にした source の hash は残っている(`P16_01_DISMISS_REPAIR2_DIAG_COPIES_PROVENANCE_R01.json`)。
|
||||
- Root はこれらを証拠の保持の欠落として保持している。新しい S の正式の試験の生の出力が失われたという意味ではない。
|
||||
|
||||
## 未達として保つもの(主張しない)
|
||||
|
||||
- 原 P16-01 は未達のまま。求人募集の通常の操作(`AR.staff.recruitment`)、奴隷市場・破産客からの獲得(D04)、雇用の区分ごとの費用の整理が残る。
|
||||
- 原 P16-03 は未達のまま。勤務・休息・気絶・救護の横断(D04)と、解雇の経路の外の退職時の参照の除去が残る。
|
||||
- 人身売買(`AR.staff.sale_*`、D04)は扱っていない。
|
||||
- `AR.staff.dismissal_severance` は A14 の決定とこの受入で接続したが、package P05 の原単位の完了には数えない。
|
||||
- 進行中の襲撃の防衛者の解雇(R9)は、原本の要件として未達のまま。退却や戦闘の規則は作っていない。
|
||||
- 試験の入力は viewport の入力で、OS の入力ではない。同じ Windows の実機・IME・OS の倍率・同じ候補での実機の受入(SPEC §10)は残る。UI は今の部品のままで、完成の見た目ではない。
|
||||
- 長い通常のプレイと、全ゲームの最終の受入は残っている。
|
||||
+875
@@ -0,0 +1,875 @@
|
||||
{
|
||||
"at_jst": "2026-10-25T18:49:38.598450+09:00",
|
||||
"auxiliary_unit": "P16-01-DISMISS",
|
||||
"original_units": [
|
||||
"P16-01",
|
||||
"P16-03"
|
||||
],
|
||||
"atomic_requirements": [
|
||||
"AR.staff.general_dismissal (P16)",
|
||||
"AR.staff.dismissal_severance (P05, D04; severance decided by A14)",
|
||||
"AR.unique.no_dismissal (P17)"
|
||||
],
|
||||
"project": "the-tree-p16-01-dismiss",
|
||||
"task": "P16-01-DISMISS",
|
||||
"state": "completed",
|
||||
"S": "f5fe2196740466a673879cb2950d96446fd99d6e",
|
||||
"Q": "4ca3bbef553259e111b35856785ae83d34380287",
|
||||
"M": "0b59d7c23c301cf014343e217827cb2d837cc11c",
|
||||
"repairs": 2,
|
||||
"Q_tree_equals_S_tree": "2cebcc3e851387c26bb7066d37a60d7ae6e51703",
|
||||
"all4_normal_gates_passed": true,
|
||||
"scope": "Auxiliary unit of the original P16-01: dismissal of a general employee (FREE_HIRE) by the ordinary dismiss_staff command and the Main staff panel, with the A14 severance (three days of the effective daily wage, the front fund first and the hidden fund covering the rest), the held unpaid wages, the refusals R1-R9 (R9 DEFENSE_LOCKED is a Root temporary boundary, not a human approval) and the cleanup at the command boundary. Not a new human gameplay approval and not a deletion of the original P16-01 or P16-03 acceptance.",
|
||||
"original_work": {
|
||||
"P16-01": "求人、市場、原本の雇用区分ごとの費用と獲得・退職を非描写状態として整理する。",
|
||||
"P16-03": "勤務、休息、給与、気絶、救護、退職時参照除去を横断検証する。"
|
||||
},
|
||||
"original_acceptance": {
|
||||
"P16-01": "求人から配置・精算・退職まで通常操作でき、ユニーク禁止操作を維持する。",
|
||||
"P16-03": "人員上限、未払い、交代中の予約・移動が整合し、消失や二重精算がない。"
|
||||
},
|
||||
"original_package_work": "雇用区分別費用、全職種業務、生成時特性、獲得/退職/処分の非描写状態処理を整理。乱数特性は生成時に固定しセーブへ保存。",
|
||||
"original_package_acceptance": "求人から配置・業務・休息・精算・退職まで通常UIで通る。費用/忠誠/特性効果の変化が可視化され、ロードで特性が再抽選されない。",
|
||||
"original_cross_cutting_acceptance": [
|
||||
"原本条件とUI操作が一致",
|
||||
"Runtimeの実利用・会計・行動へ接続",
|
||||
"途中保存・再起動で再現",
|
||||
"異常/境界/中断を検証",
|
||||
"関連回帰PASS",
|
||||
"要件別ログとソースハッシュを保存"
|
||||
],
|
||||
"acceptance_ids": [
|
||||
{
|
||||
"id": "P16-01-DISMISS-F1",
|
||||
"text": "通常の命令: §6.1 の許可の行の `FREE_HIRE` の職員を §4.1 の command で解雇でき、名簿から消え、`STAFF_REMOVED`(reason `DISMISSED`)が1つ記録される。"
|
||||
},
|
||||
{
|
||||
"id": "P16-01-DISMISS-F2",
|
||||
"text": "区分の拒否: `UNIQUE` と `ADULT_CONTRACT` は命令でも画面でも解雇できず、§5 の比べ方で業務の状態が変わらない。"
|
||||
},
|
||||
{
|
||||
"id": "P16-01-DISMISS-F3",
|
||||
"text": "退職金: §3.5 で決めた額と払う元で、解雇ごとに1回だけ払われ、日次決算と帳簿の照合が一致する。§3.5 で決めた支払ができない時は解雇されず、§5.c の比べ方で業務の状態が変わらない。"
|
||||
},
|
||||
{
|
||||
"id": "P16-01-DISMISS-F4",
|
||||
"text": "未払いの給与: 解雇の前の未払いが退職金とは別に保持され、次の 00:00 の締めで1回だけ払われ(A09 の補填を含む)、解雇の後に日給が生じない。"
|
||||
},
|
||||
{
|
||||
"id": "P16-01-DISMISS-F5",
|
||||
"text": "再送: 同じ key の再送が `DUPLICATE_IDEMPOTENCY_KEY` で、details に最初の結果がつき、§5.b の比べ方で snapshot の全体が同じ。退職金を二度払わない。"
|
||||
},
|
||||
{
|
||||
"id": "P16-01-DISMISS-F6",
|
||||
"text": "拒否の境界: §6.3 の拒否の全ての行(R1〜R9)で拒み、§5 の該当の比べ方で業務の状態が変わらない。"
|
||||
},
|
||||
{
|
||||
"id": "P16-01-DISMISS-F7",
|
||||
"text": "許可の境界と後始末: §6.1 の許可の全ての行(A1〜A10)で解雇でき、§6.2 の後始末が全て成り立つ。開き直した作業、サービスの待ちと失敗、救護のやり直し、出来事の欠けた職員の扱いは、それぞれ既存の規則どおりになる。A6・A7・A8 は §6.2.1 のとおり、命令の直後に保存して同じ process と別の process で読み込んでも参照が残らず、保存の照合が通る。"
|
||||
},
|
||||
{
|
||||
"id": "P16-01-DISMISS-F8",
|
||||
"text": "保存と再開: 解雇の前後(命令の直後、次の進行の前を含む)・締めの前後の保存を同じ process と別の process で読み、同じ状態で、退職金と未払いの給与の支払が二重にならない。"
|
||||
},
|
||||
{
|
||||
"id": "P16-01-DISMISS-F9",
|
||||
"text": "通常の画面: ゲームの本物の Runtime と Main の職員の管理の画面で、通常の入力で許可の行の一般従業員を解雇でき(退職金の額と開き直る作業を押す前に見られる)、拒否の行では操作が出ないか無効で理由が出る。fixture の直接の呼び出し(`staff_service.remove_staff`)だけで通常の入口の完了を主張しない。"
|
||||
},
|
||||
{
|
||||
"id": "P16-01-DISMISS-F10",
|
||||
"text": "回帰: 関連の回帰(狭い roster と再開の2段)と最終の S の全 roster が PASS。既存の試験の期待値の変更は `EXPECTATION_CHANGES.json` に変更前後と根拠を一覧にする。"
|
||||
}
|
||||
],
|
||||
"acceptance_baseline": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p16-01-dismiss/acceptance_baseline.json",
|
||||
"bytes": 3915,
|
||||
"sha256": "2c40932e96f6ca20e46aac8bd3b65cac51ae466fef6b0b62d7bb170e3a924258"
|
||||
},
|
||||
"spec": {
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p16-01-dismiss/SPEC.md",
|
||||
"bytes": 24414,
|
||||
"sha256": "b4cc31c0b1cbfb99f0adcbb356cd919bf45c147a150bc7efa81bd982cab05f77",
|
||||
"contract_spec_sha256": "14efffd90d7fc0ae466375030d0f1680a1319e1bbe1ec0f373c5a5d97ea59e6b"
|
||||
},
|
||||
"provenance_A14": {
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p16-01-dismiss/PROVENANCE.json",
|
||||
"bytes": 3061,
|
||||
"sha256": "c023142d76241d3c5b78b6d53c98b0fe2e1c8c72ee03170c7406c98a144f6223"
|
||||
},
|
||||
"unit_disclosures": [
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p16-01-dismiss/IMPLEMENTATION_NOTES.md",
|
||||
"bytes": 25864,
|
||||
"sha256": "4b7971deb708a424eb4ba8c70af6a479c66c6b7674e9c8d45b7882271fa7b7f4"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p16-01-dismiss/EXPECTATION_CHANGES.json",
|
||||
"bytes": 2062,
|
||||
"sha256": "99a3a39eff6307e0edda9a12411af6e3ad116b4b26a61bcfcd25428af687d9fc"
|
||||
}
|
||||
],
|
||||
"runner_task": {
|
||||
"id": "P16-01-DISMISS",
|
||||
"state": "completed",
|
||||
"submitted": "f5fe2196740466a673879cb2950d96446fd99d6e",
|
||||
"candidate": "4ca3bbef553259e111b35856785ae83d34380287",
|
||||
"main_base": "0b59d7c23c301cf014343e217827cb2d837cc11c",
|
||||
"reason": null,
|
||||
"repairs": 2
|
||||
},
|
||||
"runner_events": [
|
||||
{
|
||||
"id": 1,
|
||||
"event": "registered",
|
||||
"created": 1792807652.738952
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"event": "owner_delegated_implementer",
|
||||
"created": 1792807668.3897338
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"event": "commander_proposal",
|
||||
"created": 1792807823.042252
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"event": "submitted",
|
||||
"created": 1792813771.980511
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"event": "validating",
|
||||
"created": 1792814391.051862
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"event": "review_pending",
|
||||
"created": 1792814399.380697
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"event": "owner_prepared_actual_desktop_independent_review_input",
|
||||
"created": 1792818189.395961
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"event": "review_pending",
|
||||
"created": 1792902931.012789
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"event": "owner_actual_independent_Desktop_review_output",
|
||||
"created": 1792902941.714538
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"event": "reviewed",
|
||||
"created": 1792902941.715863
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"event": "review_pending",
|
||||
"created": 1792904036.379626
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"event": "owner_actual_independent_Desktop_review_supplement_output",
|
||||
"created": 1792904045.946167
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"event": "repair_pending",
|
||||
"created": 1792904045.9476
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"event": "commander_proposal",
|
||||
"created": 1792904253.122462
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"event": "repair_ready",
|
||||
"created": 1792904253.123292
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"event": "submitted",
|
||||
"created": 1792907145.647569
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"event": "validating",
|
||||
"created": 1792907933.879277
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"event": "review_pending",
|
||||
"created": 1792907940.6854641
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"event": "owner_prepared_actual_desktop_independent_review_input",
|
||||
"created": 1792911399.9793851
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"event": "review_pending",
|
||||
"created": 1792913267.315629
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"event": "owner_actual_independent_Desktop_review_output",
|
||||
"created": 1792913276.881197
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"event": "repair_pending",
|
||||
"created": 1792913276.8830101
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"event": "commander_proposal",
|
||||
"created": 1792913430.5026371
|
||||
},
|
||||
{
|
||||
"id": 24,
|
||||
"event": "repair_ready",
|
||||
"created": 1792913430.504103
|
||||
},
|
||||
{
|
||||
"id": 25,
|
||||
"event": "submitted",
|
||||
"created": 1792915285.8875031
|
||||
},
|
||||
{
|
||||
"id": 26,
|
||||
"event": "validating",
|
||||
"created": 1792916082.429237
|
||||
},
|
||||
{
|
||||
"id": 27,
|
||||
"event": "review_pending",
|
||||
"created": 1792916089.916318
|
||||
},
|
||||
{
|
||||
"id": 28,
|
||||
"event": "owner_prepared_actual_desktop_independent_review_input",
|
||||
"created": 1792919753.202212
|
||||
},
|
||||
{
|
||||
"id": 29,
|
||||
"event": "review_pending",
|
||||
"created": 1792921050.491998
|
||||
},
|
||||
{
|
||||
"id": 30,
|
||||
"event": "owner_actual_independent_Desktop_review_output",
|
||||
"created": 1792921061.577804
|
||||
},
|
||||
{
|
||||
"id": 31,
|
||||
"event": "reviewed",
|
||||
"created": 1792921061.580501
|
||||
},
|
||||
{
|
||||
"id": 32,
|
||||
"event": "approval_pending",
|
||||
"created": 1792921121.711384
|
||||
},
|
||||
{
|
||||
"id": 33,
|
||||
"event": "approved",
|
||||
"created": 1792921381.492307
|
||||
},
|
||||
{
|
||||
"id": 34,
|
||||
"event": "completed",
|
||||
"created": 1792921438.661454
|
||||
}
|
||||
],
|
||||
"normal_evidence_join": [
|
||||
{
|
||||
"id": 6,
|
||||
"task_id": "P16-01-DISMISS",
|
||||
"kind": "validation",
|
||||
"commit_sha": "f5fe2196740466a673879cb2950d96446fd99d6e",
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p16-01-dismiss/runs/acb324fc062c44c9a075b19991fb8e9e-validation.json",
|
||||
"passed": 1,
|
||||
"artifact_sha256": "f29b4b738fc2092430898a8deadcef86acb1ab3ac114482849fc30935df6e2cc",
|
||||
"checks": [
|
||||
{
|
||||
"id": "p16-dismiss",
|
||||
"exit_code": 0,
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p16-01-dismiss/runs/91313315f7fb46528b55818e300e26f7",
|
||||
"cwd": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p16-01-dismiss/P16-01-DISMISS-validation-af80c5bc",
|
||||
"stdout": {
|
||||
"verified": true,
|
||||
"actual_suites": 12,
|
||||
"parse_checks": 354,
|
||||
"actual_checks": 1888,
|
||||
"engines_started": 0,
|
||||
"formal_acceptance": false,
|
||||
"original_unit_complete": false
|
||||
},
|
||||
"raw": {
|
||||
"command.json": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p16-01-dismiss/runs/91313315f7fb46528b55818e300e26f7/command.json",
|
||||
"bytes": 739,
|
||||
"sha256": "3f81eca34d40772edaf0bc39112a01f42343e5d30ea253e74183445e144249d1"
|
||||
},
|
||||
"stdout.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p16-01-dismiss/runs/91313315f7fb46528b55818e300e26f7/stdout.log",
|
||||
"bytes": 167,
|
||||
"sha256": "0c677da2b25fa2e6601b2e6ac6fcfe2854c6790d84354f053ef332ff7ef16b2e"
|
||||
},
|
||||
"stderr.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p16-01-dismiss/runs/91313315f7fb46528b55818e300e26f7/stderr.log",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
"sandbox.sb": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p16-01-dismiss/runs/91313315f7fb46528b55818e300e26f7/sandbox.sb",
|
||||
"bytes": 1984,
|
||||
"sha256": "063e31b34a4160f36a2b17bbf0a003a6b92df70e8251071715511f02bcff2048"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"task_id": "P16-01-DISMISS",
|
||||
"kind": "review",
|
||||
"commit_sha": "f5fe2196740466a673879cb2950d96446fd99d6e",
|
||||
"artifact": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/p16-01-dismiss-independent-review-f5fe219-r01/actual-independent-desktop-output/result.json",
|
||||
"passed": 1,
|
||||
"artifact_sha256": "1f530e6c4e6158a469271d287763fc8a573295f44f04dd26bf9b5e6a93f8b737",
|
||||
"checks": []
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"task_id": "P16-01-DISMISS",
|
||||
"kind": "integration",
|
||||
"commit_sha": "4ca3bbef553259e111b35856785ae83d34380287",
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p16-01-dismiss/runs/15ec10bebdd243c1a4e021de0304c7e5-integration.json",
|
||||
"passed": 1,
|
||||
"artifact_sha256": "d9f6fe28a1b040a39da6e3c12d0109a1924155e2aece46037c31aeb3a69a185a",
|
||||
"checks": [
|
||||
{
|
||||
"id": "p16-dismiss",
|
||||
"exit_code": 0,
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p16-01-dismiss/runs/307bc8e26b0245aea3bc5b3a78826986",
|
||||
"cwd": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p16-01-dismiss/P16-01-DISMISS-integration-18921040",
|
||||
"stdout": {
|
||||
"verified": true,
|
||||
"actual_suites": 12,
|
||||
"parse_checks": 354,
|
||||
"actual_checks": 1888,
|
||||
"engines_started": 0,
|
||||
"formal_acceptance": false,
|
||||
"original_unit_complete": false
|
||||
},
|
||||
"raw": {
|
||||
"command.json": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p16-01-dismiss/runs/307bc8e26b0245aea3bc5b3a78826986/command.json",
|
||||
"bytes": 740,
|
||||
"sha256": "2e73d96350daeabd34569cb9f6952d02fffeadd042523f0cc6b667e3a836fbcb"
|
||||
},
|
||||
"stdout.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p16-01-dismiss/runs/307bc8e26b0245aea3bc5b3a78826986/stdout.log",
|
||||
"bytes": 167,
|
||||
"sha256": "0c677da2b25fa2e6601b2e6ac6fcfe2854c6790d84354f053ef332ff7ef16b2e"
|
||||
},
|
||||
"stderr.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p16-01-dismiss/runs/307bc8e26b0245aea3bc5b3a78826986/stderr.log",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
"sandbox.sb": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p16-01-dismiss/runs/307bc8e26b0245aea3bc5b3a78826986/sandbox.sb",
|
||||
"bytes": 1985,
|
||||
"sha256": "56cc0a16a025f5bf9be5c69d93de17994015146be92ebc07e83f03f242f33f68"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"task_id": "P16-01-DISMISS",
|
||||
"kind": "final",
|
||||
"commit_sha": "4ca3bbef553259e111b35856785ae83d34380287",
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p16-01-dismiss/runs/32d1d3797e35400aa84ce8d55df03d22-final.json",
|
||||
"passed": 1,
|
||||
"artifact_sha256": "91b6d2202dd62fec3bb11683d344c653450447746f686e903e99bd583d587561",
|
||||
"checks": [
|
||||
{
|
||||
"id": "p16-dismiss",
|
||||
"exit_code": 0,
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p16-01-dismiss/runs/92845d661406435eb46a31979a409095",
|
||||
"cwd": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p16-01-dismiss/P16-01-DISMISS-final-d27fcc32",
|
||||
"stdout": {
|
||||
"verified": true,
|
||||
"actual_suites": 12,
|
||||
"parse_checks": 354,
|
||||
"actual_checks": 1888,
|
||||
"engines_started": 0,
|
||||
"formal_acceptance": false,
|
||||
"original_unit_complete": false
|
||||
},
|
||||
"raw": {
|
||||
"command.json": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p16-01-dismiss/runs/92845d661406435eb46a31979a409095/command.json",
|
||||
"bytes": 772,
|
||||
"sha256": "adc2dc85d22491c5714a23dcab56acc5eec8546c16ced5957d154310e8d2d5e5"
|
||||
},
|
||||
"stdout.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p16-01-dismiss/runs/92845d661406435eb46a31979a409095/stdout.log",
|
||||
"bytes": 167,
|
||||
"sha256": "0c677da2b25fa2e6601b2e6ac6fcfe2854c6790d84354f053ef332ff7ef16b2e"
|
||||
},
|
||||
"stderr.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p16-01-dismiss/runs/92845d661406435eb46a31979a409095/stderr.log",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
"sandbox.sb": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p16-01-dismiss/runs/92845d661406435eb46a31979a409095/sandbox.sb",
|
||||
"bytes": 1979,
|
||||
"sha256": "dc417131d1b72066af7113145ae3aefa69951546a82d9614dab35fd8e77ee618"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"earlier_evidence_retained": [
|
||||
{
|
||||
"id": 1,
|
||||
"kind": "validation",
|
||||
"commit_sha": "b06fec17c7c1e2e4cc22116f209c51dec751215c",
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p16-01-dismiss/runs/adab90a677f04ee0b0430517aa1bdb09-validation.json",
|
||||
"passed": 1
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"kind": "review",
|
||||
"commit_sha": "b06fec17c7c1e2e4cc22116f209c51dec751215c",
|
||||
"artifact": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/p16-01-dismiss-independent-review-b06fec1-r01/actual-independent-desktop-output/result.json",
|
||||
"passed": 1
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"kind": "review",
|
||||
"commit_sha": "b06fec17c7c1e2e4cc22116f209c51dec751215c",
|
||||
"artifact": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/p16-01-dismiss-independent-review-b06fec1-r01/actual-independent-desktop-output-r02/result.json",
|
||||
"passed": 0
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"kind": "validation",
|
||||
"commit_sha": "a073ccb59b9e20e91875008b5d34431ed5a1174f",
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p16-01-dismiss/runs/4fb740b267d84783bf2d035b389103cc-validation.json",
|
||||
"passed": 1
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"kind": "review",
|
||||
"commit_sha": "a073ccb59b9e20e91875008b5d34431ed5a1174f",
|
||||
"artifact": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/p16-01-dismiss-independent-review-a073ccb-r01/actual-independent-desktop-output/result.json",
|
||||
"passed": 0
|
||||
}
|
||||
],
|
||||
"earlier_evidence_note": "evidence 2 is the first pass on S b06fec1 by local_e0348c69, superseded by the same reviewer's revised changes_requested (evidence 3); evidence 5 is the changes_requested on S a073ccb by local_cad69b68",
|
||||
"approval": {
|
||||
"task_id": "P16-01-DISMISS",
|
||||
"candidate": "4ca3bbef553259e111b35856785ae83d34380287",
|
||||
"main_base": "0b59d7c23c301cf014343e217827cb2d837cc11c",
|
||||
"owner": "Root/Codex applying existing human authorization for exact-candidate main integration and push; no new human utterance",
|
||||
"created": 1792921381.4913468
|
||||
},
|
||||
"owner_records": [
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/ROOT_P16_Q4CA3BBE_QUALIFICATION_20261025_094225_R01.json",
|
||||
"bytes": 22154,
|
||||
"sha256": "c6c5af0c64725279bfbba3959d0ee8e737eee923c6b7c2f936a41881d663370d"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/ROOT_OWNER_APPROVAL_4CA3BBE_R01.json",
|
||||
"bytes": 3748,
|
||||
"sha256": "ecd7303a9dc523d5ac04046ef78fe0f543c0030c641db9c3bc5333bf871e4f06"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/ROOT_INTEGRATE_4CA3BBE_R01.start.json",
|
||||
"bytes": 830,
|
||||
"sha256": "35aff6f3c5deaa85c265995d4d6963d336ddbc33c07c24993e350a76922e5120"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/ROOT_INTEGRATE_4CA3BBE_R01.ended.json",
|
||||
"bytes": 424,
|
||||
"sha256": "dab0fa02d8426c7c081209405153c160b5d28a5d6a8c64f40db06fb076bffbad"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/ROOT_P16_DISMISS_COMPLETED_DB_JOIN_4CA3BBE_R01.json",
|
||||
"bytes": 18156,
|
||||
"sha256": "c1eaa159e1c3dd7037044f6b9979a1daa6857a095ad3fcdf090a21894b194438"
|
||||
}
|
||||
],
|
||||
"traceability": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p16-01-dismiss/traceability/P16-01-DISMISS.json",
|
||||
"bytes": 5201,
|
||||
"sha256": "836f2bb099ab0cea0ae0b75d4f0cee981988395c58a541b56e430ea84254206c"
|
||||
},
|
||||
"independent_reviewer": {
|
||||
"task_id": "P16-01-DISMISS",
|
||||
"commit": "f5fe2196740466a673879cb2950d96446fd99d6e",
|
||||
"session_id": "local_4e746265-c2a6-4576-a894-84cbb0b7fc46",
|
||||
"model": "claude-opus-5-5",
|
||||
"context": "fresh",
|
||||
"read_only": true,
|
||||
"read_only_meaning": "The governor read every tool call of the actual reviewer transcript (762 rows, 149 assistant messages, all model claude-opus-5-5, 2026-10-25T09:18:31Z to 09:35:20Z): tools used were Bash 74, Read 5, Write 1, Edit 1, mcp__ccd_turn__update_plan 2 and mcp__ccd_turn__wrap_up 1; no Agent or Artifact call. Read: the frozen checkout's tests/helpers/p16_dismissal_fixture.gd and tests/staff/test_general_dismissal.gd only. Write and Edit: only the reviewer's own scratchpad helper write_result.py (/private/tmp/claude-501/-Users-korizatou114-Projects-------------/2edbe800-dc0f-4fbc-8e2f-54790a2406a5/scratchpad); the Edit changed one line reference in it. Every Bash command was read in full. Bash writes: only the reviewer's scratchpad (packet_data.json, spec_packet.md, diff.patch, gitdiff.patch) and its own TMPDIR (lsfiles.txt), and DESKTOP_RESULT.json created once by write_result.py (the listing just before shows no result file); rows without a timestamp are api-request-blob copies of the same tool calls, not further runs. git use: diff 12, log 1, status 1, rev-parse 1, ls-files 1 (no commit, checkout, reset, merge or worktree). No test, Godot, agent-control, tools/run_*.py or run_all_tests run. Two tool results were exit 1 of read-only cat/head commands. After the review the governor checked: frozen checkout HEAD f5fe219 with only ?? .review-material/, all 7,292 material files equal to the manifest SHA-256, packet/normal packet/schema/manifest equal to READY, no active run, task state review_pending. The empty folder .claude/.cc-writes in the review folder appeared at 18:18:44 JST, right after the reviewer's first cd into that folder (09:18:43Z); it is the harness's working-directory folder, not a file written by a tool. Schema checked by the reviewer by hand (transcript 2edbe800-dc0f-4fbc-8e2f-54790a2406a5.jsonl; tool calls {\"mcp__ccd_turn__update_plan\": 2, \"Bash\": 74, \"Read\": 5, \"Write\": 1, \"Edit\": 1, \"mcp__ccd_turn__wrap_up\": 1}; edit tools [\"Edit\", \"Write\"]; pattern matches read by the governor: 2)",
|
||||
"tests_or_engines_started": false,
|
||||
"packet_sha256": "1bd8457027d81ba2f856f9320e60b9f46f979ccf11f2edfd786d1204896614f8",
|
||||
"normal_packet_sha256": "720a675afb1238eff11ef1fb51b60e00b69740568e22681275a138115ad780f5",
|
||||
"schema_sha256": "49ca72d63dfe23d6a6ef50f4f261c7bf706b2fcb34cb4375ce733254693ebc39",
|
||||
"material_manifest_sha256": "3a149af9a30f01ea4b041b02fb7179a6564204d1c9825a8c85910f5cb8e73de2",
|
||||
"result_file": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/p16-01-dismiss-independent-review-f5fe219-r01/DESKTOP_RESULT.json",
|
||||
"result_sha256": "1f530e6c4e6158a469271d287763fc8a573295f44f04dd26bf9b5e6a93f8b737",
|
||||
"result_sha256_source": "computed by the governor from the actual DESKTOP_RESULT.json (not edited)",
|
||||
"status_reported": "pass",
|
||||
"delivered_by": "Desktop governor session local_a7f85421-3eed-4482-8030-c0dca669573a (implementer, commander and native executor of P16-01-DISMISS; did not review or edit the result)",
|
||||
"session_started_by": "Root started the proposal card task_3d0cb301 once in the Claude Desktop UI (Root record ROOT_P16_F5FE219_FULL_ROSTER_REVIEW_READY_20261025_091748_R01.json, SHA-256 6f42a5fbadcb73140e6eda4fb4f7774dceb9bab5c171ae6cc2f762c58af66adf); the governor proposed the card and did not start it; a new fresh session, not local_a7f85421 (implementer), local_e0348c69 (the S b06fec1 reviewer) or local_cad69b68 (the S a073ccb reviewer); session metadata: model claude-opus-5-5, effort xhigh, permission mode auto, parent local_a7f85421",
|
||||
"cli_authentication_recovered": false,
|
||||
"cli_review_succeeded": false,
|
||||
"recorded_at": 1792921039.984701
|
||||
},
|
||||
"independent_result": {
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p16-01-dismiss/DISMISS_INDEPENDENT_REVIEW_F5FE219.json",
|
||||
"bytes": 19385,
|
||||
"sha256": "1f530e6c4e6158a469271d287763fc8a573295f44f04dd26bf9b5e6a93f8b737",
|
||||
"copy_of": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/p16-01-dismiss-independent-review-f5fe219-r01/DESKTOP_RESULT.json"
|
||||
},
|
||||
"review_findings": [
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "項目 6・§4.3【受入を止めない。画面の試験の範囲】患者の側の結果の文は、画面で確かめていない。tests/staff/test_general_dismissal.gd 997〜1013 行では、患者は押す前の文(「この従業員への救護は取り消し、救護係を外します。」)だけを確かめて「やめる」で閉じる。運搬者を解雇した後に患者も解雇するが、その時はもう救護の作業が無く、確かめるのは名簿から消えることだけ。§4.3 が明示する結果は「成功、拒否の理由、払った退職金、保持した未払い」で、これらは画面で全て確かめている。結果の「関わっていた救護の作業は取り消しました。」は scripts/ui/staff_management_panel.gd 391〜408 行の同じ分岐で、運搬者の画面で確かめている。患者を解雇した時に interrupted_rescue_task_ids が空でないことは、source(simulation_runtime.gd 7177 行〜。患者を目標にした作業を取り消して並べる)と、命令の試験(_test_rescue_matrix の患者の場面で、作業の取り消しと救護係の解放)で読める。そのため、説明と実際の動作は合うと判断する。直すなら、運搬の途中で患者を先に解雇し、結果の文を画面で確かめる場面を足すとよい。"
|
||||
},
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "§5.a・U1【受入を止めない。画面の中の守りの試験が無い】画面の中の判定で送らない守りの分岐は、この S の試験で一度も通らない。対象は、staff_management_panel.gd 329 行〜の request_dismissal の拒否の分岐と、349 行〜の confirm_dismissal の分岐(確認を開いた後に拒否に変わった時と、古い確認の時に送らない)。修正 2 で Button.pressed.emit をやめた結果、無効のボタンへの viewport の押しは Godot が受け付けず、これらの関数に届かない。5.a のうち、無効の行で押しても送らないこと、「解雇する…」と「やめる」で送らないことは、snapshot 全体の bytes で確かめている。§4.3・F2・F9 の明示の条件は満たすので、受入は止めない。ただし、IMPLEMENTATION_NOTES §4 の「確定の時に名簿を読み直す。読み込みや変化の後の古い確認では送らない。」は、source だけの主張になっている。直すなら、確認を開いた後に資金を減らして「解雇を確定」を viewport で押し、送らないことと理由の表示を bytes で確かめる場面を足すとよい。"
|
||||
},
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "A8・F7【受入を止めない。出来事の方針の一部の試験が無い】命令の中の event_service.audit_actor_availability(simulation_runtime.gd 7117 行)は、既存の _handle_missing_actors(scripts/events/event_service.gd 1056 行〜)の4つの方針の全てを通す。一方、試験(_test_event_actors 654 行〜、再開の2段の A8・A8-continue-without)は CANCEL と CONTINUE_WITHOUT だけで、REPLACE_WITH_ROLE と BRANCH_FAILURE は解雇の命令の境目で試験していない。命令は既存の公開の関数を次の時刻より早く呼ぶだけで、どの分岐も、欠けた actor と拘束を外すか instance を取り消すので、参照を残さないと source から読める。そのため受入は止めない。"
|
||||
},
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "§4.1 (d)【受入を止めない。巻き戻しの失敗を検出しない】_rollback_staff_dismissal(simulation_runtime.gd 7196 行〜)の戻り値は、呼ぶ側(7068・7078・7100・7108 行の4つの失敗の分岐)で使われていない。戻り値には、各 restore_snapshot と最後の _validate_runtime_path_crosslinks の結果が入る。そのため、巻き戻し自体が失敗しても latch せず、TRANSACTION_APPLY_FAILED を返す。この形は既存の雇用の巻き戻し(_rollback_staff_candidate_hire)と同じで、IMPLEMENTATION_NOTES §1 が開示している。二重の失敗はこの S の受入の条件の外で、4つの枝の巻き戻しの結果は試験で確かめているので、受入は止めない。"
|
||||
},
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "来歴【受入の判断に影響しない。証拠の保持の欠落としては残る】(1) DERIVE_A073CCB_SCRIPTS_FIRST_RUN_FAILURE.md(ccae9e5e…)の欠落(1回目の2つの出力と stdout の元の bytes)は、前の S の後続のスクリプトの話。この S の分の derive_p16_f5fe219_scripts.py(ef47e675…)は1回目で exit 0(stdout 923a4d53…)。また、この S の native と全 roster の出力は、それを作ったスクリプトに依らず、凍結 checkout の bytes と直接照合して確かめた。(2) 局所の診断の記録 P16_01_DISMISS_REPAIR2_LOCAL_CHECKS_R01.json(4b5f2326…)は diagnostic_only・formal_acceptance false で、受入の根拠に使っていない。(3) 画面の場面の診断の写し2つ(stage の上の tests/staff/diag_r2_ui_flow.gd)は元の bytes が残っておらず、log と control の4つと、生成の命令の写し(05cc8b07…)だけがある。写しは IMPLEMENTATION_NOTES §4 の、修正 1 の診断の原因の説明(倍率 0.889、行の作り直し、締めの後の panel の切り替え)の根拠で、その説明は正式の証拠でないと明記されている。この S の F9 の判定は、正式の validation と全 roster が走らせた suite(f1f77bfd…。凍結 checkout と bytes で同じ)の結果と source だけに基づき、写しの bytes に依らない。(4) 全 roster の途中の R36 の clone の共有(A073CCB_CLOSED_ALIGN_R36.json 3fed617b…、script 1db5b7a5…)は、a073ccb の閉じた2つの checkout だけを対象にし、全 roster の stage には触れていない。全 roster の source-start の 1,438 ファイルが凍結 checkout と bytes で一致し、frozen_before と frozen_after が f5fe219・清潔なので、入力は変わっていない。driver の space の欄が R34 だけを挙げるのは記録の不足だが、別の記録で補われている。以上から、どの過程も入力の来歴と受入の判断を変えないと判断する。ただし、(1) と (3) の元の bytes は失われ、同じだったと誰も確かめられないので、Root が保持するとおり、証拠の保持の欠落として残る(承認でも免除でもない。受入を止める理由にはしない)。"
|
||||
}
|
||||
],
|
||||
"review_high_medium": 0,
|
||||
"review_low": 5,
|
||||
"review_provenance_records": [
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/p16-01-dismiss-independent-review-f5fe219-r01/READY_FOR_DESKTOP_REVIEW.json",
|
||||
"bytes": 2934,
|
||||
"sha256": "aa8362e46aa06494aa49750679c965f81f5a35550c7c87b7e56a96338a5a4218"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/p16-01-dismiss-independent-review-f5fe219-r01/REVIEW_BRIEF.md",
|
||||
"bytes": 14868,
|
||||
"sha256": "00d7e397eabbabb0024db6b7a4af8b6523f8fd8fcaac359af091c0dd23e8f612"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/p16-01-dismiss-independent-review-f5fe219-r01/DESKTOP_DELIVERY.json",
|
||||
"bytes": 3982,
|
||||
"sha256": "0328910727b91155d315f111d0c56b95e5b3f71822fcb50189dc9eb76422be0b"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/p16-01-dismiss-independent-review-f5fe219-r01/INTAKE_STARTED.json",
|
||||
"bytes": 217,
|
||||
"sha256": "cd5e3fbee5037d5320e249fa872f3b3dbd19b6fef5212ba8c011b2b844719ada"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/p16-01-dismiss-independent-review-f5fe219-r01/INTAKE_FINISHED.json",
|
||||
"bytes": 287,
|
||||
"sha256": "f8ef1ebd47decfb90dfa48fab25f3073addfd004f8b79f0658137e9151ea841f"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/DESKTOP_DELIVERY_INSPECT_F5FE219.stdout",
|
||||
"bytes": 32452,
|
||||
"sha256": "03740c5fbe8b608c39121290faab2a16654a88aeb94b90b97fcedc31a6fb910f"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/DESKTOP_DELIVERY_WRITE_F5FE219.stdout",
|
||||
"bytes": 192,
|
||||
"sha256": "87e2e43eb8e2d9f9324b26194e4ecdbfcfc7e9ef1aae6e72a04438481e0795bd"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/RECEIVE_REVIEW_F5FE219.stdout",
|
||||
"bytes": 179,
|
||||
"sha256": "fe6019f16786d2944f34d5bac265c4c70253bc55a680a33a77af2106a1444ebe"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/PREPARE_INTEGRATION_F5FE219_SUMMARY.json",
|
||||
"bytes": 3213,
|
||||
"sha256": "ed07d7c6f62282b7584adf713a9e8e4d5a53b619e0035b49324137d3c12302ad"
|
||||
}
|
||||
],
|
||||
"earlier_reviews_retained": [
|
||||
{
|
||||
"S": "b06fec17c7c1e2e4cc22116f209c51dec751215c",
|
||||
"reviewer": "local_e0348c69",
|
||||
"first": {
|
||||
"status": "pass",
|
||||
"result": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/p16-01-dismiss-independent-review-b06fec1-r01/DESKTOP_RESULT.json",
|
||||
"bytes": 12105,
|
||||
"sha256": "12434d831139d3b5a0a149858cdd11e33dd71c035909c33ea36f5cf2677f241b"
|
||||
}
|
||||
},
|
||||
"revised": {
|
||||
"status": "changes_requested",
|
||||
"high": 1,
|
||||
"medium": 6,
|
||||
"low": 4,
|
||||
"result": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/p16-01-dismiss-independent-review-b06fec1-r01/DESKTOP_RESULT_SUPPLEMENT_R02.json",
|
||||
"bytes": 19724,
|
||||
"sha256": "8048931a47f5cc231fe043fd9febe678f5784da2f5cecdc8a66fdb9f9889cdbb"
|
||||
}
|
||||
},
|
||||
"repair": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/COMMANDER_REPAIR_PLAN_P16_01_DISMISS_R01.md",
|
||||
"bytes": 5878,
|
||||
"sha256": "6dbb5ce11e7c8f392ba71f7dd16ea34bab5146c312f7a33718205466c581d2b2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"S": "a073ccb59b9e20e91875008b5d34431ed5a1174f",
|
||||
"reviewer": "local_cad69b68",
|
||||
"status": "changes_requested",
|
||||
"medium": 1,
|
||||
"low": 3,
|
||||
"result": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/p16-01-dismiss-independent-review-a073ccb-r01/DESKTOP_RESULT.json",
|
||||
"bytes": 17281,
|
||||
"sha256": "dacafb2225938df225cf30ef5412ab8ebded48b2307a230a76d7ad963ee87a22"
|
||||
},
|
||||
"brief": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/p16-01-dismiss-independent-review-a073ccb-r01/REVIEW_BRIEF.md",
|
||||
"bytes": 11497,
|
||||
"sha256": "98e1a1e1df97d0177752cff562520298ca287ac5b3934568d90499a1a7b0eb94"
|
||||
},
|
||||
"repair": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/COMMANDER_REPAIR_PLAN_P16_01_DISMISS_R02.md",
|
||||
"bytes": 5953,
|
||||
"sha256": "fec141abf13d4a4e3e9947d1c2b4888a04376317ce25f12be7f1f74d57dcaafd"
|
||||
}
|
||||
}
|
||||
],
|
||||
"measurement": {
|
||||
"measured_on": "f5fe2196740466a673879cb2950d96446fd99d6e",
|
||||
"native": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/NATIVE_F5FE219_START.json",
|
||||
"bytes": 872,
|
||||
"sha256": "984524b3ac62eded620d01e024b29ef5aa0462140eab3c45cf6f9c3a98a24bd5"
|
||||
},
|
||||
"native_bundle_transfer": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/NATIVE_F5FE219_BUNDLE_TRANSFER.json",
|
||||
"bytes": 1349,
|
||||
"sha256": "fad160fd2ac3af620d922852b2b85735df7208cbc3a165b642b5bdd6693bf0d7"
|
||||
},
|
||||
"formal_validation_summary": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/FORMAL_VALIDATE_F5FE219_SUMMARY.json",
|
||||
"bytes": 2819,
|
||||
"sha256": "d89ef221efec094612d719fe0b27f5839f02e970994389ca1af9d6130c75c0ce"
|
||||
},
|
||||
"full_roster_summary": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/FULL_ROSTER_F5FE219_R01_SUMMARY.json",
|
||||
"bytes": 3457,
|
||||
"sha256": "53d615aecb12a9fb37b6d1a6f21ac7fc23f2c073d47687c62d1ec9f65f262cd6"
|
||||
},
|
||||
"full_roster_placement": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/FULL_ROSTER_F5FE219_PLACEMENT.json",
|
||||
"bytes": 981,
|
||||
"sha256": "23d07fb54ba648680c43c05430324d7d265029244efc1624684a6c046ec74048"
|
||||
},
|
||||
"native_phases": 14,
|
||||
"native_suites": 12,
|
||||
"native_checks": 1888,
|
||||
"full_roster_suites": 201,
|
||||
"full_roster_checks": 24073,
|
||||
"measurement_not_relabelled_as_gate_Q": true,
|
||||
"Q_meaning": "the evidence measured on S was checked by the registered content-based verifier in fresh checkouts of Q (integration and final); nothing was measured on Q and no engine started"
|
||||
},
|
||||
"capacity_records_content_preserving": [
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/WX_CLOSED_ALIGN_R29.json",
|
||||
"bytes": 5996450,
|
||||
"sha256": "727ae9cbfd288064bd6899e12ba33c1f2a99d118b0a0ad7cc02fa91f9e8ed28a"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/B06FEC1_CLOSED_ALIGN_R30.json",
|
||||
"bytes": 1544347,
|
||||
"sha256": "25392b6da3c18f2c8adcb04fa8a6c1cd44e9a0bbdd0368ec8a69c6d636f840c2"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/P16_01_DISMISS_FULL_ROSTER_RAW_COMPRESSION_B06FEC1_R31.json",
|
||||
"bytes": 3151119,
|
||||
"sha256": "63611a45ba37a42b044ba22306643f41dbbba4accc87dc31c425a8e1f2dc951f"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/B06FEC1_BUILD_ITEMS_MOVE_R32.json",
|
||||
"bytes": 1330,
|
||||
"sha256": "e057a341803d95c4328f7758e69da74de1ccb2c2a237e5b725391bb1dac35fbc"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/P16_01_DISMISS_FULL_ROSTER_RAW_COMPRESSION_A073CCB_R33.json",
|
||||
"bytes": 3187050,
|
||||
"sha256": "d9f19a2ca8b2d51b11eed91b81f1d9d0c0a4aaa55332b7a136e552c0d0789256"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/A073CCB_BUILD_ITEMS_MOVE_R34.json",
|
||||
"bytes": 1410,
|
||||
"sha256": "6fbd0681edde6d80be21960c64ce8131c003be4eb8f8969a3d20080cc4ba681b"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/P16_01_DISMISS_FULL_ROSTER_RAW_COMPRESSION_F5FE219_R35.json",
|
||||
"bytes": 3223348,
|
||||
"sha256": "eaacdea029142ee164d6c3169f8c65a0977db87f1ba45b4329af0006ee5af66f"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/A073CCB_CLOSED_ALIGN_R36.json",
|
||||
"bytes": 3115499,
|
||||
"sha256": "3fed617b19d54eb6e91f55f493cb1c52a2149716194ded325b4006cc85dd5d3c"
|
||||
}
|
||||
],
|
||||
"capacity_note": "R36 ran during the S f5fe219 full roster (free about 0.52 GB with 23 suites left); it aligned only the closed S a073ccb checkouts and touched no roster input",
|
||||
"historical_evidence_retained": [
|
||||
{
|
||||
"S": "b06fec17c7c1e2e4cc22116f209c51dec751215c",
|
||||
"records": [
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/FORMAL_VALIDATE_B06FEC1_SUMMARY.json",
|
||||
"bytes": 2819,
|
||||
"sha256": "b11f4f8416ae9c93e7886dcc05cb3efe5ccbfd695b784efda49538bab6c2b9a6"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/FULL_ROSTER_B06FEC1_R01_SUMMARY.json",
|
||||
"bytes": 3227,
|
||||
"sha256": "636a4d69b0e3afb697266d750148398a3145750c812e8cbbb9ba5ab16c40860e"
|
||||
}
|
||||
],
|
||||
"meaning": "S before repair 1: native, validation and full roster passed; the review was revised to changes_requested"
|
||||
},
|
||||
{
|
||||
"S": "a073ccb59b9e20e91875008b5d34431ed5a1174f",
|
||||
"records": [
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/FORMAL_VALIDATE_A073CCB_SUMMARY.json",
|
||||
"bytes": 2820,
|
||||
"sha256": "c4541e73bfa024db00280203c0eb4aab4134c3c7318023bb95e50c15f575d64b"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/FULL_ROSTER_A073CCB_R01_SUMMARY.json",
|
||||
"bytes": 3331,
|
||||
"sha256": "c72f2fdfe9f916520e32952ba12ef2f408cb3d79fa581d39b053ca474b799489"
|
||||
}
|
||||
],
|
||||
"meaning": "S after repair 1: native, validation and full roster passed; the review was changes_requested (F9 normal input)"
|
||||
},
|
||||
{
|
||||
"record": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/P16_01_DISMISS_LOCAL_CHECKS_R01.json",
|
||||
"bytes": 13545,
|
||||
"sha256": "e26f9a6abec88809edeb1668dfa651c3b229db394a03a0f69ec330a04df4ea16"
|
||||
},
|
||||
"meaning": "first local checks with their failed runs (diagnostic)"
|
||||
},
|
||||
{
|
||||
"record": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/P16_01_DISMISS_REPAIR1_LOCAL_CHECKS_R01.json",
|
||||
"bytes": 14381,
|
||||
"sha256": "d3b8be1314ec522e55e4a835218c50df9eab7a6b08879cbcc572e77bc49bc91b"
|
||||
},
|
||||
"meaning": "repair 1 local checks (diagnostic)"
|
||||
},
|
||||
{
|
||||
"record": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/P16_01_DISMISS_REPAIR1_LOCAL_CHECKS_R01_CORRECTION_01.json",
|
||||
"bytes": 6197,
|
||||
"sha256": "391592eedbba654cdb0079521f6ba9f1480cbb03a0b60836e0ab7c24d7daa583"
|
||||
},
|
||||
"meaning": "correction of the repair-1 intermediate failure text (run-r1-1 7/640, run-r1-2 7/641, run-r1-3..5 2/663); the receipt kept unchanged"
|
||||
},
|
||||
{
|
||||
"record": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/P16_01_DISMISS_REPAIR2_LOCAL_CHECKS_R01.json",
|
||||
"bytes": 10924,
|
||||
"sha256": "4b5f2326d820b21fa524e159fc3aaca059cb3a1749a9623ca3450fe2681e6714"
|
||||
},
|
||||
"meaning": "repair 2 local checks with the failed first UI run (diagnostic)"
|
||||
}
|
||||
],
|
||||
"record_gaps_kept": [
|
||||
{
|
||||
"record": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/DERIVE_A073CCB_SCRIPTS_FIRST_RUN_FAILURE.md",
|
||||
"bytes": 1508,
|
||||
"sha256": "ccae9e5e9c0922843f06e6240e92a1d35a2c7abf8766cfa8ece10f662e5a17ff"
|
||||
},
|
||||
"meaning": "the first run of the a073ccb follow-up script derivation failed; its two outputs, its stdout and the first script bytes are not kept; the failure text was transcribed from the conversation"
|
||||
},
|
||||
{
|
||||
"record": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/P16_01_DISMISS_REPAIR2_DIAG_COPIES_PROVENANCE_R01.json",
|
||||
"bytes": 4072,
|
||||
"sha256": "110bda16b0751e3876d266ba9ac3a25b5eeb1c2c82971e8fb93c3eda77dafc77"
|
||||
},
|
||||
"extracts": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/DIAG_R2_UI_FLOW_COPIES_TRANSCRIPT_EXTRACTS_R01.json",
|
||||
"bytes": 21117,
|
||||
"sha256": "05cc8b07b4ecdd745cd779a0735215768c188461afbbc908e5042b3fd2cc0efa"
|
||||
},
|
||||
"meaning": "the two stage-only UI diagnostic copies are not kept as files; their logs, generating commands (transcribed) and derived-from sources are kept"
|
||||
},
|
||||
{
|
||||
"note": "Root keeps these as evidence-retention gaps; neither approved nor waived. The independent review judged they do not change the input provenance or the acceptance. They are not a loss of the new S formal test raw."
|
||||
}
|
||||
],
|
||||
"remaining_not_claimed": [
|
||||
"Original P16-01 stays unmet: the ordinary recruitment operation (AR.staff.recruitment), market and debt acquisition (AR.staff.market_acquisition, debt_acquisition, D04) and the cost arrangement per employment category (SPEC section 8)",
|
||||
"Original P16-03 stays unmet: the cross-check of shift, rest, collapse and rescue (D04) and the reference removal on departures outside the dismissal path (SPEC section 8)",
|
||||
"Human trafficking (AR.staff.sale_*, D04) is not addressed",
|
||||
"AR.staff.dismissal_severance is connected by the A14 decision and this acceptance but is not counted as the completion of the original P05 package work/AC",
|
||||
"Dismissing the defender of an unfinished raid (R9 DEFENSE_LOCKED, Root temporary boundary) stays unmet as an original requirement; no retreat or combat rule was made",
|
||||
"Review low: the patient-side result text of an interrupted rescue is not checked on screen",
|
||||
"Review low: the panel guards that refuse a stale or changed confirmation are not exercised by a test",
|
||||
"Review low: the Event missing-actor policies REPLACE_WITH_ROLE and BRANCH_FAILURE are not tested at the dismissal boundary",
|
||||
"Review low: the return value of _rollback_staff_dismissal is not used (the existing hire rollback form)",
|
||||
"The test input route is viewport input (root.push_input), not OS input; the same Windows device, IME, OS scaling and same-candidate device acceptance (SPEC section 10) remain; the UI uses the current parts and is not the finished look",
|
||||
"Long normal play and whole-game final acceptance remain"
|
||||
],
|
||||
"implementation_provenance": "Contract worker field codex is compatibility only; no Codex or Astra run or dispatch. Implementation, repairs 1 and 2, natives and full rosters by the registered Claude Opus 5.5 Desktop implementer and commander (local_a7f85421); commits made by Runner submit; independent reviews by separate fresh Opus 5.5 Desktop sessions (local_e0348c69, local_cad69b68, local_4e746265)",
|
||||
"formal_original_credit": 0,
|
||||
"original_completed_before": 24,
|
||||
"original_completed_after_ledger": 24,
|
||||
"original_delivery_total": 88,
|
||||
"packages_completed": 4,
|
||||
"packages_total": 30,
|
||||
"partial_credit": false,
|
||||
"new_engine_or_full_roster_runs_for_ledger": 0,
|
||||
"all_prior_failures_retained": false,
|
||||
"formal_test_raw_and_earlier_reviews_retained": true,
|
||||
"prior_failure_retention_scope": "Kept: the raw output of every formal test (natives, registered validations, full rosters of S b06fec1, a073ccb and f5fe219), the earlier review results and deliveries, the failed local and diagnostic run logs and the receipts. Not kept as original bytes: the first run of the a073ccb follow-up script derivation (its two outputs, its stdout and the first script bytes; the failure text was transcribed from the conversation) and the two stage-only UI diagnostic copies of repair 2 (their logs, transcribed generating commands and derived-from source hashes are kept); see record_gaps_kept. No earlier S evidence is reused for the new S.",
|
||||
"correction_note": "2026-10-25: all_prior_failures_retained was written true in the first version of this record; it is false because of the record gaps above (Root observation). The earlier version stays in the Git history (commit 8a2ea74).",
|
||||
"CLI_auth_recovered": false,
|
||||
"no_new_human_utterance": true
|
||||
}
|
||||
+40
File diff suppressed because one or more lines are too long
+19
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"task": "P16-01-DISMISS",
|
||||
"spec": "docs/plans/original-completion-2026-10-02/execution/p16-01-dismiss/SPEC.md",
|
||||
"rule": "SPEC F10: every changed expectation of an existing suite is listed with the value before and after and the reason.",
|
||||
"changes": [
|
||||
{
|
||||
"suite": "tests/business/test_required_kitchen.gd",
|
||||
"check": "whole typed loaded state unchanged except complete identity/saveID-bound IO1: legacy14-load (and the byte-identical-copy legacy migration control, which uses the same mapped expectation)",
|
||||
"before": "_approved_runtime_migrations maps A06 (mana reserve fresh start), P14-03 (elevators v1 to v2), P05-02 A12 (the five empty fund accounts) and P07-01 A13 (natural weather) into the old checkpoint before the whole typed comparison",
|
||||
"after": "the economy mapping also adds the empty STAFF_SEVERANCE_EXPENSE account (allow_negative false) when the old save lacks it and the loaded save has it; the fund accounts and the severance account are added in one step and the whole economy must then be byte-identical, so nothing else may differ",
|
||||
"reason": "A14 / SPEC section 3.6: a save from before the severance gains its empty expense account on load (FinancePolicy._prepare_severance_account, the A12 form). The genuine legacy14 fixture is such a save, so its loaded economy differs from the old checkpoint exactly in this account."
|
||||
}
|
||||
],
|
||||
"not_expectation_changes": [
|
||||
"tests/economy/test_p05_funds_runtime.gd _test_old_save_migration keeps its expectation: its old save is copied from a current save, so it already holds the severance account and only the five fund accounts are removed.",
|
||||
"No existing suite expected a save with unpaid rank tax or interest after a close to be refused; the restore fix of IMPLEMENTATION_NOTES.md section 6 changes no listed expectation.",
|
||||
"Repair 1: no existing suite reads the operation id of a staff Task's inventory reservation (staff.task.reserve.*); the reservation-count key of IMPLEMENTATION_NOTES.md section 5 changes no listed expectation."
|
||||
]
|
||||
}
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
# P16-01-DISMISS 実装の記録(実 Opus の実装担当、2026-10-24、修正 1 と修正 2 は 2026-10-25)
|
||||
|
||||
- 仕様: 同じフォルダの `SPEC.md`(登録版 r04。main `0b59d7c` に固定。ファイルの SHA-256 は `b4cc31c0…5f77`、Runner の `spec_sha256` は `14efffd9…e6b`)。出典は `PROVENANCE.json`。
|
||||
- 原本への加算は 0。原単位 P16-01・P16-03、`AR.staff.dismissal_severance` の package P05 の原 work・原 AC は閉じない(SPEC §8)。
|
||||
- 管理の記録は `artifacts/original-completion-2026-10-02/p16-01-dismiss-control-r01/` にある(git の外)。
|
||||
- 変えた製品のファイル: `autoload/simulation_runtime.gd`・`scripts/economy/finance_policy.gd`・`scripts/ui/staff_management_panel.gd`。`scripts/staff/staff_service.gd` と `scenes/main/main.gd` は変えていない(§1・§4)。
|
||||
|
||||
## 0. 修正 1 の経過(S `b06fec1` → 修正の後の S)
|
||||
|
||||
- S `b06fec1` は、同じ実 reviewer の補足(改訂)の判定で changes_requested になった(high 1・medium 6・low 4。`DESKTOP_RESULT_SUPPLEMENT_R02.json`)。1回目の pass と、その後の補足の判定は、どちらも書き換えずに残している。
|
||||
- 主な所見と直し:
|
||||
- high: S `b06fec1` は、乗ったままの救護の組の解雇を、実装の判断で `RESCUE_PAIR_ABOARD` として拒んでいた。これは SPEC §6.2 の最後の文「直し、拒否に変えない」、F1・F7(許可の全ての行)に合わない。拒否を消し、組を解く後始末を作った(§3)。
|
||||
- medium: 開き直した予約つきの作業を別の職員が取れなかった(`IDEMPOTENCY_CONFLICT` で BLOCKED)。予約の操作 ID を直した(§5)。
|
||||
- medium: preview と結果が、命令の中で取り消す救護の搬送を「開き直す」と書いていた。分けて実際どおりにした(§4)。
|
||||
- medium: preview が R5(運営の終了)と R3(予約を解けない)を判定していなかった。判定を足した(§1)。
|
||||
- medium と low: 条文が明示する確かめ方を代用していた所がある(同じ process の読み込み、通勤の昇降機、拒否の行の画面、bytes の比べ、別の process の境目など)。条文どおりの試験を足した(§7)。
|
||||
- 新しい拒否や、R9 に似た境界は足していない。受入の条件も削っていない。
|
||||
|
||||
### 修正 2 の経過(S `a073ccb` → 修正の後の S)
|
||||
|
||||
- S `a073ccb` は、新しい fresh な別の実 reviewer の判定で changes_requested になった(medium 1・low 3。`p16-01-dismiss-independent-review-a073ccb-r01/DESKTOP_RESULT.json`)。F1〜F8 と F10 は満たし、F9 を満たさないという判定。結果・配送・transcript は書き換えずに残している。
|
||||
- 所見と直し(製品のファイルは変えていない。試験と記録だけ):
|
||||
- medium(F9・§4.3 の通常の入力): S `a073ccb` の画面の試験は、Main のキーの処理を直接呼び、ボタンの `pressed` の信号を直接出していた。viewport の入力の経路で、解雇と確定のボタンに届くことを確かめていなかった。画面の試験を viewport の入力に置き換えた(§4・§7)。
|
||||
- low(§4.1 (d)): 試験で失敗を起こしていなかった2つの巻き戻しの枝を、開示した fixture で起こした。記録の「起こせない」という理由は、`remove_staff` の枝について正しくなかったので直した(§1)。
|
||||
- low(§4.3): 裏金からの補填の文と、命令の中で取り消す救護の文を、画面で確かめた(§7)。
|
||||
- low(後続のスクリプトの1回目の元の bytes の欠落): 管理の記録の欠落として残す。この W の中で直すものは無い。
|
||||
|
||||
## 1. 命令と判定の順(SPEC §4.1)
|
||||
|
||||
- `SimulationRuntime` に通常の command `dismiss_staff`(payload `staff_id`)を足した(`_execute_dismiss_staff`)。
|
||||
- (a) 運営の終了(`BANKRUPT`)と (b) 同じ key の再送(`DUPLICATE_IDEMPOTENCY_KEY`)は、今の `submit_command` と `CommandQueue.enqueue` のまま受付の前・受付で止まる(R5・R6、5.b)。
|
||||
- (c) executor は、状態を変える前に次の順で確かめる。どれかに当たれば、その理由で失敗の結果を返す(5.c)。
|
||||
1. payload に `staff_id` が無い → `INVALID_PAYLOAD`
|
||||
2. 職員が居ない → `NOT_FOUND`(R4)
|
||||
3. `UNIQUE` → `UNIQUE`(R1。今の `remove_staff` と同じ code と文)
|
||||
4. `FREE_HIRE` でない(`ADULT_CONTRACT`)→ `NOT_GENERAL_EMPLOYEE`(R7)
|
||||
5. 外部が持つ作業(仕入れの物流の受け取り・配達・返送)→ `EXTERNAL_TASK`(R2。今の `remove_staff` と同じ code と文)
|
||||
6. 持っている作業の予約を解けない → `RESERVATION_RELEASE_FAILED`(R3。今の `remove_staff` と同じ code と文)
|
||||
- 修正 1 で、変える前の判定にした(`_staff_task_reservations_releasable`)。在庫の解放は、予約が無い時と消費済みの時に失敗する(`InventoryService.release_reservation`)。それを、読むだけの `get_reservation` で前もって見る。
|
||||
- `remove_staff` の同じ拒否は、最後の守りとして残す。そこで止まった時も、全体を巻き戻す。
|
||||
7. 終わっていない襲撃の防衛者 → `DEFENSE_LOCKED`(R9。Root の一時の境界)
|
||||
8. 表金と裏金の合計が退職金に足りない → `INSUFFICIENT_FUNDS`(R8)
|
||||
- (d) 全て通れば、次を1つの操作として行う。
|
||||
1. 乗ったままの救護の組なら、解雇する人の座席を解く(§3)。
|
||||
2. 既存の削除の契約 `StaffService.remove_staff(staff_id, &"DISMISSED", true, 分)`。
|
||||
3. 退職金の記帳(§2)。
|
||||
4. 救護の後始末(§3)、施設の人員の投影(`_sync_facility_staffing`)、出来事の欠けた職員の扱い(`event_service.audit_actor_availability`)。
|
||||
- (d) の途中で失敗すれば、命令の前の写し(staff・inventory・path・elevators・移動の残り・出来事・ID・帳簿・Gold の写し)に全て戻して、`TRANSACTION_APPLY_FAILED` を返す。理由は次のとおり。
|
||||
- 座席を解けない時 `RESCUE_SEAT_RELEASE_FAILED`
|
||||
- `remove_staff` が予約を解けない時 `RESERVATION_RELEASE_FAILED`(R3 の最後の守り)
|
||||
- 退職金を記帳できない時 `SEVERANCE_POST_FAILED`
|
||||
- 救護の作業を取り消せない時 `RESCUE_CLEANUP_FAILED`
|
||||
- 4つとも、試験で失敗を起こし、5.c の比べ方で何も変わらないことを確かめる(修正 2 で座席と `remove_staff` の2つを足した。§7)。
|
||||
- 退職金: 記帳の key を先に使う(開示した fixture)。乗ったままの組の段でも起こし、座席を解いた後の path と昇降機の巻き戻しを確かめる。
|
||||
- 救護の後始末: 迎えの作業に無い予約の ID を持たせる(開示した fixture)。
|
||||
- `remove_staff`: 在庫の解放の操作 ID(`staff.task.release.<作業>.<予約>`)を、先に別の予約の操作で束縛する(開示した fixture)。前もった判定(予約が無い・消費済み)は通るが、在庫が `IDEMPOTENCY_CONFLICT` で解放を拒む。修正 1 の記録の「試験の組み立てからは起こせない」は、この枝について誤りだった。
|
||||
- 座席: 運搬者の Path の乗車の要求を、公開の `PathService.complete_elevator_request` で先に終える(開示した fixture)。この状態は保存の照合が拒む形で、通常のゲームの状態からは起きない。
|
||||
- 巻き戻しの関数 `_rollback_staff_dismissal` の戻り値は使っていない。既存の雇用の巻き戻し(`_rollback_staff_candidate_hire`)と同じ形のまま。
|
||||
- 画面と試験のための読むだけの関数 `get_staff_dismissal_preview(staff_id)`。返すのは、解雇できるか・理由・退職金・未払い・開き直す作業・命令の中で取り消す救護の作業と役(運搬者か患者か)・表金・資金の合計。
|
||||
- 運営の終了の後は、理由を `BANKRUPT` にする(R5。命令は受付の前に拒まれる)。
|
||||
- `get_staff_management_report` の各行に `dismissal` として載る。
|
||||
|
||||
## 2. 退職金(SPEC §3.5・§3.6、A14)
|
||||
|
||||
- `FinancePolicy` に、`SEVERANCE_EXPENSE`(口座 `STAFF_SEVERANCE_EXPENSE`)・`SEVERANCE_CATEGORY`(区分 `STAFF_SEVERANCE`)・`SEVERANCE_DAYS`(3)・`severance(日給)` を足した。額は `get_effective_daily_wage() × 3`。
|
||||
- 記帳は命令の時刻に、1つの取引(idempotency key `staff.severance.<command の ID>`)で行う。
|
||||
- 表金で足りない分: 裏金 → 表金の補填(区分 `BACK_FUND_TOP_UP`。P05-02 の返済と同じ形)。
|
||||
- 退職金: 表金 → `STAFF_SEVERANCE_EXPENSE`(区分 `STAFF_SEVERANCE`、source は職員の ID)。
|
||||
- 日次決算の照合では、`cost_of_goods`(画面「運営費(仕入れ・契約金等)」)に入れる。
|
||||
- 写した表は `_build_operations_daily_financials` と `_operations_expected_category_totals` の2つ。施設ごとの表は、施設でない source を読まないので変えていない。
|
||||
- 補填は、fund report v2 の `back_top_up`(その日の全ての補填)に入る。
|
||||
- 旧セーブ: `FinancePolicy.prepare_accounts` の最後で、退職金の口座が無ければ空の口座として足す。A12 の5つの口座とは別に判定するので、P05-02 の後・P16 の前のセーブにも足す。口座があって負を許す設定なら拒む。
|
||||
- 解雇の前に貯まった未払いの給与は、今の契約のまま `_departed_payroll` に保持し、次の 00:00 の締めで払う(A09 の補填を含む)。解雇の日の日給は、今の給与の契約のまま(締めの時に雇用中の職員だけ)。
|
||||
|
||||
## 3. 後始末(SPEC §6.2・§6.2.1)
|
||||
|
||||
- 勤務・予定・配置は `remove_staff` が行う。経路の actor・昇降機の予約・移動の残り・`STAFF_REMOVED`(reason `DISMISSED`)は、今の `_on_staff_removed` が行う。
|
||||
- 施設の人員の投影は、命令の中で `_sync_facility_staffing` をすぐ行う。サービスの途中の職員が抜けて人数が `staff_min` を欠けると、次の施設の更新から今の規則で職員を待ち(5 分)、代わりが来なければ失敗する(A4 の試験)。
|
||||
- **乗ったままの救護の組(修正 1)**: S `b06fec1` の `RESCUE_PAIR_ABOARD` の拒否を消した。組の段階でも解雇できる。
|
||||
- 解雇する人の座席だけを、命令の時に組から解く(`_release_dismissed_rescue_seat`)。SPEC §10 の「非描写の状態として命令の時点で取り除く」に当たる。
|
||||
- 手順は、`ElevatorService` 自身の乗っている1人の取り消し(`_cancel_single`)と同じ。公開の `path_service.complete_elevator_request` で乗車の要求を終え、昇降機の公開の snapshot から、その人の trip・座席・相方の `companion_id` を外して `restore_snapshot` に渡す。
|
||||
- `ElevatorService` は変えていない。`restore_snapshot` の照合(Path の要求と trip の対応、座席、相方の対応)を通る形なので、命令の直後の保存も照合を通る。
|
||||
- 残る人(患者または運搬者)は、今の規則(`_advance_staff_rescue` の、乗っている間は組を保つ分岐)どおり、1人で乗ったまま本物の階に着く。
|
||||
- 運搬者を解雇した時: 患者は着いた階で倒れたままになり、次の時刻の救護の確認が、迎えを今の規則で作り直す。
|
||||
- 患者を解雇した時: 運搬者は着いた階で手が空く。
|
||||
- 試験は組の段階で運搬者と患者のそれぞれを解雇し、次を確かめる(§7)。
|
||||
- 参照が残らない。
|
||||
- 残る人が本物の階(昇降機の止まる階)で降りる。
|
||||
- 救護が今の規則でやり直される。
|
||||
- 命令の直後の保存を、同じ process(写しの復元・ディスク)と別の process(再開の2段)で読む。
|
||||
- 救護(`_interrupt_dismissed_staff_rescue`): 次の時刻の救護の確認(`_advance_staff_rescue` の最初の段)と同じ規則を、命令の中で、解雇した職員に関わる作業にだけ当てる。
|
||||
- 患者を解雇した時: その患者を目標にした、終わっていない救護の作業を `RESCUE_INTERRUPTED` で取り消し、運搬者を作業から外す。
|
||||
- 運搬者を解雇した時: `remove_staff` が開き直した搬送(`rescue_transport`)は、運搬者が居ないので取り消す(今の規則と同じ)。迎え(`rescue_pickup`)は開き直ったまま、別の医療職を待つ(今の規則でも有効な作業)。
|
||||
- 昇降機の予約の取り消しは、乗っていない生きた人だけに行う。乗っている人は、着くまで乗せたままにする(今の規則)。
|
||||
- 出来事: 命令の中で、公開の `event_service.audit_actor_availability` を呼ぶ。進行中・選択待ち・中断の出来事の欠けた職員を、今の `missing_actor_policy` で扱う(CANCEL と CONTINUE_WITHOUT を試験)。予約した費用は、今の規則で1回だけ戻る。この audit は他の欠けた actor も今の規則で扱う(次の時刻と同じ処理が早く行われるだけ)。
|
||||
- 命令の直後に保存し、同じ process と別の process で読み込んでも、欠けた職員への生きた参照が残らず、保存の照合が通る。生きた参照とは、終わっていない作業の患者と運搬者、昇降機の予約と相方、経路の actor、施設の人員、出来事の拘束と actor のこと。
|
||||
- 履歴として残るもの: 終わった作業の記録の `target_entity_id`、サービスの `staff_ids`(始めた時の記録)、給与の締めの行、雇った候補の記録(同じ候補は二度雇えない今の規則のまま)。
|
||||
|
||||
## 4. 画面(SPEC §4.3)
|
||||
|
||||
- `StaffManagementPanel` に、「解雇する…」と確定の欄(「解雇を確定」「やめる」)を足した。判定は Runtime の preview に従う。
|
||||
- 押す前に、退職金の額(日給の3日分)、裏金からの補填、未払いの給与、開き直す作業を出す。
|
||||
- 修正 1 で、命令の中で取り消す救護を分けて出す(`interrupted_rescue_roles`)。運搬者なら「運んでいる救護の搬送は取り消し、患者の救護をやり直します。」、患者なら「この従業員への救護は取り消し、救護係を外します。」。
|
||||
- 拒否の行(R1・R2・R3・R5・R7・R8・R9)ではボタンを無効にし、理由を出す。`UNIQUE` は今の「ユニーク:雇用・維持費無料 / 解雇・売却不可」、それ以外は「解雇不可:…」。無効のボタンを無理に押しても、命令を送らない(5.a)。
|
||||
- 確定の時に名簿を読み直す。読み込みや変化の後の古い確認では送らない。
|
||||
- 時間が止まっている時はすぐに、進んでいる時は次の時刻に、命令が処理される。
|
||||
- 結果として、払った退職金・補填・保持した未払い・開き直した作業・取り消した救護、または拒否の理由(押した後に資金が減って拒まれた場合を含む)を出し、名簿と選択を更新する。
|
||||
- 解雇の受理を待つ間は、その職員の配属の変更とドラッグを止める。
|
||||
- `Main` の職員の管理の画面は、今の部品と送り手(`_audio_feedback.submit_command`)のままで使えるので、`scenes/main/main.gd` は変えていない。UI は今の部品のままで、完成の見た目ではない。
|
||||
- 入力の経路(F9、修正 2): 試験は本物の Main と Runtime を使い、viewport の入力で操作する。
|
||||
- キー(雇用の候補を開く H)は `root.push_input` の `InputEventKey`。
|
||||
- ボタン(候補の雇用・「雇用済み」・名簿の行・「解雇する…」・「やめる」・「解雇を確定」・締めの後に戻る「従業員」)は、全 roster で PASS している `tests/ui/test_business_management.gd` の `_click_principal_button` と同じ形で押す。ScrollContainer で見える位置へ送り、マウスの移動、押しと離しを viewport の local の座標で送り、各フレームを待つ。
|
||||
- 拒否の行では、無効のボタンを viewport の入力で押しても命令を送らず、snapshot の全体の bytes が同じことを確かめる。
|
||||
- 時計が動いている時の押し方: 離す瞬間だけ時計を動かし、すぐ止める。命令は次の分の処理になり、フレームが分を進めない(1分は実時間 1/6 秒なので、フレームを待つ間に時計を動かしたままにすると、確かめる前に分が進む)。
|
||||
- 修正 1 の headless の診断でボタンが反応しなかった原因(修正 2 の局所の診断で確かめた。正式の証拠ではない):
|
||||
- Main は基準の大きさ 1440×810 から縮めて表示される(窓 1280×720 で倍率 0.889)。修正 1 の診断は `push_input` に local の座標を指定せず(もう1つの形の `Input.parse_input_event` も窓の座標)、押した位置が約 1.125 倍ずれてボタンから外れていた。
|
||||
- 名簿の行は `refresh` で作り直される。そのフレームに足した部品では `ScrollContainer.ensure_control_visible` がスクロールしないので、1フレーム待ってから見える位置へ送る。
|
||||
- 0 時の締めの後、Main は側面の panel を帳簿に切り替え、職員の画面を含む部分を隠す(今の `_on_snapshot_published` の動き)。試験は、プレイヤーと同じく「従業員」のボタンで戻る。
|
||||
- どれも試験の押し方の問題で、画面の部品や配置は変えていない。
|
||||
- これは viewport の入力で、OS の入力ではない。Windows の実機・IME・OS の倍率・同じ候補での実機の受入は、SPEC §10「Windows 実機は後」の後続の条件として、実施しておらず未達のまま残す。
|
||||
|
||||
## 5. 予約の操作 ID(修正 1、A5・F7)
|
||||
|
||||
- 原因: 在庫の予約の操作 ID が、作業と品目だけで決まっていた(`staff.task.reserve.<作業>.<品目>`)。在庫の照合の fingerprint には予約した分が入るので、開き直した作業を別の分に別の職員が取ると、`IDEMPOTENCY_CONFLICT` になって作業が BLOCKED になる(戻す経路は無い)。同じ分なら、閉じた旧い予約の結果が再生される。
|
||||
- 直し(`_reserve_staff_task_items`): 操作 ID に、その作業のその品目のこれまでの予約の数を足す(`staff.task.reserve.<作業>.<品目>.<回数>`)。在庫は予約も照合の記録も消さないので、回数は増える一方で、予約ごとに別の操作になる。分ではなく回数にしたので、同じ分に解雇して取り直しても、旧い予約に当たらない。
|
||||
- 試験(§7):
|
||||
- 元の予約・解雇・別の職員の再予約を同じ分で起こし、別の職員が終えるまで。同じ分の派遣は、StaffService の公開の `dispatch_now` で起こす。
|
||||
- 列で処理される解雇と同じ tick での再予約から、完了まで。
|
||||
- 清掃(`facility_clean`)の作業の開き直しと、別の清掃係による完了。
|
||||
- 既存の試験は、この操作 ID の文字列を見ていない(期待の変更なし)。
|
||||
|
||||
## 6. 見つけた既存の不具合と直し(S `b06fec1` で直したもの、変更なし)
|
||||
|
||||
- 締めの後に税・利息の未払いが残ったゲームの保存が読めなかった(P05-02 からの不具合)。
|
||||
- 保存を読む時の Operations の照合(`_parse_operations_runtime_snapshot`)は、締めた未払いを「給与 + 維持費」だけで数えていた。実行中の照合(`_operations_closed_arrears_total`)が足している税・利息の未払い(`_finance_arrears_through`)を、数えていなかった。
|
||||
- 読む時の照合を、実行中と同じ式にそろえた。照合を緩めたのではない。
|
||||
- 試験: 帳簿の日の流れが、税の未払い 30 を確かめてから締めの後の保存を復元する。同じ process(修正 1 で全ての保存点)と別の process でも読む。
|
||||
|
||||
## 7. 試験
|
||||
|
||||
- 新しい suite `tests/staff/test_general_dismissal.gd`(F1〜F9。§6 の全ての行。共通の部品は `tests/helpers/p16_dismissal_fixture.gd`)。最後の場面がゲームの Main を使うので、suite-policy で ordinary の音の後片付けの証明(`single`)を持つ。修正 1 で足した場面:
|
||||
- 乗ったままの組を含む、救護の全ての段階での運搬者と患者の解雇。preview と結果で、開き直すと取り消すを分けて確かめる。
|
||||
- 通勤の職員が上の階の職場へ向かう時に、昇降機を待つ段と乗っている段で解雇する(A2)。
|
||||
- 予約つきの作業の同じ分の再予約と完了、同じ tick の再予約と完了。清掃の作業の開き直しと完了(A5・F7)。
|
||||
- 帳簿の日の流れの全ての保存点を、同じ process でディスクから読み、最後まで続ける(F8)。
|
||||
- 物流の返送の段の拒否(R2)。
|
||||
- 救護の後始末の失敗の巻き戻し(5.c)。
|
||||
- 列で待つ命令の処理を 5.c の増分で比べる(A9)。
|
||||
- その命令は、Runtime 自身の命令の段(`_process_commands`)で、tick の他の処理なしに処理する。
|
||||
- Main の画面で、拒否の行の全て(R1・R2・R3・R5・R7・R8・R9)を確かめる。ボタンが無効で、理由が出て、無理に押しても snapshot の全体の bytes が同じ。
|
||||
- 保持した未払い(本物の 0 時の締めで資金が足りず、日給が未払いになる)も、押す前と結果で確かめる。
|
||||
- 押した後の拒否の理由も確かめる。
|
||||
- 画面の比べは全て `var_to_bytes`。
|
||||
- 修正 2 で変えた・足した場面:
|
||||
- Main の画面の場面を、viewport の入力で操作する形に置き換えた(§4)。`Button.pressed.emit()` と `_unhandled_key_input` の直接の呼び出しは、この suite から無くした。
|
||||
- Main の画面で、裏金からの補填(表金 G100・退職金 G300・補填 G200)の押す前と結果の文を確かめる。資金の fixture は、解雇の後に元の残高に戻る形にした。
|
||||
- Main の画面で、救護室へ歩いて運ぶ搬送の段に、患者と運搬者の押す前の文と、運搬者の結果の文(取り消した救護)を確かめる。
|
||||
- 巻き戻しの枝(`_test_rollback_branches`): `remove_staff` の予約の解放の失敗、乗ったままの組の座席の解放の失敗、乗ったままの組で座席を解いた後の退職金の記帳の失敗。どれも 5.c と 5.d の比べ方で、組が乗ったままであることも確かめる。
|
||||
- 再開の2段 `tests/staff/test_general_dismissal_restart_1_write.gd`(唯一の生成ファイルは `artifacts/p16-01-dismiss/restart-handoff.json`)と `..._2_read.gd`。
|
||||
- 帳簿の6つの保存点を読む。
|
||||
- 次の命令の境目の保存を読む。修正 1 で、A6 の迎え・A6 と A7 の乗ったままの組・A8 の CONTINUE_WITHOUT を足した。
|
||||
- A6: 搬送を徒歩で運ぶ段・迎えの段・組で乗っている段
|
||||
- A7: 運搬者が1人で乗っている段・組で乗っている段
|
||||
- A8: CANCEL・CONTINUE_WITHOUT
|
||||
- 局所の確かめで分かった、既存の規則による制約(製品の不具合ではない。試験の組み立てを直した):
|
||||
- 保持した未払いの給与は未払いの費用として数えられ、建設と雇用を止める(`UNPAID_EXPENSES`)。
|
||||
- 締めを通らない未払いを持たせた職員の登録は、運営の照合(`OPERATIONS_CROSSLINK_MISMATCH`)を止める。そのため画面の試験は、本物の締めで未払いを作る。
|
||||
- validation `p16-dismiss`: `tools/run_p16_dismiss_tests.py`。`tools/run_p07_weather_tests.py` から、名前・名簿・再開の段・handoff・protocol 名だけを明示の置き換えで変えた(段の手順・厳密な照合・ordinary と old_bounded の分類は同じ)。実 12 段(新しい suite 1 + 既存 9 + 再開 2)と import・parse。
|
||||
- suite-policy と marker contract に新しい3本の行を足し、今の source の SHA-256 で固定した。
|
||||
- 既存の試験の期待値の変更は `EXPECTATION_CHANGES.json`(kitchen の旧セーブの写しに退職金の口座を足す1件)。
|
||||
|
||||
## 8. 範囲の外として残すもの(主張しない)
|
||||
|
||||
- まだ始まっていない(待ち行列の)出来事の actor の ID は、拘束を持たない。始まる時に、今の規則(`start_next_event` の欠けた actor の扱い)で扱われる。命令の中では変えない。
|
||||
- 進行中の襲撃の防衛者の解雇(R9)は、原本の要件として未達のまま(SPEC §6.3・§8)。
|
||||
- 解雇した職員が歩いて出る描写は作らない(SPEC §10)。UI は今の部品のまま。Windows の実機・IME・OS の倍率・同じ候補の実機の受入は後。
|
||||
- 原単位 P16-01・P16-03 の残りは未達のまま(SPEC §8): 求人募集、奴隷市場・破産客からの獲得、人身売買、勤務・休息・気絶・救護の横断、解雇の経路の外の退職時の参照の除去。
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"by": "Claude Opus 5.5 Desktop (local_a7f85421-3eed-4482-8030-c0dca669573a), the commander that wrote the proposal; the question was asked by Root (Codex)",
|
||||
"decision_record": {
|
||||
"path": "artifacts/original-completion-2026-10-02/USER_DECISIONS_WEATHER_SEVERANCE_R01.json",
|
||||
"sha256": "c4a08ad4369ceec1738543540611a8c8921bd263d9fbe6b3e2621698f2957390"
|
||||
},
|
||||
"human_decision": {
|
||||
"id": "A14",
|
||||
"questionItemId": "[\"request_user_input_async\",\"call_6696d05036264581b24f6416e62e673a\",0]",
|
||||
"question_verbatim": "原本には「解雇(退職金)」とありますが、金額と支払方法が未指定です。一般従業員の解雇は、次の初期仕様で進めてよいですか。\n\n・退職金は、その職員の日給の3日分。\n・表金から払い、不足分を裏金で補う。合計でも足りなければ解雇しない。\n・未払い給与は退職金と分けて保持し、次の午前0時に精算する。\n・退職金は帳簿で区別し、日次決算では運営費に計上する。\n\n数値と支払方法は実装担当の提案で、まだ確定していません。[具体案](/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p16-01-dismiss-registration-r02/SEVERANCE_DECISION_PROPOSAL.md)",
|
||||
"answer_verbatim": "この初期仕様で進める",
|
||||
"source": "Actual user send_user_message_question_reply in this Codex conversation; recorded time is not the original answer time.",
|
||||
"recorded_at_utc": "2026-10-23T18:07:36Z",
|
||||
"proposal": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p16-01-dismiss-registration-r02/SEVERANCE_DECISION_PROPOSAL.md",
|
||||
"sha256": "02039fa25c50b43732dfa34e17eb7ba72de8eafaba0e19a151f9d08960de3842"
|
||||
}
|
||||
},
|
||||
"root_boundary_not_human": {
|
||||
"id": "R9 DEFENSE_LOCKED",
|
||||
"record": "artifacts/original-completion-2026-10-02/p16-01-dismiss-registration-r03/ROOT_DECISION_R9_AND_COMMAND_BOUNDARY_R01.md",
|
||||
"sha256": "58918a1a652673d3e7fb589414a34faaa45bde3a80b841319ee4a69590b97e31"
|
||||
},
|
||||
"interpretation_boundary": [
|
||||
"Human-approved: the four items of the question and the linked proposal as presented.",
|
||||
"Implementer choices (not a new human approval): integer wage x3, the single transaction with the top-up, the STAFF_SEVERANCE category and its account, the empty-account addition for old saves, once-per-command recording (SPEC 3.6).",
|
||||
"Not approved: any combat or raid dismissal rule, acceptance waivers, P05-03 answers (pending)."
|
||||
],
|
||||
"limits": [
|
||||
"These answers supersede the stale heartbeat statements that weather and severance are awaiting user answers.",
|
||||
"P05-03 questions remain unanswered.",
|
||||
"No approval of additional combat dismissal rules, acceptance waivers, deletion of models/runtime/personal data, or exact future integration candidates is asserted.",
|
||||
"This record is a decision record, not evidence of implementation or acceptance. Original completion remains 24/88 and 4/30 packages."
|
||||
],
|
||||
"original_completed": 24
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
# P16-01-DISMISS 一般従業員の通常操作での解雇(退職金を含む) 仕様(登録版 r04。P16-01 の補助の作業)
|
||||
|
||||
- 状態: 登録の前の仕様(実 Opus の commander、2026-10-24)。§3.5 の退職金はユーザーの決定(A14、Root の記録 2026-10-23T18:07:36Z)で決まり、凍結できる。§6.3 R9 は Root が既存の範囲の権限で採った、この補助の作業の一時の境界(2026-10-23、`../p16-01-dismiss-registration-r03/ROOT_DECISION_R9_AND_COMMAND_BOUNDARY_R01.md`)で、人の承認ではない。Root が main に `docs/plans/original-completion-2026-10-02/execution/p16-01-dismiss/SPEC.md` として固定し、その commit(P07-01 の統合の後の main の上)を `base_commit` に通常の登録をする。製品は登録まで変えない。出典は同じフォルダの `PROVENANCE.json`。
|
||||
- 履歴: r01・r02(誤りを含む)と r03(退職金の決定の前の版)は書き換えずに残す。r03 からの違いは `CHANGES_FROM_R03.md`。
|
||||
- 原本への加算: 0。原単位 P16-01・P16-03、原子要件 `AR.staff.dismissal_severance` の package P05 の原 work・原 AC は、この作業では閉じない(§8)。
|
||||
|
||||
## 1. 原本の条件(原文。変えない)
|
||||
|
||||
- `docs/traceability/original/BASELINE.txt`:
|
||||
- 98 行 `* **一般従業員:** ギルド雇用。日給発生。ストレスが溜まりやすく解雇自由。`
|
||||
- 99 行 `* **奴隷:** 市場購入や借金客拘束で獲得。食費のみ。ストレス耐性が高く「人身売買」で売却可能。`
|
||||
- 113 行 `* **処分:** 解雇(退職金)、人身売買(ステータスにより価格変動。他の従業員の忠誠度低下リスクあり)。`
|
||||
- 116 行 `* **特性:** 代替不可能、雇用コスト・維持費無料、成長上限が高い、解雇・売却不可。固有スキルを所持。`(ユニークの節)
|
||||
- 原子要件: `AR.staff.general_dismissal`(P16)「一般従業員を通常操作で解雇できる。」、`AR.staff.dismissal_severance`(P05、D04 待ち)「解雇で退職金を支払う。」、`AR.unique.no_dismissal`(P17)「ユニーク人物を解雇できない。」
|
||||
- 原単位の文: P16-01 の AC「求人から配置・精算・退職まで通常操作でき、ユニーク禁止操作を維持する。」。P16-03 の work「勤務、休息、給与、気絶、救護、退職時参照除去を横断検証する。」
|
||||
- ユーザーの言葉(原文): 「まずゲームとしての機能実装完了を優先して」(2026-10-23、P05-02 の `PROVENANCE.json` と同じ出典)。「開発作業を進めて。またがわわは後で変えるので、、統合試験の動作速度のチューニングを、今やるよりも機能実装の完了を優先して」(2026-10-23、Root が共有した原文)。以下の要約は要約と記す。
|
||||
- 要件 ID: `P16-01-DISMISS`、`AR.staff.general_dismissal`、`AR.staff.dismissal_severance`、`AR.unique.no_dismissal`。
|
||||
|
||||
## 2. 解雇できる職員の区分(今の製品の区分)
|
||||
|
||||
- 一般従業員 = `StaffRuntime.EmploymentKind.FREE_HIRE` の職員。根拠: ギルドの候補の雇用(`hire_staff_candidate`)は、`scripts/data/mvp_content_catalog.gd` 329 行で `heroine_anri` を `UNIQUE`、それ以外の全ての候補を `FREE_HIRE` にする。`FREE_HIRE` は日給を持つ(`StaffRuntime.get_effective_daily_wage`、`UNIQUE` だけ 0)。原本 98 行の「ギルド雇用。日給発生。」に当たる。
|
||||
- 解雇の命令の対象にしない区分(この作業の外。他の処分の流れには触れない):
|
||||
- `UNIQUE`: 原本 116 行と `AR.unique.no_dismissal`。今の `StaffService.remove_staff` も拒む。
|
||||
- `ADULT_CONTRACT`: `scripts/staff/staff_runtime.gd` 31 行に定義があるが、今のゲームにこの区分を作る経路は無く、原本 98 行の一般従業員と同じとは確かめられない。命令は拒む(理由 `NOT_GENERAL_EMPLOYEE`)。これは対象の区分の範囲で、新しいゲームの規則ではない。
|
||||
- 奴隷(原本 99 行)と人身売買(113 行): 今のゲームに区分も流れも無く、この作業で作らない。
|
||||
|
||||
## 3. 退職金(原本の要件と A14 の決定)
|
||||
|
||||
- 3.1 原本 113 行「解雇(退職金)」と `AR.staff.dismissal_severance` により、通常の解雇は退職金の支払と切り離せない。
|
||||
- 3.2 原本に額・式・払う元は無い(D04)。A09 の元の質問の対象(維持費・仕入れ・人件費・返済・利息・税)に退職金は入っていない。
|
||||
- 3.3 退職金 0 を前提にしない(A14 で額が決まった)。
|
||||
- 3.4 既存の規則(変えない): 解雇の前に貯まった未払いの給与は、退職金とは別に `_departed_payroll` に保持し、次の 00:00 の締めで払う(A09 の順)。
|
||||
- 3.5 **A14(ユーザーの決定。回答の時刻は記録に無く、Root の記録は 2026-10-23T18:07:36Z)**: 問い(原文は `PROVENANCE.json`)の4項目に、ユーザーが「この初期仕様で進める」と答えた。
|
||||
- 退職金は、その職員の日給の3日分。
|
||||
- 表金から払い、不足分を裏金で補う。合計でも足りなければ解雇しない。
|
||||
- 未払い給与は退職金と分けて保持し、次の午前0時に精算する。
|
||||
- 退職金は帳簿で区別し、日次決算では運営費に計上する。
|
||||
- 3.6 実装の詳細(問いが示した案 `../p16-01-dismiss-registration-r02/SEVERANCE_DECISION_PROPOSAL.md` §3、SHA-256 `02039fa2…3842` のとおり。案の詳細は実装担当の選択で、人の新しい承認として扱わない):
|
||||
- 額 = `StaffRuntime.get_effective_daily_wage` × 3(整数)。日給 0 の職員は 0。
|
||||
- 支払は解雇の命令の時に行う。表金で足りない分を裏金から表金へ補填し(`BACK_FUND_TOP_UP`、P05-02 と同じ)、補填と退職金を1つの取引で記帳する。
|
||||
- 表金と裏金の合計が足りなければ R8 で拒み、§5.c の比べ方で状態を変えない(滞納にしない)。
|
||||
- 帳簿の新しい区分 `STAFF_SEVERANCE`: 表金から、給与(`WAGE`)の照合と混ざらない退職金の費用の口座へ記帳する。日次決算の照合では `cost_of_goods`(画面「運営費(仕入れ・契約金等)」。雇用の契約金 `STAFF_SIGNING_COST` と同じ)に入れる。
|
||||
- 旧セーブに退職金の費用の口座が無ければ、空の口座として足す(残高・既存の口座・取引の履歴は変えない。P05-02 の A12 の口座の追加と同じ形)。
|
||||
- 命令ごとに1回だけ記帳し、再送と保存再開で二度払わない。
|
||||
|
||||
## 4. 挙動
|
||||
|
||||
- 4.1 命令: `SimulationRuntime` に通常の command `dismiss_staff`(payload `staff_id`)を足す。判定の順: (a) 運営の終了なら受付の前に拒む(今の `submit_command` の規則)。(b) 同じ idempotency key なら受付で拒む(今の `CommandQueue.enqueue` の規則)。(c) executor で、職員が存在するか、区分が `FREE_HIRE` か、§6 の拒否の行に当たらないか、§3.5 の退職金を払えるかを、状態を変える前に全て確かめる。(d) 全て通れば、退職金の支払(§3.5)、`StaffService.remove_staff(staff_id, &"DISMISSED", true, 分)`、§6.2 の後始末を1つの操作として行い、途中で失敗すれば前の状態に戻す。
|
||||
- 4.2 成功の結果: `staff_id`、払った退職金(§3.5)、保持した未払いの給与、消した勤務の ID、開き直した作業の ID。`STAFF_REMOVED`(reason `DISMISSED`)の出来事。
|
||||
- 4.3 通常の画面: 職員の管理の画面(`StaffManagementPanel`)で、選んだ職員が §6 の許可の行に当たる時だけ「解雇」の操作を出し、通常の入力(Main の画面の操作)で 4.1 の命令を送る。拒否の行に当たる時は操作を出さないか無効にし、理由(`UNIQUE` は今の「ユニーク:雇用・維持費無料 / 解雇・売却不可」、それ以外は §6 の理由)を出す。退職金の額(§3.5)と、作業を持つ職員なら開き直る作業を、押す前に出す。結果(成功、拒否の理由、払った退職金、保持した未払い)を出し、名簿と選択を更新する。UI は今の部品のままで、完成の見た目は求めない。
|
||||
|
||||
## 5. 拒否の時に変わってよいもの(原子性の比べ方)
|
||||
|
||||
- 5.a 画面で操作を出さない・無効の時、画面の中の判定で送らない時: 命令を送らないので、Runtime の snapshot(`create_snapshot`)の全体が bytes で同じ。
|
||||
- 5.b 運営の終了の後(4.1 a)、同じ key の再送(4.1 b): 受付の前・受付で止まり command の ID を使わないので、snapshot の全体が bytes で同じ。同じ key の再送の結果は `DUPLICATE_IDEMPOTENCY_KEY` で、details に最初の結果がつく。
|
||||
- 5.c executor の拒否(4.1 c): 命令は受け付けて処理されるので、次の4つの区画だけが、決まった形で変わってよい。`systems.runtime.next_command_id`(ちょうど 1 増える)、`systems.runtime.command_queue`(その command の処理済みの失敗の結果・受付の記録、key を付けた時はその key と command の ID の対応だけが増える)、`systems.runtime.id_generator`(出来事の番号が 1 進む)、`systems.runtime.domain_events`(その command の ID の `COMMAND_FAILED` の出来事がちょうど1つ増え、他の出来事は変わらない)。他の全ての区画(`staff`・`economy`・`facilities`・`path`・`elevators`・`guests`・`operations`・`defense`・`events` など)は bytes で同じ。
|
||||
- 5.d 試験は、5.c の4つの区画から上の決まった増分だけを取り除いた写しの全体を bytes で比べ、増分そのもの(command の ID、失敗の結果の理由、出来事の種類)も確かめる。局所の確認で他の区画が変わると分かったら、それは直す不具合で、比べる範囲を後から広げない。
|
||||
|
||||
## 6. 許可と拒否の表(実際の削除と所有の契約から)
|
||||
|
||||
- 6.0 使う契約: 解雇は既存の削除の契約 `StaffService.remove_staff`(main `48d3b00` でも 888〜935 行。`staff_service.gd` は P05-02 で変わっていない)に従う。この契約は、`UNIQUE`(897 行)、外部が持つ作業(`is_externally_driven_task`、899 行)、作業の予約を解けない時(905 行)だけを拒み、取り消せる今の作業は予約を解いて開き直す(902〜909 行)。配置の変更の規則 `_staff_assignment_block_reason`(Runtime 6964〜6973 行)は配置の変更のための規則なので、解雇には使わない。作業の有無や状態だけで一律に解雇を止めない。
|
||||
- 6.1 許可(`FREE_HIRE`、§3.5 の支払ができる時。どの行も §6.2 の後始末を確かめる):
|
||||
- A1 非番(作業なし)。
|
||||
- A2 通勤中(昇降機を待つ・乗っている時を含む)。`_on_staff_removed` が昇降機の予約を取り消し、経路の actor を消す。
|
||||
- A3 勤務中で手が空いている(施設に配置)。
|
||||
- A4 施設でサービスの途中(`ServiceInstance.staff_ids` に入っている)。施設の人員の投影から外れ、人数が `staff_min` を欠けると、今の施設の規則でサービスが職員を待ち(`STAFF_REPLACEMENT_GRACE_MINUTES` 5 分)、代わりが来なければ失敗になる(`scripts/facility/facility_service.gd` 21 行と 875〜891 行)。
|
||||
- A5 取り消せる作業を持つ(清掃など。予約つきを含む)。`remove_staff` が予約を解き、作業を開き直す(進み具合は作業の方針のまま)。清掃の派遣は生きた職員と作業から毎回導く判定(`scripts/environment/facility_cleaning_bridge.gd` 12〜15 行)で、保存された参照は残らない。
|
||||
- A6 救護の運搬者(`rescue_pickup`・`rescue_transport`。外部が持つ作業ではない)。作業は開き直り、次の時刻の救護の確認(`_advance_staff_rescue`、Runtime 13475〜13494 行)が、運搬者の居ない運搬を `RESCUE_INTERRUPTED` で取り消し(13492 行。運搬者が居ないので 13490 行の「乗っている間は保つ」は当たらず、患者の昇降機の予約も取り消す、13493 行)、倒れた職員の救護を今の規則でやり直す。次の時刻の確認だけに頼らず、命令の境目でも整合を保つ(§6.2.1)。
|
||||
- A7 倒れている(INCAPACITATED。救護の対象)。`remove_staff` は状態を見ない。次の時刻の救護の確認が、患者の居ない救護の作業を取り消し、運搬者の昇降機の予約も取り消す。運搬の完了は患者が居なければ何もしない(Runtime 12460〜12467 行)。命令の境目の整合は §6.2.1。救護の確認は、運搬者が昇降機に乗っている時だけ着くまで保つ(13490〜13491 行)。
|
||||
- A8 出来事の拘束中(`event_service` の `_actor_locks`)。出来事の側の既存の契約 `_handle_missing_actors`(`scripts/events/event_service.gd` 1056 行〜。`missing_actor_policy` の続ける・役で置き換える・失敗の分岐が 1069〜1098 行)が、次の評価で欠けた職員を扱い、拘束を外す。欠けの判定は `_missing_actor_ids`(1226 行)と `_actor_is_available`(1234〜1235 行)。命令の境目の整合は §6.2.1。
|
||||
- A9 同じ職員を対象にした別の命令が待ち行列にある時。その命令は解雇の後に `NOT_FOUND` などで拒まれ、何も変えない。
|
||||
- A10 未払いの給与がある時(§3.4 で保持)。
|
||||
- 6.2 後始末(許可の全ての行。決めた範囲のファイルの中で行う): 勤務・予定・配置(`remove_staff`)、経路の actor と昇降機の予約(`_on_staff_removed`)、施設の人員の投影(解雇の命令の中で `_sync_facility_staffing`(Runtime 9565 行)をすぐ行い、保存の時の施設の人員の照合(1241〜1255 行)と食い違わない)、画面の選択(`StaffManagementPanel`)、`audit_orphan_tasks` で孤立 0、`STAFF_REMOVED`(reason `DISMISSED`)が1つ。関係の系(P18)はユニークだけを参照し(Runtime 7051〜7067 行)、一般従業員の参照は無い。局所の確認で他の参照が残ると分かったら、それを §6.2 の後始末として決めた範囲の中で直し、拒否に変えない。
|
||||
- 6.2.1 命令の境目の整合(A6・A7・A8。Root 2026-10-23、`ROOT_DECISION_R9_AND_COMMAND_BOUNDARY_R01.md`): 次の時刻の後始末(`_advance_staff_rescue`、出来事の評価)だけに頼らない。解雇の命令の中で(同じ時刻、次の進行の前に)、欠けた職員を患者か運搬者に持つ救護の作業を今の規則(`RESCUE_INTERRUPTED` の取り消しと昇降機の予約の取り消し)で片づけ、出来事は公開の `event_service.audit_actor_availability`(`scripts/events/event_service.gd` 556〜571 行。Runtime の進行も 8909 行で使う)で欠けた職員を今の `missing_actor_policy` で扱う。命令の直後に保存し、同じ process と別の process で読み込んでも、欠けた職員への参照(救護の作業の患者と運搬者、昇降機の予約、出来事の actor と拘束)が残らず、保存の照合が通る。運搬者と患者が一緒に昇降機に乗っている時は、着くまで保つ今の規則(13490〜13491 行)と命令の直後の保存の照合の両方が成り立つ形を、決めた範囲(Runtime と `StaffService`)の中で実装で決めて記録する。退却や戦闘のような新しい規則は作らず、許可の行を全面の拒否に置き換えない。
|
||||
- 6.3 拒否(業務の状態を変えない。5.b か 5.c の比べ方):
|
||||
- 既存の規則による拒否:
|
||||
- R1 `UNIQUE`(アンリなど)→ 今の `remove_staff` の拒否の文(`Unique Staff cannot be dismissed, sold or permanently removed.`)。5.c。
|
||||
- R2 外部が持つ作業(仕入れの物流の受け取り・配達・返送。`supply_logistics_bridge.owns_task`)→ 今の `remove_staff` の拒否の文(`Finish the employee external operation before removal.`)。取り消すと物流の要求と荷物が孤立するので、外部の操作の終わりを待つ。5.c。
|
||||
- R3 作業の予約を解けない → 今の `remove_staff` の拒否の文(`Staff removal could not release Task Reservations.`)。5.c。
|
||||
- R4 存在しない職員 → `NOT_FOUND`。5.c。
|
||||
- R5 運営の終了の後 → 今の `submit_command` の拒否(reason `BANKRUPT`)。5.b。
|
||||
- R6 同じ key の再送 → `DUPLICATE_IDEMPOTENCY_KEY`。5.b。
|
||||
- 対象の区分の範囲による拒否: R7 `ADULT_CONTRACT` → `NOT_GENERAL_EMPLOYEE`(§2)。5.c。
|
||||
- §3.5 の決定による拒否: R8 表金と裏金の合計が退職金に足りない時 → `INSUFFICIENT_FUNDS`(A14「合計でも足りなければ解雇しない」)。5.c。
|
||||
- **Root が採った一時の境界(既存の解雇の契約に無い。根拠つき)**: R9 進行中(終わっていない)の襲撃の防衛者(`_defense_lock_owner(staff_id, &"DEFENDER")` が空でない)→ `DEFENSE_LOCKED`。5.c。
|
||||
- 根拠: 終わっていない襲撃の防衛側の職員について、Runtime の防衛の照合は、職員が存在し、状態が `UNAVAILABLE` で、作業を持たないことを求める(`simulation_runtime.gd` 10919〜10924 行。準備中は体力・疲労の一致も 10925〜10929 行。終わった襲撃は履歴なので照合しない、10915〜10920 行)。襲撃の結果の当てはめも `staff_service.has_staff` を求める(10194〜10195 行)。そのため、進行中の襲撃の防衛者を消すと、保存の照合と襲撃の結果の当てはめが失敗する。
|
||||
- 後始末で避けられない理由: 襲撃の名簿から防衛者を外すには `scripts/defense/**`(決めた範囲の外)を変える必要があり、防衛者が途中で抜けた時の戦闘の扱い(退却、戦力の減り方)は原本にも決定にも無い。
|
||||
- Root が 2026-10-23 に、既存の範囲の権限で、この補助の作業の一時の境界として採った(`ROOT_DECISION_R9_AND_COMMAND_BOUNDARY_R01.md`)。人への追加の問いは要らない。進行中の襲撃の防衛者の解雇は原本の要件として未達のまま開示し(§8)、退却や戦闘の規則を作らず、自由な解雇の完了として扱わない。
|
||||
|
||||
## 7. 受入条件(acceptance_baseline の本文と同じ)
|
||||
|
||||
- P16-01-DISMISS-F1 通常の命令: §6.1 の許可の行の `FREE_HIRE` の職員を §4.1 の command で解雇でき、名簿から消え、`STAFF_REMOVED`(reason `DISMISSED`)が1つ記録される。
|
||||
- P16-01-DISMISS-F2 区分の拒否: `UNIQUE` と `ADULT_CONTRACT` は命令でも画面でも解雇できず、§5 の比べ方で業務の状態が変わらない。
|
||||
- P16-01-DISMISS-F3 退職金: §3.5 で決めた額と払う元で、解雇ごとに1回だけ払われ、日次決算と帳簿の照合が一致する。§3.5 で決めた支払ができない時は解雇されず、§5.c の比べ方で業務の状態が変わらない。
|
||||
- P16-01-DISMISS-F4 未払いの給与: 解雇の前の未払いが退職金とは別に保持され、次の 00:00 の締めで1回だけ払われ(A09 の補填を含む)、解雇の後に日給が生じない。
|
||||
- P16-01-DISMISS-F5 再送: 同じ key の再送が `DUPLICATE_IDEMPOTENCY_KEY` で、details に最初の結果がつき、§5.b の比べ方で snapshot の全体が同じ。退職金を二度払わない。
|
||||
- P16-01-DISMISS-F6 拒否の境界: §6.3 の拒否の全ての行(R1〜R9)で拒み、§5 の該当の比べ方で業務の状態が変わらない。
|
||||
- P16-01-DISMISS-F7 許可の境界と後始末: §6.1 の許可の全ての行(A1〜A10)で解雇でき、§6.2 の後始末が全て成り立つ。開き直した作業、サービスの待ちと失敗、救護のやり直し、出来事の欠けた職員の扱いは、それぞれ既存の規則どおりになる。A6・A7・A8 は §6.2.1 のとおり、命令の直後に保存して同じ process と別の process で読み込んでも参照が残らず、保存の照合が通る。
|
||||
- P16-01-DISMISS-F8 保存と再開: 解雇の前後(命令の直後、次の進行の前を含む)・締めの前後の保存を同じ process と別の process で読み、同じ状態で、退職金と未払いの給与の支払が二重にならない。
|
||||
- P16-01-DISMISS-F9 通常の画面: ゲームの本物の Runtime と Main の職員の管理の画面で、通常の入力で許可の行の一般従業員を解雇でき(退職金の額と開き直る作業を押す前に見られる)、拒否の行では操作が出ないか無効で理由が出る。fixture の直接の呼び出し(`staff_service.remove_staff`)だけで通常の入口の完了を主張しない。
|
||||
- P16-01-DISMISS-F10 回帰: 関連の回帰(狭い roster と再開の2段)と最終の S の全 roster が PASS。既存の試験の期待値の変更は `EXPECTATION_CHANGES.json` に変更前後と根拠を一覧にする。
|
||||
|
||||
## 8. 原単位との差(開示)
|
||||
|
||||
- P16-01 の残り: 求人募集の通常の操作(`AR.staff.recruitment`)、奴隷市場・破産客からの獲得(`AR.staff.market_acquisition`・`debt_acquisition`、D04)、雇用の区分ごとの費用の整理。P16-03 の残り: 勤務・休息・気絶・救護の横断(D04)、解雇の経路の外の退職時の参照の除去。人身売買(`AR.staff.sale_*`、D04)。どれも未達のまま。`AR.staff.dismissal_severance` は §3.5 の決定とこの作業の受入で接続するが、package P05 の原単位の完了には数えない。§6.3 R9 により、進行中の襲撃の防衛者の解雇は原本の要件として未達のまま開示する(自由な解雇の完了ではない)。
|
||||
|
||||
## 9. 検証
|
||||
|
||||
- validation `p16-dismiss`(`tools/run_p16_dismiss_tests.py --output {build_dir}/p16-dismiss-evidence`。P05-02 の `tools/run_p05_funds_tests.py` と同じ guarded の段の手順を、この作業の中で作る)。
|
||||
- 狭い roster: 新しい suite `tests/staff/test_general_dismissal.gd`(F1〜F9。§6 の全ての行。退職金の額・補填・不足の拒否・決算の区分を含む)、既存の `tests/ui/test_staff_management.gd`・`tests/ui/test_selection_departure.gd`・`tests/s3/test_runtime_staff_assignment.gd`・`tests/s3/test_staff_service.gd`・`tests/ui/test_anri_recruitment.gd`・`tests/ui/test_approved_free_unique_arrears_ui.gd`・`tests/s6/test_runtime_save_lifecycle.gd`・`tests/s6/test_runtime_snapshot_serialization.gd`・`tests/mvp/test_runtime_operations_integration.gd`。再開の2段 `tests/staff/test_general_dismissal_restart_1_write.gd`(解雇の後・締めの前に保存。唯一の生成ファイルは handoff)と `..._2_read.gd`(別の process で読み、締めまで進めて1回だけの支払を確かめる)。実 12 段。
|
||||
- その後: 正式の validation → 最終の S の全 roster を1回 → fresh な独立レビュー(この補助の作業の範囲。原単位の whole-unit の受入ではない)。速度の調整の作業と、微小な変更ごとの全回帰はしない。
|
||||
|
||||
## 10. 開示
|
||||
|
||||
- UI は今の部品のまま(完成の見た目ではない)。Windows 実機は後。解雇の日の日給は今の給与の契約(締めの時に雇用中の職員に日給)のまま新しい規則を作らない。解雇した候補の再雇用は今の `hire_staff_candidate` の規則(同じ候補を二度雇えない)を変えない。解雇した職員が歩いて出る描写は作らない(非描写の状態として命令の時点で取り除く)。
|
||||
|
||||
- 行番号は main `48d3b00` のもの(P05-02 で `simulation_runtime.gd` の行が動いたので r03 から直した)。登録の時の main で読み直す。
|
||||
@@ -0,0 +1,177 @@
|
||||
{
|
||||
"by": "Claude Opus 5.5 Desktop (local_a7f85421-3eed-4482-8030-c0dca669573a), the commander that wrote SPEC.md r01; before registration; read only",
|
||||
"unit": "P24-02-EVLOG",
|
||||
"auxiliary_of": "P24-02",
|
||||
"original_credit": 0,
|
||||
"original_completed": 24,
|
||||
"original_total": 88,
|
||||
"packages_completed": 4,
|
||||
"packages_total": 30,
|
||||
"spec": {
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p24-02-evlog/SPEC.md",
|
||||
"sha256": "a08bf2455cbfe01433655544edc578de7eb67bc17eaa313d3dac891b6b5ef206",
|
||||
"bytes": 10654
|
||||
},
|
||||
"main_read": "50207e9e6e41e361dab5397ecdff8417b98b5dfc",
|
||||
"human_decision_required": false,
|
||||
"human_decision_note": "No new game rule, value or human decision. The two Japanese titles are a presentation choice of the implementer (to be recorded at implementation); the weather words reuse the existing HUD words. Not a new human approval.",
|
||||
"original_text": {
|
||||
"path": "docs/traceability/original/BASELINE.txt",
|
||||
"sha256": "75e4bdbdeb379c9269553f0c776bdb9cc96e01f2d6aa5ca650d132c63a8aeb1a",
|
||||
"lines": {
|
||||
"223": "## 11.4 建設・通知UX",
|
||||
"224": " * **建設:** グリッドスナップ、接続ライン警告表示。",
|
||||
"225": " * **通知:** ユニット頭上のフキダシ(Zzz、怒り、ハート)、イベントログ。",
|
||||
"226": " * **カットイン:** 「NTR侵食開始」などの重要警告、決闘発生、防衛戦開始演出。"
|
||||
},
|
||||
"cited": {
|
||||
"225": " * **通知:** ユニット頭上のフキダシ(Zzz、怒り、ハート)、イベントログ。"
|
||||
},
|
||||
"requirement_group": "B-11.4"
|
||||
},
|
||||
"original_package": {
|
||||
"id": "P24",
|
||||
"title": "インスペクター・通知・操作完成",
|
||||
"requirement_ids": [
|
||||
"B-11.1",
|
||||
"B-11.2",
|
||||
"B-11.3",
|
||||
"B-11.4",
|
||||
"B-13.4"
|
||||
],
|
||||
"work": "シフトのドラッグ配置、欲求/思考ログ、感情/盾、料金内訳、稼働阻害理由、接続線、フキダシ、カットインを接続。クリックによる代替操作も用意。",
|
||||
"acceptance": "無効ドロップで状態を壊さず理由を表示。保存後も対象選択を回復。ログの説明と実際の選択が一致。モーダル/停止/再開の入力を検証。",
|
||||
"depends_on": [
|
||||
"P09",
|
||||
"P15",
|
||||
"P16",
|
||||
"P18",
|
||||
"P23",
|
||||
"P19"
|
||||
],
|
||||
"decisions": [
|
||||
"D09"
|
||||
],
|
||||
"state": "in_progress",
|
||||
"cross_cutting_acceptance": [
|
||||
"原本条件とUI操作が一致",
|
||||
"Runtimeの実利用・会計・行動へ接続",
|
||||
"途中保存・再起動で再現",
|
||||
"異常/境界/中断を検証",
|
||||
"関連回帰PASS",
|
||||
"要件別ログとソースハッシュを保存",
|
||||
"Windows部分実機確認。不可なら未検証を残す"
|
||||
],
|
||||
"cross_cutting_acceptance_scope": "Completion requirements only, not achieved results. Apply each to relevant slices; non-applicable views require a recorded reason.",
|
||||
"package_dependencies_scope": "integration prerequisites; a vertical slice may start once its own required contracts are accepted"
|
||||
},
|
||||
"original_unit": {
|
||||
"id": "P24-02",
|
||||
"title": "操作と通知",
|
||||
"work": "配置、無効ドロップ、モーダル、停止、再開、フキダシ、ログ、カットインを整える。",
|
||||
"acceptance": "無効操作を説明し、クリック代替と日本語入力・表示倍率を確認する。",
|
||||
"state": "in_progress",
|
||||
"remaining_gap_event_log": "event_log: Current native event-log suite passed. Real production sequence, Japanese amounts/time/reasons and pause/load nonduplication remain; legacy evidence stays original-version unless applicability proven."
|
||||
},
|
||||
"known_residual_closed_by_this_unit": {
|
||||
"from": "P07-01 residuals_outside_unit_acceptance",
|
||||
"text": "Event log shows the daily WEATHER_FORECAST_DRAWN as 未識別の記録 (scripts/ui/event_log_dialog.gd outside the WX allowed_paths); proposed as a separate unit"
|
||||
},
|
||||
"cross_cutting_application": {
|
||||
"原本条件とUI操作が一致": "F2-F4 and F7: the event log rows of the existing published facts read in Japanese, opened and selected by viewport input in the real Main",
|
||||
"Runtimeの実利用・会計・行動へ接続": "F2-F4: rows of the real 06:00 weather draw, real warp travel (with the mana fare) and real elevator trips; presentation only, the Runtime is not changed (F5)",
|
||||
"途中保存・再起動で再現": "F6: same-process and separate-process loads give the same rows once",
|
||||
"異常/境界/中断を検証": "F1 unknown-kind fallback kept, F4 unresolvable-target fallback kept, F5 no state change",
|
||||
"関連回帰PASS": "F8: narrow roster, restart pair and one final S full roster",
|
||||
"要件別ログとソースハッシュを保存": "registered validation p24-evlog with source-hashed suites and markers (SPEC section 9)",
|
||||
"Windows部分実機確認。不可なら未検証を残す": "not performed in this unit; the same Windows device, IME and OS scaling stay unverified and are disclosed (SPEC section 10)"
|
||||
},
|
||||
"U1": "Disclosure of what stays unmet: SPEC sections 8 and 10 (the rest of P24-02, the P24 package, Windows/IME/OS scaling)",
|
||||
"U2": "Evidence applicability: all measurements on the submitted S; local diagnostics and earlier evidence are not formal evidence",
|
||||
"accepted_upstream": [
|
||||
{
|
||||
"what": "domain event stream and the event log dialog",
|
||||
"paths": [
|
||||
"scripts/core/domain_event_stream.gd",
|
||||
"scripts/ui/event_log_dialog.gd"
|
||||
],
|
||||
"existing_suite": "tests/ui/test_event_log_ui.gd (in the full roster)"
|
||||
},
|
||||
{
|
||||
"what": "natural weather and the 06:00 forecast draw (WEATHER_FORECAST_DRAWN)",
|
||||
"runner": {
|
||||
"project": "the-tree-p07-01-wx",
|
||||
"task": [
|
||||
"P07-01-WX",
|
||||
"completed",
|
||||
"db87270972c9b243e3a1fccdf2c1fa96975dffc1",
|
||||
"9b5b09b35e888e5ac1a49174b8a7f73e51c502d2",
|
||||
2
|
||||
]
|
||||
},
|
||||
"ledger": "P07-01-WX auxiliary formally completed, Q 9b5b09b"
|
||||
},
|
||||
{
|
||||
"what": "warp travel and fare (WARP_TRAVELED) and elevator trips",
|
||||
"ledger": {
|
||||
"P14_package_state": "completed",
|
||||
"units": {
|
||||
"P14-01": "completed",
|
||||
"P14-02": "completed",
|
||||
"P14-03": "completed"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"upstream_not_needed": "The P24 package dependencies (P09, P15, P16, P18, P23, P19) are for the inspector, thought logs, bubbles and cut-ins; this unit only presents facts the product already publishes, so it registers only the range whose upstream is accepted (package_dependencies_scope).",
|
||||
"held_not_in_scope": [
|
||||
"The whole original P24-02 and the P24 package (placement and invalid drop, modal and pause/resume, bubbles, cut-ins, click alternatives, Japanese input and scale, the failure reasons in the event log)",
|
||||
"Important-warning cut-ins of P18/P19/P21 wait for their natural occurrence",
|
||||
"IME, OS scaling and the same Windows device conditions stay unmet",
|
||||
{
|
||||
"earlier_runner_project_not_resumed_or_merged": "the-tree-p24-02-operations",
|
||||
"task": [
|
||||
"P24-02",
|
||||
"submitted",
|
||||
"fe44f550750acbf5c510346e7cacbea2ccbdd184",
|
||||
null,
|
||||
6
|
||||
]
|
||||
},
|
||||
"P28 speed and appearance work and the P16-01-DISMISS low 5 reinforcement are not in this unit"
|
||||
],
|
||||
"relation_to_P16_01_RECRUIT": "Recruitment waits for the human answer and is unregistered and unimplemented. One implementer, no concurrent implementation. The allowed paths can overlap in the future (scenes/main/main.gd, tests/**, suite-policy, marker contract), so whichever is registered later takes the latest main as its base.",
|
||||
"source_pins_note": "SPEC.md says the read sources are in SOURCE_PINS.json in the same folder; that file is in the registration folder artifacts/original-completion-2026-10-02/p24-02-evlog-registration-r01/, and its content is copied here.",
|
||||
"source_pins": {
|
||||
"file_sha256": "6c85d349832402902aca0086a5b54941a15adcc26825ca1c56a3467fb2290522",
|
||||
"main": "50207e9e6e41e361dab5397ecdff8417b98b5dfc",
|
||||
"read_only": true,
|
||||
"files": {
|
||||
"docs/traceability/original/BASELINE.txt": "75e4bdbdeb379c9269553f0c776bdb9cc96e01f2d6aa5ca650d132c63a8aeb1a",
|
||||
"docs/plans/original-completion-2026-10-02/work_packages.json": "dfd1bb4adf29178379bdd189bcf5cea9c7906bbd1aa59dd5e4136da7398ab38f",
|
||||
"docs/plans/original-completion-2026-10-02/atomic_requirements.json": "44530bb929fcc8c68c06d0d94da66dd04b8db7680af731b5037cd44677502938",
|
||||
"scripts/ui/event_log_dialog.gd": "62000f559c49c3a58b244b991c10c1738add10702bfa66cb7cb40798cb559290",
|
||||
"scenes/main/main.gd": "0fded7badf0a125fb04c6b98488f8a590312765c199daf5920b84849aecda3ef",
|
||||
"autoload/simulation_runtime.gd": "f1135eeea519a3f1146605fc2b471e79658043d90da5d6760fdee6d095d6e391",
|
||||
"scripts/core/domain_event_stream.gd": "f9b57a4b4b53cf7d67132a159ea90378c633b4f19d8de28dbe7a33ebce8e3d79",
|
||||
"scripts/core/domain_event.gd": "cc9a903c366d9f889134561694b0c7fd52da260e97236a7b60830d7a68947bd4",
|
||||
"scripts/transport/warp_service.gd": "7602c6b8a9672a703bfa98a1424c2cf68cf50184f87f72349d564af6a5ca9d41",
|
||||
"scripts/transport/elevator_service.gd": "6f2b9cac49970b80fbfb0f71da38dee3431d78df3d361c9233e763e1bf6a8cd4",
|
||||
"scripts/environment/natural_weather_policy.gd": "7b5ff916554bc63497d55b2008d34a7a658db53b642fcd1645a4a9f353b7729e",
|
||||
"tests/ui/test_event_log_ui.gd": "9b1d7cb112790bb58969338ab1dc0bbe2a1c26b4307c4bfcd37491cf950c557b",
|
||||
"tests/transport/test_runtime_warp.gd": "cfbb69e63aca12d9d198393fd853c76be63df95e8d1e777e7a14194dbd181234",
|
||||
"tests/environment/test_p07_weather_runtime.gd": "e34b52ba3511f7391de1435c2f907997dd10235742ebc029831808af969c68e0",
|
||||
"tools/regression/suite-policy.json": "bc0539529f53fd6e5f9ad62ed83d7b75c537243a7d65781e37b9f2ac0b8125f0",
|
||||
"tools/run_p16_dismiss_tests.py": "39ef80e42feabc9841b64e7e35b0855626e7a7e8b868466d0a14e56ba8ccf2a5"
|
||||
},
|
||||
"blob_equals_worktree": true
|
||||
},
|
||||
"registration_material_sha256": {
|
||||
"acceptance_baseline.json": "6d02aea176e5c37c9abfec973dfc0043a954382091292df9c781860d9cd5ad38",
|
||||
"validations.json": "e7fc20221c7727888ba9b7fb1eb1df4f571107ad849c037f8db591c7eee14a22",
|
||||
"policy.json": "4f2536fa12ff29fc085d63645a4fe46e40bcce9b997fef708d1a688fe510d653",
|
||||
"task_contract.template.json": "9959af23862cb341fa28ed6721046fa9794bfb1bcae3a1e513bf92dc3b82bb24",
|
||||
"REGISTRATION_STEPS.md": "35d89c88b55819970841c745e059c57537edb9e7776eee7dbbbd8ebc42e37f08"
|
||||
},
|
||||
"not_done": "No product change, no registration, no commander RUN. Root does init/add/prepare and the delegation; the implementer records its RUN afterwards."
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
# P24-02-EVLOG 出来事の一覧で読めない記録をなくす 仕様(登録の前の版 r01。P24-02 の補助の作業)
|
||||
|
||||
- 状態: 登録の前の仕様(実 Opus の commander、2026-10-25)。人の新しい決定は要らない。Root が main に `docs/plans/original-completion-2026-10-02/execution/p24-02-evlog/SPEC.md` として固定し、その commit を `base_commit` に通常の登録をする。製品は登録まで変えない。読んだ source は同じフォルダの `SOURCE_PINS.json`(main `50207e9`)。
|
||||
- 原本への加算: 0。原単位 P24-02 の原 work・原 AC は、この作業では閉じない(§8)。
|
||||
- 表示だけの作業: Runtime の状態・記録の流れ・保存の形は変えない。変えるのは、出来事の一覧の画面の文の組み立てだけ。
|
||||
|
||||
## 1. 原本の条件(原文。変えない)
|
||||
|
||||
- `docs/traceability/original/BASELINE.txt` 223〜225 行(11.4 建設・通知UX): 225 行 `* **通知:** ユニット頭上のフキダシ(Zzz、怒り、ハート)、イベントログ。`
|
||||
- package P24「インスペクター・通知・操作完成」の acceptance の一部: 「ログの説明と実際の選択が一致。」
|
||||
- 原単位 P24-02「操作と通知」の work「配置、無効ドロップ、モーダル、停止、再開、フキダシ、ログ、カットインを整える。」と acceptance「無効操作を説明し、クリック代替と日本語入力・表示倍率を確認する。」の、「ログ」の部分。
|
||||
- 台帳の P24-02 の残件 `event_log`: 「実際の記録の流れ、日本語の金額・時刻・理由、停止と読み込みで重複しないこと」が残る。
|
||||
- 既知の残件: P07-01-WX の残件「出来事の一覧で毎日の `WEATHER_FORECAST_DRAWN` が『未識別の記録』と出る」(`work_packages.json` の P07-01 の `residuals_outside_unit_acceptance`)。
|
||||
|
||||
## 2. 今の製品の欠落(main `50207e9`、読むだけで確かめた)
|
||||
|
||||
- `scripts/ui/event_log_dialog.gd` の `EVENT_NAMES`(88 種類)に無い種類は、題が「未識別の記録」になる。製品の source が出す種類のうち、名前が無いのは次の2つ。
|
||||
- `WEATHER_FORECAST_DRAWN`(毎朝 06:00 の天候の抽選。`autoload/simulation_runtime.gd` の天候の抽選の箇所。対象の ID は無い)
|
||||
- `WARP_TRAVELED`(ワープの移動と料金。`_charge_warp_fare`。対象は connector の ID と乗った人の経路の actor の ID)
|
||||
- 一覧の文は、記録の中身(payload)を出さない。そのため、予報の行は「未識別の記録 — 対象の指定なし」とだけ出て、天候と時刻が分からない。
|
||||
- 対象の名前の解決(`_entity_name`)は、候補・施設・来客の ID だけを知る。次の ID の形は「名称を確認できない対象」になる。
|
||||
- 経路の actor の ID(`staff:<職員の ID>`・`guest:<来客の ID>`): ワープと昇降機の記録の対象
|
||||
- ワープの connector の ID(`warp.connector:<施設の ID>|<施設の ID>`)
|
||||
- 昇降機の shaft の ID(`transport.shaft.<層>.<x>.<y>.<幅>`)
|
||||
- 種類の全数の取り方(実装の時に試験で固める):
|
||||
- `SimulationDomainEvent.new(` の2つ目の引数の文字の種類
|
||||
- `_publish_actor_event(` の1つ目の引数の文字の種類
|
||||
- `_complete_save_operation(` の2つ目の引数の文字の種類
|
||||
- 変数で種類を渡す2か所(保存の終わり・検査の到着と遮断)の式の中の文字の種類
|
||||
- main `50207e9` では、これで出す種類の全てが `EVENT_NAMES` の 88 種類と上の2つに入る。
|
||||
|
||||
## 3. 挙動(この作業で作るもの)
|
||||
|
||||
1. 名前: `WEATHER_FORECAST_DRAWN` と `WARP_TRAVELED` に、日本語の題を足す(例「その日の天候が決まった」「ワープで移動」。文言は実装で決めて記録する)。未知の種類の「未識別の記録」の守りは残す。
|
||||
2. 予報の行: 天候の名前を、上部の表示と同じ語(晴れ・雨・嵐・マナ霧)で出す。雨などは始まりと終わりの時刻(`HH:MM〜HH:MM`)を、晴れは変化が無いことを出す。語は1か所にまとめ、上部の表示と一覧で同じものを使う。
|
||||
3. ワープの行: 出発と到着の施設の名前、乗った人の名前と人数、払ったマナ(payload の `mana`)を出す。
|
||||
4. 対象の名前: 経路の actor の ID は、職員の名前(候補の表示の名前)または「来客」に、ワープの connector は両端の施設の名前に、昇降機の shaft は、その位置の昇降機の施設の名前に解決する。名前を確認できない対象は、今の守り(「名称を確認できない対象(n件)」)のまま。
|
||||
5. 一覧を開く・読む・選ぶ・文を組み立てることは、Runtime の状態を変えない(今と同じ)。
|
||||
|
||||
## 4. 変えないもの
|
||||
|
||||
- Runtime の記録の流れ(`DomainEventStream`)、記録の種類・中身・順序、保存の形と照合。
|
||||
- 他の画面(上部の天候の表示の文は、語を共有するだけで変えない)。
|
||||
- 未知の種類と未知の対象の守り。
|
||||
|
||||
## 5. 比べ方
|
||||
|
||||
- 画面の操作と文の組み立ての前後で、Runtime の snapshot の全体が bytes で同じ(今の `tests/ui/test_event_log_ui.gd` と同じ比べ方)。
|
||||
|
||||
## 6. 受入条件
|
||||
|
||||
- P24-02-EVLOG-F1 名前の全数: §2 の取り方で集めた、製品の source が出す全ての記録の種類に、日本語の題がある。新しい種類が題なしで足されると、試験が止まる。未知の種類は「未識別の記録」のまま。
|
||||
- P24-02-EVLOG-F2 天候の予報: 本物の Runtime の毎朝 06:00 の抽選の行が、上部の表示と同じ語の天候の名前と、時刻(雨・嵐・マナ霧は始まり〜終わり、晴れは変化なし)を出す。4つの天候の全てで確かめる。
|
||||
- P24-02-EVLOG-F3 ワープ: 本物のワープの移動の行が、出発と到着の施設の名前、乗った人の名前と人数、払ったマナを出す。客の移動と、職員の移動(救護の2人を含む)で確かめる。
|
||||
- P24-02-EVLOG-F4 対象の名前: 経路の actor の ID(職員・来客)、ワープの connector、昇降機の shaft を、今の名前に解決する。昇降機の記録の行(依頼・乗車・到着・救護の依頼)でも確かめる。名前を確認できない対象は今の守りのまま。
|
||||
- P24-02-EVLOG-F5 状態を変えない: 一覧を開く・読む・選ぶ・文を組み立てる前後で、Runtime の snapshot の全体が bytes で同じ。
|
||||
- P24-02-EVLOG-F6 保存と読み込み: 予報とワープの行を含む保存を、同じ process と別の process で読むと、行は重複せず、同じ文で出る(読み込みの記録の1行が足されるだけ)。
|
||||
- P24-02-EVLOG-F7 通常の画面: ゲームの本物の Runtime と Main で、viewport の入力(`root.push_input`。キーとボタンの押しと離しを local の座標で送る)で出来事の一覧を開き、予報とワープの行を選ぶと、その文が一覧と詳細に出る。OS の入力の受入は主張しない。
|
||||
- P24-02-EVLOG-F8 回帰: 関連の回帰(狭い roster と再開の2段)と最終の S の全 roster が PASS。既存の試験の期待値の変更は `EXPECTATION_CHANGES.json` に変更前後と根拠を一覧にする。
|
||||
|
||||
## 7. 実装の範囲(allowed_paths)
|
||||
|
||||
- `scripts/ui/event_log_dialog.gd`(題・文・対象の名前の解決)
|
||||
- `scenes/main/main.gd`(上部の天候の語を共有する場合だけ。上部の表示の文は変えない)
|
||||
- `tests/**`、`tools/run_p24_evlog_tests.py`、`tools/regression/suite-policy.json`
|
||||
- `docs/plans/original-completion-2026-10-02/execution/p24-02-evlog/**`、`docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json`
|
||||
- `autoload/simulation_runtime.gd` と他の製品の file は変えない。新しい `.gd` の file を足すなら、import が作る `.uid` も同じ範囲(`tests/**`)の中に限る。製品の側に新しい file が要るなら、登録の前に Root と範囲を見直す。
|
||||
|
||||
## 8. 原単位との差(開示)
|
||||
|
||||
- P24-02 の残りは未達のまま: 配置・無効ドロップ・モーダル・停止と再開・フキダシ・カットイン・クリックの代替・日本語の入力と表示の倍率、`event_log` のうち「理由」(操作の失敗の理由を日本語で出す)と、長い通常の記録の流れ。
|
||||
- P24 の package の上流(P09・P15・P16・P18・P19・P23)は、インスペクター・思考の記録・フキダシ・カットインなどに要る。この作業は、今の製品が出す記録の表示だけを扱うので、その上流を要らない。P24 の package の完了には数えない。
|
||||
- P07-01-WX の残件「予報が未識別の記録と出る」は、この作業の受入で解消する。P07-01 の原単位は、この作業では変わらない。
|
||||
|
||||
## 9. 検証
|
||||
|
||||
- validation `p24-evlog`(`tools/run_p24_evlog_tests.py --output {build_dir}/p24-evlog-evidence`。`tools/run_p16_dismiss_tests.py` と同じ guarded の段の手順を、名前・名簿・再開の段・handoff・protocol 名の明示の置き換えで作る)。
|
||||
- 狭い roster: 新しい suite `tests/ui/test_event_log_readable.gd`(F1〜F7)と、既存の `tests/ui/test_event_log_ui.gd`・`tests/transport/test_runtime_warp.gd`・`tests/environment/test_p07_weather_runtime.gd`・`tests/staff/test_rescue_elevator.gd`・`tests/s6/test_runtime_snapshot_serialization.gd`・`tests/s6/test_runtime_save_lifecycle.gd`。再開の2段 `tests/ui/test_event_log_readable_restart_1_write.gd`(予報とワープの行を含む保存。唯一の生成ファイルは handoff)と `..._2_read.gd`(別の process で読み、行と文が同じで重複しないことを確かめる)。
|
||||
- その後: 正式の validation → 最終の S の全 roster を1回 → fresh な独立レビュー(この補助の作業の範囲。原単位の whole-unit の受入ではない)。速度の調整の作業と、微小な変更ごとの全回帰はしない。
|
||||
|
||||
## 10. 開示
|
||||
|
||||
- UI は今の部品のまま(完成の見た目ではない)。Windows 実機・IME・OS の倍率は後。
|
||||
- 題の文言(§3 の 1)は実装の選択で、人の新しい承認ではない。天候の語は上部の表示の既存の語を使う。
|
||||
- 試験の入力は viewport の入力で、OS の入力ではない。
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"recorded": 1792502634.787176,
|
||||
"method": "Limited read-only reconciliation of current ledger and six retained acceptance records; independent helper plus Root hash and count confirmation. No implementation, native tests or whole-atom re-audit.",
|
||||
"packages": 30,
|
||||
"units": 88,
|
||||
"unit_state_counts": {
|
||||
"completed": 22,
|
||||
"in_progress": 19,
|
||||
"planned": 47
|
||||
},
|
||||
"package_state_counts": {
|
||||
"completed": 4,
|
||||
"in_progress": 26
|
||||
},
|
||||
"partial_credit": false,
|
||||
"reconciled_units": [
|
||||
{
|
||||
"id": "P06-01",
|
||||
"title": "供給と蓄積の契約",
|
||||
"state": "completed",
|
||||
"evidence": "docs/plans/original-completion-2026-10-02/execution/p06-01-a06/ACCEPTANCE.json",
|
||||
"sha256": "6f471e3f32460758b8a617051a507f780d70b33f254a7910dfada87a8c5c3484",
|
||||
"helper_and_root_hash_match": true
|
||||
},
|
||||
{
|
||||
"id": "P06-02",
|
||||
"title": "全館停止の統合",
|
||||
"state": "completed",
|
||||
"evidence": "docs/plans/original-completion-2026-10-02/execution/p06-02-whole-stop/ACCEPTANCE.json",
|
||||
"sha256": "d5592fa8f6e3646f7d8a81952cf34f894ca4d1048ab0b2a10d87927cd176b9a2",
|
||||
"helper_and_root_hash_match": true
|
||||
},
|
||||
{
|
||||
"id": "P06-03",
|
||||
"title": "復帰と表示",
|
||||
"state": "completed",
|
||||
"evidence": "docs/plans/original-completion-2026-10-02/execution/p06-03-recovery/ACCEPTANCE.json",
|
||||
"sha256": "4e3da801cec700ba5b4c87e881de931bc4e2e971d4ab44d38af6e964baabc9a7",
|
||||
"helper_and_root_hash_match": true
|
||||
},
|
||||
{
|
||||
"id": "P14-01",
|
||||
"title": "交通の失敗時契約",
|
||||
"state": "completed",
|
||||
"evidence": "artifacts/original-completion-2026-10-02/p14-01-independent-performance-review-r01.json",
|
||||
"sha256": "190b6e27537c9f73a756232b9754efc08ec42da74c946a59dd9f768b51c3acd1",
|
||||
"helper_and_root_hash_match": true
|
||||
},
|
||||
{
|
||||
"id": "P14-02",
|
||||
"title": "ワープの通常接続",
|
||||
"state": "completed",
|
||||
"evidence": "docs/plans/original-completion-2026-10-02/execution/p14-02-warp/ACCEPTANCE.json",
|
||||
"sha256": "ac7bfd6fb88cb8ac8c195c8051ebe22ba95a6a6393342de089aae88fdcc73307",
|
||||
"helper_and_root_hash_match": true
|
||||
},
|
||||
{
|
||||
"id": "P14-03",
|
||||
"title": "混雑と経路選択",
|
||||
"state": "completed",
|
||||
"evidence": "docs/plans/original-completion-2026-10-02/execution/p14-03-route-wait/ACCEPTANCE.json",
|
||||
"sha256": "14967ffbfdbf9578fd1747024f7e4bdadca6754419d76a59024e1c4e5d973e75",
|
||||
"helper_and_root_hash_match": true
|
||||
}
|
||||
],
|
||||
"header_issue": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/progress_snapshot.json",
|
||||
"scope_stale_count": 16,
|
||||
"source_head_legacy": "b1810ff4b2809018fe7434be7bdcf6f6411c91f4",
|
||||
"current_unit_count": 22,
|
||||
"disposition": "Correct current header at next current ledger adoption; retain previous checkpoints/history, do not undo valid accepted units."
|
||||
},
|
||||
"current": "P28-01",
|
||||
"current_state": "in_progress",
|
||||
"local_collector_submitted": "3ac3243dfb691774bc2db25d8a6720bff47cf311",
|
||||
"local_review_result": "changes_requested; C1/C9 pending, C2-C8 satisfied, previous findings resolved",
|
||||
"local_helper_credit": 0,
|
||||
"original_P28_01_unmet": [
|
||||
"collector normal review/integration/final",
|
||||
"exact integrated-source windowed R/O measurement",
|
||||
"applicable save/separate-PID restart",
|
||||
"whole original work/acceptance/cross6 evidence review and final"
|
||||
],
|
||||
"next_original": "P28-02",
|
||||
"no_whole_game_or_Windows_acceptance_inferred": true
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
{
|
||||
"at": 1792510013.129046,
|
||||
"project": "the-tree-p28-01-perf-baseline",
|
||||
"task_id": "P28-01-COLLECTOR",
|
||||
"submitted": "5db97d78a15c2895ac5379a8bbba2fb95f0004dd",
|
||||
"candidate": "0f53a67210227207efa63045de88a9a7c6e45409",
|
||||
"main_base": "90e3e312fece101a932f0434de4e76afad31db63",
|
||||
"state": "completed",
|
||||
"helper_acceptance_complete": 9,
|
||||
"helper_acceptance_total": 9,
|
||||
"original_credit": 0,
|
||||
"original_completed": 22,
|
||||
"original_total": 88,
|
||||
"packages_completed": 4,
|
||||
"packages_total": 30,
|
||||
"evidence": [
|
||||
{
|
||||
"kind": "validation",
|
||||
"commit_sha": "5db97d78a15c2895ac5379a8bbba2fb95f0004dd",
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-01-perf-baseline/runs/494eafc467cf4a3e92ac92260da9d5c1-validation.json",
|
||||
"passed": 1,
|
||||
"sha256": "bef659a588349d594d9f6aaace38d2b2ea3a9f6db01561680e6ee9fe46f89922",
|
||||
"raw_checks": [
|
||||
{
|
||||
"id": "p28-01-collector-unit",
|
||||
"exit_code": 0,
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-01-perf-baseline/runs/9110f0b9ff114161946c4b6544782e7e",
|
||||
"passed": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "review",
|
||||
"commit_sha": "5db97d78a15c2895ac5379a8bbba2fb95f0004dd",
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-01-perf-baseline/runs/05499b4430e145c0b4b804095cf450d1/result.json",
|
||||
"passed": 1,
|
||||
"sha256": "23eed6741996a7b9327fcceda2e852104315954105c9c09f0dfcdf35c88c8d41",
|
||||
"raw_checks": null
|
||||
},
|
||||
{
|
||||
"kind": "integration",
|
||||
"commit_sha": "0f53a67210227207efa63045de88a9a7c6e45409",
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-01-perf-baseline/runs/f6dcbb4c9e6b4f88ade47c0f00f7a905-integration.json",
|
||||
"passed": 1,
|
||||
"sha256": "ec0658af52ed286e49829310511688e64b1ee0924dc63ccb09e29eba50df1071",
|
||||
"raw_checks": [
|
||||
{
|
||||
"id": "p28-01-collector-unit",
|
||||
"exit_code": 0,
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-01-perf-baseline/runs/75259f17bc2a4be1885334f90e5c9223",
|
||||
"passed": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "final",
|
||||
"commit_sha": "0f53a67210227207efa63045de88a9a7c6e45409",
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-01-perf-baseline/runs/6461542b644049289edb809ab1563ab2-final.json",
|
||||
"passed": 1,
|
||||
"sha256": "965610d04da6947172b055dbfe15183c30dcc46b77ce07c53564330dc33a20b7",
|
||||
"raw_checks": [
|
||||
{
|
||||
"id": "p28-01-collector-unit",
|
||||
"exit_code": 0,
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-01-perf-baseline/runs/ef0202500572402a8dfeaef70551fe04",
|
||||
"passed": true
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"approval": {
|
||||
"task_id": "P28-01-COLLECTOR",
|
||||
"candidate": "0f53a67210227207efa63045de88a9a7c6e45409",
|
||||
"main_base": "90e3e312fece101a932f0434de4e76afad31db63",
|
||||
"owner": "Root delegated decision under existing human main/push authorization and explicit instruction to decide approvals without repeated confirmation; exact Q0f53a672/M90e3e312 audited in ROOT_EXACT_CANDIDATE_0F53A67.json (de31405646459c062ba31d267b3588e7c12dc9f5ba09349ae3c16c1479ea826d); no new human SHA-specific utterance is claimed.",
|
||||
"created": 1792509753.810463
|
||||
},
|
||||
"traceability_path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-01-perf-baseline/traceability/P28-01-COLLECTOR.json",
|
||||
"known_low_findings": 3,
|
||||
"P28_original_unit_complete": false,
|
||||
"remaining": [
|
||||
"Integrated final-source full9 windowedR/O measurement",
|
||||
"SeparatePIDsave/restart",
|
||||
"OriginalP28-01 whole-unit evidence/review/final/ledger",
|
||||
"Windows/packagebudget/gamewhole remain unmet"
|
||||
],
|
||||
"diagnostic_only": {
|
||||
"x1_mean_us": 379427,
|
||||
"x1_limit_us": 4000,
|
||||
"stall_max_us": 466395,
|
||||
"stall_limit_us": 50000,
|
||||
"both_budget_passed": false,
|
||||
"final_measurement": false
|
||||
},
|
||||
"no_new_human_SHA_specific_utterance_claimed": true
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"task_id": "P28-01-COLLECTOR",
|
||||
"commit": "5db97d78a15c2895ac5379a8bbba2fb95f0004dd",
|
||||
"status": "pass",
|
||||
"summary": "対象は5db97d7の4ファイルで、すべてallowed_paths(collector/**)の中。製品・fixture・予算値の変更はなく、試験の削除や無効化もない。登録validation p28-01-collector-unitは「Ran 37 tests … OK」で、生ログで確認した。読込は tools/run_all_tests.py と tools/regression/owned_outer.py の2つだけ(load_toolのallowlistとAST試験で確認)。\n\n実機の証拠はROOT_NATIVE_DIAG_5DB97D7。import、parse-base、parse-wrapper、windowed R0の4つが、それぞれ別PIDで実行され、どれもexit0。主な結果:\n- R0は602→612分のちょうど10分で、stop_reason=complete\n- restore=true、登録数は200/50/150/20000\n- start_reportはINVALID_START_TIME、operations_enabled_after=false\n- seed 28012026とrng_streamsは別の欄\n- GPU/CPU描画時間は0のためnullで、理由つき\n- audio drainは実際に通った\n- cleanup_errors=[]\n\nexecution.jsonがpassed=falseなのは、診断で4phaseしか実行していないため、全9phaseの順序チェックが ValueError('phase order') を出したもの。これは全体runの成功ではない。この失敗は記録どおり残し、合格扱いにはしていない。\n\n条件ごとの判断:\n- C1: 速度は main._set_speed(main.gd:2727→GameClock.set_speed、paused=false)だけで与えている。checkerは、f64 deltaによるaccumulatorの再帰(simulation_kernel.gd:54-89と同じ式)、上限8、frame/orderの連続性を照合する。前回のmedium(歩調未検証)は解消し、実R0データでも再計算が一致した。\n- C2: 入力SHAと準備状態SHA、登録・実稼働・描画の3種を別の欄に記録している。描画数は製品の判定と同じ方法で数え、checkerは流用を拒否する。\n- C3: seedとRNG stateは別の欄で、nullには理由が必要。\n- C4: 0以下の生値が1つでもあると、その区間はnullと理由になり、checkerは0や偽の合格を拒否する。\n- C5: R0/O0の段階0、外挿、projected_over_limitならengineを起動しない、run_phaseの上限1800s、probe期限1740s、timeout時もrawを保持。どれも実装と試験がある。\n- C6: private user:// のGuard、owned PID、cleanup、通常playerの照合、stage全体のinventory、raw/owned filesのSHA結合。実runで確認した。\n- C7: source mapを開始前・phaseごと・終了後に照合し、git blobとも照合する。\n- C8: 全frameに機種・build・seed・速度・区間を要求する。平均・P95・最大は再計算して照合する。可読辞書の欠落キーと読取不能を区別するP1〜P3も確認した。\n- C9: 既定のautoloadの世界を使い、§4.3の順序どおりに起動し、mainの実際のOps結果を記録する。開始前の不足(input_shortfall)と計測中の自然な変化を区別し、再注入・状態固定・別Runtimeはない。\n\n以前の指摘も解消を確認した: 前回high(audio close)、K2、K3(最初の不完全frameをbaselineとして除外)、H/R/Jの各指摘。\n\n全9条件を満たすと判断する。ただし次の点は未確認のまま残る。\n- O系(O0/P3/P4)はまだ実機で実行していない\n- 全9phaseの本測定\n- 原本P28-01の完了。統合後にRootの実測が必要\n\n原本の加算は0。",
|
||||
"acceptance_ids": [
|
||||
"P28-01-C1",
|
||||
"P28-01-C2",
|
||||
"P28-01-C3",
|
||||
"P28-01-C4",
|
||||
"P28-01-C5",
|
||||
"P28-01-C6",
|
||||
"P28-01-C7",
|
||||
"P28-01-C8",
|
||||
"P28-01-C9"
|
||||
],
|
||||
"findings": [
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "[C8/§7 checkerの防御の厚み] check_budget_snapshotは、snapshot.values()を回すだけで、metricのキー集合を要求していない。\nそのため、stop_reason=completeのreportで budget_at_warmup_end.data={} にすると、budget_verdictがcounts_unavailable(null)になり、それでもcheckerを通る。予算未達を「取得不能」に偽装できることになる。\nさらに、len(samples_us)==min(lifetime_count,512) のような製品の不変条件も照合していない。\n実際の悪用可能性は低い。probeはSHAで固定され、budget_snapshotは固定の3metricを必ず出す。raw stdoutとreportの結合もある。\n修正案は次の2つ。\n・warmup_endedに達したreport(complete、または measurement.frames>0)では、両方のsnapshotで set(data)=={simulation_x1,simulation_x3,main_thread_stall} を要求する\n・可読エントリで len(samples)==min(count,512) を要求する\nあわせて、それぞれの拒否試験を追加する。"
|
||||
},
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "[計測の偏り・記録済み] 実R0では、observer_usが1フレームあたり約157 msあり、frame_wallの約16%を占める。最初のframeは5.5 sで、セル数の初回計算による。\nこのコストは次のframeのframe_wall_usと、stall_over_50000_us(frame基準)に含まれ、最大値と平均を押し上げる。observer_noteで開示されており、kernelの予算判定(_samples)には影響しない。\nP1〜P4では初回のコストはwarm-up内に収まる。ただし毎frameの負荷が残るため、毎frameのJSON行に入るmetadataとrng_streamsの重複を減らすなど、軽量化を検討するとよい。"
|
||||
},
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "[未実機の経路・情報] Operations ONの新規開始(O0/P3/P4)は、どの診断でもまだ実機で実行していない。\n静的には次の点を確認した: opening_minute==360、start_report.ok、enabled、および失敗時にinput_shortfallを記録すること。\n本測定では、この経路を初めて通すことになる。R0の実測では、x1でも1分あたりkernel約370 ms、frame約1 sで、予算を大幅に超えている(verdictはfalseで正しく記録)。\nRootの本測定でO系の開始とoperations_pauseの停止が実際にどう振る舞うかを確認し、その記録を判断担当に渡すこと。"
|
||||
}
|
||||
]
|
||||
}
|
||||
+6027
File diff suppressed because it is too large
Load Diff
+37
@@ -0,0 +1,37 @@
|
||||
# P28-01 正式受入記録
|
||||
|
||||
P28-01原work/acceptance/横断6を全9条件(原7枠+補助2)の独立fresh Desktop Opus5.5レビューPASS、高/中0・low4、通常validation/review/integration/final全PASSと実DB/traceability結合で正式完了。Sddde6f8→Qae9ae47。実測db9d613全9/r02各scope/S954528a別PID再開の版を区別し、製品1407等の実一致を確認。正式23/88・4/30、部分点なし。性能予算6件、200客OpsON、Windows/長期/全ゲーム受入は別未達を維持。
|
||||
|
||||
提出 S: `ddde6f8ab50db6baa03f25453a02814b78bbf365`
|
||||
統合・最終検証 Q: `ae9ae47646434dd57fc21f3e8566d0d35b242ab1`
|
||||
基準 M: `0aee05e1d987e126ee9c00536aed5d9c75115d00`
|
||||
|
||||
正式証拠: `P28_01_FORMAL_COMPLETION_AE9AE47.json` SHA256 `f778ac8df366110dd7c9ff0b6daa7bde824913e3d813ad090ebd8c20053915a5`
|
||||
|
||||
独立レビューは別fresh Claude Desktop Opus5.5。原work/acceptanceと横断6を7枠、補助U1/U2を2枠として判定した。原88成果の分母は増やさない。25合成試験は構造確認であり完了数の代用ではない。全条件の証拠結合、独立レビュー、同一候補の通常integration/finalと台帳を確認して原成果1件を加算した。
|
||||
|
||||
G3適用判断: Persisted Runtime state and paused reproduce; wall-clock backlog/sub-minute accumulator deliberately discarded by restore_clock, outside persisted-state contract. Actual writers nonzero accumulator/readers0 and all30 complete typed states/newPID match. Not inferred from non-occurrence.
|
||||
|
||||
保存再開は実際の別4PID・各30分の全typed状態と390分の全16イベントに基づく。過負荷停止→保存→復帰の通し試験は未実施で、非0backlog保存の証拠とはしない。
|
||||
|
||||
性能予算の失敗は次の6件を維持する。R0は10分参照、P1/P2は各本測定。
|
||||
|
||||
| Phase | Metric | Aggregation | Actual (us) | Limit (us) |
|
||||
|---|---|---|---:|---:|
|
||||
| R0 | main_thread_stall | maximum | 468148 | 50000 |
|
||||
| R0 | simulation_x1 | average | 383229 | 4000 |
|
||||
| P1 | main_thread_stall | maximum | 4809099 | 50000 |
|
||||
| P1 | simulation_x1 | average | 479210 | 4000 |
|
||||
| P2 | main_thread_stall | maximum | 5891466 | 50000 |
|
||||
| P2 | simulation_x3 | p95 | 5062697 | 12000 |
|
||||
|
||||
実測はdb9d613、r02はheadless/OpsOFF/x1、保存再開はS954528a。ゲートQで測定したと書き換えない。全1407製品ソース等の一致で適用範囲を検証した。登録150施設と稼働100施設、tiny OperationsON、GPU null理由、r02のseed欄なし/同一入力SHAによる根拠を保持する。
|
||||
|
||||
独立レビューlow4(原結果の全文は `P28_01_INDEPENDENT_REVIEW_DDDE6F8.json`):
|
||||
|
||||
- U1 budget disclosure is incomplete. GATE_CONDITIONS.unmet and SPEC §5 list four budget falses (P1 x2, P2 x2). The full9 raw R0 report also has two false verdicts: simulation_x1 average 383.229 ms > 4 and main_thread_stall max 468.148 ms > 50. They are not counted as pass, since the verifier rejects false-as-pass in every phase, and the raw is SHA-bound. The disclosure should still list all six, or say that R0 is a 10-minute reference phase, when final/ledger records are written.
|
||||
- The verifier's output 'commit' is the hardcoded registered BASE 0aee05e, not the verified S ddde6f8, so the JSON alone mislabels the version that was checked. Version binding still holds: the Runner receipt records ddde6f8, and the MANIFEST/CONDITIONS seals exist only in S's gate files. This is a labeling defect, not missing evidence. A future revision should name the field base_commit and emit the verified HEAD and the verifier's own SHA.
|
||||
- G3 residual: the overload-triggered pause→save→load path was not run end to end (overloads 0, backlog_at_save 0). Only manual-pause saves were exercised. It is judged out of the persisted-state contract: paused is persisted, while backlog and the sub-minute accumulator are discarded by design in restore_clock. Keep RESTART low #1 in the ledger, and do not cite this run as proof that a non-zero backlog survives.
|
||||
- Verifier strength: verify_conditions(conditions, load_json(craw)) compares the file with itself, so only the CONDITIONS_SHA256 seal gives protection. r02 Operations OFF is not asserted against the raw (only --headless and the absent seed are checked); it rests on report.scope and the sealed scopes. Neither weakens this run.
|
||||
|
||||
次成果P28-02は実測上位の改善。P28-03、同一Windows配布版実機、長期通常プレイ、全施設・人物・ルート・エンド・図鑑・完成素材・全ゲーム受入は未完。
|
||||
+1227
File diff suppressed because it is too large
Load Diff
+35
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"task_id": "P28-01-GATE",
|
||||
"commit": "ddde6f8ab50db6baa03f25453a02814b78bbf365",
|
||||
"status": "pass",
|
||||
"summary": "Fresh independent Desktop whole-unit review (claude-opus-5-5), read-only; nothing executed, edited or committed. Integrity: actual-review-packet SHA 791cf223… (normal packet 8eb2fc73… differs only by the material-pointer line), schema 4334b9c4…, manifest 35495218…, and all 3200 material files re-hash to their manifest SHA. Scope: S ddde6f8 vs base 0aee05e changes only the 4 files under gate/** (new; no test removed). Validation raw: p28-01-gate-unit ran 25 synthetic tests, OK. p28-01-gate-verify against the real bundle exited 0 with passed=true, verified_files 3175 and source_files 1407. Its stdout equals build/p28-01-gate-verify.json. The MANIFEST/CONDITIONS seals that exist only in S matched, so S's verifier was what ran. G0 met: Tick and render CPU (P1–P4: 150/50/2398/745) come from full9, actually measured at db9d613, not at Q. Path, snapshot (count 5), save/autosave and load come from r02 (headless, Operations OFF per report.scope, x1 count 300, path cold 245). Every report carries machine Mac16,10, build/engine c7cccbf8…, seed, speed, interval and mean/P95/max with counts. r02 has no seed field and none is invented: its input_sha256 eb4f7700… equals the R0 input, the fixture and readiness, and R0's seed is 28012026. Registered 20000/200/50/150 with 100 active; the P2 warm-up is 31. G1: collector C1/C9 UI speed path and RESTART RS1 UI save/load; Mac only, Windows not verified. G2: default autoload world; O is a real Operations ON run on a tiny input (guests 0–2, facilities 0, 50 cells). G3 met (independent judgment): the original wording 「途中保存・再起動で再現」 requires the persisted Runtime state to reproduce after a mid-run save in a new process. The product's save contract persists paused in clock_snapshot, so an overload stop survives as paused. restore_clock (simulation_kernel.gd:217-231) deliberately sets _accumulator=0 and _skip_next_process: backlog and the sub-minute phase are wall-clock catch-up debt, not game state, and create_snapshot (simulation_runtime.gd:521-583) does not carry them. Requiring them to survive would contradict the product contract and is not implied by the original text. This is not inferred from 'did not occur'. Raw shows the writers saved with a non-zero sub-minute accumulator (R 0.0539 s, O 0.1 s) while the readers restored 0. All 30 per-minute complete typed SHAs still matched across 4 distinct PIDs. Tamper was rejected with CHECKSUM_MISMATCH, and O IDs 9–24 / counter 24 at 390 are equal across the restart. G4: collector stop contract and phase failures, RESTART tamper, paused-idle and boundary. G5: formal 192/192, 22198 checks; source map equals the current 1407-file product map and the full9/r02/RESTART maps; marker and engine match. G6: every receipt is SHA-bound through the sealed manifest. U1/U2 are recorded as auxiliary, not original. U2: each evidence is applied only to its own commit, input and scope. C1 is confirmed independently: SPEC.md at 0aee05e and ddde6f8 has raw SHA e2bff09a… and text.strip() SHA 641ce9ea…, the same blob with no criteria change. The first b78c718 block was a correct contract question with 0 edits. This is the gate judgment only; RESTART completed is not original completion. P28-01 stays 22/88 until prepare, approval, integration, final and the ledger.",
|
||||
"acceptance_ids": [
|
||||
"P28-01-G0",
|
||||
"P28-01-G1",
|
||||
"P28-01-G2",
|
||||
"P28-01-G3",
|
||||
"P28-01-G4",
|
||||
"P28-01-G5",
|
||||
"P28-01-G6",
|
||||
"P28-01-U1",
|
||||
"P28-01-U2"
|
||||
],
|
||||
"findings": [
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "U1 budget disclosure is incomplete. GATE_CONDITIONS.unmet and SPEC §5 list four budget falses (P1 x2, P2 x2). The full9 raw R0 report also has two false verdicts: simulation_x1 average 383.229 ms > 4 and main_thread_stall max 468.148 ms > 50. They are not counted as pass, since the verifier rejects false-as-pass in every phase, and the raw is SHA-bound. The disclosure should still list all six, or say that R0 is a 10-minute reference phase, when final/ledger records are written."
|
||||
},
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "The verifier's output 'commit' is the hardcoded registered BASE 0aee05e, not the verified S ddde6f8, so the JSON alone mislabels the version that was checked. Version binding still holds: the Runner receipt records ddde6f8, and the MANIFEST/CONDITIONS seals exist only in S's gate files. This is a labeling defect, not missing evidence. A future revision should name the field base_commit and emit the verified HEAD and the verifier's own SHA."
|
||||
},
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "G3 residual: the overload-triggered pause→save→load path was not run end to end (overloads 0, backlog_at_save 0). Only manual-pause saves were exercised. It is judged out of the persisted-state contract: paused is persisted, while backlog and the sub-minute accumulator are discarded by design in restore_clock. Keep RESTART low #1 in the ledger, and do not cite this run as proof that a non-zero backlog survives."
|
||||
},
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "Verifier strength: verify_conditions(conditions, load_json(craw)) compares the file with itself, so only the CONDITIONS_SHA256 seal gives protection. r02 Operations OFF is not asserted against the raw (only --headless and the absent seed are checked); it rests on report.scope and the sealed scopes. Neither weakens this run."
|
||||
}
|
||||
]
|
||||
}
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
{
|
||||
"status": "proposal for Root; nothing registered, prepared, dispatched or run by this Desktop session",
|
||||
"assignment": {
|
||||
"implementation": {
|
||||
"worker": "codex",
|
||||
"model": "gpt-6-astra",
|
||||
"route": "complex",
|
||||
"reason": "Godot windowed probe(GDScript)とPython collector(owned process・guard・stage照合)の複数領域にまたがり、段階0の停止判定など設計判断を含むためbounded(OpenCode)ではなくcomplex。UI実装ではないためAntigravityは使わない。"
|
||||
},
|
||||
"independent_review": {
|
||||
"model": "claude-opus-5-5",
|
||||
"session": "独立一時セッション(実装・仕様作成と別)"
|
||||
},
|
||||
"commander": "通常commander(Root経由)。この提案は契約案で、正式開始判断はcommanderの記録による。",
|
||||
"measurement_execution": "Root(統合後、windowed実行のためGUIログインセッションのMacで、owned PID/guard/cleanup付き)",
|
||||
"quota_fallback": "構造化providerエラーでquota確定時のみ同役割でAntigravity claude-opus-5-5-highへ1回。その他の失敗はblocked。"
|
||||
},
|
||||
"register_project": {
|
||||
"project": "the-tree-p28-01-perf-baseline",
|
||||
"repo": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部",
|
||||
"worktrees": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-01-perf-baseline",
|
||||
"baseline": [
|
||||
{
|
||||
"id": "P28-01-C1",
|
||||
"requirement_id": "P28-01",
|
||||
"text": "速度はmain._set_speed/_toggle_pause→SimulationKernel._processのみで与え、直接advanceの値をx3としない(checkerは不整合拒否、probeは静的レビュー対象)。",
|
||||
"validation_ids": [
|
||||
"p28-01-collector-unit"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-C2",
|
||||
"requirement_id": "P28-01",
|
||||
"text": "入力・phase準備状態のSHA、登録・実稼働・描画(SPEC§5のworld_preview model定義)の3種実数を別欄に記録する。",
|
||||
"validation_ids": [
|
||||
"p28-01-collector-unit"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-C3",
|
||||
"requirement_id": "P28-01",
|
||||
"text": "seed実値(world_seed)とRNG stateを別欄にし、未取得はnullと理由にする。",
|
||||
"validation_ids": [
|
||||
"p28-01-collector-unit"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-C4",
|
||||
"requirement_id": "P28-01",
|
||||
"text": "GPU/描画時間の非対応・未取得はnullと理由にし、0や合格への補完をcheckerが拒否する。",
|
||||
"validation_ids": [
|
||||
"p28-01-collector-unit"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-C5",
|
||||
"requirement_id": "P28-01",
|
||||
"text": "段階0(R・O各々)と外挿記録、phase上限1800s、超過時は原記録保存で停止を実装する。",
|
||||
"validation_ids": [
|
||||
"p28-01-collector-unit"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-C6",
|
||||
"requirement_id": "P28-01",
|
||||
"text": "private user://・owned PID/guard/cleanup・通常player保全・全rawログ/SHA結合をr02同等に保ち、collector依存はtracked tools/run_all_tests.pyとtools/regression/owned_outer.pyのみ(gitignore外部adapterへ依存しない)。",
|
||||
"validation_ids": [
|
||||
"p28-01-collector-unit"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-C7",
|
||||
"requirement_id": "P28-01",
|
||||
"text": "製品source mapが開始前後で不変であることを照合し、変化時は拒否する。",
|
||||
"validation_ids": [
|
||||
"p28-01-collector-unit"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-C8",
|
||||
"requirement_id": "P28-01",
|
||||
"text": "機種・build・seed・速度・区間・平均・P95・最大の欄を全sampleに要求し、欠落・偽値をcheckerが拒否する。",
|
||||
"validation_ids": [
|
||||
"p28-01-collector-unit"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-C9",
|
||||
"requirement_id": "P28-01",
|
||||
"text": "SPEC§4.3の起動順序(既定autoload世界、main._readyの実Ops結果記録)と§6の停止条件(開始前不足と自然変化を区別、再注入・状態固定なし)を実装する。",
|
||||
"validation_ids": [
|
||||
"p28-01-collector-unit"
|
||||
]
|
||||
}
|
||||
],
|
||||
"validations": {
|
||||
"p28-01-collector-unit": {
|
||||
"argv": [
|
||||
"{python}",
|
||||
"docs/plans/original-completion-2026-10-02/execution/p28-01/collector/test_collect_p28_01.py"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"register_task_contract": {
|
||||
"id": "P28-01-COLLECTOR",
|
||||
"title": "現HEADの性能基準:windowed x1/x3・Operations ON collector",
|
||||
"requirement_ids": [
|
||||
"P28-01"
|
||||
],
|
||||
"acceptance_ids": [
|
||||
"P28-01-C1",
|
||||
"P28-01-C2",
|
||||
"P28-01-C3",
|
||||
"P28-01-C4",
|
||||
"P28-01-C5",
|
||||
"P28-01-C6",
|
||||
"P28-01-C7",
|
||||
"P28-01-C8",
|
||||
"P28-01-C9"
|
||||
],
|
||||
"spec_ref": "docs/plans/original-completion-2026-10-02/execution/p28-01/SPEC.md",
|
||||
"base_commit": "8fe128c5a886b33827ca9019e287eeff86e3dbda",
|
||||
"worker": "codex",
|
||||
"allowed_paths": [
|
||||
"docs/plans/original-completion-2026-10-02/execution/p28-01/collector/**"
|
||||
],
|
||||
"depends_on": [],
|
||||
"resource_locks": [
|
||||
"p28-01-perf-collector"
|
||||
],
|
||||
"validation_ids": [
|
||||
"p28-01-collector-unit"
|
||||
],
|
||||
"max_repair_attempts": null
|
||||
},
|
||||
"task_instructions": "docs/plans/original-completion-2026-10-02/execution/p28-01/TASK_INSTRUCTIONS.md",
|
||||
"readonly_inputs_for_worker": {
|
||||
"place_under": "build/p28-01-readonly/",
|
||||
"copy_from": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01",
|
||||
"files": {
|
||||
"collect_perf.py": "43f3e42900a670957a01cfcaca7e485809527b79912a358917c935c57ea9c063",
|
||||
"measure.gd": "95e5967835cb05ad96e8e6f257689d0bd831792aa9c228c022e3af81448ff76a",
|
||||
"PROCEDURE.md": "228eae9862f7a8ea49eb8c88456097023bf479f97da27e73e938bba33f15ba11",
|
||||
"run-r02/profile_guard.gd": "597696c705cc8f0e79a35265ca3ef8df55f7fc3c4e0e5a2ce9a4e20e29e29a6f",
|
||||
"run-r02/guarded.gd": "c9deebb39ee643d312a77bae64f74cf6851697bb31036fc56d67dd7c4037ee09",
|
||||
"run-r02/execution.json": "553971f55635e529b48634ef86ad7d4198c8bafe04d5a6a5ef778a4de0439fc1",
|
||||
"run-r02/after/evidence/report.json": "f1d447937158a884a1b287c3b5be38b6e73b73c5d748f9576401877402dabe1b"
|
||||
},
|
||||
"note": "worker tree へcommitしない。fixture/engineはRootの実測時のみ使用。",
|
||||
"reference_only_not_imported": {
|
||||
"copy_from": "artifacts/original-completion-2026-10-02/windows-current1297-tomari-release-adapter-prep-r01",
|
||||
"place_under": "build/p28-01-readonly/adapter/",
|
||||
"files": {
|
||||
"windows_adapter.py": "7dffd18c46ff36eeb8d901579eb65b11998d255c7c1da27ad2499a592dfc7b7d",
|
||||
"legacy/windows_adapter.py": "66dbff042925d9efc6ff6faddd148421a285ac1c4839b940a8329c601991a17b"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sequence": [
|
||||
"Root: register project/task(base 8fe128c、spec SHAをspec_sha256_at_baseと照合、baseline・validation凍結)",
|
||||
"Root: 通常commanderへこの契約を渡し正式開始判断を記録、Desktopへ読戻し",
|
||||
"dispatch: codex実装→validate(unittest、engineなし)",
|
||||
"独立Opus review",
|
||||
"prepare-integration→final validate",
|
||||
"Root: 統合済みcollectorで段階0→P1〜P4を実測(owned PID・raw保存)",
|
||||
"証拠ゲート(receipt結合)通過後にのみ原P28-01を完了(22→23)"
|
||||
],
|
||||
"credit": "この collector task 完了では原本加算0。",
|
||||
"unchanged": [
|
||||
"予算値(performance_budget.gd:17-24)",
|
||||
"原30/88・受入分母",
|
||||
"製品source map 1407(正式192証拠を保持)",
|
||||
"権限/政策/モデル/DB"
|
||||
],
|
||||
"base_commit_note": "8fe128cは改訂SPEC.md/TASK_INSTRUCTIONS.mdを含む。本proposalのcommitはその後で、base自己参照はしない。",
|
||||
"spec_sha256_at_base": {
|
||||
"docs/plans/original-completion-2026-10-02/execution/p28-01/SPEC.md": "7794b09904bc948d1a5961ab8167512039027eb97c2d6006a9ab5328945a3ae7",
|
||||
"docs/plans/original-completion-2026-10-02/execution/p28-01/TASK_INSTRUCTIONS.md": "ed6fed8368c3af5385d0734282dd051484278850a7a3f59b737c3407a7dbf3e1"
|
||||
},
|
||||
"tracked_imports_allowed": {
|
||||
"tools/run_all_tests.py": "320ca668732b4ff72fc4449998af2327d4e5c527b06896740806aaf0487282b4",
|
||||
"tools/regression/owned_outer.py": "c2e3483b76db1a3ff940f23f88ea0083fc78736e874ebcdb385d766dde8866e7",
|
||||
"tools/regression/owned_diagnostic_process.py": "e31e93c6e2c5ea46a7d954ef8bc0b4caec1c08b86b8aef19e93b52ef24993087"
|
||||
},
|
||||
"selected_inputs": {
|
||||
"R": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-current1299-runtime-scale-build-candidate-native-r01/fixture/reference.variant",
|
||||
"sha256": "eb4f770071eb07cd7e136fe54c3f78768cc8de7a4264e62a9af1f06a8f90f9bd",
|
||||
"minute": 602,
|
||||
"operations": "OFF(main._readyのenableはINVALID_START_TIME、実結果を記録)"
|
||||
},
|
||||
"O": {
|
||||
"source": "autoload SimulationRuntime._ready initialize(GameClock, GameState, DEFAULT_WORLD_SEED)(simulation_runtime.gd:201-206)",
|
||||
"minute": 360,
|
||||
"operations": "ON(main._readyの実enable)",
|
||||
"scale": "代表規模ではない。実数併記"
|
||||
},
|
||||
"unmet": "200客Operations ONは製品上限100(operations_coordinator.gd:85,203,333/simulation_runtime.gd:1579)で不可能、P28未達として保持"
|
||||
}
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
{
|
||||
"at_jst": "2026-10-21T03:59:44.437107+09:00",
|
||||
"original_credit": 0,
|
||||
"whole_unit_accepted": false,
|
||||
"original_completed": 22,
|
||||
"original_total": 88,
|
||||
"current_S": "954528a7c5a0e27e2e50b642373168fa0a550aba",
|
||||
"base": "2132bbe3d39a0f1b69a97c731bb09213bf9d7a91",
|
||||
"formal_validation": {
|
||||
"S": "954528a7c5a0e27e2e50b642373168fa0a550aba",
|
||||
"passed": true,
|
||||
"tests": 24,
|
||||
"seconds": 5.429,
|
||||
"job": "27cf502813e341e89b5f0b68aa4832eb",
|
||||
"receipt": "335b64bb98804e9bb39e353e9bd017e2-validation.json",
|
||||
"receipt_sha256": "52b59357dca6888896212feb40fde6d154c45b4493e399ddbd316290132975b8",
|
||||
"raw": "8b7f60346fc1460b81fa6c9078fd34fc",
|
||||
"fullcopy": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/formal-validation-954528a-passed-r01"
|
||||
},
|
||||
"native": {
|
||||
"S": "954528a7c5a0e27e2e50b642373168fa0a550aba",
|
||||
"state": "running",
|
||||
"root_exec": 34944,
|
||||
"root_pid": 87112,
|
||||
"driver_pid": 87116,
|
||||
"output": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/native-restart-954528a-r01",
|
||||
"started": {
|
||||
"at": 1792522587.6806011,
|
||||
"root_pid": 87112,
|
||||
"actual_S": "954528a7c5a0e27e2e50b642373168fa0a550aba",
|
||||
"authority": "Existing human Root delegation; actual existing Desktop Opus257 read formal24PASS fullraw and exact repair8 three-file diff; exact newS native7 once, textual StringName comparator/collision/real16vectors; original readonly and all8 unchanged.",
|
||||
"formal_validation_job": "27cf502813e341e89b5f0b68aa4832eb",
|
||||
"free_bytes_before_native": 1445199872,
|
||||
"required_free_bytes": 1200000000,
|
||||
"check_sha256": "b591bd7f091f11e7dacd4e94ff9dfa21ef1d4aa86777efda26840c789a450439",
|
||||
"source1407_exact_full9_match": true,
|
||||
"phases_modified": false,
|
||||
"driver_sha256": "57455e5718785cad02644800f45150851574a0639c6b40df610654a3cb460d1a",
|
||||
"original_credit": 0
|
||||
},
|
||||
"engines_to_run": 7,
|
||||
"no_acceptance_claim": true
|
||||
},
|
||||
"prior_native_failure_e524ff2": {
|
||||
"S": "e524ff2924a539535ea67f0135792b20f0f79729",
|
||||
"passed": false,
|
||||
"error": "ValueError(at390 raw/canonical SHA)",
|
||||
"phase": "O-writer report verification",
|
||||
"engine_phases_ended": 6,
|
||||
"O_reader_started": false,
|
||||
"execution_sha256": "6bbb8ebdd486a489af43d21fc9efffa90e499625ae9a7b18c9024ab8bb1c84e5",
|
||||
"all_owned_pids_ended": true,
|
||||
"source_player_worker_preserved": true,
|
||||
"cleanup_errors": 0,
|
||||
"fullcopy": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/native-restart-e524ff2-r01"
|
||||
},
|
||||
"serialization_diagnostic": {
|
||||
"R02": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/native-e524ff2-canonical-failure-r01/serialization-diag-r02/RESULT.json",
|
||||
"expected_actual_Godot_SHA": "f28afa0899d7efea59ab99ae06276ba354779a73cac705fa4801162f27c592d7",
|
||||
"reproduced_native_hash": true,
|
||||
"first_differing_byte": 239,
|
||||
"actual_Godot_nested_key_order": [
|
||||
"old_state",
|
||||
"state",
|
||||
"reason"
|
||||
],
|
||||
"Python_nested_key_order": [
|
||||
"old_state",
|
||||
"reason",
|
||||
"state"
|
||||
],
|
||||
"all_payload_keys_type": "StringName",
|
||||
"PID76068_ended": true,
|
||||
"default_player_source_input_unchanged": true,
|
||||
"R01_auxiliary_indent_failure": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/native-e524ff2-canonical-failure-r01/serialization-diag/ROOT_SELF_DIAGNOSTIC_FAILURE.json",
|
||||
"readonly_full_copy": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-01-restart/.runtime/P28-01-RESTART/build/serialization-e524ff2-diagnostics",
|
||||
"copy_manifest_sha256": "a6d2cac44971d80aa5373c08aeaa26581054a5095cb3c2883a74118eb335f49f",
|
||||
"original_credit": 0
|
||||
},
|
||||
"actual_governor": "existing Claude Desktop local_a7f85421-3eed-4482-8030-c0dca669573a Opus5.5 messages249/251/253/257",
|
||||
"actual_implementation": {
|
||||
"run": "7eb66751c0c14c4aa342dad53e1d2dc7",
|
||||
"pid": 81918,
|
||||
"ended": true,
|
||||
"model": "gpt-6-astra",
|
||||
"repairs": 8,
|
||||
"root_exec67205": "closed exit0"
|
||||
},
|
||||
"actual_desktop_start_decision": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/restart-repair8-owner-input/DECISION.json",
|
||||
"sha256": "a1ba13ceaf34ca29983af26ce68efe19815df78420a08dfbdf0aa36b96a73bb5",
|
||||
"normal_schema_commander_target_event_dispatch_maintained": true,
|
||||
"CLI_authentication_recovered": false,
|
||||
"CLI_failed_job": "b4c3ef83db5546868d7bed8bc0f9efb7"
|
||||
},
|
||||
"change_scope": "3probe files +76/-1; textual StringName comparator/collision rejection/real16event tests; restart_pair.py unchanged",
|
||||
"storage_preservation": {
|
||||
"stage_archive": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/ended-stages-content-preserved-20261021-r02/RESULT.json",
|
||||
"all3_stages_restore_verified": true,
|
||||
"full_bytes_and_attrs_retained": true,
|
||||
"archive_sha256": "b5bbb126b16460a4d25c83d4d77361532b2861b7456406da7b00d9b2a9576c89",
|
||||
"validation_checkout": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/ENDED_RESTART_954528A_VALIDATION_RESULT.json",
|
||||
"ended_p18_p19_copies": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/ENDED_P18_P19_TWO_COPIES_RECLAIM_20261021.json",
|
||||
"free_space_intervals_overlap": "stage archival and ended validation cleanup ran on independent paths; their individual free deltas are overlapping observations, NOT additive attribution",
|
||||
"archive_utility_r01_failure": "os.listxattr missing on macOS Python; failed before source modification, oldscript/failure retained; r02 uses native macOS xattrs and full restore comparisons"
|
||||
},
|
||||
"reuse": "full9 and formal192/22198 only if all selected source/settings/input/engine/guard/policy match; no repeated regression",
|
||||
"remaining": [
|
||||
"ExactS native7 and complete Root raw/source/PID/sidecar binding",
|
||||
"Independent separate actual Opus all8 review, including old H1/M2/L2 and all raw failures/diagnostics",
|
||||
"Normal prepare/exactcandidate Root delegated approval/integration/final",
|
||||
"Original P28-01 work/acceptance/cross6 wholeunit gate and independent review/final/ledger"
|
||||
],
|
||||
"global_unmet": "R budgets false; GPU null reason; O tinyinput only, 200OpsON not established; same Windows/wholegame/longplay pending"
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"at_jst": "2026-10-21T03:10:19.441649+09:00",
|
||||
"source_S": "5e4d96d9100cc1c0a7cea95a06b7e97d791d542f",
|
||||
"worker_run": "555b819028ac46938ee71a853cc7d0bb",
|
||||
"formal_validation": {
|
||||
"passed": true,
|
||||
"tests": 20,
|
||||
"seconds": 5.124,
|
||||
"job": "4fa549e11d1147c681c83a18b948c0c9",
|
||||
"receipt": "55e301e3b29a446082211b8a620a91c5-validation.json",
|
||||
"raw": "7e8e47a1869e488fbbbc2825149f978d"
|
||||
},
|
||||
"native": {
|
||||
"passed": false,
|
||||
"phase": "parse-base",
|
||||
"import_passed": true,
|
||||
"script_error_even_engine_exit0": true,
|
||||
"reason": "restart_probe.gd402 backlog direct type inference from file-scope GameClock:Variant",
|
||||
"source1407_defaultplayer_worker4_preserved": true,
|
||||
"all2engine_and2RootPIDs_ended": true,
|
||||
"writers_readers_started": false,
|
||||
"cleanup_errors": 0,
|
||||
"normal_evidence_event_state_APIs_recorded_false": true
|
||||
},
|
||||
"next_current_repair": {
|
||||
"number": 7,
|
||||
"governor": "actual DesktopOpus243",
|
||||
"worker_model": "gpt-6-astra",
|
||||
"actual_run": "542cdd9bbbc446a4bd2bfb2145ab75bf",
|
||||
"pid": 63493,
|
||||
"scope": "one GD explicitint line plus general directVariant inference staticguard and meaningful tests, sameW/driver unchanged"
|
||||
},
|
||||
"files": [
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/formal-validation-5e4d96d-passed-r01/receipt.json",
|
||||
"bytes": 386,
|
||||
"sha256": "75aafac4ef3bfe409ea1018c1fbdfd5ea6e77af176554daf4286c551c9d837e1"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/formal-validation-5e4d96d-passed-r01/stderr.log",
|
||||
"bytes": 2103,
|
||||
"sha256": "c1a09597f5dc408962b39813210a14923e8d364eb62d329aeca9040c95fbe4e1"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/native-restart-5e4d96d-r01/ROOT_NORMAL_NATIVE_FAILURE_RECORD.json",
|
||||
"bytes": 6975,
|
||||
"sha256": "b7c4871cc03333f8f4e600908dc0fbe0357a55d44ca0c35d02cfb4e769b2533c"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/native-restart-5e4d96d-r01/ROOT_NORMAL_NATIVE_FAILURE_RECORDED.json",
|
||||
"bytes": 326,
|
||||
"sha256": "0fda11d60a6f5ff4347a36249bf21540e85fbb232cab6a76e6cf6de39cff8586"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/native-restart-5e4d96d-r01/run/execution.json",
|
||||
"bytes": 3062103,
|
||||
"sha256": "036c703e665bc441371f0a54e84a16239368746f34bc8a90c1234e1a64dfab0f"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/native-restart-5e4d96d-r01/run/parse-base/stderr.log",
|
||||
"bytes": 637,
|
||||
"sha256": "ad9490b5f8090a2c61439b7327e029f5943e1b81ac2879eecd9da5f692520ca5"
|
||||
}
|
||||
],
|
||||
"original_unit_accepted": false,
|
||||
"original_credit_added": 0,
|
||||
"denominator": {
|
||||
"packages": 30,
|
||||
"units": 88
|
||||
},
|
||||
"formal_completed_units": 22,
|
||||
"formal_completed_packages": 4
|
||||
}
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
{
|
||||
"at": 1792524526.738174,
|
||||
"helper_task": "P28-01-RESTART",
|
||||
"project": "the-tree-p28-01-restart",
|
||||
"state": "completed",
|
||||
"all4_normal_gates_passed": true,
|
||||
"acceptance_ids": [
|
||||
"P28-01-RS1",
|
||||
"P28-01-RS2",
|
||||
"P28-01-RS3",
|
||||
"P28-01-RS4",
|
||||
"P28-01-RS5",
|
||||
"P28-01-RS6",
|
||||
"P28-01-RS7",
|
||||
"P28-01-RS8"
|
||||
],
|
||||
"helper_acceptance": {
|
||||
"completed": 8,
|
||||
"total": 8
|
||||
},
|
||||
"S": "954528a7c5a0e27e2e50b642373168fa0a550aba",
|
||||
"Q": "3ac2cedc8dc977778fd5a3ba152197cc6cf00fa2",
|
||||
"M": "3086af2ccd970ddee271a184214dad037bd1d6af",
|
||||
"approval": {
|
||||
"task_id": "P28-01-RESTART",
|
||||
"candidate": "3ac2cedc8dc977778fd5a3ba152197cc6cf00fa2",
|
||||
"main_base": "3086af2ccd970ddee271a184214dad037bd1d6af",
|
||||
"owner": "Codex Root acting under existing user explicit main/push permission and delegation of candidate judgment; exact Q3ac2ced verified in ROOT_CANDIDATE_3AC2CED_QUALIFICATION.json SHA5bd8030acddc385eb728441286f6a740ec1ee966125474b60ef4b1efe2a2c343; not a new human SHA-specific statement.",
|
||||
"created": 1792524415.091752
|
||||
},
|
||||
"normal_evidence_join": [
|
||||
{
|
||||
"id": 8,
|
||||
"task_id": "P28-01-RESTART",
|
||||
"kind": "validation",
|
||||
"commit_sha": "954528a7c5a0e27e2e50b642373168fa0a550aba",
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-01-restart/runs/335b64bb98804e9bb39e353e9bd017e2-validation.json",
|
||||
"passed": 1,
|
||||
"artifact_sha256": "52b59357dca6888896212feb40fde6d154c45b4493e399ddbd316290132975b8",
|
||||
"checks": [
|
||||
{
|
||||
"id": "p28-01-restart-unit",
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-01-restart/runs/8b7f60346fc1460b81fa6c9078fd34fc",
|
||||
"raw": {
|
||||
"command.json": {
|
||||
"bytes": 743,
|
||||
"sha256": "0bdf9f0c8e885154a4f5c91cdd065c87a198512f6d05be136f6f4dd273ed1f39"
|
||||
},
|
||||
"stdout.log": {
|
||||
"bytes": 178,
|
||||
"sha256": "6b7ff3a63727394e801467b7a75bc60c81c712838c7f9d6c1ff8bb4a3e0ee30a"
|
||||
},
|
||||
"stderr.log": {
|
||||
"bytes": 2497,
|
||||
"sha256": "e8b19c5284b54009e4afbfd06dc605ebe63fea13c287590d0d930f60588fe8e6"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"task_id": "P28-01-RESTART",
|
||||
"kind": "review",
|
||||
"commit_sha": "954528a7c5a0e27e2e50b642373168fa0a550aba",
|
||||
"artifact": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/restart-independent-review-954528a-r01/actual-output-after-receiver-timeout/result.json",
|
||||
"passed": 1,
|
||||
"artifact_sha256": "b8fff13e8789797a4a50a5e2900f3d84ef20a6682a8f25feca225edc7fe00300",
|
||||
"checks": []
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"task_id": "P28-01-RESTART",
|
||||
"kind": "integration",
|
||||
"commit_sha": "3ac2cedc8dc977778fd5a3ba152197cc6cf00fa2",
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-01-restart/runs/aa8aad481aa346c8a3358847a21f431e-integration.json",
|
||||
"passed": 1,
|
||||
"artifact_sha256": "85caa375586e707d3c736042b45b68acad8ab2821b6f52753dde62c3d6ed75f3",
|
||||
"checks": [
|
||||
{
|
||||
"id": "p28-01-restart-unit",
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-01-restart/runs/f1e305c37cd9498ab3210d4aae76b074",
|
||||
"raw": {
|
||||
"command.json": {
|
||||
"bytes": 744,
|
||||
"sha256": "699b9c7e76930685a98eb6468a4a4e0bffbb6c08602330f947c179c8613e0c8b"
|
||||
},
|
||||
"stdout.log": {
|
||||
"bytes": 172,
|
||||
"sha256": "d40088aec669a9bbcad7191068385673beb897b9f4ef1557a5b2858cef3b5ccd"
|
||||
},
|
||||
"stderr.log": {
|
||||
"bytes": 2497,
|
||||
"sha256": "6df4c67bfbf0419620b40ef379fa125e2be762ad4d3f34e5c19ad44182dad5e9"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"task_id": "P28-01-RESTART",
|
||||
"kind": "final",
|
||||
"commit_sha": "3ac2cedc8dc977778fd5a3ba152197cc6cf00fa2",
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-01-restart/runs/67f2fc17aca343bb9a92dd7d04b0f2b6-final.json",
|
||||
"passed": 1,
|
||||
"artifact_sha256": "856450b4401b9c3bb29688937d66dcdc5965adb014c6b5c4149a25b3c7f18308",
|
||||
"checks": [
|
||||
{
|
||||
"id": "p28-01-restart-unit",
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-01-restart/runs/f0f3945d5b9b41438f8d771326134572",
|
||||
"raw": {
|
||||
"command.json": {
|
||||
"bytes": 738,
|
||||
"sha256": "b2bd6b6162ed297828f5c0a0fe7cab9858c332f55f78670a0361e1ad6d40e323"
|
||||
},
|
||||
"stdout.log": {
|
||||
"bytes": 175,
|
||||
"sha256": "ebbafcedb3072d5593fabca1b15aa440ed536383df3b55ac8aad979a8dd9745f"
|
||||
},
|
||||
"stderr.log": {
|
||||
"bytes": 2497,
|
||||
"sha256": "35b7c82b3c6ebfdf0e2ad458d64afe26514b4c1db297c140eb7aac1eb2a7da86"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"traceability_path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-01-restart/traceability/P28-01-RESTART.json",
|
||||
"traceability_sha256": "1febc7a16824fd8701a01e9d2d9f4482ee99e7a744742dd8b4267d06049c9ce4",
|
||||
"native_execution_sha256": "c1ec566402896f9f3fdefdfc268de6ed0bc46b8a37a9eb6bfb252b0cc4ebb514",
|
||||
"native_Root243_sha256": "6dbb3cbb4ac1838b66034323cab22a9c5a968c1da0efe8d34676073b3f980cfb",
|
||||
"candidate_qualification_sha256": "5bd8030acddc385eb728441286f6a740ec1ee966125474b60ef4b1efe2a2c343",
|
||||
"independent_reviewer_session": "local_251adf7f-dd2e-4726-9141-fff58a45eb14",
|
||||
"actual_result_sha256": "b8fff13e8789797a4a50a5e2900f3d84ef20a6682a8f25feca225edc7fe00300",
|
||||
"review_high_medium": 0,
|
||||
"review_low": 3,
|
||||
"receiver_timeout_kept_failed": true,
|
||||
"new_reviewer_inference_on_rejoin": false,
|
||||
"CLI_auth_recovered": false,
|
||||
"original_credit": 0,
|
||||
"original_completed": 22,
|
||||
"original_total": 88,
|
||||
"original_P28_01_whole_unit_accepted": false,
|
||||
"remaining_original_gate_plan": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/P28-01_WHOLE_UNIT_GATE_PLAN_DESKTOP_233.md"
|
||||
}
|
||||
+1380
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,198 @@
|
||||
# P28-01 現HEADの性能基準:最小バッチ仕様
|
||||
|
||||
この文書は Runner の `spec_ref` です。work_packages.json 全体の代わりに、この対象に必要な原本定義だけを固定します。
|
||||
|
||||
## 1. 原本定義(変更しない)
|
||||
|
||||
- 分母は原30パッケージ・88成果単位で、部分点はありません。開始時点の正式完了は22/88、完了パッケージは4/30です。
|
||||
- 成果単位 P28-01「現HEADの性能基準」(`work_packages.json` の P28.remaining_deliverables[0])
|
||||
- work: 実Runtimeの20,000マス・200客・50職員・150施設を基準にTick、経路、snapshot、描画、保存を測る。
|
||||
- acceptance: 機種、ビルド、シード、速度、測定区間、平均・P95・最大を残す。
|
||||
- パッケージ P28「性能・操作感・長期バランス」
|
||||
- requirement_ids: B-1.1, B-3.1a, B-4.3a, B-4.3c, B-11.1, B-12.1, B-14.4
|
||||
- decisions: D04, D08, D10
|
||||
- package acceptance(P28-01 の完了では満たしたことにしない): 既存性能予算と新しいWindows描画目標を満たす。到達不可能な収支/信仰/ランク条件なし。高速自動再生だけでプレイ時間・操作感を合格にしない。
|
||||
- P28 横断条件6件(各成果で適用し、適用外にするときは理由を記録する)
|
||||
1. 原本条件とUI操作が一致
|
||||
2. Runtimeの実利用・会計・行動へ接続
|
||||
3. 途中保存・再起動で再現
|
||||
4. 異常/境界/中断を検証
|
||||
5. 関連回帰PASS
|
||||
6. 要件別ログとソースハッシュを保存
|
||||
|
||||
## 2. 完全原本参照
|
||||
|
||||
`docs/traceability/original/BASELINE.txt`(SHA256 `75e4bdbdeb379c9269553f0c776bdb9cc96e01f2d6aa5ca650d132c63a8aeb1a`)。行番号は `requirement_coverage.json` の source_lines です。
|
||||
|
||||
| ID | 行 | この対象での意味 |
|
||||
|---|---|---|
|
||||
| B-1.1 | 2–10 | PC (Windows)、想定プレイ時間 |
|
||||
| B-3.1a | 58–60 | 現実10秒=ゲーム1時間、Pause/x1/x2/x3 |
|
||||
| B-4.3a / B-4.3c | 84–87 | A*経路、EV待ち時間、渋滞時の50%減速 |
|
||||
| B-11.1 | 213–216 | メイン画面、スクロール、ズーム、表と裏 |
|
||||
| B-12.1 | 228–230 | ドット絵マップの描画 |
|
||||
| B-14.4 | 258 | UIレスポンス向上 |
|
||||
|
||||
既存の予算値は `scripts/core/performance_budget.gd:17-24` のとおりで、変更しません。x1 平均4 ms、x3 P95 12 ms、経路 P95 2 ms、main-thread stall の最大50 ms、保存5 s、読込8 s です。
|
||||
|
||||
## 3. 再利用する既存証拠(再生成しない)
|
||||
|
||||
- **source**: main `8c36c100b2073baf8a9c536528f319a5d0837a63`。製品 source map は正式全回帰 `p14-03-formal-regression-83b170a-r01` と同じ1407件です。この対象では製品と source map の対象(autoload/scripts/scenes/data/tests/assets/tools と直下4ファイル)を変更しません。変更がなければ、正式192 suite の証拠はそのまま有効です。
|
||||
- **代表入力**: `artifacts/original-completion-2026-10-02/p28-current1299-runtime-scale-build-candidate-native-r01/fixture/reference.variant`(SHA `eb4f7700…f9bd`)。20,000/200/50/150、Operations OFF です。全生成はしません。
|
||||
- **engine**: `/Applications/Godot.app/Contents/MacOS/Godot`(SHA `c7cccbf8…8ccf`、4.7.2.stable.official.ed1daf0bf)。
|
||||
- **既存の headless 実測 r02**(`p14-03-representative-perf-r01/run-r02/execution.json` SHA `553971f5…9fc1`)
|
||||
- 条件: Mac16,10・10コア・24 GB、Operations OFF、x1、300分
|
||||
- 1分 tick: 平均826.6 ms、P95 1702.7 ms、最大2918.1 ms
|
||||
- 経路 cold: P95 0.61 ms
|
||||
- snapshot: 平均77.6 ms
|
||||
- 保存: manual 297 ms、autosave 327 ms
|
||||
- 読込: 平均3193 ms、最大3368 ms
|
||||
- これは x1 headless・Operations OFF の行としてそのまま採用し、再測定しません。
|
||||
- **collector の型**: `p14-03-representative-perf-r01/collect_perf.py`(SHA `43f3e429…c063`)と `measure.gd`(SHA `95e59678…f76a`)、`run-r02/profile_guard.gd`(SHA `597696c7…a6f`)。以下を引き継ぎます。
|
||||
- 私有の CoW stage、private user:// 保存
|
||||
- owned Popen による PID の捕捉、guard、cleanup
|
||||
- 通常 player の保全
|
||||
- 全 raw stdout と SHA の結合
|
||||
- 上の artifacts/ は gitignore 対象のため、Root が worker へ build/ 配下の読取専用として渡します。
|
||||
- **collector の依存は自己完結させる。** r02 の `collect_perf.py` は、gitignore 対象の `windows-current1297-tomari-release-adapter-prep-r01/windows_adapter.py` を動的に import しています(:153)。そのadapterは、さらに `legacy/windows_adapter.py` と `support/owned_outer.py` を読み込みます。新しい collector はこの外部パスに依存しません。
|
||||
- import してよいのは、Git で追跡している次の2つだけで、どちらも変更しません。
|
||||
- `tools/run_all_tests.py`: `source_refs`、`default_player`、`private_project`、`copy_source`、`profile_path`
|
||||
- `tools/regression/owned_outer.py`: `run_phase`、`finish_captured`。SHA `c2e3483b…66e7` で、adapter の support 版とバイト一致し、内部で `owned_diagnostic_process.py` を読み込みます
|
||||
- それ以外の補助処理は、allowed_paths の中に実装します。対象は、private import 用の project、`run_engine` 相当の owned phase の実行(上限は1800 s で、adapter の固定値1200 s ではない)、`profile_files`、`no_live_engine`、厳密な診断です。
|
||||
- adapter の2ファイルは仕様の参照にだけ使い、import しません。読取専用入力の SHA は REGISTRATION_PROPOSAL.json に記載します。
|
||||
|
||||
## 4. 入力の選択と前提(API監査の結果を反映)
|
||||
|
||||
### 4.1 Operations ON は代表入力では開始できない(事実)
|
||||
|
||||
- `SimulationRuntime.enable_production_operations()`(`autoload/simulation_runtime.gd:1534-1544`)は、Day 1 06:00、つまり `absolute_minute == OPERATIONS_START_MINUTE`(360、:49)のときだけ開始できます。
|
||||
- 代表入力は602分(Day 1 10:02)に保存されているため、`INVALID_START_TIME` になります。
|
||||
- clock だけを巻き戻すことは禁止です。
|
||||
- Operations ON の人口上限は100人です(`scripts/operations/operations_coordinator.gd:85`、:203、:333)。
|
||||
- 開始時には `set_population_count(guest_count)`(simulation_runtime.gd:1579)が上限を照合するため、200客では `INITIAL_POPULATION_INVALID` になります。
|
||||
- 正規契約の上での200客 Operations ON の状態は存在しません(PERFORMANCE_AUDIT.md「100客上限と200客診断」)。
|
||||
- 既存の成果物の中に、代表規模の Operations ON の保存はありません。代表入力の build 記録は、`operations_enabled_mixed_load_accepted: false` です。
|
||||
|
||||
### 4.2 選択した入力(製品変更なし、fixtureの全生成なし)
|
||||
|
||||
| 入力 | 中身 | Operations の実状態 | 使い道 |
|
||||
|---|---|---|---|
|
||||
| **R:代表入力** | 上の reference.variant(SHA `eb4f7700…`、602分、20,000/200/50/150) | main の `_ready` が `enable_production_operations()` を呼び、`INVALID_START_TIME` で失敗する。この実結果を記録したうえで OFF | 原 work の「20,000マス・200客・50職員・150施設」の windowed x1/x3 |
|
||||
| **O:新規開始** | 起動時に autoload の `SimulationRuntime._ready` が行う `initialize(GameClock, GameState, DEFAULT_WORLD_SEED)`(:201-206)。Day 1 06:00 | main の `_ready` が開始に成功した実際の ON | 通常UIで始まる Operations ON の自然営業 |
|
||||
|
||||
- O は代表規模ではありません。O の値を「200客」や「代表負荷」と呼ばず、実数を併記します。
|
||||
- 200客での Operations ON は、製品契約(上限100)のために測定できません。これは P28 の未達として残し、P28-01 では満たしたことにしません。
|
||||
|
||||
### 4.3 起動順序(独立した Runtime は作らない)
|
||||
|
||||
計測するのは、既定の autoload(`/root/GameClock`、`/root/GameState`、`/root/SimulationRuntime`)と、main が描画する同じ世界だけです。`SimulationRuntime.new()` や別の `initialize` で作った Runtime は計測しません。
|
||||
|
||||
1. engine は phase ごとに1プロセスを起動し、r02 と同じ Guard の bootstrap(`profile_guard.gd`)と `--script` の wrapper を使います。windowed なので `--headless` は付けません。
|
||||
2. autoload の準備が終わったら、`GameClock.set_paused(true)` で時間を止めます。
|
||||
3. **R の場合**: `/root/SimulationRuntime.restore_snapshot(<get_var(false)で読んだ入力>)` で復元します。
|
||||
- 復元が成功し、登録数が20,000/200/50/150と完全に一致すること、および入力 SHA の一致を確認します。
|
||||
- 一致しなければ開始前の不足として停止します(§5)。
|
||||
- **O の場合**は何も復元しません。
|
||||
4. `res://scenes/main/main.tscn` を instantiate して root に追加します。main の `_ready`(main.gd:218-250)の処理は次のとおりで、それぞれの戻り値と `get_operations_report().enabled` の実値を記録します。
|
||||
- `_pause_fresh_opening`
|
||||
- `enable_production_save_policy`
|
||||
- `enable_production_operations` の自動呼出し
|
||||
5. `focus_operating_area` の deferred 処理が終わってから、camera(zoom、pan、layer、viewport の大きさ)を記録し、計測中はそのまま固定します。
|
||||
6. 速度は `main._set_speed(n)`(:2727、`GameClock.set_speed` を呼ぶ)で設定し、一時停止の解除は `main._toggle_pause()`(:2689)で行います。どちらも UI のボタンと同じ処理です。
|
||||
- 以降、時間は `SimulationKernel._process → process_real_delta` だけで進めます(MAX_TICKS_PER_FRAME 8、MAX_BACKLOG 120、simulation_kernel.gd:30-31、:54-72)。
|
||||
- `advance_minutes`、`advance_one_minute`、`spawn_guest` などの直接呼出しはしません。
|
||||
7. production save policy の下では autosave が起きることがあります。保存先は collector の private user:// に限り、通常 player の保全を前後で照合します。区間内に起きた autosave は stall の原因として記録します。
|
||||
|
||||
## 5. 測定
|
||||
|
||||
phase は次の4つです。それぞれ別の engine プロセスで実行し、区間は warm-up 30ゲーム分のあと、計測120ゲーム分です。
|
||||
|
||||
| phase | 入力 | 速度 |
|
||||
|---|---|---|
|
||||
| P1 | R(OFF) | x1 |
|
||||
| P2 | R(OFF) | x3 |
|
||||
| P3 | O(ON) | x1 |
|
||||
| P4 | O(ON) | x3 |
|
||||
|
||||
- x1 headless での保存・読込・経路・snapshot は r02 の値を採用し、windowed では測りません。
|
||||
|
||||
**段階0(同じ collector)**
|
||||
- P1・P2 の長い計測の前に、R を x1 で10ゲーム分だけ動かします。O についても P3・P4 の前に、x1 で10ゲーム分の限定動作を行います。
|
||||
- 各段階0で次のものを記録してから、各 phase の所要時間を外挿します。
|
||||
- Operations の enable の実結果
|
||||
- 1分ごとの時間
|
||||
- 実数
|
||||
- 完了時間
|
||||
- 見積り: R は 827 ms × 150分 × 2 ≒ 250 s です。O は段階0の実測から外挿します。
|
||||
- phase ごとの上限は1800 s です。
|
||||
|
||||
**各 sample に残すもの**(取得できない値は null と理由にし、0 や合格で補いません)
|
||||
- 機種: hw.model、コア数、メモリ
|
||||
- build: engine の文字列・hash・SHA、project の commit、source map の SHA
|
||||
- seed: restore 後、または新規開始後の `SimulationRuntime.world_seed` の実値
|
||||
- RNG state: `rng_streams`。seed とは別欄にします
|
||||
- 入力 SHA と、phase 準備状態の SHA
|
||||
- 速度と、`GameClock.speed` の実値
|
||||
- 区間: 開始・終了のゲーム分と wall 時刻、frame 数、各 frame で進んだ分数
|
||||
- 時間の統計: frame 時間、kernel の1分 tick(`get_performance_reports`)、stall(50 ms を超えた frame の数と最大)、描画 CPU/GPU の時間(`RenderingServer.viewport_set_measure_render_time`、`viewport_get_measured_render_time_cpu`/`_gpu`)、`Performance.TIME_PROCESS`。それぞれについて平均・P95・最大・標本数を記録します
|
||||
- **実数は3種類を分けて記録します**
|
||||
- 登録総数: 客・職員・施設・歩行可能セル
|
||||
- 実稼働数: 退館中でない在館の客、勤務中の職員、稼働中の施設
|
||||
- 描画数: 描画に実際に使われた model の数です。world_preview は Canvas に即時描画するため(`_draw` :512、`_draw_pixel_actor` :1525)、人数分の Node はありません。そのため Node の数ではなく、次のように数えます。
|
||||
- actor: `world_preview.get_life_observation_report()`(:564)の `actor_drawings` のうち、現在の layer にあるもの(`_observe_actors` が layer で絞ったもの)の件数と、そのうち `at` が `Rect2(Vector2.ZERO, size)` の中にあるものの件数。observation_serial も添えます
|
||||
- 施設: 完成した施設のうち、現在の layer にあり、cell の外接矩形が viewport と交わるもの(`_draw_world_entity_icons` :966 と同じ判定)の件数
|
||||
- セル: bounds の中で `get_cell_rect(cell)` が viewport と交わるセル(`_draw_world_cells` :765 と同じ判定)の件数
|
||||
- あわせて layer、zoom、pan、viewport の大きさを記録します
|
||||
|
||||
**判定**: 予算(§2)との合否を生の値で並べます。新しい許容値は作りません。
|
||||
|
||||
## 6. 停止条件と人数の扱い
|
||||
|
||||
- **開始前の不足**: 次のいずれかに当たれば、その phase の計測を始めずに原記録を残して止め、`input_shortfall` とします。
|
||||
- R の入力 SHA の不一致
|
||||
- 復元の失敗
|
||||
- 登録数が20,000/200/50/150と一致しない
|
||||
- O の Operations が開始できない(enabled が false、または開始が360分でない)
|
||||
- **計測中の自然な変化**: 客の退館・来館、職員の状態の変化は、すべての sample の実数として記録します。これは停止条件ではありません。
|
||||
- 計測の途中で客を再注入したり、状態を固定したり、復元をやり直したりすることは、通常の計測に入れません。
|
||||
- 区間の要約には、登録数と実稼働数の最小・最大を必ず併記します。登録が200で、実稼働数も示した区間だけを「200客」と表記できます。
|
||||
- **途中停止**: 次のいずれかに当たれば、そこまでの原記録を残して止めます。次の最小手順は判断担当が決めます。
|
||||
- phase の上限(1800 s)を超えた
|
||||
- engine が異常終了した
|
||||
- Operations の runtime failure の latch、または terminal(破産)になった
|
||||
- 要対応イベントのために clock が止まり、進まない
|
||||
- collector がイベントを自動で選んで解消することはしません。
|
||||
|
||||
## 7. 証明の役割分担
|
||||
|
||||
| 担当 | 確認すること | 確認しないこと |
|
||||
|---|---|---|
|
||||
| checker(`check_p28_01_report.py`)と、合成 JSON での unittest | report の形式、必須の欄、null に理由があること、自己矛盾がないこと。たとえば次のものを拒否します。<br>・速度とゲーム分・frame の不整合<br>・seed 欄が RNG state と同じ値<br>・登録数が稼働数や描画数の欄に流用されている<br>・GPU 時間が 0 で理由がない<br>・「200客」の表記と実数の不一致 | 経路・seed・実稼働数が事実かどうか |
|
||||
| probe の実経路の静的レビュー(独立 Opus) | §4.3 の順序、`_set_speed`/`_toggle_pause`/`_process` の経路、autoload の世界を計測していること、直接 advance・再注入・別 Runtime がないこと | 実行したかどうか |
|
||||
| Root の実測(owned PID、raw の結合) | 実際の engine プロセスで、PID が一致すること、raw stdout と report が一致すること、source map と入力 SHA を照合した実行であること | — |
|
||||
|
||||
この3つがすべてそろったときに限り、記録は事実として扱われます。
|
||||
|
||||
## 8. 対象外と非主張
|
||||
|
||||
- Windows 実機は未検証のまま残します。
|
||||
- 200客での Operations ON は、製品の上限100のために測定できず、未達として残します。
|
||||
- P28 パッケージの予算達成、P28-02、P28-03、長期バランス、プレイ時間は対象外です。
|
||||
- 4回分の全回帰と、fixture の全生成は行いません。
|
||||
|
||||
## 9. 完了の扱い
|
||||
|
||||
- **この Runner task**: collector、probe、checker が C1〜C9 を満たし、Runner の validation、独立レビュー、integration、final を通ること。これで原本の加算は0です。
|
||||
- **原本 P28-01 の完了**: Root が統合済みの collector で実測し、その receipt を結合した後の証拠ゲートが通ったときに限ります。記録の項目がそろえば、予算が未達でも P28-01 の受入は成立します。未達の予算と200客 Operations ON は、P28 の未達として残します。
|
||||
|
||||
## 10. 登録条件(collector task の baseline)
|
||||
|
||||
- **C1**: 速度は `main._set_speed`/`_toggle_pause`→`SimulationKernel._process` だけで与え、直接 advance の値を x3 とは呼ばない。checker は不整合を拒否し、probe は静的レビューの対象とする。
|
||||
- **C2**: 入力 SHA と phase 準備状態の SHA、登録・実稼働・描画(§5の定義)の3種類の実数を、別の欄に記録する。
|
||||
- **C3**: seed の実値(`world_seed`)と RNG state を別の欄にし、取得できなければ null と理由にする。
|
||||
- **C4**: GPU や描画時間を取得できなければ null と理由にし、0 や合格への補完を checker が拒否する。
|
||||
- **C5**: 段階0(R と O のそれぞれ)と外挿の記録、phase ごとの上限1800 s、超過時に原記録を保存して止めることを実装する。
|
||||
- **C6**: private user://、owned PID・guard・cleanup・通常 player の保全・全 raw ログと SHA の結合を、r02 と同等に保つ。
|
||||
- **C7**: 製品 source map が開始前と終了後で変わらないことを照合し、変われば拒否する。
|
||||
- **C8**: 機種・build・seed・速度・区間・平均・P95・最大の各欄を全 sample に要求し、欠けた欄や偽の値を checker が拒否する。
|
||||
- **C9**: §4.3 の起動順序(autoload の世界、main の `_ready` の実際の Operations の結果を記録)と、§6 の停止条件(開始前の不足と、計測中の自然な変化を区別する。再注入も状態の固定もしない)を実装する。
|
||||
@@ -0,0 +1,49 @@
|
||||
# P28-01 collector task 指示(worker 向け)
|
||||
|
||||
仕様は `SPEC.md`(同じディレクトリ)です。この指示と SPEC.md は変更しないでください。
|
||||
|
||||
## 作るもの(allowed_paths `docs/plans/original-completion-2026-10-02/execution/p28-01/collector/**` の中だけ)
|
||||
|
||||
1. **`collect_p28_01.py`**: r02 の `collect_perf.py` を基にした外部 collector です。
|
||||
- 1版(main の現 source)だけを、私有の CoW stage に materialize します。
|
||||
- import は1回だけ行い、その後は R の段階0、P1、P2、O の段階0、P3、P4 の順に、それぞれ別の engine プロセスで1つずつ実行します。
|
||||
- `--check` は入力と予定の argv を検証して表示するだけで、stage も engine も作りません。
|
||||
- `--run` は実測します。Root が実行します。
|
||||
2. **`measure_p28_01.gd`**: windowed の main scene 用 probe です。
|
||||
- 代表入力(R)は `FileAccess.get_var(false)` で読み、既定の autoload `/root/SimulationRuntime.restore_snapshot` に復元します。新規開始(O)は何も復元しません。どちらの場合も、別の Runtime の生成や `initialize` の再呼出しはしません(SPEC §4.3)。保存は private user:// だけです。
|
||||
- main.tscn は、復元の後に root へ追加します。`_ready` で起きた Operations の実際の結果を記録します。
|
||||
- 速度は `main._set_speed`、一時停止の解除は `main._toggle_pause` で行い、あとは `SimulationKernel._process` が進めるのを観測するだけにします。直接 advance を呼んではいけません。
|
||||
- 計測は frame ごとに行います。
|
||||
- stdout にちょうど1行 `P28_01_PERF_REPORT=<json>` を出します。
|
||||
3. **`check_p28_01_report.py`**: report と収集結果の checker です。合格の条件と、拒否すべき偽の report は下に示します。
|
||||
4. **`test_collect_p28_01.py`**: 合成した入力だけで動く unittest です。engine も artifacts も不要にします。
|
||||
|
||||
## 必須事項
|
||||
|
||||
- SPEC.md の §4〜§7 と C1〜C9 を、そのとおりに実装します。
|
||||
- **checker が拒否すべきもの**(それぞれに対応する拒否試験を書く)
|
||||
- x3 が直接 advance で作られている(Clock の速度と frame の記録が一致しない)
|
||||
- 登録数を稼働数や描画数の欄に流用している
|
||||
- seed の欄に RNG state が入っている
|
||||
- GPU 時間が 0 で、理由がない
|
||||
- 必須の欄が欠けている
|
||||
- source map が変わっている
|
||||
- raw stdout と report が一致しない
|
||||
- PID が一致しない
|
||||
- **製品 source map の対象には触れません。** autoload、scripts、scenes、data、tests、assets、tools と、`project.godot`、`export_presets.cfg`、`Makefile`、`AGENTS.md` です。
|
||||
- 予算の値、原30/88、受入分母は変えません。
|
||||
- 次のことはしません。
|
||||
- 全回帰
|
||||
- native engine の起動(実測は Root が統合後に行います)
|
||||
- fixture の生成
|
||||
- P14 の再調査
|
||||
- 必ず守ること: SPEC §4.3 の起動順序と、§6 の停止条件。§4.1 のとおり、代表入力(R)で Operations は開始できず、200客での Operations ON は製品の上限で不可能です。clock を巻き戻すこと、上限を上書きすること、客を再注入すること、状態を固定することは禁止です。
|
||||
- 描画数は Node の数ではなく、SPEC §5 の model の定義で数えます。
|
||||
- checker が証明するのは形式と矛盾だけです(SPEC §7)。経路や seed が事実かどうかは、静的レビューと Root の実測で確かめます。
|
||||
- 依存は自己完結させます(SPEC §3)。gitignore 対象の windows_adapter.py とその legacy/support は import しません。import してよいのは `tools/run_all_tests.py` と `tools/regression/owned_outer.py` だけで、どちらも変更しません。unittest の中で、collector の import 先にこの2つ以外の外部パスがないことを確認します。
|
||||
- 参照用の入力(r02 の `collect_perf.py`、`measure.gd`、`profile_guard.gd`、`run-r02/execution.json` と report、さらに adapter の `windows_adapter.py`・`legacy/windows_adapter.py`)は、Root が `build/p28-01-readonly/` に読取専用で置きます。SHA は SPEC.md §3 と REGISTRATION_PROPOSAL.json の readonly_inputs_for_worker のとおりです。それ以外の場所や、拒否されたパスにはアクセスしないでください。
|
||||
- commit はしません(Runner が submit します)。最終出力には変更したファイルと、試験の実行結果をそのまま出します。
|
||||
|
||||
## 検証
|
||||
|
||||
Runner の validation `p28-01-collector-unit` が `test_collect_p28_01.py` を実行します。そのあと、独立した Opus(claude-opus-5-5)が内容をレビューします。自己申告や終了コード0は、合格の証拠になりません。
|
||||
+732
@@ -0,0 +1,732 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Structural/consistency gate, not proof of execution or truthful observations."""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
import re
|
||||
import struct
|
||||
|
||||
PREFIX = "P28_01_PERF_REPORT="
|
||||
RAW_SOURCE = "clock.performance_budget (product diagnostic, raw, not a verdict)"
|
||||
CAP_SOURCE = "SPEC §5 phase cap"
|
||||
DEADLINE_SOURCE = "H6 probe deadline (cap−60s)"
|
||||
VERDICT_SCOPE = ("Budget verdicts come only from windowed phases, using kernel samples, for "
|
||||
"simulation_x1, simulation_x3 and main_thread_stall. Save, load and path_query "
|
||||
"verdicts are null in this collector, reason: not_measured_with_kernel_definition.")
|
||||
RAW_FIELDS = {"budget_at_warmup_end", "budget_at_phase_end", "kernel_at_warmup_end", "kernel_at_phase_end"}
|
||||
METRICS = {"simulation_x1", "simulation_x3", "main_thread_stall"}
|
||||
R02_FIELDS = {
|
||||
**{"x1_outer_advance_one_minute_wall_" + stat:
|
||||
("advance_one_minute_wall_ms_x1." + stat, stat,
|
||||
"headless outer advance_one_minute wall; not kernel-defined") for stat in ("mean", "p95", "max")},
|
||||
"path_cold_p95": ("path_after_tick_ms.found_cold.p95", "p95", "headless probe path timing; not kernel-defined"),
|
||||
"snapshot_mean": ("save_load_ms.snapshot.mean", "mean", "headless probe snapshot timing; not kernel-defined"),
|
||||
**{kind + "_" + stat: ("save_load_ms." + kind + "." + stat, stat,
|
||||
"headless probe " + kind + " timing; not kernel-defined")
|
||||
for kind in ("manual", "autosave", "load") for stat in ("mean", "max")},
|
||||
}
|
||||
PHASES = {"R0": ("R", 1, 0, 10), "P1": ("R", 1, 30, 120),
|
||||
"P2": ("R", 3, 30, 120), "O0": ("O", 1, 0, 10),
|
||||
"P3": ("O", 1, 30, 120), "P4": ("O", 3, 30, 120)}
|
||||
FORBIDDEN = ("_toggle_pause", "clear(", "enable_production_", "set_paused(false)",
|
||||
"advance_", "spawn_guest", "SimulationRuntime.new", "initialize(")
|
||||
|
||||
# Only declared collector fields are normalized. Product dictionaries (RNG,
|
||||
# startup, raw reports) retain their original JSON representation.
|
||||
INTEGER_FIELDS = {
|
||||
"engine_frame_pre", "engine_frame_post", "pre_order", "post_order",
|
||||
"minute_at_frame_start", "minute_after", "speed_at_frame_start",
|
||||
"allowed_ticks_observation", "short_of_allowed_frames",
|
||||
"cores", "memory_bytes", "pid", "seed", "frame", "game_start", "game_end", "game_minute",
|
||||
"minutes_advanced", "start_minute", "end_minute", "opening_minute", "actual_minutes",
|
||||
"warmup_target", "measurement_target", "speed", "speed_after", "backlog",
|
||||
"toggle_pause_calls", "performance_clear_calls", "zero_minute_frames_excluded",
|
||||
"observation_serial", "carried_from_serial", "counts_from_minute",
|
||||
"actors_in_layer", "actors_in_viewport", "facilities", "cells", "guests",
|
||||
"staff", "walkable_cells", "count", "lifetime_count", "observed_minutes", "target_minutes",
|
||||
}
|
||||
OPAQUE_FIELDS = {"rng_streams", "startup", "kernel_at_warmup_end", "kernel_at_phase_end",
|
||||
"operations_at_end", "restore_result", "save_policy_after"}
|
||||
|
||||
def check_budget_scope(value, report_paths=(), path=(), raw=False):
|
||||
"""Walk the entire receipt; privileges are tied to exact report locations."""
|
||||
if isinstance(value, list):
|
||||
for i, child in enumerate(value):
|
||||
check_budget_scope(child, report_paths, path + (i,), raw)
|
||||
return
|
||||
if not isinstance(value, dict):
|
||||
return
|
||||
raw_root = any(path == root + (name,) for root in report_paths for name in RAW_FIELDS)
|
||||
verdict = any(len(path) == len(root) + 2 and path[:len(root)] == root
|
||||
and path[-2] == "budget_verdict" and path[-1] in METRICS for root in report_paths)
|
||||
has_pass = bool({"passed", "within_budget"} & value.keys())
|
||||
in_report = any(path[:len(root)] == root for root in report_paths)
|
||||
if has_pass and in_report and not raw:
|
||||
need(verdict, "pass flag copied outside kernel verdict")
|
||||
if "budget_verdict" in value:
|
||||
need(path in report_paths and isinstance(value["budget_verdict"], dict)
|
||||
and set(value["budget_verdict"]) <= METRICS, "budget verdict location/metrics")
|
||||
if has_pass and {"limit", "limit_us", "limit_ms"} & value.keys():
|
||||
need(verdict, "budget verdict outside report.budget_verdict")
|
||||
if raw_root:
|
||||
need(value.get("source") == RAW_SOURCE, "raw diagnostic source missing")
|
||||
keys(value, ("source", "data"))
|
||||
if not raw:
|
||||
need("budget_exceeded" not in value, "copied product budget_exceeded")
|
||||
need("limit_us" not in value or verdict, "copied product limit_us")
|
||||
if "limit_seconds" in value:
|
||||
need(path and path[-1] in ("projection", "phase_stop", "probe_deadline"), "limit location")
|
||||
deadline = path[-1] == "probe_deadline"
|
||||
need(not has_pass and value["limit_seconds"] == (1740 if deadline else 1800)
|
||||
and value.get("source") == (DEADLINE_SOURCE if deadline else CAP_SOURCE), "execution limit")
|
||||
for key, child in value.items():
|
||||
check_budget_scope(child, report_paths, path + (key,), raw or (raw_root and key == "data"))
|
||||
|
||||
|
||||
def check_admission(admission):
|
||||
check_budget_scope(admission)
|
||||
if "planned_argv" in admission:
|
||||
from collect_p28_01 import argv_for, parse_argv
|
||||
planned = admission["planned_argv"]
|
||||
need(len(planned) == 3 + len(admission["phases"]), "internal phase plan length")
|
||||
engine = planned[0][0]
|
||||
need("--path" in planned[0], "import stage missing")
|
||||
stage = Path(planned[0][planned[0].index("--path") + 1])
|
||||
wrapper = Path(planned[2][-1])
|
||||
need(wrapper.name == "guarded.gd", "wrapper plan target")
|
||||
equal(planned, [argv_for(engine, stage, wrapper, True),
|
||||
parse_argv(engine, stage, wrapper.parent / "measure_p28_01.gd"),
|
||||
parse_argv(engine, stage, wrapper)] +
|
||||
[argv_for(engine, stage, wrapper) for _ in admission["phases"]], "execution argv plan")
|
||||
need(admission.get("budget_verdict_scope") == VERDICT_SCOPE, "admission verdict scope")
|
||||
reused = admission["reused_r02"]
|
||||
def no_verdict_fields(value):
|
||||
if isinstance(value, dict):
|
||||
need(all(not k.startswith(("limit", "budget")) and k not in ("passed", "within_budget")
|
||||
for k in value), "verdict fields in reused_r02")
|
||||
for child in value.values():
|
||||
no_verdict_fields(child)
|
||||
elif isinstance(value, list):
|
||||
for child in value:
|
||||
no_verdict_fields(child)
|
||||
no_verdict_fields(reused)
|
||||
observations = reused["observations"]
|
||||
need(set(observations) == set(R02_FIELDS), "r02 observations")
|
||||
for name, (field, statistic, scope) in R02_FIELDS.items():
|
||||
item = observations[name]
|
||||
keys(item, ("value", "unit", "statistic", "scope", "r02_field"))
|
||||
need(number(item["value"]) and item["value"] >= 0, "r02 value")
|
||||
need((item["unit"], item["statistic"], item["scope"], item["r02_field"]) ==
|
||||
("ms", statistic, scope, field), "r02 observation provenance")
|
||||
|
||||
|
||||
def normalize_report(value, path=()):
|
||||
"""Single ingestion boundary, also used when checking raw stdout binding."""
|
||||
if isinstance(value, dict):
|
||||
return {k: (v if k in OPAQUE_FIELDS else normalize_report(v, path + (k,)))
|
||||
for k, v in value.items()}
|
||||
if isinstance(value, list):
|
||||
return [normalize_report(v, path + ("[]",)) for v in value]
|
||||
key = path[-1] if path else ""
|
||||
declared = key in INTEGER_FIELDS or key.endswith("_us")
|
||||
declared = declared or (key == "frames" and not isinstance(value, list))
|
||||
declared = declared or (len(path) >= 2 and (path[-2] == "sample_counts" or
|
||||
(key == "[]" and path[-2] in ("samples_us", "kernel_stall_samples_us"))))
|
||||
declared = declared or ("budget_verdict" in path and key == "value")
|
||||
declared = declared or ("count_ranges" in path and key in ("min", "max"))
|
||||
declared = declared or (len(path) >= 2 and path[-2] in
|
||||
("frame_wall_us", "stall_over_50000_us", "observer_us", "render_submit_wall_us")
|
||||
and key in ("p95", "max"))
|
||||
if declared and value is not None:
|
||||
need(type(value) in (int, float) and math.isfinite(value)
|
||||
and abs(value) <= 2**53 and (type(value) is int or value.is_integer()),
|
||||
"invalid declared integer: " + ".".join(path))
|
||||
return int(value)
|
||||
return value
|
||||
|
||||
|
||||
def check_budget_snapshot(snapshot):
|
||||
need(isinstance(snapshot, dict), "budget snapshot object")
|
||||
for entry in snapshot.values():
|
||||
keys(entry, ("samples_us", "lifetime_count", "reason", "provenance"))
|
||||
provenance = entry["provenance"]
|
||||
columns = ("samples_us", "lifetime_count")
|
||||
keys(provenance, columns)
|
||||
for column in columns:
|
||||
keys(provenance[column], ("dictionary_readable", "key_form"))
|
||||
need(type(provenance[column]["dictionary_readable"]) is bool,
|
||||
"budget dictionary availability")
|
||||
both_readable = all(provenance[c]["dictionary_readable"] for c in columns)
|
||||
for column, zero in (("samples_us", []), ("lifetime_count", 0)):
|
||||
observed = provenance[column]
|
||||
form, value = observed["key_form"], entry[column]
|
||||
if not observed["dictionary_readable"]:
|
||||
need(form is None and value is None, "budget unreadable dictionary must remain null")
|
||||
elif form in ("absent", "absent_means_zero_per_product"):
|
||||
if both_readable:
|
||||
need(form == "absent_means_zero_per_product" and type(value) is type(zero)
|
||||
and value == zero, "budget readable absent key must use product zero")
|
||||
else:
|
||||
need(form == "absent" and value is None,
|
||||
"budget absent key without readable dictionaries")
|
||||
else:
|
||||
need(form in ("String", "StringName"), "budget present key form")
|
||||
if value is not None:
|
||||
need((isinstance(value, list) and all(type(x) is int and x >= 0 for x in value))
|
||||
if column == "samples_us" else type(value) is int and value >= 0,
|
||||
"budget sample value")
|
||||
expected = "counts_unavailable" if entry["samples_us"] is None or entry["lifetime_count"] is None else None
|
||||
need(entry["reason"] == expected, "budget availability reason")
|
||||
|
||||
|
||||
def budget_verdict(before, after, speed):
|
||||
result = {}
|
||||
for metric, aggregation, limit in ((f"simulation_x{speed}", "average" if speed == 1 else "p95",
|
||||
4000 if speed == 1 else 12000),
|
||||
("main_thread_stall", "maximum", 50000)):
|
||||
begin = (before or {}).get(metric, {})
|
||||
end = (after or {}).get(metric, {})
|
||||
a, b = begin.get("lifetime_count"), end.get("lifetime_count")
|
||||
raw = end.get("samples_us")
|
||||
reason = None
|
||||
if type(a) is not int or type(b) is not int or b < a or not isinstance(raw, list):
|
||||
reason = "counts_unavailable"
|
||||
elif b == a:
|
||||
reason = "no_interval_samples"
|
||||
elif b - a > len(raw):
|
||||
reason = "interval_exceeds_window"
|
||||
samples = [] if reason else raw[-(b - a):]
|
||||
need(all(type(x) is int and x >= 0 for x in samples), "invalid kernel sample")
|
||||
summary = None
|
||||
if samples:
|
||||
ordered = sorted(samples)
|
||||
summary = {"count": len(samples), "average_us": sum(samples) // len(samples),
|
||||
"p95_us": ordered[(95 * len(samples) + 99) // 100 - 1],
|
||||
"maximum_us": max(samples)}
|
||||
value = summary[aggregation + "_us"] if summary else None
|
||||
result[metric] = {"source": "clock.performance_budget._samples", "aggregation": aggregation,
|
||||
"limit_us": limit, "value": value,
|
||||
"passed": value <= limit if value is not None else None,
|
||||
"window": "interval_exact_from_samples" if not reason else None,
|
||||
"reason": reason, "statistics": summary}
|
||||
return result
|
||||
|
||||
|
||||
def need(ok, reason):
|
||||
if not ok:
|
||||
raise ValueError(reason)
|
||||
|
||||
|
||||
def keys(obj, names):
|
||||
need(isinstance(obj, dict), "expected object")
|
||||
need(set(names) <= obj.keys(), "missing fields: " + str(set(names) - obj.keys()))
|
||||
|
||||
|
||||
def number(value):
|
||||
return type(value) in (int, float) and math.isfinite(value)
|
||||
|
||||
|
||||
def sha_string(value):
|
||||
return isinstance(value, str) and re.fullmatch("[0-9a-f]{64}", value) is not None
|
||||
|
||||
|
||||
def nullable(obj, field):
|
||||
need(field in obj, "missing " + field)
|
||||
if obj[field] is None:
|
||||
need(bool(obj.get(field + "_reason")), "null without reason: " + field)
|
||||
|
||||
|
||||
def stats(values, reason="no_samples"):
|
||||
if not values:
|
||||
return dict(count=0, mean=None, p95=None, max=None, reason=reason)
|
||||
ordered = sorted(values)
|
||||
return dict(count=len(values), mean=sum(values) / len(values),
|
||||
p95=ordered[math.ceil(len(values) * .95) - 1], max=max(values), reason=None)
|
||||
|
||||
|
||||
def equal(actual, expected, label):
|
||||
if isinstance(expected, dict):
|
||||
need(isinstance(actual, dict) and actual.keys() == expected.keys(), label + " keys")
|
||||
for key in expected:
|
||||
equal(actual[key], expected[key], label + "." + key)
|
||||
elif type(expected) is int:
|
||||
need(number(actual) and actual == expected, label)
|
||||
elif number(expected):
|
||||
need(number(actual) and math.isclose(actual, expected, rel_tol=1e-9, abs_tol=1e-7), label)
|
||||
else:
|
||||
need(actual == expected, label)
|
||||
|
||||
|
||||
def metadata(meta):
|
||||
keys(meta, ("machine", "build"))
|
||||
machine = meta["machine"]
|
||||
keys(machine, ("hw.model", "cores", "memory_bytes"))
|
||||
need(isinstance(machine["hw.model"], str) and machine["hw.model"], "model")
|
||||
need(type(machine["cores"]) is int and machine["cores"] > 0, "cores")
|
||||
need(type(machine["memory_bytes"]) is int and machine["memory_bytes"] > 0, "memory")
|
||||
build = meta["build"]
|
||||
keys(build, ("engine_string", "engine_hash", "engine_sha256", "project_commit", "source_map_sha256"))
|
||||
need(build["engine_string"] == "4.7.2.stable.official.ed1daf0bf", "engine string")
|
||||
need(build["engine_hash"] == "ed1daf0bf", "engine hash")
|
||||
need(re.fullmatch("[0-9a-f]{40}", build["project_commit"]) is not None, "commit")
|
||||
need(sha_string(build["engine_sha256"]) and sha_string(build["source_map_sha256"]), "build SHA")
|
||||
|
||||
|
||||
def interval(report, name):
|
||||
rows = [f for f in report["frames"] if f["segment"] == name]
|
||||
value = report[name]
|
||||
keys(value, ("frames", "actual_minutes", "start_minute", "end_minute", "start_wall", "end_wall",
|
||||
"zero_minute_frames_excluded", "count_ranges", "frame_observation"))
|
||||
need("budget" not in value and "stall_budget" not in value and "passed" not in value,
|
||||
"probe budget forbidden")
|
||||
observation = value["frame_observation"]
|
||||
need(set(observation) == {"frame_wall_us", "sim_us_per_minute", "stall_over_50000_us"},
|
||||
"frame observation must not contain verdict")
|
||||
need(value["frames"] == len(rows), "frame count")
|
||||
need(value.get("short_of_allowed_frames") ==
|
||||
sum(f["minutes_advanced"] < f["allowed_ticks_observation"] for f in rows),
|
||||
"short-of-allowed observation count")
|
||||
need(value["actual_minutes"] == sum(f["minutes_advanced"] for f in rows), "false interval duration")
|
||||
for field, expected in (("start_minute", rows[0]["game_start"] if rows else None),
|
||||
("end_minute", rows[-1]["game_end"] if rows else None),
|
||||
("start_wall", rows[0]["wall_start"] if rows else None),
|
||||
("end_wall", rows[-1]["wall"] if rows else None)):
|
||||
equal(value[field], expected, field)
|
||||
need(value.get("empty_reason") == ("no_frames" if not rows else None), "empty interval reason")
|
||||
wall = [f["frame_wall_us"] for f in rows]
|
||||
minutes = [f["frame_wall_us"] / f["minutes_advanced"] for f in rows if f["minutes_advanced"]]
|
||||
equal(observation["frame_wall_us"], stats(wall), "frame stats")
|
||||
equal(observation["sim_us_per_minute"], stats(minutes), "minute stats")
|
||||
equal(observation["stall_over_50000_us"], stats([x for x in wall if x > 50000]), "stalls")
|
||||
equal(value["observer_us"], stats([f["observer_us"] for f in rows]), "observer stats")
|
||||
equal(value["render_submit_wall_us"], stats([f["render_submit_wall_us"] for f in rows
|
||||
if f["render_submit_wall_us"] is not None]), "render submission stats")
|
||||
equal(value["time_process_seconds"], stats([f["time_process_seconds"] for f in rows]), "process")
|
||||
need(value["zero_minute_frames_excluded"] == sum(f["minutes_advanced"] == 0 for f in rows), "excluded frames")
|
||||
for kind in ("cpu", "gpu"):
|
||||
samples = [f["render_" + kind + "_raw_ms"] for f in rows]
|
||||
expected = (stats([], "zero_from_api_on_gl_compatibility_unsupported_or_unmeasured")
|
||||
if any(x <= 0 for x in samples) else stats(samples))
|
||||
equal(value["render_" + kind + "_ms"], expected, "render " + kind)
|
||||
ranges = {}
|
||||
for kind in ("registered", "active"):
|
||||
ranges[kind] = {key: {"min": min(f[kind][key] for f in rows), "max": max(f[kind][key] for f in rows)}
|
||||
for key in rows[0][kind]} if rows else {}
|
||||
equal(value["count_ranges"], ranges, "count ranges")
|
||||
|
||||
|
||||
def check_report(report):
|
||||
check_budget_scope(report, ((),))
|
||||
for name in ("budget_at_warmup_end", "budget_at_phase_end"):
|
||||
check_budget_snapshot(report[name]["data"])
|
||||
for name in RAW_FIELDS:
|
||||
keys(report[name], ("source", "data"))
|
||||
equal(report["probe_deadline"], {"limit_seconds": 1740, "source": DEADLINE_SOURCE}, "probe deadline")
|
||||
equal(report["budget_verdict"], budget_verdict(report["budget_at_warmup_end"]["data"],
|
||||
report["budget_at_phase_end"]["data"], report["speed"]), "kernel interval budget")
|
||||
keys(report, ("schema", "pid", "phase", "input", "speed", "metadata", "input_sha256",
|
||||
"frames", "stop_reason", "warmup", "measurement", "actual_minutes",
|
||||
"elapsed_wall_seconds", "kernel_at_phase_end", "operations_at_end", "end_wall",
|
||||
"window_note", "toggle_pause_calls", "performance_clear_calls", "overloads", "checkpoints"))
|
||||
need(report["schema"] == "p28-01-v1", "schema")
|
||||
need(type(report["pid"]) is int and report["pid"] > 0, "pid")
|
||||
need(report["phase"] in PHASES, "unknown phase")
|
||||
source, speed, warmup, target = PHASES[report["phase"]]
|
||||
need((report["input"], report["speed"], report["warmup_target"], report["measurement_target"]) ==
|
||||
(source, speed, warmup, target), "phase parameters")
|
||||
need(report["speed_path"] == "main._set_speed->SimulationKernel._process", "speed route")
|
||||
need(report["toggle_pause_calls"] == 0 and report["performance_clear_calls"] == 0, "forbidden mutation")
|
||||
need("512" in report["window_note"] and "warm-up" in report["window_note"], "rolling report note")
|
||||
metadata(report["metadata"])
|
||||
nullable(report, "input_sha256")
|
||||
if source == "R":
|
||||
need(report["input_sha256"] == "eb4f770071eb07cd7e136fe54c3f78768cc8de7a4264e62a9af1f06a8f90f9bd",
|
||||
"input SHA")
|
||||
else:
|
||||
need(report["input_sha256"] is None, "fresh input cannot claim fixture")
|
||||
frames = report["frames"]
|
||||
need(isinstance(frames, list), "frames")
|
||||
need(report["stop_reason"] in ("complete", "input_shortfall", "simulation_overloaded",
|
||||
"operations_runtime_failure", "operations_terminal", "operations_pause", "clock_paused",
|
||||
"camera_changed", "timeout"), "stop reason")
|
||||
# Shortfall reports preserve partial startup observations rather than invent them.
|
||||
if report["stop_reason"] == "input_shortfall":
|
||||
need(not frames and report["actual_minutes"] == 0, "shortfall after measurement")
|
||||
return
|
||||
keys(report, ("startup", "preparation_sha256", "seed", "rng_streams", "camera",
|
||||
"start_minute", "start_wall", "speed_after", "paused_after_speed", "renderer"))
|
||||
need(sha_string(report["preparation_sha256"]), "preparation SHA")
|
||||
need(report["speed_after"] == speed and report["paused_after_speed"] is False, "start speed")
|
||||
startup = report["startup"]
|
||||
keys(startup, ("pause_fresh_opening", "save_policy", "save_policy_after", "start_report",
|
||||
"operations_enabled_after", "opening_minute"))
|
||||
need(startup["pause_fresh_opening"]["return"] is None and
|
||||
startup["pause_fresh_opening"]["reason"] == "void_function", "pause return invented")
|
||||
need(type(startup["pause_fresh_opening"]["paused_before"]) is bool and
|
||||
type(startup["pause_fresh_opening"]["paused_after"]) is bool, "pause observations")
|
||||
equal(startup["save_policy"], {"return": None, "reason": "return_discarded_by_main(main.gd:222)"},
|
||||
"save policy return")
|
||||
need(isinstance(startup["save_policy_after"], dict), "save policy actual")
|
||||
start = startup["start_report"]
|
||||
need(isinstance(start, dict), "start_report must retain dictionary")
|
||||
if not start:
|
||||
need(bool(startup.get("start_report_reason")), "empty start report reason")
|
||||
if source == "O":
|
||||
need(start.get("ok") is True and startup["operations_enabled_after"] is True
|
||||
and startup["opening_minute"] == 360, "O startup")
|
||||
else:
|
||||
need(start.get("code") == "INVALID_START_TIME" and startup["operations_enabled_after"] is False, "R startup")
|
||||
previous = report["start_minute"]
|
||||
previous_wall = report["start_wall"]
|
||||
previous_drawing = None
|
||||
previous_counts = None
|
||||
keys(report, ("baseline",))
|
||||
previous_pair = report["baseline"]
|
||||
check_pair(previous_pair)
|
||||
need(previous_pair.get("excluded_from_statistics") is True and
|
||||
previous_pair["minute_after"] == previous and previous_pair.get("wall") == previous_wall,
|
||||
"missing/exposed baseline")
|
||||
for index, frame in enumerate(frames):
|
||||
check_pair(frame, previous_pair, speed)
|
||||
previous_pair = frame
|
||||
keys(frame, ("frame", "game_start", "game_end", "minutes_advanced", "frame_wall_us", "wall", "wall_start",
|
||||
"speed", "paused", "segment", "registered", "active", "drawn", "seed", "rng_streams",
|
||||
"metadata", "input_sha256", "preparation_sha256", "render_cpu_raw_ms",
|
||||
"render_gpu_raw_ms", "time_process_seconds", "observer_us", "stall_causes",
|
||||
"kernel_stall_samples_us", "is_minute_sample", "counts_from_minute",
|
||||
"render_submit_wall_us", "render_time_source"))
|
||||
need(type(frame["observer_us"]) is int and frame["observer_us"] >= 0, "observer cost")
|
||||
nullable(frame, "render_submit_wall_us")
|
||||
need(frame["render_submit_wall_us"] is None or
|
||||
(type(frame["render_submit_wall_us"]) is int and frame["render_submit_wall_us"] >= 0),
|
||||
"render submit duration")
|
||||
need(frame["render_time_source"] == "RenderingServer.viewport_get_measured_render_time",
|
||||
"render submit is not GPU time")
|
||||
need(frame["is_minute_sample"] is (frame["minutes_advanced"] > 0), "minute sampling")
|
||||
if frame["is_minute_sample"]:
|
||||
need(frame["counts_from_minute"] == frame["game_end"], "minute sample provenance")
|
||||
elif previous_counts:
|
||||
equal({k: frame[k] for k in previous_counts}, previous_counts, "carried minute counts")
|
||||
previous_counts = {k: frame[k] for k in ("registered", "active", "rng_streams", "counts_from_minute")}
|
||||
saves = [s for s in report["save_events"] if s["frame"] == index]
|
||||
kernel_stalls = frame["kernel_stall_samples_us"]
|
||||
need(isinstance(kernel_stalls, list) and all(type(x) is int and x >= 0 for x in kernel_stalls),
|
||||
"kernel frame stalls")
|
||||
cause = bool(saves) and (frame["frame_wall_us"] > 50000 or any(x > 50000 for x in kernel_stalls))
|
||||
equal(frame["stall_causes"], ["save_operation_completed"] if cause else [], "save stall cause")
|
||||
need(frame["frame"] == index and frame["game_start"] == previous, "frame continuity")
|
||||
need(type(frame["minutes_advanced"]) is int and 0 <= frame["minutes_advanced"] <= 8, "kernel frame cap")
|
||||
need(frame["game_end"] - previous == frame["minutes_advanced"], "minute delta")
|
||||
need(frame["speed"] == speed, "Clock speed disagreement")
|
||||
need(number(frame["frame_wall_us"]) and frame["frame_wall_us"] > 0, "frame timing")
|
||||
need(number(frame["wall"]) and frame["wall"] >= previous_wall, "wall chronology")
|
||||
equal(frame["wall_start"], previous_wall, "frame wall continuity")
|
||||
need(frame["segment"] == ("warmup" if previous - report["start_minute"] < warmup else "measurement"),
|
||||
"segment boundary")
|
||||
for field in ("seed", "rng_streams"):
|
||||
nullable(frame, field)
|
||||
need(frame["seed"] is None or type(frame["seed"]) is int, "seed is not scalar")
|
||||
need(frame["rng_streams"] is None or isinstance(frame["rng_streams"], dict), "RNG state")
|
||||
need(frame["seed"] != frame["rng_streams"] or frame["seed"] is None, "seed/RNG conflation")
|
||||
metadata(frame["metadata"])
|
||||
need(frame["metadata"] == report["metadata"] and frame["seed"] == report["seed"], "sample metadata")
|
||||
need(frame["input_sha256"] == report["input_sha256"] and
|
||||
frame["preparation_sha256"] == report["preparation_sha256"], "sample SHA")
|
||||
need(set(frame["registered"]) == {"guests", "staff", "facilities", "walkable_cells"}, "registered schema")
|
||||
need(set(frame["active"]) == {"guests", "staff", "facilities"}, "active copied from registered")
|
||||
for kind in ("registered", "active"):
|
||||
need(all(type(x) is int and x >= 0 for x in frame[kind].values()), "counts")
|
||||
need(all(frame["active"][k] <= frame["registered"][k] for k in frame["active"]), "active > registered")
|
||||
drawn = frame["drawn"]
|
||||
keys(drawn, ("actors_in_layer", "actors_in_viewport", "facilities", "cells",
|
||||
"observation_serial", "carried_from_serial", "camera", "cell_count_from_hash"))
|
||||
need(sha_string(drawn["cell_count_from_hash"]), "cell cache provenance")
|
||||
for k in ("actors_in_layer", "actors_in_viewport", "facilities", "cells", "observation_serial"):
|
||||
nullable(drawn, k)
|
||||
need(drawn[k] is None or (type(drawn[k]) is int and drawn[k] >= 0), "draw counts")
|
||||
if drawn["actors_in_viewport"] is not None and drawn["actors_in_layer"] is not None:
|
||||
need(drawn["actors_in_viewport"] <= drawn["actors_in_layer"], "draw viewport")
|
||||
if previous_drawing and drawn["cell_count_from_hash"] == previous_drawing["cell_count_from_hash"]:
|
||||
equal(drawn["cells"], previous_drawing["cells"], "cached cell count changed")
|
||||
if previous_drawing and drawn["observation_serial"] is not None and \
|
||||
drawn["observation_serial"] == previous_drawing["observation_serial"]:
|
||||
need(drawn["carried_from_serial"] == drawn["observation_serial"], "carried observation called fresh")
|
||||
for k in ("actors_in_layer", "actors_in_viewport", "facilities"):
|
||||
equal(drawn[k], previous_drawing[k], "carried changed")
|
||||
else:
|
||||
need(drawn["carried_from_serial"] is None, "fresh drawing marked carried")
|
||||
need(drawn["camera"] == report["camera"], "camera changed")
|
||||
for field in ("render_cpu_raw_ms", "render_gpu_raw_ms", "time_process_seconds"):
|
||||
need(number(frame[field]) and frame[field] >= 0, "raw time")
|
||||
if frame["paused"]:
|
||||
need(index == len(frames) - 1 and report["stop_reason"] != "complete", "continued after pause")
|
||||
previous, previous_wall, previous_drawing = frame["game_end"], frame["wall"], drawn
|
||||
need(report["actual_minutes"] == previous - report["start_minute"], "total minutes")
|
||||
need(number(report["elapsed_wall_seconds"]) and report["elapsed_wall_seconds"] >= 0, "elapsed")
|
||||
if report["overloads"]:
|
||||
need(report["stop_reason"] == "simulation_overloaded", "ignored overload")
|
||||
need(all(x["frame"] == len(frames) - 1 for x in report["overloads"]), "continued after overload")
|
||||
if report["stop_reason"] == "complete":
|
||||
need(report["actual_minutes"] >= warmup + target and report["measurement"]["actual_minutes"] >= target,
|
||||
"incomplete interval called complete")
|
||||
need("kernel_at_warmup_end" in report, "warmup kernel report")
|
||||
for name in ("warmup", "measurement"):
|
||||
interval(report, name)
|
||||
need(report["budget_scope"] == "kernel process_real_delta only; excludes observer_us", "budget scope")
|
||||
need(report["render_submit_note"] == "CPU側の描画提出wall、GPU時間ではない", "render submit scope")
|
||||
equal(report["budget_verdict"], budget_verdict(report["budget_at_warmup_end"]["data"],
|
||||
report["budget_at_phase_end"]["data"], speed), "kernel interval budget")
|
||||
need(number(report["startup_wall_seconds"]) and report["startup_wall_seconds"] >= 0, "startup timing")
|
||||
for save in report["save_events"]:
|
||||
keys(save, ("frame", "game_minute", "ok", "operation", "path", "sample_counts"))
|
||||
need(type(save["frame"]) is int and 0 <= save["frame"] < len(frames), "save frame")
|
||||
for field in ("ok", "operation", "path"):
|
||||
nullable(save, field)
|
||||
need(save["path"] is None or
|
||||
(isinstance(save["path"], str) and not Path(save["path"]).is_absolute()
|
||||
and ".." not in Path(save["path"]).parts and not save["path"].startswith("user://")),
|
||||
"save path must be private relative")
|
||||
if report.get("label") == "200客":
|
||||
need(frames and all(f["registered"]["guests"] == 200 for f in frames), "200 guest claim")
|
||||
|
||||
|
||||
def check_parse_process(row, directory):
|
||||
from collect_p28_01 import PARSE_NA, diagnostics, sha
|
||||
equal(row["parse_na"], PARSE_NA, "parse-only N/A reasons")
|
||||
need(not any(k in row for k in ("report", "guard", "audio_close", "runtime_profile")),
|
||||
"parse claims runtime evidence")
|
||||
need(row["passed"] is True, "parse failed")
|
||||
need(row["source_preserved"] is True and row["player_preserved"] is True, "parse preservation")
|
||||
need(set(row["raw"]) == {"stdout.log", "stderr.log", "engine-native.log"}, "parse raw missing")
|
||||
text = ""
|
||||
for name, pin in row["raw"].items():
|
||||
path = directory / row["phase"] / name
|
||||
need(pin == {"sha256": sha(path), "bytes": path.stat().st_size}, "parse raw pin")
|
||||
text += path.read_text(errors="replace") + "\n"
|
||||
need(not diagnostics(text) and row["diagnostics"] == [], "parse diagnostics")
|
||||
need("P28_01_PERF_REPORT=" not in text and "P28_01_AUDIO_CLOSE=" not in text,
|
||||
"runtime markers during parse")
|
||||
process, capture = row["process"], row["capture"]
|
||||
need(process["exit_code"] == 0 and process["helper_exit_code"] == 0 and
|
||||
process["timed_out"] is False and process["error_lines"] == [] and
|
||||
process["owned_popen_ended"] is True and process["old_helper_engine_ended"] is True and
|
||||
process["start_receipt_written"] is True and process["end_receipt_written"] is True and
|
||||
process["lifecycle_error"] is None and process["external_process_or_group_signals"] == 0,
|
||||
"parse process failure")
|
||||
need(capture["passed"] is True and capture["all_captured_ended"] is True and
|
||||
capture["actual_captured_pids"] == [process["pid"]] and
|
||||
not capture["outer_cleanup_errors"] and capture["foreign_process_or_group_signals"] == 0 and
|
||||
all(c["engine_ended"] is True and not c["outer_cleanup_errors"] and
|
||||
not c["cleanup"].get("errors") for c in capture["cleanup"]), "parse ownership")
|
||||
equal(json.loads((directory / row["phase"] / "process.json").read_text()), process,
|
||||
"parse process binding")
|
||||
for name, pin in row["owned_files"].items():
|
||||
path = directory / row["phase"] / name
|
||||
need(pin == {"sha256": sha(path), "bytes": path.stat().st_size}, "parse owned pin")
|
||||
from collect_p28_01 import OWNED_FILES
|
||||
need(set(row["owned_files"]) == set(OWNED_FILES), "parse ownership evidence missing")
|
||||
saved_capture = json.loads((directory / row["phase"] / "outer-owned-finally.json").read_text())
|
||||
equal(saved_capture, {k: v for k, v in capture.items()
|
||||
if k not in ("passed", "outer_receipt_written", "outer_receipt_error")},
|
||||
"parse capture binding")
|
||||
|
||||
|
||||
def check_parse(row, directory, source):
|
||||
from collect_p28_01 import parse_argv, sha, digest
|
||||
check_parse_process(row, directory)
|
||||
target = directory / ("measure_p28_01.gd" if row["phase"] == "parse-base" else "guarded.gd")
|
||||
command = parse_argv(row["argv"][0], directory / "stage", target)
|
||||
command += ["--log-file", str(directory / row["phase"] / "engine-native.log"), "--verbose"]
|
||||
equal(row["argv"], command, "parse argv")
|
||||
equal(row["process"]["argv"], command, "parse owned argv")
|
||||
equal(row["script_pins"], {str(directory / n): sha(directory / n)
|
||||
for n in ("measure_p28_01.gd", "guarded.gd", "profile_guard.gd")},
|
||||
"parse script pins")
|
||||
startup = row["startup_project"]
|
||||
need(not (directory / (row["phase"] + "-guard.json")).exists(), "Guard ran during parse")
|
||||
path = directory / (row["phase"] + "-startup-project.godot")
|
||||
need(startup["path"] == str(path) and startup["sha256"] == sha(path) and
|
||||
startup["fresh_before"] is True, "parse startup evidence")
|
||||
from collect_p28_01 import load_tool, EXTERNAL_IMPORTS
|
||||
standard = load_tool(EXTERNAL_IMPORTS[0])
|
||||
public = (directory / "stage/project.godot").read_bytes()
|
||||
need(path.read_bytes() == standard.private_project(public, startup["name"]) and
|
||||
startup["profile"] == str(standard.profile_path(startup["name"])) and
|
||||
startup["restored_sha256"] == hashlib.sha256(public).hexdigest(), "parse private project")
|
||||
stage_inventory = standard.inventory(directory / "stage", stage=True)
|
||||
need(row["stage_before_sha256"] == row["stage_after_sha256"] ==
|
||||
digest(stage_inventory), "parse stage preservation")
|
||||
need(all(stage_inventory.get(k) == v for k, v in source.items()), "parse product preservation")
|
||||
|
||||
|
||||
def check_receipt(receipt, directory):
|
||||
from collect_p28_01 import OWNED_FILES, projection, extrapolation, guard_gate
|
||||
check_budget_scope(receipt, tuple(("phases", i, "report")
|
||||
for i, row in enumerate(receipt["phases"]) if "report" in row))
|
||||
check_admission(receipt["admission"])
|
||||
equal(receipt["admission"], json.loads((directory / "admission.json").read_text()), "admission binding")
|
||||
need(receipt["source_before"] == receipt["source_after"], "source map changed")
|
||||
need(receipt["default_player_before"] == receipt["default_player_after"], "player changed")
|
||||
need(not receipt["cleanup_errors"] and all(r["engine_ended"] is True for r in receipt["cleanup"]), "cleanup")
|
||||
need(receipt["error"] is None and receipt["passed"] is True, "failed collection")
|
||||
probe = receipt["probe_check"]
|
||||
text = (directory / "measure_p28_01.gd").read_text()
|
||||
need(probe == {"sha256": hashlib.sha256(text.encode()).hexdigest(), "passed": True},
|
||||
"execution probe pin/check")
|
||||
check_probe(text)
|
||||
rows = receipt["phases"]
|
||||
from collect_p28_01 import PARSE_PHASES
|
||||
need([r["phase"] for r in rows] == ["import", *PARSE_PHASES, *PHASES], "phase order")
|
||||
parse_profiles = [r["startup_project"]["profile"] for r in rows if r["phase"] in PARSE_PHASES]
|
||||
need(len(set(parse_profiles)) == len(parse_profiles), "parse profiles reused")
|
||||
for row in rows:
|
||||
equal(row["phase_stop"], {"limit_seconds": 1800, "source": CAP_SOURCE}, "phase cap origin")
|
||||
forecast = None
|
||||
if row["phase"] in ("P1", "P2", "P3", "P4"):
|
||||
source = PHASES[row["phase"]][0]
|
||||
stage0 = next(r for r in rows if r["phase"] == source + "0")
|
||||
forecast = projection(stage0)
|
||||
equal(row["projection"], forecast, "phase projection")
|
||||
if row.get("stop_reason") == "projected_over_limit":
|
||||
need(forecast is not None and forecast["stop_reason"] == "projected_over_limit"
|
||||
and row["engines_started"] == 0 and row["passed"] is False,
|
||||
"invalid skipped phase")
|
||||
need("report" not in row and "raw" not in row, "skipped phase claims execution")
|
||||
continue
|
||||
need(forecast is None or forecast["stop_reason"] is None, "started over projected limit")
|
||||
need(row["timeout_seconds"] == 1800 and row["passed"] is True, "phase timeout/pass")
|
||||
need(row["probe_check"] == probe, "phase execution copy check")
|
||||
need(set(row["owned_files"]) == set(OWNED_FILES), "owned evidence missing")
|
||||
for name, pin in row["owned_files"].items():
|
||||
data = (directory / row["phase"] / name).read_bytes()
|
||||
need(hashlib.sha256(data).hexdigest() == pin["sha256"] and len(data) == pin["bytes"],
|
||||
"owned evidence changed")
|
||||
for name, pin in row["raw"].items():
|
||||
data = (directory / row["phase"] / name).read_bytes()
|
||||
need(hashlib.sha256(data).hexdigest() == pin["sha256"] and len(data) == pin["bytes"], "raw SHA")
|
||||
if row["phase"] == "import":
|
||||
continue
|
||||
if row["phase"] in PARSE_PHASES:
|
||||
check_parse(row, directory, receipt["source_before"])
|
||||
index = 1 + PARSE_PHASES.index(row["phase"])
|
||||
equal(row["argv"][:-3], receipt["admission"]["planned_argv"][index], "parse plan binding")
|
||||
continue
|
||||
guard_gate(row["guard"], Path(row["runtime_profile"]), row["runtime_name"])
|
||||
stdout = (directory / row["phase"] / "stdout.log").read_text()
|
||||
printed = [normalize_report(json.loads(x[len(PREFIX):])) for x in stdout.splitlines() if x.startswith(PREFIX)]
|
||||
need(printed == [row["report"]], "raw/report mismatch")
|
||||
equal(audio_close(stdout, row["report"]), row["audio_close"], "audio close binding")
|
||||
need(row["capture"]["actual_captured_pids"] == [row["report"]["pid"]], "PID mismatch")
|
||||
check_report(row["report"])
|
||||
if row["phase"] in ("R0", "O0"):
|
||||
equal(row["extrapolation"], extrapolation(row["report"]), "extrapolation")
|
||||
|
||||
|
||||
def decode_f64(value):
|
||||
need(isinstance(value, str) and re.fullmatch("[0-9a-f]{16}", value) is not None,
|
||||
"missing/malformed f64 evidence")
|
||||
result = struct.unpack("<d", bytes.fromhex(value))[0]
|
||||
need(math.isfinite(result), "nonfinite f64 evidence")
|
||||
return result
|
||||
|
||||
|
||||
def check_pair(pair, previous=None, speed=None):
|
||||
keys(pair, ("engine_frame_pre", "engine_frame_post", "pre_order", "post_order",
|
||||
"paused_at_frame_start", "speed_at_frame_start", "skip_at_frame_start",
|
||||
"acc_at_frame_start_f64_hex", "minute_at_frame_start", "process_delta_f64_hex",
|
||||
"post_delta_f64_hex", "paused_after", "minute_after", "acc_after_f64_hex"))
|
||||
for key in ("engine_frame_pre", "engine_frame_post", "pre_order", "post_order",
|
||||
"minute_at_frame_start", "minute_after", "speed_at_frame_start"):
|
||||
need(type(pair[key]) is int and pair[key] >= 0, "invalid pair integer")
|
||||
for key in ("paused_at_frame_start", "paused_after", "skip_at_frame_start"):
|
||||
need(type(pair[key]) is bool, "invalid pair boolean")
|
||||
need(pair["engine_frame_pre"] == pair["engine_frame_post"] and
|
||||
pair["post_order"] == pair["pre_order"] + 1, "unpaired frame")
|
||||
acc = decode_f64(pair["acc_at_frame_start_f64_hex"])
|
||||
after = decode_f64(pair["acc_after_f64_hex"])
|
||||
delta = decode_f64(pair["process_delta_f64_hex"])
|
||||
decode_f64(pair["post_delta_f64_hex"])
|
||||
need(pair["process_delta_f64_hex"] == pair["post_delta_f64_hex"], "different process deltas")
|
||||
if previous is None:
|
||||
# First complete pair is an excluded anchor, never an inferred pre-begin state.
|
||||
return
|
||||
need(pair["engine_frame_pre"] == previous["engine_frame_post"] + 1 and
|
||||
pair["pre_order"] == previous["post_order"] + 1, "frame order gap")
|
||||
need(pair["minute_at_frame_start"] == previous["minute_after"] and
|
||||
pair["acc_at_frame_start_f64_hex"] == previous["acc_after_f64_hex"], "pair continuity")
|
||||
need(pair["speed_at_frame_start"] == speed, "cadence speed")
|
||||
advanced = pair["minute_after"] - pair["minute_at_frame_start"]
|
||||
allowed = 0
|
||||
if not pair["skip_at_frame_start"] and not pair["paused_at_frame_start"] and speed > 0 and delta > 0:
|
||||
acc += delta * speed
|
||||
allowed = min(int(acc / (10.0 / 60.0)), 8)
|
||||
need(0 <= advanced <= allowed, "cadence upper bound")
|
||||
need(type(pair.get("allowed_ticks_observation")) is int and
|
||||
pair["allowed_ticks_observation"] == allowed, "allowed tick observation")
|
||||
for _ in range(advanced):
|
||||
acc -= 10.0 / 60.0
|
||||
need(struct.pack("<d", acc).hex() == pair["acc_after_f64_hex"], "accumulator recurrence")
|
||||
need(advanced == pair["minutes_advanced"] and
|
||||
pair["game_start"] == pair["minute_at_frame_start"] and
|
||||
pair["game_end"] == pair["minute_after"] and pair["paused"] is pair["paused_after"],
|
||||
"pair/frame reconciliation")
|
||||
|
||||
|
||||
def audio_close(stdout, report):
|
||||
prefix = "P28_01_AUDIO_CLOSE="
|
||||
rows = [json.loads(line[len(prefix):]) for line in stdout.splitlines() if line.startswith(prefix)]
|
||||
need(len(rows) == 1, "exactly one AUDIO_CLOSE required")
|
||||
row = rows[0]
|
||||
need(type(report.get("main_added")) is bool, "missing main-added observation")
|
||||
need(type(report.get("audio_owner_present")) is bool, "missing audio owner observation")
|
||||
if row.get("coordinator") is True:
|
||||
need(report["main_added"] and report["audio_owner_present"] and row.get("passed") is True and
|
||||
row.get("deadline_reached") is False and row.get("all_owned_targets_released") is True,
|
||||
"audio close drain failed")
|
||||
need(type(row.get("waited_actual_us")) in (int, float) and
|
||||
math.isfinite(row["waited_actual_us"]) and row["waited_actual_us"] >= 0,
|
||||
"audio close wait")
|
||||
else:
|
||||
need(not report["main_added"] or not report["audio_owner_present"], "owner drain bypassed")
|
||||
need(row == {"coordinator": False, "reason":
|
||||
"no_audio_owner" if report["main_added"] else "main_not_added"},
|
||||
"invalid no-owner receipt")
|
||||
return row
|
||||
|
||||
|
||||
def check_probe(text):
|
||||
# The SceneTree bootstrap name is required; all other initialization is forbidden.
|
||||
body = text.replace("func _initialize()", "func BOOTSTRAP()")
|
||||
for token in FORBIDDEN:
|
||||
need(token not in body, "forbidden probe token: " + token)
|
||||
need('main.call("_set_speed", speed)' in body, "missing UI speed path")
|
||||
close_branch = '''\tif not is_instance_valid(main) or not main.is_inside_tree():
|
||||
\t\temit_audio_close({"coordinator": false, "reason": "main_not_added"})
|
||||
\t\tquit(0)
|
||||
\telif not root.has_meta(UIAudioOwnerShutdownScript.OWNER_META):
|
||||
\t\temit_audio_close({"coordinator": false, "reason": "no_audio_owner"})
|
||||
\t\tquit(0)
|
||||
\telse:
|
||||
\t\tmain.ui_audio_window_close_drained.connect(emit_audio_close, CONNECT_ONE_SHOT)
|
||||
\t\troot.close_requested.emit()'''
|
||||
need(close_branch in body, "missing product close branch")
|
||||
need(re.search(r"\bquit\s*\(", body.replace(close_branch, "")) is None,
|
||||
"quit outside no-main/no-owner branches")
|
||||
need("tests/helpers/ui_audio_fixture_cleanup" not in body, "fixture close forbidden")
|
||||
for token in ("pre_observer.process_priority = -1000000", "observer.process_priority = 100000",
|
||||
"PackedFloat64Array([value]).to_byte_array().hex_encode()", "anchor_baseline()"):
|
||||
need(token in body, "missing passive pair observation: " + token)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("receipt", type=Path)
|
||||
args = parser.parse_args()
|
||||
check_receipt(json.loads(args.receipt.read_text()), args.receipt.parent)
|
||||
print("P28-01 structural consistency only; execution/static-review gates remain separate.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+456
@@ -0,0 +1,456 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Owned windowed P28-01 performance collection. Only Root executes --run."""
|
||||
import argparse
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
from check_p28_01_report import check_report, check_receipt, check_probe, normalize_report, check_admission, audio_close, R02_FIELDS, VERDICT_SCOPE, CAP_SOURCE
|
||||
HERE = Path(__file__).resolve().parent
|
||||
ROOT = HERE.parents[5]
|
||||
FIXTURE_SHA = "eb4f770071eb07cd7e136fe54c3f78768cc8de7a4264e62a9af1f06a8f90f9bd"
|
||||
ENGINE_SHA = "c7cccbf8fb143e34e02fd6521e09be2c2b974f0d5db080b19071c9c570718ccf"
|
||||
VERSION = "4.7.2.stable.official.ed1daf0bf"
|
||||
LIMIT = 1800
|
||||
PHASES = (("R0", "R", 1, 0, 10), ("P1", "R", 1, 30, 120),
|
||||
("P2", "R", 3, 30, 120), ("O0", "O", 1, 0, 10),
|
||||
("P3", "O", 1, 30, 120), ("P4", "O", 3, 30, 120))
|
||||
EXTERNAL_IMPORTS = ("tools/run_all_tests.py", "tools/regression/owned_outer.py")
|
||||
PARSE_PHASES = ("parse-base", "parse-wrapper")
|
||||
PARSE_NA = {key: {"value": None, "reason": "--check-only does not run Runtime or Guard"}
|
||||
for key in ("PERF_REPORT", "AUDIO_CLOSE", "Guard.activate", "runtime_profile")}
|
||||
|
||||
|
||||
def require(value, message):
|
||||
if not value:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def sha(path):
|
||||
return hashlib.sha256(Path(path).read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def digest(value):
|
||||
return hashlib.sha256(json.dumps(value, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
|
||||
|
||||
|
||||
def write(path, value):
|
||||
with Path(path).open("x", encoding="utf-8") as stream:
|
||||
json.dump(value, stream, ensure_ascii=False, indent=2, allow_nan=False)
|
||||
stream.write("\n")
|
||||
|
||||
|
||||
def now():
|
||||
return datetime.now(timezone.utc).isoformat()
|
||||
|
||||
|
||||
def load_tool(relative):
|
||||
require(relative in EXTERNAL_IMPORTS, "external import outside allowlist")
|
||||
spec = importlib.util.spec_from_file_location("p28_" + Path(relative).stem, ROOT / relative)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def profile_files(directory):
|
||||
directory = Path(directory)
|
||||
result = {}
|
||||
if not directory.exists():
|
||||
return result
|
||||
require(not directory.is_symlink(), "profile/stage symlink")
|
||||
for path in sorted(directory.rglob("*")):
|
||||
require(not path.is_symlink(), "nested profile/stage symlink")
|
||||
if path.is_file():
|
||||
result[path.relative_to(directory).as_posix()] = {"sha256": sha(path), "bytes": path.stat().st_size}
|
||||
return result
|
||||
|
||||
|
||||
def live_engines():
|
||||
rows = subprocess.check_output(["ps", "-axo", "pid=,comm="], text=True)
|
||||
return [line for line in rows.splitlines()
|
||||
if re.search(r"(?:^|/)(?:Godot|godot)(?:[ ._-]|$)", line.split(None, 1)[-1])]
|
||||
|
||||
|
||||
def no_live_engine():
|
||||
require(not live_engines(), "another engine exists")
|
||||
|
||||
|
||||
def diagnostics(text):
|
||||
return [line for line in text.splitlines() if re.search(
|
||||
r"ERROR:|SCRIPT ERROR:|Parse Error:|^\s*FAIL(?:ED)?[: ]|"
|
||||
r"Unicode parsing error|ObjectDB instances leaked|instances were leaked|"
|
||||
r"resources? still in use at exit|Leaked instance:", line)]
|
||||
|
||||
|
||||
def source_map(standard, root):
|
||||
return {name: {"bytes": row["bytes"], "sha256": row["sha256"]}
|
||||
for name, row in standard.source_refs(root).items()}
|
||||
|
||||
|
||||
def argv_for(engine, stage, wrapper, importing=False):
|
||||
if importing:
|
||||
return [str(engine), "--headless", "--editor", "--import", "--path", str(stage), "--quit"]
|
||||
return [str(engine), "--path", str(stage), "--script", str(wrapper)]
|
||||
|
||||
|
||||
def parse_argv(engine, stage, target):
|
||||
return [str(engine), "--headless", "--path", str(stage), "--check-only", "--script", str(target)]
|
||||
|
||||
|
||||
def execution_plan(engine, stage, wrapper):
|
||||
return [argv_for(engine, stage, wrapper, True),
|
||||
parse_argv(engine, stage, wrapper.parent / "measure_p28_01.gd"),
|
||||
parse_argv(engine, stage, wrapper)] + [argv_for(engine, stage, wrapper) for _ in PHASES]
|
||||
|
||||
|
||||
def run_private_engine(standard, outer, command, folder, environment, stage, public, name):
|
||||
"""Keep launch settings outside the owned folder and restore even on failure."""
|
||||
project = stage / "project.godot"
|
||||
private = standard.private_project(public, name)
|
||||
startup = standard.profile_path(name)
|
||||
require(not startup.exists(), "startup profile not fresh")
|
||||
evidence = folder.parent / (folder.name + "-startup-project.godot")
|
||||
evidence.write_bytes(private)
|
||||
try:
|
||||
project.write_bytes(private)
|
||||
require(project.read_bytes() == private, "private startup project differs")
|
||||
row = run_engine(outer, command, folder, environment)
|
||||
require(project.read_bytes() == private, "startup project changed during execution")
|
||||
finally:
|
||||
project.write_bytes(public)
|
||||
row["startup_project"] = {"path": str(evidence), "sha256": sha(evidence),
|
||||
"name": name, "profile": str(startup), "fresh_before": True,
|
||||
"restored_sha256": sha(project)}
|
||||
require(project.read_bytes() == public, "project restoration failed")
|
||||
return row
|
||||
|
||||
|
||||
def checked_probe(path):
|
||||
text = path.read_text()
|
||||
check_probe(text)
|
||||
return {"sha256": sha(path), "passed": True}
|
||||
|
||||
|
||||
def projection(stage0):
|
||||
evidence = stage0["extrapolation"]
|
||||
seconds = (evidence["seconds_per_phase"] + stage0["report"]["startup_wall_seconds"]
|
||||
if evidence["seconds_per_phase"] is not None else None)
|
||||
return {"seconds_per_phase": evidence["seconds_per_phase"],
|
||||
"startup_wall_seconds": stage0["report"]["startup_wall_seconds"],
|
||||
"projected_total_seconds": seconds, "limit_seconds": LIMIT, "source": CAP_SOURCE,
|
||||
"stage0_phase": stage0["phase"],
|
||||
"stop_reason": "projected_over_limit" if seconds is not None and seconds > LIMIT else None}
|
||||
|
||||
|
||||
OWNED_FILES = ("process.json", "started.json", "ended.json", "adapter-owned-cleanup.json",
|
||||
"outer-owned-finally.json")
|
||||
|
||||
|
||||
def extrapolation(report):
|
||||
minutes = report["actual_minutes"]
|
||||
return {"observed_minutes": minutes, "observed_seconds": report["elapsed_wall_seconds"],
|
||||
"target_minutes": 150,
|
||||
"seconds_per_phase": report["elapsed_wall_seconds"] * 150 / minutes if minutes else None,
|
||||
"reason": None if minutes else "no_minutes_observed",
|
||||
"method": "stage0 wall seconds per game minute; x1/x3 same conservative estimate"}
|
||||
|
||||
|
||||
def stage_gate(before, after, importing=False):
|
||||
require(all(after.get(k) == v for k, v in before.items()), "stage files changed")
|
||||
added = set(after) - set(before)
|
||||
require(not added or importing and all(
|
||||
(n.endswith(".gd.uid") and n[:-4] in before) or
|
||||
(n.endswith(".import") and n[:-7] in before) for n in added), "unexpected stage files")
|
||||
|
||||
|
||||
def guard_gate(value, runtime, name):
|
||||
expected = {"verified": True, "conditions": [True] * 5, "conditions_checked": 5,
|
||||
"actual_user_data_dir": str(runtime), "expected_user_data_dir": str(runtime),
|
||||
"globalized_probe": str(runtime / "guard-probe"), "custom_user_dir_name": name,
|
||||
"fresh_before_parent_initialize": True, "profile_writes_before_guard": 0,
|
||||
"project_settings_persisted": False}
|
||||
require(all(k in value and type(value[k]) is type(v) and value[k] == v
|
||||
for k, v in expected.items()), "profile guard failure")
|
||||
|
||||
|
||||
def bound_report(folder, row):
|
||||
printed = [line.split("=", 1)[1] for line in (folder / "stdout.log").read_text().splitlines()
|
||||
if line.startswith("P28_01_PERF_REPORT=")]
|
||||
require(len(printed) == 1, "exactly one raw report required")
|
||||
report = normalize_report(json.loads(printed[0]))
|
||||
require(row["capture"]["actual_captured_pids"] == [report["pid"]], "PID binding")
|
||||
try:
|
||||
row["audio_close"] = audio_close((folder / "stdout.log").read_text(), report)
|
||||
except (ValueError, KeyError, TypeError):
|
||||
row.update(passed=False, stop_reason="engine_failure")
|
||||
raise
|
||||
return report
|
||||
|
||||
|
||||
def phase_schedule(rows):
|
||||
"""Caller appends each executed result; exceptions abort the consuming loop."""
|
||||
for index, phase in enumerate((("import", "", 0, 0, 0),) +
|
||||
tuple((label, "", 0, 0, 0) for label in PARSE_PHASES) + PHASES):
|
||||
label, source, _, _, _ = phase
|
||||
forecast = None
|
||||
if label in ("P1", "P2", "P3", "P4"):
|
||||
stage0 = next(r for r in rows if r["phase"] == source + "0")
|
||||
forecast = projection(stage0)
|
||||
if forecast["stop_reason"]:
|
||||
rows.append({"phase": label, "passed": False, "engines_started": 0,
|
||||
"stop_reason": "projected_over_limit", "projection": forecast,
|
||||
"phase_stop": {"limit_seconds": LIMIT, "source": CAP_SOURCE}})
|
||||
continue
|
||||
yield index, phase, forecast
|
||||
|
||||
|
||||
def run_engine(outer, command, folder, environment):
|
||||
no_live_engine()
|
||||
native = folder / "engine-native.log"
|
||||
command = command + ["--log-file", str(native), "--verbose"]
|
||||
env = {key: value for key, value in os.environ.items() if not key.startswith("THE_TREE_")}
|
||||
env.update(environment)
|
||||
result, capture = outer.run_phase(command, folder, LIMIT, env, now_fn=now,
|
||||
dump_fn=write, sha_fn=sha, live_engines_fn=live_engines)
|
||||
raw = {}
|
||||
text = ""
|
||||
for name in ("stdout.log", "stderr.log", "engine-native.log"):
|
||||
path = folder / name
|
||||
raw[name] = {"sha256": sha(path), "bytes": path.stat().st_size} if path.is_file() else None
|
||||
if path.is_file():
|
||||
text += path.read_text(errors="replace")
|
||||
bad = diagnostics(text)
|
||||
clean = bool(result) and all((
|
||||
result.get("exit_code") == 0, result.get("helper_exit_code") == 0,
|
||||
result.get("timed_out") is False, result.get("error_lines") == [],
|
||||
result.get("owned_popen_ended") is True, result.get("old_helper_engine_ended") is True,
|
||||
result.get("start_receipt_written") is True, result.get("end_receipt_written") is True,
|
||||
result.get("lifecycle_error") is None, result.get("external_process_or_group_signals") == 0,
|
||||
capture.get("passed") is True, capture.get("all_captured_ended") is True,
|
||||
len(capture.get("actual_captured_pids", [])) == 1, all(raw.values()), not bad))
|
||||
row = {"passed": clean, "process": result, "capture": capture, "raw": raw,
|
||||
"diagnostics": bad, "timeout_seconds": LIMIT, "argv": command,
|
||||
"phase_stop": {"limit_seconds": LIMIT, "source": CAP_SOURCE}}
|
||||
row["owned_files"] = {name: {"sha256": sha(folder / name),
|
||||
"bytes": (folder / name).stat().st_size}
|
||||
for name in OWNED_FILES if (folder / name).is_file()}
|
||||
row["passed"] = clean and set(row["owned_files"]) == set(OWNED_FILES)
|
||||
write(folder.parent / (folder.name + "-collector-receipt.json"), row)
|
||||
return row
|
||||
|
||||
|
||||
def inputs(readonly):
|
||||
manifest_path = readonly / "ROOT_INPUT_MANIFEST.json"
|
||||
require(sha(manifest_path) == "9dac0a51d52087e691c5723b4f78f171f7998c78b582b2b6c4c904798e77644d",
|
||||
"Root input manifest changed")
|
||||
manifest = json.loads(manifest_path.read_text())
|
||||
pins = {}
|
||||
for row in manifest["files"]:
|
||||
path = Path(row["destination"])
|
||||
require(path.resolve().is_relative_to(readonly.resolve()), "input escapes read-only directory")
|
||||
require(sha(path) == row["sha256"] and path.stat().st_size == row["bytes"], "read-only input changed")
|
||||
pins[str(path.relative_to(readonly))] = row["sha256"]
|
||||
return {"manifest_sha256": sha(manifest_path), "files": pins}
|
||||
|
||||
|
||||
def reused_observations(readonly, pins):
|
||||
relative = "run-r02/after/evidence/report.json"
|
||||
path = readonly / relative
|
||||
require(sha(path) == pins["files"][relative], "r02 report SHA changed")
|
||||
raw = json.loads(path.read_text())
|
||||
observations = {}
|
||||
for name, (field, statistic, scope) in R02_FIELDS.items():
|
||||
value = raw
|
||||
for part in field.split("."):
|
||||
value = value[part]
|
||||
observations[name] = {"value": value, "unit": "ms", "statistic": statistic,
|
||||
"scope": scope, "r02_field": field}
|
||||
return {"execution_sha256": pins["files"]["run-r02/execution.json"],
|
||||
"report_sha256": pins["files"][relative],
|
||||
"scope": "x1 headless Operations OFF; reused without remeasurement",
|
||||
"observations": observations}
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--readonly", type=Path, required=True)
|
||||
parser.add_argument("--fixture", type=Path, required=True)
|
||||
parser.add_argument("--engine", type=Path, default=Path("/Applications/Godot.app/Contents/MacOS/Godot"))
|
||||
parser.add_argument("--output", type=Path, required=True)
|
||||
mode = parser.add_mutually_exclusive_group(required=True)
|
||||
mode.add_argument("--check", action="store_true")
|
||||
mode.add_argument("--run", action="store_true")
|
||||
args = parser.parse_args()
|
||||
standard = load_tool(EXTERNAL_IMPORTS[0])
|
||||
pins = inputs(args.readonly.resolve())
|
||||
fixture_actual = sha(args.fixture) if args.fixture.is_file() else None
|
||||
if fixture_actual != FIXTURE_SHA:
|
||||
shortfall = {"stop_reason": "input_shortfall", "fixture_expected_sha256": FIXTURE_SHA,
|
||||
"fixture_actual_sha256": fixture_actual, "engines_started": 0, "stages_created": 0}
|
||||
if args.run:
|
||||
require(not args.output.exists(), "fresh output required")
|
||||
args.output.mkdir(parents=True)
|
||||
write(args.output / "input-shortfall.json", shortfall)
|
||||
print(json.dumps(shortfall))
|
||||
return 1
|
||||
require(sha(args.engine) == ENGINE_SHA, "engine SHA")
|
||||
before = source_map(standard, ROOT)
|
||||
commit = subprocess.check_output(["git", "-C", str(ROOT), "rev-parse", "HEAD"], text=True).strip()
|
||||
tracked = subprocess.check_output(["git", "-C", str(ROOT), "ls-tree", "-r", commit], text=True)
|
||||
selected = {}
|
||||
for line in tracked.splitlines():
|
||||
info, name = line.split("\t", 1)
|
||||
if name.split("/", 1)[0] in ("autoload", "scripts", "scenes", "data", "tests", "assets", "tools") \
|
||||
or name in ("project.godot", "export_presets.cfg", "Makefile", "AGENTS.md"):
|
||||
if Path(name).name != ".DS_Store" and "__pycache__" not in Path(name).parts:
|
||||
selected[name] = info.split()[2]
|
||||
require(set(selected) == set(before), "source map differs from committed file set")
|
||||
for name, blob in selected.items():
|
||||
actual = subprocess.check_output(["git", "-C", str(ROOT), "hash-object", str(ROOT / name)], text=True).strip()
|
||||
require(actual == blob, "dirty source: " + name)
|
||||
stage = args.output.resolve() / "stage"
|
||||
wrapper = args.output.resolve() / "guarded.gd"
|
||||
planned = execution_plan(args.engine, stage, wrapper)
|
||||
admission = {"commit": commit, "source_map": before, "source_map_sha256": digest(before),
|
||||
"readonly": pins, "engine_sha256": ENGINE_SHA, "engine_version": VERSION,
|
||||
"fixture_sha256": FIXTURE_SHA, "planned_argv": planned, "phases": [list(p) for p in PHASES],
|
||||
"budget_verdict_scope": VERDICT_SCOPE,
|
||||
"reused_r02": reused_observations(args.readonly, pins)}
|
||||
require(admission == json.loads(json.dumps(admission)), "admission JSON representation drift")
|
||||
check_admission(admission)
|
||||
if args.check:
|
||||
print(json.dumps(dict(admission, stages_created=0, engines_started=0), ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
require(sys.platform == "darwin", "reviewed CoW execution requires macOS")
|
||||
require(not args.output.exists(), "fresh output required")
|
||||
no_live_engine()
|
||||
player = standard.default_player(ROOT)
|
||||
outer = load_tool(EXTERNAL_IMPORTS[1])
|
||||
out = args.output.resolve()
|
||||
out.mkdir(parents=True)
|
||||
write(out / "admission.json", admission)
|
||||
rows = []
|
||||
error = None
|
||||
profiles = []
|
||||
probe_check = None
|
||||
try:
|
||||
stage.mkdir()
|
||||
for name in before:
|
||||
target = stage / name
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
standard.copy_source(ROOT / name, target)
|
||||
require(source_map(standard, stage) == before, "CoW source differs")
|
||||
probe = out / "measure_p28_01.gd"
|
||||
probe.write_bytes((HERE / probe.name).read_bytes())
|
||||
probe_check = checked_probe(probe)
|
||||
guard = out / "profile_guard.gd"
|
||||
guard.write_bytes((args.readonly / "run-r02/profile_guard.gd").read_bytes())
|
||||
wrapper.write_text("extends " + json.dumps(str(probe)) + "\nconst Guard = preload(" +
|
||||
json.dumps(str(guard)) + ")\nfunc _initialize() -> void:\n"
|
||||
"\tif not Guard.activate():\n\t\tquit(97)\n\t\treturn\n\tsuper._initialize()\n")
|
||||
script_pins = {str(p): sha(p) for p in (probe, guard, wrapper)}
|
||||
hardware = subprocess.check_output(
|
||||
["sysctl", "-n", "hw.model", "hw.logicalcpu", "hw.memsize"], text=True).splitlines()
|
||||
meta = {"machine": {"hw.model": hardware[0], "cores": int(hardware[1]), "memory_bytes": int(hardware[2])},
|
||||
"build": {"engine_string": VERSION, "engine_hash": "ed1daf0bf", "engine_sha256": ENGINE_SHA,
|
||||
"project_commit": commit, "source_map_sha256": digest(before)}}
|
||||
public = (stage / "project.godot").read_bytes()
|
||||
expected = standard.inventory(stage, stage=True)
|
||||
require(expected == before, "initial whole stage differs")
|
||||
for index, phase, forecast in phase_schedule(rows):
|
||||
label, source, speed, warmup, minutes = phase
|
||||
require(source_map(standard, ROOT) == before and standard.default_player(ROOT) == player,
|
||||
"source/player changed before phase")
|
||||
require(standard.inventory(stage, stage=True) == expected, "stage changed before phase")
|
||||
require(all(sha(Path(p)) == pin for p, pin in script_pins.items()), "execution scripts changed")
|
||||
require(checked_probe(probe) == probe_check, "probe check changed")
|
||||
require(sha(args.fixture) == FIXTURE_SHA, "input_shortfall: changed input")
|
||||
name = "TheTree-PCK-tests-" + uuid.uuid4().hex + "-p28-startup"
|
||||
startup = standard.profile_path(name)
|
||||
require(not startup.exists(), "startup profile not fresh")
|
||||
runtime_name = "TheTree-PCK-tests-" + uuid.uuid4().hex + "-p28-runtime"
|
||||
runtime = standard.profile_path(runtime_name)
|
||||
require(not runtime.exists(), "runtime profile not fresh")
|
||||
profiles.extend([startup, runtime])
|
||||
guard_path = out / (label + "-guard.json")
|
||||
env = {"THE_TREE_PACK_PROFILE_NAME": runtime_name, "THE_TREE_PACK_PROFILE_EXPECTED": str(runtime),
|
||||
"THE_TREE_PACK_PROFILE_REPORT": str(guard_path), "THE_TREE_P28_PHASE": label,
|
||||
"THE_TREE_P28_INPUT": source, "THE_TREE_P28_SPEED": str(speed),
|
||||
"THE_TREE_P28_WARMUP": str(warmup), "THE_TREE_P28_MINUTES": str(minutes),
|
||||
"THE_TREE_P28_FIXTURE": str(args.fixture.resolve()), "THE_TREE_P28_FIXTURE_SHA": FIXTURE_SHA,
|
||||
"THE_TREE_P28_META": json.dumps(meta)}
|
||||
row = run_private_engine(standard, outer, planned[index], out / label, env,
|
||||
stage, public, name)
|
||||
row["phase"] = label
|
||||
row["projection"] = forecast
|
||||
row["probe_check"] = probe_check
|
||||
if label in PARSE_PHASES:
|
||||
row.update(parse_na=PARSE_NA, script_pins=script_pins,
|
||||
stage_before_sha256=digest(expected),
|
||||
stage_after_sha256=digest(standard.inventory(stage, stage=True)),
|
||||
source_preserved=source_map(standard, ROOT) == before,
|
||||
player_preserved=standard.default_player(ROOT) == player)
|
||||
rows.append(row)
|
||||
if label in PARSE_PHASES:
|
||||
require(row["stage_before_sha256"] == row["stage_after_sha256"] and
|
||||
row["source_preserved"] and row["player_preserved"] and
|
||||
all(sha(Path(p)) == pin for p, pin in script_pins.items()),
|
||||
"parse preservation failed")
|
||||
require(row["passed"], "native phase failed; raw retained: " + label)
|
||||
after = standard.inventory(stage, stage=True)
|
||||
if label == "import":
|
||||
stage_gate(expected, after, importing=True)
|
||||
expected = after
|
||||
elif label in PARSE_PHASES:
|
||||
require(after == expected, "stage changed during parse")
|
||||
row["stage_after_sha256"] = digest(after)
|
||||
else:
|
||||
require(after == expected, "stage changed during measurement")
|
||||
observation = json.loads(guard_path.read_text())
|
||||
guard_gate(observation, runtime, runtime_name)
|
||||
row["runtime_profile"] = str(runtime)
|
||||
row["runtime_name"] = runtime_name
|
||||
report = bound_report(out / label, row)
|
||||
row.update(report=report, report_sha256=digest(report), guard=observation,
|
||||
guard_sha256=sha(guard_path))
|
||||
write(out / (label + "-report.json"), report)
|
||||
check_report(report)
|
||||
row["stop_reason"] = report["stop_reason"]
|
||||
if label in ("R0", "O0"):
|
||||
row["extrapolation"] = extrapolation(report)
|
||||
require(standard.default_player(ROOT) == player, "default player changed")
|
||||
require(source_map(standard, ROOT) == before, "source changed after phase")
|
||||
require(sha(args.fixture) == FIXTURE_SHA, "input changed after phase")
|
||||
require(all(sha(Path(p)) == pin for p, pin in script_pins.items()), "scripts changed after phase")
|
||||
except BaseException as caught:
|
||||
error = repr(caught)
|
||||
finally:
|
||||
cleanup, cleanup_errors = outer.finish_captured(outer.OWNED)
|
||||
after = source_map(standard, ROOT)
|
||||
player_after = standard.default_player(ROOT)
|
||||
receipt = {"admission": admission, "phases": rows, "error": error, "source_before": before, "source_after": after,
|
||||
"probe_check": probe_check,
|
||||
"default_player_before": player, "default_player_after": player_after,
|
||||
"cleanup": cleanup, "cleanup_errors": cleanup_errors,
|
||||
"profiles": {str(p): profile_files(p) for p in profiles},
|
||||
"passed": error is None and not cleanup_errors and after == before and player_after == player,
|
||||
"original_credit": 0, "windows_verified": False, "operations_200_guests_verified": False}
|
||||
if receipt["passed"]:
|
||||
try:
|
||||
check_receipt(receipt, out)
|
||||
except (ValueError, KeyError, TypeError) as caught:
|
||||
receipt.update(passed=False, error="checker: " + repr(caught))
|
||||
write(out / "execution.json", receipt)
|
||||
return 0 if receipt["passed"] else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+679
@@ -0,0 +1,679 @@
|
||||
extends SceneTree
|
||||
const UIAudioOwnerShutdownScript = preload("res://scripts/ui/ui_audio_owner_shutdown.gd")
|
||||
## External, passive observer of the default autoload world.
|
||||
## Raw per-frame lines survive an owned timeout before the final report.
|
||||
|
||||
class PreObserver:
|
||||
extends Node
|
||||
var owner_probe: Variant
|
||||
func _process(delta: float) -> void:
|
||||
owner_probe.pre_sample(delta)
|
||||
|
||||
class Observer:
|
||||
extends Node
|
||||
var owner_probe: Variant
|
||||
func _process(delta: float) -> void:
|
||||
var observer_started_us := Time.get_ticks_usec()
|
||||
owner_probe.sample(observer_started_us, delta)
|
||||
|
||||
var pending_pair := {}
|
||||
var baseline := {}
|
||||
var last_pair := {}
|
||||
var finished := false
|
||||
var audio_emitted := false
|
||||
var observation_order := 0
|
||||
var pre_cost_us := 0
|
||||
var clock: Variant
|
||||
var runtime: Variant
|
||||
var main: Variant
|
||||
var preview: Variant
|
||||
var report := {}
|
||||
var frames: Array = []
|
||||
var ready_to_sample := false
|
||||
var start_us := 0
|
||||
var previous_us := 0
|
||||
var previous_minute := 0
|
||||
var start_minute := 0
|
||||
var warmup := 0
|
||||
var target := 0
|
||||
var speed := 0
|
||||
var previous_serial: Variant = null
|
||||
var drawing := {}
|
||||
var camera := {}
|
||||
var overloads: Array = []
|
||||
var checkpoints: Array = []
|
||||
var warmup_ended := false
|
||||
var measurement_start := 0
|
||||
var minute_ticks: Array = []
|
||||
var previous_tick_us := 0
|
||||
var previous_wall := 0.0
|
||||
var key_forms := {}
|
||||
var cached_registered := {}
|
||||
var cached_active := {}
|
||||
var cached_rng := {}
|
||||
var counts_minute := 0
|
||||
var cell_hash := ""
|
||||
var cached_cells: Variant = null
|
||||
var save_events: Array = []
|
||||
var last_stall_count: Variant = null
|
||||
var render_started_us := -1
|
||||
var render_submit_us: Variant = null
|
||||
var render_serial := 0
|
||||
var last_render_serial := -1
|
||||
|
||||
# Product dictionaries may expose StringName keys. Never supply inferred defaults.
|
||||
func field(d: Variant, name: String) -> Variant:
|
||||
if d is Dictionary:
|
||||
if d.has(StringName(name)):
|
||||
key_forms[name] = {"key_form": "StringName", "reason": null}
|
||||
return d[StringName(name)]
|
||||
if d.has(String(name)):
|
||||
key_forms[name] = {"key_form": "String", "reason": null}
|
||||
return d[String(name)]
|
||||
key_forms[name] = {"key_form": null, "reason": "missing_product_field"}
|
||||
return null
|
||||
|
||||
func budget_snapshot() -> Dictionary:
|
||||
var result := {}
|
||||
var profiler: Variant = clock.performance_budget
|
||||
var sample_dictionary: Variant = profiler.get("_samples")
|
||||
var count_dictionary: Variant = profiler.get("_sample_counts")
|
||||
var both_readable := sample_dictionary is Dictionary and count_dictionary is Dictionary
|
||||
for metric: String in ["simulation_x1", "simulation_x3", "main_thread_stall"]:
|
||||
var entry := {}
|
||||
var provenance := {}
|
||||
for column: String in ["samples_us", "lifetime_count"]:
|
||||
var dictionary: Variant = sample_dictionary if column == "samples_us" else count_dictionary
|
||||
var value: Variant = null
|
||||
var key_form: Variant = null
|
||||
if dictionary is Dictionary:
|
||||
key_form = "absent"
|
||||
for key: Variant in dictionary.keys():
|
||||
if (key is StringName or key is String) and String(key) == metric:
|
||||
key_form = "StringName" if key is StringName else "String"
|
||||
value = dictionary[key]
|
||||
break
|
||||
if key_form == "absent" and both_readable:
|
||||
# performance_budget.gd uses get(metric, []) / get(metric, 0).
|
||||
value = [] if column == "samples_us" else 0
|
||||
key_form = "absent_means_zero_per_product"
|
||||
provenance[column] = {"dictionary_readable": dictionary is Dictionary, "key_form": key_form}
|
||||
entry[column] = (value.duplicate() if value is Array else null) if column == "samples_us" else value
|
||||
entry["provenance"] = provenance
|
||||
entry["reason"] = "counts_unavailable" if entry.samples_us == null or entry.lifetime_count == null else null
|
||||
result[metric] = entry
|
||||
return result
|
||||
|
||||
func kernel_verdict(before: Dictionary, after: Dictionary) -> Dictionary:
|
||||
var result := {}
|
||||
for metric: String in ["simulation_x%d" % speed, "main_thread_stall"]:
|
||||
var first: Dictionary = before.get(metric, {})
|
||||
var last: Dictionary = after.get(metric, {})
|
||||
var a: Variant = first.get("lifetime_count")
|
||||
var b: Variant = last.get("lifetime_count")
|
||||
var raw: Variant = last.get("samples_us")
|
||||
var reason: Variant = null
|
||||
var selected: Array = []
|
||||
if a == null or b == null or b < a or not raw is Array:
|
||||
reason = "counts_unavailable"
|
||||
elif b == a:
|
||||
reason = "no_interval_samples"
|
||||
elif b - a > raw.size():
|
||||
reason = "interval_exceeds_window"
|
||||
else:
|
||||
selected = raw.slice(raw.size() - (b - a))
|
||||
var aggregate := "maximum" if metric == "main_thread_stall" else ("average" if speed == 1 else "p95")
|
||||
var limit := 50000 if metric == "main_thread_stall" else (4000 if speed == 1 else 12000)
|
||||
var statistics: Variant = null
|
||||
var value: Variant = null
|
||||
if not selected.is_empty():
|
||||
var total: int = 0
|
||||
for duration: int in selected:
|
||||
total += duration
|
||||
selected.sort()
|
||||
var count: int = selected.size()
|
||||
statistics = {"count": count, "average_us": total / count,
|
||||
"p95_us": selected[(95 * count + 99) / 100 - 1], "maximum_us": selected.back()}
|
||||
value = statistics[aggregate + "_us"]
|
||||
result[metric] = {"source": "clock.performance_budget._samples", "aggregation": aggregate,
|
||||
"limit_us": limit, "value": value, "passed": value <= limit if value != null else null,
|
||||
"window": "interval_exact_from_samples" if reason == null else null,
|
||||
"reason": reason, "statistics": statistics}
|
||||
return result
|
||||
|
||||
func on_save(result: Dictionary) -> void:
|
||||
var path: Variant = field(result, "save_path")
|
||||
var relative: Variant = null
|
||||
if path is String and path.begins_with("user://") and not ".." in path.split("/"):
|
||||
relative = path.trim_prefix("user://")
|
||||
var counts: Variant = clock.performance_budget.get("_sample_counts")
|
||||
var event := {"frame": frames.size(), "game_minute": clock.absolute_minute,
|
||||
"ok": field(result, "ok"), "ok_reason": key_forms["ok"].reason,
|
||||
"operation": field(result, "operation"), "operation_reason": key_forms["operation"].reason,
|
||||
"path": relative, "path_reason": "missing_or_not_private_user_path" if relative == null else null,
|
||||
"sample_counts": counts.duplicate(true)}
|
||||
save_events.append(event)
|
||||
print("P28_01_SAVE=" + JSON.stringify(event))
|
||||
|
||||
func on_pre_draw() -> void:
|
||||
render_started_us = Time.get_ticks_usec()
|
||||
|
||||
func on_post_draw() -> void:
|
||||
if render_started_us >= 0:
|
||||
render_submit_us = Time.get_ticks_usec() - render_started_us
|
||||
render_serial += 1
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("begin")
|
||||
|
||||
func hashed(value: Variant) -> String:
|
||||
var hash_context := HashingContext.new()
|
||||
hash_context.start(HashingContext.HASH_SHA256)
|
||||
hash_context.update(var_to_bytes(value))
|
||||
return hash_context.finish().hex_encode()
|
||||
|
||||
func vec(value: Vector2) -> Array:
|
||||
return [value.x, value.y]
|
||||
|
||||
func camera_state() -> Dictionary:
|
||||
return {"layer": preview.layer_mode, "zoom": preview.zoom_level,
|
||||
"pan": vec(preview.pan_offset), "viewport": vec(preview.size),
|
||||
"window_size": vec(root.size), "screen_size": vec(DisplayServer.screen_get_size()),
|
||||
"content_scale_size": vec(root.content_scale_size),
|
||||
"content_scale_factor": root.content_scale_factor,
|
||||
"vsync_mode": DisplayServer.window_get_vsync_mode()}
|
||||
|
||||
func registered() -> Dictionary:
|
||||
return {"guests": runtime.guest_service.get_guests().size(),
|
||||
"staff": runtime.staff_service.get_staff_members().size(),
|
||||
"facilities": runtime.facility_service.get_instances().size(),
|
||||
"walkable_cells": runtime.world_service.get_walkable_cells().size()}
|
||||
|
||||
func active_counts() -> Dictionary:
|
||||
var guests := 0
|
||||
var staff := 0
|
||||
var facilities := 0
|
||||
for guest: Variant in runtime.guest_service.get_guests():
|
||||
if guest.state not in [GuestRuntime.State.SPAWNING, GuestRuntime.State.LEAVING, GuestRuntime.State.DESPAWNING]:
|
||||
guests += 1
|
||||
for person: Variant in runtime.staff_service.get_staff_members():
|
||||
var shift: Variant = runtime.staff_service.get_shift(person.shift_id)
|
||||
if shift != null and not shift.cancelled and shift.actual_started_at >= 0 \
|
||||
and shift.actual_ended_at < 0 and (shift.contains_minute(clock.absolute_minute) or person.is_overtime):
|
||||
staff += 1
|
||||
for facility: Variant in runtime.facility_service.get_instances():
|
||||
if facility.operation_state == FacilityInstance.OperationState.OPERATING:
|
||||
facilities += 1
|
||||
return {"guests": guests, "staff": staff, "facilities": facilities}
|
||||
|
||||
func rng_state() -> Dictionary:
|
||||
var result := {}
|
||||
var streams: Dictionary = runtime.get("_rng_streams")
|
||||
for key: Variant in streams:
|
||||
var stream: Variant = field(streams, String(key))
|
||||
result[String(key)] = stream.snapshot() if stream != null else null
|
||||
return result
|
||||
|
||||
func draw_counts() -> Dictionary:
|
||||
var observation: Dictionary = preview.get_life_observation_report()
|
||||
var serial: Variant = field(observation, "observation_serial")
|
||||
update_cell_count()
|
||||
if serial != null and serial == previous_serial and not drawing.is_empty():
|
||||
var carried := drawing.duplicate(true)
|
||||
carried["carried_from_serial"] = serial
|
||||
carried["cells"] = cached_cells
|
||||
carried["cells_reason"] = "missing_bounds" if cached_cells == null else null
|
||||
carried["cell_count_from_hash"] = cell_hash
|
||||
carried["camera"] = camera_state()
|
||||
return carried
|
||||
previous_serial = serial
|
||||
var area := Rect2(Vector2.ZERO, preview.size)
|
||||
var actors: Variant = field(observation, "actor_drawings")
|
||||
var visible: Variant = null
|
||||
if actors is Array:
|
||||
visible = 0
|
||||
for actor: Dictionary in actors:
|
||||
var at: Variant = field(actor, "at")
|
||||
if at == null:
|
||||
visible = null
|
||||
break
|
||||
if area.has_point(at):
|
||||
visible += 1
|
||||
var facilities: Variant = 0
|
||||
for facility: Dictionary in preview.call("_completed_facilities_for_draw"):
|
||||
var layer: Variant = field(facility, "layer")
|
||||
var occupied: Variant = field(facility, "occupied_cells")
|
||||
if layer == null or occupied == null:
|
||||
facilities = null
|
||||
break
|
||||
if int(layer) != int(preview.layer_mode):
|
||||
continue
|
||||
var cells: Array = preview.call("_cells_from_serialized", occupied)
|
||||
if not cells.is_empty() and preview.call("_cell_bounds_rect", cells).intersects(area):
|
||||
facilities += 1
|
||||
drawing = {"actors_in_layer": actors.size() if actors is Array else null,
|
||||
"actors_in_layer_reason": "missing_actor_drawings" if not actors is Array else null,
|
||||
"actors_in_viewport": visible, "actors_in_viewport_reason": "missing_actor_geometry" if visible == null else null,
|
||||
"facilities": facilities, "facilities_reason": "missing_facility_geometry" if facilities == null else null,
|
||||
"cells": cached_cells, "observation_serial": serial,
|
||||
"observation_serial_reason": "missing_observation_serial" if serial == null else null,
|
||||
"cells_reason": "missing_bounds" if cached_cells == null else null,
|
||||
"cell_count_from_hash": cell_hash,
|
||||
"carried_from_serial": null, "camera": camera_state()}
|
||||
return drawing.duplicate(true)
|
||||
|
||||
func update_cell_count() -> void:
|
||||
var area := Rect2(Vector2.ZERO, preview.size)
|
||||
var bounds: Dictionary = preview.call("_world_bounds")
|
||||
var geometry_hash := hashed({"bounds": bounds, "camera": camera_state()})
|
||||
if geometry_hash != cell_hash:
|
||||
cell_hash = geometry_hash
|
||||
cached_cells = null
|
||||
var min_y: Variant = field(bounds, "min_y")
|
||||
var max_y: Variant = field(bounds, "max_y")
|
||||
var min_x: Variant = field(bounds, "min_x")
|
||||
var max_x: Variant = field(bounds, "max_x")
|
||||
if min_y != null and max_y != null and min_x != null and max_x != null:
|
||||
cached_cells = 0
|
||||
for y in range(int(min_y), int(max_y) + 1):
|
||||
for x in range(int(min_x), int(max_x) + 1):
|
||||
if preview.get_cell_rect(Vector3i(x, y, preview.layer_mode)).intersects(area):
|
||||
cached_cells += 1
|
||||
|
||||
func begin() -> void:
|
||||
clock = root.get_node("GameClock")
|
||||
runtime = root.get_node("SimulationRuntime")
|
||||
clock.set_paused(true)
|
||||
var input := OS.get_environment("THE_TREE_P28_INPUT")
|
||||
speed = int(OS.get_environment("THE_TREE_P28_SPEED"))
|
||||
warmup = int(OS.get_environment("THE_TREE_P28_WARMUP"))
|
||||
target = int(OS.get_environment("THE_TREE_P28_MINUTES"))
|
||||
report = {"schema": "p28-01-v1", "pid": OS.get_process_id(), "main_added": false,
|
||||
"phase": OS.get_environment("THE_TREE_P28_PHASE"), "input": input,
|
||||
"metadata": JSON.parse_string(OS.get_environment("THE_TREE_P28_META")),
|
||||
"input_sha256": null, "input_sha256_reason": "fresh_autoload_initialization",
|
||||
"speed": speed, "speed_path": "main._set_speed->SimulationKernel._process",
|
||||
"toggle_pause_calls": 0, "performance_clear_calls": 0,
|
||||
"renderer": ProjectSettings.get_setting("rendering/renderer/rendering_method"),
|
||||
"warmup_target": warmup, "measurement_target": target,
|
||||
"window_note": "Product reports are raw rolling capacity-512 reports; warm-up may remain; no subtraction.",
|
||||
"budget_scope": "kernel process_real_delta only; excludes observer_us",
|
||||
"render_submit_note": "CPU側の描画提出wall、GPU時間ではない",
|
||||
"observer_note": "observer_us includes reads, serialization and frame emission; excludes its own timing line and final report; never subtracted",
|
||||
"sample_definition": "is_minute_sample marks a changed game minute; other frames carry counts_from_minute",
|
||||
"budget_at_warmup_end": {"source": "clock.performance_budget (product diagnostic, raw, not a verdict)", "data": {}},
|
||||
"kernel_at_warmup_end": {"source": "clock.performance_budget (product diagnostic, raw, not a verdict)", "data": null, "reason": "warmup_not_reached"},
|
||||
"probe_deadline": {"limit_seconds": 1740, "source": "H6 probe deadline (cap−60s)"}, "save_events": save_events, "key_form": key_forms,
|
||||
"count_definitions": {"registered": "service collections; walkable cells",
|
||||
"active": "guests exclude SPAWNING/LEAVING/DESPAWNING; staff current attended unended shift; facilities OPERATING",
|
||||
"drawn": "world_preview submitted model serial and viewport geometry"},
|
||||
"overloads": overloads, "checkpoints": checkpoints, "frames": frames, "minute_ticks": minute_ticks}
|
||||
if input == "R":
|
||||
var path := OS.get_environment("THE_TREE_P28_FIXTURE")
|
||||
report["input_sha256"] = FileAccess.get_sha256(path)
|
||||
report["input_sha256_reason"] = null
|
||||
if report["input_sha256"] != OS.get_environment("THE_TREE_P28_FIXTURE_SHA"):
|
||||
finish("input_shortfall")
|
||||
return
|
||||
var file := FileAccess.open(path, FileAccess.READ)
|
||||
if file == null:
|
||||
finish("input_shortfall")
|
||||
return
|
||||
var snapshot: Variant = file.get_var(false)
|
||||
file.close()
|
||||
var restored: Variant = runtime.restore_snapshot(snapshot)
|
||||
report["restore_result"] = restored
|
||||
if not restored:
|
||||
finish("input_shortfall")
|
||||
return
|
||||
if registered() != {"guests": 200, "staff": 50, "facilities": 150, "walkable_cells": 20000}:
|
||||
report["initial_registered"] = registered()
|
||||
finish("input_shortfall")
|
||||
return
|
||||
# Restore may carry a paused value. Pause before adding main, never resume here.
|
||||
clock.set_paused(true)
|
||||
var paused_before: bool = clock.paused
|
||||
var opening_minute: int = clock.absolute_minute
|
||||
main = load("res://scenes/main/main.tscn").instantiate()
|
||||
root.add_child(main)
|
||||
report["main_added"] = true
|
||||
report["startup"] = {"pause_fresh_opening": {"return": null, "reason": "void_function",
|
||||
"paused_before": paused_before, "paused_after": clock.paused},
|
||||
"save_policy": {"return": null, "reason": "return_discarded_by_main(main.gd:222)"},
|
||||
"save_policy_after": runtime.get_save_policy(),
|
||||
"start_report": main.get("_operations_start_report").duplicate(true),
|
||||
"operations_enabled_after": field(runtime.get_operations_report(), "enabled"),
|
||||
"opening_minute": opening_minute}
|
||||
if report.startup.start_report.is_empty():
|
||||
report.startup["start_report_reason"] = "main_retained_empty_dictionary"
|
||||
var start: Dictionary = report.startup.start_report
|
||||
var expected: bool = field(start, "ok") == true and report.startup.operations_enabled_after == true and opening_minute == 360
|
||||
if input == "R":
|
||||
expected = report.startup.operations_enabled_after == false and field(start, "code") == "INVALID_START_TIME"
|
||||
if not expected:
|
||||
finish("input_shortfall")
|
||||
return
|
||||
await process_frame
|
||||
await process_frame
|
||||
preview = main.get("world_preview")
|
||||
camera = camera_state()
|
||||
report["camera"] = camera
|
||||
report["initial_registered"] = registered()
|
||||
report["preparation_sha256"] = hashed(runtime.create_snapshot())
|
||||
report["seed"] = runtime.world_seed
|
||||
report["rng_streams"] = rng_state()
|
||||
RenderingServer.viewport_set_measure_render_time(root.get_viewport_rid(), true)
|
||||
clock.simulation_overloaded.connect(on_overload)
|
||||
clock.tick_completed.connect(on_tick)
|
||||
runtime.checkpoint_presentation_requested.connect(on_checkpoint)
|
||||
runtime.save_operation_completed.connect(on_save)
|
||||
RenderingServer.frame_pre_draw.connect(on_pre_draw)
|
||||
RenderingServer.frame_post_draw.connect(on_post_draw)
|
||||
var pre_observer := PreObserver.new()
|
||||
pre_observer.owner_probe = self
|
||||
pre_observer.process_priority = -1000000
|
||||
root.add_child(pre_observer)
|
||||
var observer := Observer.new()
|
||||
observer.owner_probe = self
|
||||
observer.process_priority = 100000
|
||||
root.add_child(observer)
|
||||
cached_registered = registered()
|
||||
cached_active = active_counts()
|
||||
cached_rng = rng_state()
|
||||
main.call("_set_speed", speed)
|
||||
report["speed_after"] = clock.speed
|
||||
report["paused_after_speed"] = clock.paused
|
||||
if clock.paused or clock.speed != speed:
|
||||
finish("input_shortfall")
|
||||
return
|
||||
ready_to_sample = true
|
||||
|
||||
func anchor_baseline() -> void:
|
||||
start_minute = clock.absolute_minute
|
||||
measurement_start = start_minute if warmup == 0 else -1
|
||||
previous_minute = start_minute
|
||||
start_us = Time.get_ticks_usec()
|
||||
previous_us = start_us
|
||||
previous_tick_us = start_us
|
||||
counts_minute = start_minute
|
||||
var initial_budget := budget_snapshot()
|
||||
last_stall_count = initial_budget.main_thread_stall.lifetime_count
|
||||
if warmup == 0:
|
||||
warmup_ended = true
|
||||
report["kernel_at_warmup_end"] = {"source": "clock.performance_budget (product diagnostic, raw, not a verdict)", "data": clock.get_performance_reports()}
|
||||
report["budget_at_warmup_end"] = {"source": "clock.performance_budget (product diagnostic, raw, not a verdict)", "data": initial_budget}
|
||||
report["startup_wall_seconds"] = float(start_us) / 1000000.0
|
||||
report["start_minute"] = start_minute
|
||||
report["start_wall"] = Time.get_unix_time_from_system()
|
||||
previous_wall = report["start_wall"]
|
||||
baseline["wall"] = report.start_wall
|
||||
baseline["excluded_from_statistics"] = true
|
||||
report["baseline"] = baseline
|
||||
cached_registered = registered()
|
||||
cached_active = active_counts()
|
||||
cached_rng = rng_state()
|
||||
|
||||
func on_overload(backlog: int) -> void:
|
||||
overloads.append({"backlog": backlog, "game_minute": clock.absolute_minute, "frame": frames.size()})
|
||||
|
||||
func on_checkpoint(_presentation: Dictionary) -> void:
|
||||
checkpoints.append({"game_minute": clock.absolute_minute, "frame": frames.size(),
|
||||
"wall": Time.get_unix_time_from_system(), "cause": "production_checkpoint_presentation_requested"})
|
||||
|
||||
func on_tick(minute: int) -> void:
|
||||
if not ready_to_sample or baseline.is_empty():
|
||||
return
|
||||
var tick_us := Time.get_ticks_usec()
|
||||
var row := {"game_minute": minute, "wall": Time.get_unix_time_from_system(),
|
||||
"wall_us_since_previous_tick": tick_us - previous_tick_us,
|
||||
"frame": frames.size(), "note": "passive tick_completed; includes inter-frame waiting"}
|
||||
minute_ticks.append(row)
|
||||
previous_tick_us = tick_us
|
||||
print("P28_01_MINUTE=" + JSON.stringify(row))
|
||||
|
||||
func f64(value: float) -> String:
|
||||
return PackedFloat64Array([value]).to_byte_array().hex_encode()
|
||||
|
||||
func pre_sample(delta: float) -> void:
|
||||
if not ready_to_sample:
|
||||
return
|
||||
var began := Time.get_ticks_usec()
|
||||
if not pending_pair.is_empty():
|
||||
report["frame_order_evidence"] = pending_pair
|
||||
finish("frame_order_unverified")
|
||||
return
|
||||
observation_order += 1
|
||||
pending_pair = {"engine_frame_pre": Engine.get_process_frames(), "pre_order": observation_order,
|
||||
"paused_at_frame_start": clock.paused, "speed_at_frame_start": clock.speed,
|
||||
"skip_at_frame_start": clock.get("_skip_next_process"),
|
||||
"acc_at_frame_start_f64_hex": f64(clock.get("_accumulator")),
|
||||
"minute_at_frame_start": clock.absolute_minute, "process_delta_f64_hex": f64(delta)}
|
||||
var allowed := 0
|
||||
if not pending_pair.skip_at_frame_start and not clock.paused and clock.speed > 0 and delta > 0:
|
||||
allowed = mini(int((float(clock.get("_accumulator")) + delta * clock.speed) / (10.0 / 60.0)), 8)
|
||||
pending_pair["allowed_ticks_observation"] = allowed
|
||||
pre_cost_us = Time.get_ticks_usec() - began
|
||||
|
||||
func sample(observer_started_us: int, delta: float) -> void:
|
||||
if not ready_to_sample:
|
||||
return
|
||||
if pending_pair.is_empty():
|
||||
if baseline.is_empty():
|
||||
report["leading_partial_frame"] = {"engine_frame_post": Engine.get_process_frames()}
|
||||
return
|
||||
report["frame_order_evidence"] = {"engine_frame_post": Engine.get_process_frames()}
|
||||
finish("frame_order_unverified")
|
||||
return
|
||||
var pair := pending_pair
|
||||
pending_pair = {}
|
||||
observation_order += 1
|
||||
pair.merge({"engine_frame_post": Engine.get_process_frames(), "post_order": observation_order,
|
||||
"post_delta_f64_hex": f64(delta), "paused_after": clock.paused,
|
||||
"minute_after": clock.absolute_minute, "acc_after_f64_hex": f64(clock.get("_accumulator"))})
|
||||
if pair.engine_frame_pre != pair.engine_frame_post or pair.process_delta_f64_hex != pair.post_delta_f64_hex:
|
||||
report["frame_order_evidence"] = pair
|
||||
finish("frame_order_unverified")
|
||||
return
|
||||
if baseline.is_empty():
|
||||
baseline = pair
|
||||
last_pair = pair
|
||||
anchor_baseline()
|
||||
baseline["observer_us"] = pre_cost_us + Time.get_ticks_usec() - observer_started_us
|
||||
return
|
||||
if pair.engine_frame_pre != last_pair.engine_frame_post + 1 or pair.pre_order != last_pair.post_order + 1 \
|
||||
or pair.minute_at_frame_start != last_pair.minute_after or pair.acc_at_frame_start_f64_hex != last_pair.acc_after_f64_hex:
|
||||
report["frame_order_evidence"] = {"previous": last_pair, "current": pair}
|
||||
finish("frame_order_unverified")
|
||||
return
|
||||
last_pair = pair
|
||||
var current_us := Time.get_ticks_usec()
|
||||
var minute: int = clock.absolute_minute
|
||||
var elapsed := current_us - previous_us
|
||||
var advanced := minute - previous_minute
|
||||
if advanced > 0:
|
||||
cached_registered = registered()
|
||||
cached_active = active_counts()
|
||||
cached_rng = rng_state()
|
||||
counts_minute = minute
|
||||
var stall_raw: Variant = field(clock.performance_budget.get("_samples"), "main_thread_stall")
|
||||
var stall_count: Variant = field(clock.performance_budget.get("_sample_counts"), "main_thread_stall")
|
||||
var kernel_stalls: Array = []
|
||||
if stall_count != null and last_stall_count != null and stall_raw is Array:
|
||||
var stall_delta: int = stall_count - last_stall_count
|
||||
if stall_delta > 0 and stall_delta <= stall_raw.size():
|
||||
kernel_stalls = stall_raw.slice(stall_raw.size() - stall_delta)
|
||||
last_stall_count = stall_count
|
||||
var causes: Array = []
|
||||
for event: Dictionary in save_events:
|
||||
if event.frame == frames.size() and (elapsed > 50000 or (not kernel_stalls.is_empty() and kernel_stalls.max() > 50000)):
|
||||
causes = ["save_operation_completed"]
|
||||
break
|
||||
var row := {"frame": frames.size(), "game_start": previous_minute, "game_end": minute,
|
||||
"minutes_advanced": advanced, "frame_wall_us": elapsed, "wall_start": previous_wall,
|
||||
"wall": Time.get_unix_time_from_system(),
|
||||
"speed": clock.speed, "paused": clock.paused,
|
||||
"segment": "warmup" if previous_minute - start_minute < warmup else "measurement",
|
||||
"registered": cached_registered, "active": cached_active, "drawn": draw_counts(),
|
||||
"seed": runtime.world_seed, "rng_streams": cached_rng,
|
||||
"is_minute_sample": advanced > 0, "counts_from_minute": counts_minute,
|
||||
"stall_causes": causes, "kernel_stall_samples_us": kernel_stalls,
|
||||
"render_submit_wall_us": render_submit_us if render_serial != last_render_serial else null,
|
||||
"render_submit_wall_us_reason": "no_new_draw_submission" if render_serial == last_render_serial or render_submit_us == null else null,
|
||||
"render_time_source": "RenderingServer.viewport_get_measured_render_time",
|
||||
"metadata": report.metadata, "input_sha256": report.input_sha256,
|
||||
"input_sha256_reason": report.input_sha256_reason,
|
||||
"preparation_sha256": report.preparation_sha256,
|
||||
"render_cpu_raw_ms": RenderingServer.viewport_get_measured_render_time_cpu(root.get_viewport_rid()),
|
||||
"render_gpu_raw_ms": RenderingServer.viewport_get_measured_render_time_gpu(root.get_viewport_rid()),
|
||||
"time_process_seconds": Performance.get_monitor(Performance.TIME_PROCESS)}
|
||||
row.merge(pair)
|
||||
last_render_serial = render_serial
|
||||
previous_us = current_us
|
||||
previous_wall = row.wall
|
||||
previous_minute = minute
|
||||
if not warmup_ended and minute - start_minute >= warmup:
|
||||
warmup_ended = true
|
||||
report["kernel_at_warmup_end"] = {"source": "clock.performance_budget (product diagnostic, raw, not a verdict)", "data": clock.get_performance_reports()}
|
||||
report["budget_at_warmup_end"] = {"source": "clock.performance_budget (product diagnostic, raw, not a verdict)", "data": budget_snapshot()}
|
||||
if warmup > 0:
|
||||
measurement_start = minute
|
||||
var operations: Dictionary = runtime.get_operations_report()
|
||||
var reason := ""
|
||||
if not overloads.is_empty():
|
||||
reason = "simulation_overloaded"
|
||||
elif field(operations, "runtime_failure_latched") == true:
|
||||
reason = "operations_runtime_failure"
|
||||
elif runtime.operations_coordinator != null and runtime.operations_coordinator.is_terminal():
|
||||
reason = "operations_terminal"
|
||||
elif field(operations, "tick_blocked") == true:
|
||||
reason = "operations_pause"
|
||||
elif clock.paused:
|
||||
reason = "clock_paused"
|
||||
elif camera_state() != camera:
|
||||
reason = "camera_changed"
|
||||
elif float(current_us) / 1000000.0 >= 1740.0:
|
||||
reason = "timeout"
|
||||
elif measurement_start >= 0 and minute - measurement_start >= target:
|
||||
reason = "complete"
|
||||
# This includes count/drawing reads and serialization; emission and final
|
||||
# summary are disclosed separately and are never subtracted from frame wall.
|
||||
var raw_line := JSON.stringify(row)
|
||||
print("P28_01_FRAME=" + raw_line)
|
||||
row["observer_us"] = pre_cost_us + Time.get_ticks_usec() - observer_started_us
|
||||
frames.append(row)
|
||||
print("P28_01_OBSERVER=" + JSON.stringify({"frame": row.frame, "observer_us": row.observer_us}))
|
||||
if not reason.is_empty():
|
||||
finish(reason)
|
||||
|
||||
func stats(values: Array, reason := "no_samples") -> Dictionary:
|
||||
if values.is_empty():
|
||||
return {"count": 0, "mean": null, "p95": null, "max": null, "reason": reason}
|
||||
var ordered := values.duplicate()
|
||||
ordered.sort()
|
||||
var total := 0.0
|
||||
for value: float in values:
|
||||
total += value
|
||||
return {"count": values.size(), "mean": total / values.size(),
|
||||
"p95": ordered[int(ceil(values.size() * 0.95)) - 1], "max": ordered.back(), "reason": null}
|
||||
|
||||
func summary(segment: String) -> Dictionary:
|
||||
var wall: Array = []
|
||||
var per_minute: Array = []
|
||||
var cpu: Array = []
|
||||
var gpu: Array = []
|
||||
var process: Array = []
|
||||
var selected: Array = []
|
||||
var stalls: Array = []
|
||||
var observer: Array = []
|
||||
var submit: Array = []
|
||||
var excluded := 0
|
||||
var short_of_allowed := 0
|
||||
var minutes := 0
|
||||
var cpu_missing := false
|
||||
var gpu_missing := false
|
||||
var ranges := {"registered": {}, "active": {}}
|
||||
for frame: Dictionary in frames:
|
||||
if frame.segment != segment:
|
||||
continue
|
||||
selected.append(frame)
|
||||
wall.append(frame.frame_wall_us)
|
||||
observer.append(frame.observer_us)
|
||||
if frame.render_submit_wall_us != null:
|
||||
submit.append(frame.render_submit_wall_us)
|
||||
process.append(frame.time_process_seconds)
|
||||
minutes += frame.minutes_advanced
|
||||
if frame.minutes_advanced < frame.allowed_ticks_observation:
|
||||
short_of_allowed += 1
|
||||
if frame.frame_wall_us > 50000:
|
||||
stalls.append(frame.frame_wall_us)
|
||||
if frame.minutes_advanced > 0:
|
||||
per_minute.append(float(frame.frame_wall_us) / frame.minutes_advanced)
|
||||
else:
|
||||
excluded += 1
|
||||
cpu_missing = cpu_missing or frame.render_cpu_raw_ms <= 0
|
||||
gpu_missing = gpu_missing or frame.render_gpu_raw_ms <= 0
|
||||
cpu.append(frame.render_cpu_raw_ms)
|
||||
gpu.append(frame.render_gpu_raw_ms)
|
||||
for kind: String in ["registered", "active"]:
|
||||
for key: String in frame[kind]:
|
||||
var value: int = frame[kind][key]
|
||||
if not ranges[kind].has(key):
|
||||
ranges[kind][key] = {"min": value, "max": value}
|
||||
ranges[kind][key].min = mini(ranges[kind][key].min, value)
|
||||
ranges[kind][key].max = maxi(ranges[kind][key].max, value)
|
||||
var unavailable := "zero_from_api_on_gl_compatibility_unsupported_or_unmeasured"
|
||||
var minute_stats := stats(per_minute)
|
||||
return {"frames": selected.size(), "actual_minutes": minutes,
|
||||
"short_of_allowed_frames": short_of_allowed,
|
||||
"start_minute": selected.front().game_start if not selected.is_empty() else null,
|
||||
"end_minute": selected.back().game_end if not selected.is_empty() else null,
|
||||
"start_wall": selected.front().wall_start if not selected.is_empty() else null,
|
||||
"end_wall": selected.back().wall if not selected.is_empty() else null,
|
||||
"empty_reason": "no_frames" if selected.is_empty() else null,
|
||||
"frame_observation": {"frame_wall_us": stats(wall), "sim_us_per_minute": minute_stats,
|
||||
"stall_over_50000_us": stats(stalls)},
|
||||
"observer_us": stats(observer), "render_submit_wall_us": stats(submit),
|
||||
"zero_minute_frames_excluded": excluded,
|
||||
"render_cpu_ms": stats([], unavailable) if cpu_missing else stats(cpu),
|
||||
"render_gpu_ms": stats([], unavailable) if gpu_missing else stats(gpu),
|
||||
"time_process_seconds": stats(process), "count_ranges": ranges}
|
||||
|
||||
func finish(reason: String) -> void:
|
||||
if finished:
|
||||
return
|
||||
finished = true
|
||||
ready_to_sample = false
|
||||
if not pending_pair.is_empty():
|
||||
report["trailing_partial_frame"] = pending_pair
|
||||
pending_pair = {}
|
||||
# Early input shortfalls also need observed startup time for stage0 evidence.
|
||||
if not report.has("startup_wall_seconds"):
|
||||
report["startup_wall_seconds"] = float(Time.get_ticks_usec()) / 1000000.0
|
||||
report["stop_reason"] = reason
|
||||
report["actual_minutes"] = int(clock.absolute_minute) - start_minute if start_us > 0 else 0
|
||||
report["elapsed_wall_seconds"] = float(Time.get_ticks_usec() - start_us) / 1000000.0 if start_us > 0 else 0.0
|
||||
report["end_wall"] = Time.get_unix_time_from_system()
|
||||
report["kernel_at_phase_end"] = {"source": "clock.performance_budget (product diagnostic, raw, not a verdict)", "data": clock.get_performance_reports()}
|
||||
report["budget_at_phase_end"] = {"source": "clock.performance_budget (product diagnostic, raw, not a verdict)", "data": budget_snapshot()}
|
||||
if reason != "frame_order_unverified":
|
||||
report["budget_verdict"] = kernel_verdict(report.budget_at_warmup_end.data, report.budget_at_phase_end.data)
|
||||
report["operations_at_end"] = runtime.get_operations_report()
|
||||
report["warmup"] = summary("warmup")
|
||||
report["measurement"] = summary("measurement")
|
||||
report["audio_owner_present"] = root.has_meta(UIAudioOwnerShutdownScript.OWNER_META)
|
||||
print("P28_01_PERF_REPORT=" + JSON.stringify(report))
|
||||
if not is_instance_valid(main) or not main.is_inside_tree():
|
||||
emit_audio_close({"coordinator": false, "reason": "main_not_added"})
|
||||
quit(0)
|
||||
elif not root.has_meta(UIAudioOwnerShutdownScript.OWNER_META):
|
||||
emit_audio_close({"coordinator": false, "reason": "no_audio_owner"})
|
||||
quit(0)
|
||||
else:
|
||||
main.ui_audio_window_close_drained.connect(emit_audio_close, CONNECT_ONE_SHOT)
|
||||
root.close_requested.emit()
|
||||
|
||||
func emit_audio_close(result: Dictionary) -> void:
|
||||
if audio_emitted:
|
||||
return
|
||||
audio_emitted = true
|
||||
print("P28_01_AUDIO_CLOSE=" + JSON.stringify(result))
|
||||
+1098
File diff suppressed because it is too large
Load Diff
+23684
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,873 @@
|
||||
{
|
||||
"schema": "p28-01-gate-conditions-v1",
|
||||
"conditions": [
|
||||
{
|
||||
"id": "P28-01-G0",
|
||||
"original": true,
|
||||
"evidence": [
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/full-measurement-db9d613-r01/ROOT_RAW_ACCEPTANCE_JOIN.json",
|
||||
"sha256": "a76082f767e0a0d52cb70e2f66fa371b62a88754e9be319da3c08d4b48e78ba0"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/full-measurement-db9d613-r01/run/admission.json",
|
||||
"sha256": "c4a3b5ba7a2b6427e55a6f85f037ded08e0c6bfd8c0a766647747ea9cc94378d"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/full-measurement-db9d613-r01/run/execution.json",
|
||||
"sha256": "a4d10e9b1a75a17b981792265338d64fac61178cb6f87dd646c5b486292db73c"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-formal-regression-83b170a-r01/bootstrap/profile_guard.gd",
|
||||
"sha256": "597696c705cc8f0e79a35265ca3ef8df55f7fc3c4e0e5a2ce9a4e20e29e29a6f"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/collect_perf.py",
|
||||
"sha256": "43f3e42900a670957a01cfcaca7e485809527b79912a358917c935c57ea9c063"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/after/evidence/report.json",
|
||||
"sha256": "f1d447937158a884a1b287c3b5be38b6e73b73c5d748f9576401877402dabe1b"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/after/measure-native/stdout.log",
|
||||
"sha256": "294730160536fc862ca657654fbae290b874acc65b77a6d24aaccd9a859907f7"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/after/measure.stdout.log",
|
||||
"sha256": "294730160536fc862ca657654fbae290b874acc65b77a6d24aaccd9a859907f7"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/before/evidence/report.json",
|
||||
"sha256": "a070c061ca783aaec37152bf66ad6b6e6dc69b80d9c4c024eceab916d4c5d4e9"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/execution.json",
|
||||
"sha256": "553971f55635e529b48634ef86ad7d4198c8bafe04d5a6a5ef778a4de0439fc1"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/guarded.gd",
|
||||
"sha256": "c9deebb39ee643d312a77bae64f74cf6851697bb31036fc56d67dd7c4037ee09"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/measure.gd",
|
||||
"sha256": "95e5967835cb05ad96e8e6f257689d0bd831792aa9c228c022e3af81448ff76a"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/profile_guard.gd",
|
||||
"sha256": "597696c705cc8f0e79a35265ca3ef8df55f7fc3c4e0e5a2ce9a4e20e29e29a6f"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/readiness.json",
|
||||
"sha256": "da93aecdd2c1648952e2a80382acb7e3f30cfe48a7e5df982315791161fedde2"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/full-measurement-db9d613-r01/run/profile_guard.gd",
|
||||
"sha256": "597696c705cc8f0e79a35265ca3ef8df55f7fc3c4e0e5a2ce9a4e20e29e29a6f"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/native-restart-954528a-r01/run/profile_guard.gd",
|
||||
"sha256": "597696c705cc8f0e79a35265ca3ef8df55f7fc3c4e0e5a2ce9a4e20e29e29a6f"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-current1299-runtime-scale-build-candidate-native-r01/fixture/reference.variant",
|
||||
"sha256": "eb4f770071eb07cd7e136fe54c3f78768cc8de7a4264e62a9af1f06a8f90f9bd"
|
||||
}
|
||||
],
|
||||
"basis": [
|
||||
"Tick と描画(CPU は P1〜P4)は E2。経路・snapshot・保存・読込は E3",
|
||||
"E2 と E3 の各 report の欄"
|
||||
],
|
||||
"scope_and_unmet": [
|
||||
"稼働していた施設は100。GPU は null。E3 は headless・Ops OFF・x1",
|
||||
"記録の項目がそろえば成立する。予算の合否は条件に含まれない"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-G1",
|
||||
"original": true,
|
||||
"evidence": [
|
||||
{
|
||||
"path": "external/018d39f49eb251238be3/result.json",
|
||||
"sha256": "23eed6741996a7b9327fcceda2e852104315954105c9c09f0dfcdf35c88c8d41"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p28-01/COLLECTOR_FORMAL_COMPLETION_0F53A67.json",
|
||||
"sha256": "2886547ae2ccd000a85cf8374304f292f85ed2899d9ebe9818833600dc9ff55f"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p28-01/COLLECTOR_INDEPENDENT_REVIEW_5DB97D7.json",
|
||||
"sha256": "23eed6741996a7b9327fcceda2e852104315954105c9c09f0dfcdf35c88c8d41"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/RESTART_3AC2CED_FORMAL_DB_JOIN.json",
|
||||
"sha256": "5c9b82a70f6bdb972d720aaa64f5d27aa54130f27f9d1f7cea1c7b02524f84be"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/ROOT_CANDIDATE_3AC2CED_QUALIFICATION.json",
|
||||
"sha256": "5bd8030acddc385eb728441286f6a740ec1ee966125474b60ef4b1efe2a2c343"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/native-restart-954528a-r01/ROOT_RAW_ACCEPTANCE_JOIN.json",
|
||||
"sha256": "6dbb3cbb4ac1838b66034323cab22a9c5a968c1da0efe8d34676073b3f980cfb"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/native-restart-954528a-r01/run/execution.json",
|
||||
"sha256": "c1ec566402896f9f3fdefdfc268de6ed0bc46b8a37a9eb6bfb252b0cc4ebb514"
|
||||
}
|
||||
],
|
||||
"basis": [
|
||||
"collector C1/C9(`_set_speed`→`_process`、main)と RESTART RS1(UI での保存と読込)"
|
||||
],
|
||||
"scope_and_unmet": [
|
||||
"測定の範囲は Mac(Mac16,10)。Windows での最終受入は未達のまま"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-G2",
|
||||
"original": true,
|
||||
"evidence": [
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/full-measurement-db9d613-r01/ROOT_RAW_ACCEPTANCE_JOIN.json",
|
||||
"sha256": "a76082f767e0a0d52cb70e2f66fa371b62a88754e9be319da3c08d4b48e78ba0"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/full-measurement-db9d613-r01/run/admission.json",
|
||||
"sha256": "c4a3b5ba7a2b6427e55a6f85f037ded08e0c6bfd8c0a766647747ea9cc94378d"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/full-measurement-db9d613-r01/run/execution.json",
|
||||
"sha256": "a4d10e9b1a75a17b981792265338d64fac61178cb6f87dd646c5b486292db73c"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-formal-regression-83b170a-r01/bootstrap/profile_guard.gd",
|
||||
"sha256": "597696c705cc8f0e79a35265ca3ef8df55f7fc3c4e0e5a2ce9a4e20e29e29a6f"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/collect_perf.py",
|
||||
"sha256": "43f3e42900a670957a01cfcaca7e485809527b79912a358917c935c57ea9c063"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/after/evidence/report.json",
|
||||
"sha256": "f1d447937158a884a1b287c3b5be38b6e73b73c5d748f9576401877402dabe1b"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/after/measure-native/stdout.log",
|
||||
"sha256": "294730160536fc862ca657654fbae290b874acc65b77a6d24aaccd9a859907f7"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/after/measure.stdout.log",
|
||||
"sha256": "294730160536fc862ca657654fbae290b874acc65b77a6d24aaccd9a859907f7"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/before/evidence/report.json",
|
||||
"sha256": "a070c061ca783aaec37152bf66ad6b6e6dc69b80d9c4c024eceab916d4c5d4e9"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/execution.json",
|
||||
"sha256": "553971f55635e529b48634ef86ad7d4198c8bafe04d5a6a5ef778a4de0439fc1"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/guarded.gd",
|
||||
"sha256": "c9deebb39ee643d312a77bae64f74cf6851697bb31036fc56d67dd7c4037ee09"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/measure.gd",
|
||||
"sha256": "95e5967835cb05ad96e8e6f257689d0bd831792aa9c228c022e3af81448ff76a"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/profile_guard.gd",
|
||||
"sha256": "597696c705cc8f0e79a35265ca3ef8df55f7fc3c4e0e5a2ce9a4e20e29e29a6f"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/readiness.json",
|
||||
"sha256": "da93aecdd2c1648952e2a80382acb7e3f30cfe48a7e5df982315791161fedde2"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/full-measurement-db9d613-r01/run/profile_guard.gd",
|
||||
"sha256": "597696c705cc8f0e79a35265ca3ef8df55f7fc3c4e0e5a2ce9a4e20e29e29a6f"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/native-restart-954528a-r01/run/profile_guard.gd",
|
||||
"sha256": "597696c705cc8f0e79a35265ca3ef8df55f7fc3c4e0e5a2ce9a4e20e29e29a6f"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-current1299-runtime-scale-build-candidate-native-r01/fixture/reference.variant",
|
||||
"sha256": "eb4f770071eb07cd7e136fe54c3f78768cc8de7a4264e62a9af1f06a8f90f9bd"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/native-restart-954528a-r01/ROOT_RAW_ACCEPTANCE_JOIN.json",
|
||||
"sha256": "6dbb3cbb4ac1838b66034323cab22a9c5a968c1da0efe8d34676073b3f980cfb"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/native-restart-954528a-r01/run/execution.json",
|
||||
"sha256": "c1ec566402896f9f3fdefdfc268de6ed0bc46b8a37a9eb6bfb252b0cc4ebb514"
|
||||
}
|
||||
],
|
||||
"basis": [
|
||||
"既定の autoload の世界、O の実際の Ops ON、RESTART の実際の継続"
|
||||
],
|
||||
"scope_and_unmet": [
|
||||
"O は小さな入力(客0〜2)。200客での Ops ON は上限100のため不可"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-G3",
|
||||
"original": true,
|
||||
"evidence": [
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/RESTART_3AC2CED_FORMAL_DB_JOIN.json",
|
||||
"sha256": "5c9b82a70f6bdb972d720aaa64f5d27aa54130f27f9d1f7cea1c7b02524f84be"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/ROOT_CANDIDATE_3AC2CED_QUALIFICATION.json",
|
||||
"sha256": "5bd8030acddc385eb728441286f6a740ec1ee966125474b60ef4b1efe2a2c343"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/native-restart-954528a-r01/ROOT_RAW_ACCEPTANCE_JOIN.json",
|
||||
"sha256": "6dbb3cbb4ac1838b66034323cab22a9c5a968c1da0efe8d34676073b3f980cfb"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/native-restart-954528a-r01/run/execution.json",
|
||||
"sha256": "c1ec566402896f9f3fdefdfc268de6ed0bc46b8a37a9eb6bfb252b0cc4ebb514"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/independent-review-bc703c9-changes-requested-r01/result.json",
|
||||
"sha256": "f2e67cb2adbef9c9294ec1e879da70b500fa27d2220d9ae1b9fb50c96cbdc3b2"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/restart-independent-review-954528a-r01/DESKTOP_DELIVERY.json",
|
||||
"sha256": "90b6943d86bf370599c3a8629c57a69806374a5863ce18957a7b7d696a5be9fd"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/restart-independent-review-954528a-r01/DESKTOP_RESULT.json",
|
||||
"sha256": "b8fff13e8789797a4a50a5e2900f3d84ef20a6682a8f25feca225edc7fe00300"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/restart-independent-review-954528a-r01/actual-output-after-receiver-timeout/origin.json",
|
||||
"sha256": "90b6943d86bf370599c3a8629c57a69806374a5863ce18957a7b7d696a5be9fd"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/restart-independent-review-954528a-r01/actual-output-after-receiver-timeout/result.json",
|
||||
"sha256": "b8fff13e8789797a4a50a5e2900f3d84ef20a6682a8f25feca225edc7fe00300"
|
||||
}
|
||||
],
|
||||
"basis": [
|
||||
"RESTART RS1〜RS5(E5、E6)と、下の明示した証拠"
|
||||
],
|
||||
"scope_and_unmet": [
|
||||
"下の判断材料を参照。最終的な判定は whole-unit の独立レビューが行う"
|
||||
],
|
||||
"product_contract": [
|
||||
{
|
||||
"path": "scripts/core/simulation_kernel.gd",
|
||||
"sha256": "341c0c878c4894adbac628d0db6362ff69fc3347bdd9e61e50936e15dc2d1edb",
|
||||
"lines": "217-231"
|
||||
},
|
||||
{
|
||||
"path": "autoload/simulation_runtime.gd",
|
||||
"sha256": "dbde152e4d93a297e1215a281b22ccd97bed994541c3fc89417cb8f56304ad95",
|
||||
"lines": "521-583"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-G4",
|
||||
"original": true,
|
||||
"evidence": [
|
||||
{
|
||||
"path": "external/018d39f49eb251238be3/result.json",
|
||||
"sha256": "23eed6741996a7b9327fcceda2e852104315954105c9c09f0dfcdf35c88c8d41"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p28-01/COLLECTOR_FORMAL_COMPLETION_0F53A67.json",
|
||||
"sha256": "2886547ae2ccd000a85cf8374304f292f85ed2899d9ebe9818833600dc9ff55f"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p28-01/COLLECTOR_INDEPENDENT_REVIEW_5DB97D7.json",
|
||||
"sha256": "23eed6741996a7b9327fcceda2e852104315954105c9c09f0dfcdf35c88c8d41"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/full-measurement-db9d613-r01/ROOT_RAW_ACCEPTANCE_JOIN.json",
|
||||
"sha256": "a76082f767e0a0d52cb70e2f66fa371b62a88754e9be319da3c08d4b48e78ba0"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/full-measurement-db9d613-r01/run/admission.json",
|
||||
"sha256": "c4a3b5ba7a2b6427e55a6f85f037ded08e0c6bfd8c0a766647747ea9cc94378d"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/full-measurement-db9d613-r01/run/execution.json",
|
||||
"sha256": "a4d10e9b1a75a17b981792265338d64fac61178cb6f87dd646c5b486292db73c"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/RESTART_3AC2CED_FORMAL_DB_JOIN.json",
|
||||
"sha256": "5c9b82a70f6bdb972d720aaa64f5d27aa54130f27f9d1f7cea1c7b02524f84be"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/ROOT_CANDIDATE_3AC2CED_QUALIFICATION.json",
|
||||
"sha256": "5bd8030acddc385eb728441286f6a740ec1ee966125474b60ef4b1efe2a2c343"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/native-restart-954528a-r01/ROOT_RAW_ACCEPTANCE_JOIN.json",
|
||||
"sha256": "6dbb3cbb4ac1838b66034323cab22a9c5a968c1da0efe8d34676073b3f980cfb"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/native-restart-954528a-r01/run/execution.json",
|
||||
"sha256": "c1ec566402896f9f3fdefdfc268de6ed0bc46b8a37a9eb6bfb252b0cc4ebb514"
|
||||
}
|
||||
],
|
||||
"basis": [
|
||||
"collector の停止契約と phase の失敗の記録、RESTART の改ざん・停止・境界"
|
||||
],
|
||||
"scope_and_unmet": [
|
||||
"実施していない分岐は U1 で開示する"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-G5",
|
||||
"original": true,
|
||||
"evidence": [
|
||||
{
|
||||
"path": "external/018d39f49eb251238be3/result.json",
|
||||
"sha256": "23eed6741996a7b9327fcceda2e852104315954105c9c09f0dfcdf35c88c8d41"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p28-01/COLLECTOR_FORMAL_COMPLETION_0F53A67.json",
|
||||
"sha256": "2886547ae2ccd000a85cf8374304f292f85ed2899d9ebe9818833600dc9ff55f"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p28-01/COLLECTOR_INDEPENDENT_REVIEW_5DB97D7.json",
|
||||
"sha256": "23eed6741996a7b9327fcceda2e852104315954105c9c09f0dfcdf35c88c8d41"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-formal-regression-83b170a-r01/all-tests.json",
|
||||
"sha256": "31da4c4672f4321469183996e12fb23064b3772a69e8330052ab3e2ea66044f1"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-formal-regression-83b170a-r01/runner-ended.json",
|
||||
"sha256": "705fb2c12007bab125548742dc8d9b07918e30cae3a63c7d89e965c43b8b5f7b"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-formal-regression-83b170a-r01/runner-input.json",
|
||||
"sha256": "5bc9760c0c114a80a3c9e93634e733fc8fa93d4eeea5d8bcfda85d8e4eebd094"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json",
|
||||
"sha256": "0ff86a20891da66d84295f0c66ae7517ef586b54a1a668acb7778a450d473cd2"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/RESTART_3AC2CED_FORMAL_DB_JOIN.json",
|
||||
"sha256": "5c9b82a70f6bdb972d720aaa64f5d27aa54130f27f9d1f7cea1c7b02524f84be"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/ROOT_CANDIDATE_3AC2CED_QUALIFICATION.json",
|
||||
"sha256": "5bd8030acddc385eb728441286f6a740ec1ee966125474b60ef4b1efe2a2c343"
|
||||
}
|
||||
],
|
||||
"basis": [
|
||||
"E4 を再利用(source と marker の一致)。collector と RESTART の unit validation"
|
||||
],
|
||||
"scope_and_unmet": [
|
||||
"製品が変わったら、全 roster をやり直す"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-G6",
|
||||
"original": true,
|
||||
"evidence": [
|
||||
{
|
||||
"path": "external/018d39f49eb251238be3/result.json",
|
||||
"sha256": "23eed6741996a7b9327fcceda2e852104315954105c9c09f0dfcdf35c88c8d41"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p28-01/COLLECTOR_FORMAL_COMPLETION_0F53A67.json",
|
||||
"sha256": "2886547ae2ccd000a85cf8374304f292f85ed2899d9ebe9818833600dc9ff55f"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p28-01/COLLECTOR_INDEPENDENT_REVIEW_5DB97D7.json",
|
||||
"sha256": "23eed6741996a7b9327fcceda2e852104315954105c9c09f0dfcdf35c88c8d41"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/full-measurement-db9d613-r01/ROOT_RAW_ACCEPTANCE_JOIN.json",
|
||||
"sha256": "a76082f767e0a0d52cb70e2f66fa371b62a88754e9be319da3c08d4b48e78ba0"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/full-measurement-db9d613-r01/run/admission.json",
|
||||
"sha256": "c4a3b5ba7a2b6427e55a6f85f037ded08e0c6bfd8c0a766647747ea9cc94378d"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/full-measurement-db9d613-r01/run/execution.json",
|
||||
"sha256": "a4d10e9b1a75a17b981792265338d64fac61178cb6f87dd646c5b486292db73c"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-formal-regression-83b170a-r01/bootstrap/profile_guard.gd",
|
||||
"sha256": "597696c705cc8f0e79a35265ca3ef8df55f7fc3c4e0e5a2ce9a4e20e29e29a6f"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/collect_perf.py",
|
||||
"sha256": "43f3e42900a670957a01cfcaca7e485809527b79912a358917c935c57ea9c063"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/after/evidence/report.json",
|
||||
"sha256": "f1d447937158a884a1b287c3b5be38b6e73b73c5d748f9576401877402dabe1b"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/after/measure-native/stdout.log",
|
||||
"sha256": "294730160536fc862ca657654fbae290b874acc65b77a6d24aaccd9a859907f7"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/after/measure.stdout.log",
|
||||
"sha256": "294730160536fc862ca657654fbae290b874acc65b77a6d24aaccd9a859907f7"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/before/evidence/report.json",
|
||||
"sha256": "a070c061ca783aaec37152bf66ad6b6e6dc69b80d9c4c024eceab916d4c5d4e9"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/execution.json",
|
||||
"sha256": "553971f55635e529b48634ef86ad7d4198c8bafe04d5a6a5ef778a4de0439fc1"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/guarded.gd",
|
||||
"sha256": "c9deebb39ee643d312a77bae64f74cf6851697bb31036fc56d67dd7c4037ee09"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/measure.gd",
|
||||
"sha256": "95e5967835cb05ad96e8e6f257689d0bd831792aa9c228c022e3af81448ff76a"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/profile_guard.gd",
|
||||
"sha256": "597696c705cc8f0e79a35265ca3ef8df55f7fc3c4e0e5a2ce9a4e20e29e29a6f"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/readiness.json",
|
||||
"sha256": "da93aecdd2c1648952e2a80382acb7e3f30cfe48a7e5df982315791161fedde2"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/full-measurement-db9d613-r01/run/profile_guard.gd",
|
||||
"sha256": "597696c705cc8f0e79a35265ca3ef8df55f7fc3c4e0e5a2ce9a4e20e29e29a6f"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/native-restart-954528a-r01/run/profile_guard.gd",
|
||||
"sha256": "597696c705cc8f0e79a35265ca3ef8df55f7fc3c4e0e5a2ce9a4e20e29e29a6f"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-current1299-runtime-scale-build-candidate-native-r01/fixture/reference.variant",
|
||||
"sha256": "eb4f770071eb07cd7e136fe54c3f78768cc8de7a4264e62a9af1f06a8f90f9bd"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-formal-regression-83b170a-r01/all-tests.json",
|
||||
"sha256": "31da4c4672f4321469183996e12fb23064b3772a69e8330052ab3e2ea66044f1"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-formal-regression-83b170a-r01/runner-ended.json",
|
||||
"sha256": "705fb2c12007bab125548742dc8d9b07918e30cae3a63c7d89e965c43b8b5f7b"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-formal-regression-83b170a-r01/runner-input.json",
|
||||
"sha256": "5bc9760c0c114a80a3c9e93634e733fc8fa93d4eeea5d8bcfda85d8e4eebd094"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json",
|
||||
"sha256": "0ff86a20891da66d84295f0c66ae7517ef586b54a1a668acb7778a450d473cd2"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/RESTART_3AC2CED_FORMAL_DB_JOIN.json",
|
||||
"sha256": "5c9b82a70f6bdb972d720aaa64f5d27aa54130f27f9d1f7cea1c7b02524f84be"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/ROOT_CANDIDATE_3AC2CED_QUALIFICATION.json",
|
||||
"sha256": "5bd8030acddc385eb728441286f6a740ec1ee966125474b60ef4b1efe2a2c343"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/native-restart-954528a-r01/ROOT_RAW_ACCEPTANCE_JOIN.json",
|
||||
"sha256": "6dbb3cbb4ac1838b66034323cab22a9c5a968c1da0efe8d34676073b3f980cfb"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/native-restart-954528a-r01/run/execution.json",
|
||||
"sha256": "c1ec566402896f9f3fdefdfc268de6ed0bc46b8a37a9eb6bfb252b0cc4ebb514"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/independent-review-bc703c9-changes-requested-r01/result.json",
|
||||
"sha256": "f2e67cb2adbef9c9294ec1e879da70b500fa27d2220d9ae1b9fb50c96cbdc3b2"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/restart-independent-review-954528a-r01/DESKTOP_DELIVERY.json",
|
||||
"sha256": "90b6943d86bf370599c3a8629c57a69806374a5863ce18957a7b7d696a5be9fd"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/restart-independent-review-954528a-r01/DESKTOP_RESULT.json",
|
||||
"sha256": "b8fff13e8789797a4a50a5e2900f3d84ef20a6682a8f25feca225edc7fe00300"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/restart-independent-review-954528a-r01/actual-output-after-receiver-timeout/origin.json",
|
||||
"sha256": "90b6943d86bf370599c3a8629c57a69806374a5863ce18957a7b7d696a5be9fd"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/restart-independent-review-954528a-r01/actual-output-after-receiver-timeout/result.json",
|
||||
"sha256": "b8fff13e8789797a4a50a5e2900f3d84ef20a6682a8f25feca225edc7fe00300"
|
||||
}
|
||||
],
|
||||
"basis": [
|
||||
"全 receipt の SHA、Root の結合、verifier の manifest"
|
||||
],
|
||||
"scope_and_unmet": [
|
||||
"—"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-U1",
|
||||
"original": false,
|
||||
"evidence": [
|
||||
{
|
||||
"path": "external/018d39f49eb251238be3/result.json",
|
||||
"sha256": "23eed6741996a7b9327fcceda2e852104315954105c9c09f0dfcdf35c88c8d41"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p28-01/COLLECTOR_FORMAL_COMPLETION_0F53A67.json",
|
||||
"sha256": "2886547ae2ccd000a85cf8374304f292f85ed2899d9ebe9818833600dc9ff55f"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p28-01/COLLECTOR_INDEPENDENT_REVIEW_5DB97D7.json",
|
||||
"sha256": "23eed6741996a7b9327fcceda2e852104315954105c9c09f0dfcdf35c88c8d41"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/full-measurement-db9d613-r01/ROOT_RAW_ACCEPTANCE_JOIN.json",
|
||||
"sha256": "a76082f767e0a0d52cb70e2f66fa371b62a88754e9be319da3c08d4b48e78ba0"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/full-measurement-db9d613-r01/run/admission.json",
|
||||
"sha256": "c4a3b5ba7a2b6427e55a6f85f037ded08e0c6bfd8c0a766647747ea9cc94378d"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/full-measurement-db9d613-r01/run/execution.json",
|
||||
"sha256": "a4d10e9b1a75a17b981792265338d64fac61178cb6f87dd646c5b486292db73c"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-formal-regression-83b170a-r01/bootstrap/profile_guard.gd",
|
||||
"sha256": "597696c705cc8f0e79a35265ca3ef8df55f7fc3c4e0e5a2ce9a4e20e29e29a6f"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/collect_perf.py",
|
||||
"sha256": "43f3e42900a670957a01cfcaca7e485809527b79912a358917c935c57ea9c063"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/after/evidence/report.json",
|
||||
"sha256": "f1d447937158a884a1b287c3b5be38b6e73b73c5d748f9576401877402dabe1b"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/after/measure-native/stdout.log",
|
||||
"sha256": "294730160536fc862ca657654fbae290b874acc65b77a6d24aaccd9a859907f7"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/after/measure.stdout.log",
|
||||
"sha256": "294730160536fc862ca657654fbae290b874acc65b77a6d24aaccd9a859907f7"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/before/evidence/report.json",
|
||||
"sha256": "a070c061ca783aaec37152bf66ad6b6e6dc69b80d9c4c024eceab916d4c5d4e9"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/execution.json",
|
||||
"sha256": "553971f55635e529b48634ef86ad7d4198c8bafe04d5a6a5ef778a4de0439fc1"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/guarded.gd",
|
||||
"sha256": "c9deebb39ee643d312a77bae64f74cf6851697bb31036fc56d67dd7c4037ee09"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/measure.gd",
|
||||
"sha256": "95e5967835cb05ad96e8e6f257689d0bd831792aa9c228c022e3af81448ff76a"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/profile_guard.gd",
|
||||
"sha256": "597696c705cc8f0e79a35265ca3ef8df55f7fc3c4e0e5a2ce9a4e20e29e29a6f"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/readiness.json",
|
||||
"sha256": "da93aecdd2c1648952e2a80382acb7e3f30cfe48a7e5df982315791161fedde2"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/full-measurement-db9d613-r01/run/profile_guard.gd",
|
||||
"sha256": "597696c705cc8f0e79a35265ca3ef8df55f7fc3c4e0e5a2ce9a4e20e29e29a6f"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/native-restart-954528a-r01/run/profile_guard.gd",
|
||||
"sha256": "597696c705cc8f0e79a35265ca3ef8df55f7fc3c4e0e5a2ce9a4e20e29e29a6f"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-current1299-runtime-scale-build-candidate-native-r01/fixture/reference.variant",
|
||||
"sha256": "eb4f770071eb07cd7e136fe54c3f78768cc8de7a4264e62a9af1f06a8f90f9bd"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-formal-regression-83b170a-r01/all-tests.json",
|
||||
"sha256": "31da4c4672f4321469183996e12fb23064b3772a69e8330052ab3e2ea66044f1"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-formal-regression-83b170a-r01/runner-ended.json",
|
||||
"sha256": "705fb2c12007bab125548742dc8d9b07918e30cae3a63c7d89e965c43b8b5f7b"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-formal-regression-83b170a-r01/runner-input.json",
|
||||
"sha256": "5bc9760c0c114a80a3c9e93634e733fc8fa93d4eeea5d8bcfda85d8e4eebd094"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json",
|
||||
"sha256": "0ff86a20891da66d84295f0c66ae7517ef586b54a1a668acb7778a450d473cd2"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/RESTART_3AC2CED_FORMAL_DB_JOIN.json",
|
||||
"sha256": "5c9b82a70f6bdb972d720aaa64f5d27aa54130f27f9d1f7cea1c7b02524f84be"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/ROOT_CANDIDATE_3AC2CED_QUALIFICATION.json",
|
||||
"sha256": "5bd8030acddc385eb728441286f6a740ec1ee966125474b60ef4b1efe2a2c343"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/native-restart-954528a-r01/ROOT_RAW_ACCEPTANCE_JOIN.json",
|
||||
"sha256": "6dbb3cbb4ac1838b66034323cab22a9c5a968c1da0efe8d34676073b3f980cfb"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/native-restart-954528a-r01/run/execution.json",
|
||||
"sha256": "c1ec566402896f9f3fdefdfc268de6ed0bc46b8a37a9eb6bfb252b0cc4ebb514"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/independent-review-bc703c9-changes-requested-r01/result.json",
|
||||
"sha256": "f2e67cb2adbef9c9294ec1e879da70b500fa27d2220d9ae1b9fb50c96cbdc3b2"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/restart-independent-review-954528a-r01/DESKTOP_DELIVERY.json",
|
||||
"sha256": "90b6943d86bf370599c3a8629c57a69806374a5863ce18957a7b7d696a5be9fd"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/restart-independent-review-954528a-r01/DESKTOP_RESULT.json",
|
||||
"sha256": "b8fff13e8789797a4a50a5e2900f3d84ef20a6682a8f25feca225edc7fe00300"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/restart-independent-review-954528a-r01/actual-output-after-receiver-timeout/origin.json",
|
||||
"sha256": "90b6943d86bf370599c3a8629c57a69806374a5863ce18957a7b7d696a5be9fd"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/restart-independent-review-954528a-r01/actual-output-after-receiver-timeout/result.json",
|
||||
"sha256": "b8fff13e8789797a4a50a5e2900f3d84ef20a6682a8f25feca225edc7fe00300"
|
||||
}
|
||||
],
|
||||
"basis": [
|
||||
"未達と、実施していない分岐を、合格に数えずに開示する"
|
||||
],
|
||||
"scope_and_unmet": [
|
||||
"予算の未達: P1 x1 平均 479.210 ms > 4、P1 stall 最大 4809.099 ms > 50、P2 x3 P95 5062.697 ms > 12、P2 stall 最大 5891.466 ms > 50。",
|
||||
"200客での Operations ON(不可)。O の P3/P4 の合格は、小さな入力に限られる。",
|
||||
"GPU 時間は null(gl_compatibility)。R0 と O0 は CPU も null。",
|
||||
"同一の Windows 実機、長期の操作感とバランス、ゲーム全体の受入。",
|
||||
"RESTART の low 3件と、G3 の判断の結果。"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-U2",
|
||||
"original": false,
|
||||
"evidence": [
|
||||
{
|
||||
"path": "external/018d39f49eb251238be3/result.json",
|
||||
"sha256": "23eed6741996a7b9327fcceda2e852104315954105c9c09f0dfcdf35c88c8d41"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p28-01/COLLECTOR_FORMAL_COMPLETION_0F53A67.json",
|
||||
"sha256": "2886547ae2ccd000a85cf8374304f292f85ed2899d9ebe9818833600dc9ff55f"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p28-01/COLLECTOR_INDEPENDENT_REVIEW_5DB97D7.json",
|
||||
"sha256": "23eed6741996a7b9327fcceda2e852104315954105c9c09f0dfcdf35c88c8d41"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/full-measurement-db9d613-r01/ROOT_RAW_ACCEPTANCE_JOIN.json",
|
||||
"sha256": "a76082f767e0a0d52cb70e2f66fa371b62a88754e9be319da3c08d4b48e78ba0"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/full-measurement-db9d613-r01/run/admission.json",
|
||||
"sha256": "c4a3b5ba7a2b6427e55a6f85f037ded08e0c6bfd8c0a766647747ea9cc94378d"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/full-measurement-db9d613-r01/run/execution.json",
|
||||
"sha256": "a4d10e9b1a75a17b981792265338d64fac61178cb6f87dd646c5b486292db73c"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-formal-regression-83b170a-r01/bootstrap/profile_guard.gd",
|
||||
"sha256": "597696c705cc8f0e79a35265ca3ef8df55f7fc3c4e0e5a2ce9a4e20e29e29a6f"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/collect_perf.py",
|
||||
"sha256": "43f3e42900a670957a01cfcaca7e485809527b79912a358917c935c57ea9c063"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/after/evidence/report.json",
|
||||
"sha256": "f1d447937158a884a1b287c3b5be38b6e73b73c5d748f9576401877402dabe1b"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/after/measure-native/stdout.log",
|
||||
"sha256": "294730160536fc862ca657654fbae290b874acc65b77a6d24aaccd9a859907f7"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/after/measure.stdout.log",
|
||||
"sha256": "294730160536fc862ca657654fbae290b874acc65b77a6d24aaccd9a859907f7"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/before/evidence/report.json",
|
||||
"sha256": "a070c061ca783aaec37152bf66ad6b6e6dc69b80d9c4c024eceab916d4c5d4e9"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/execution.json",
|
||||
"sha256": "553971f55635e529b48634ef86ad7d4198c8bafe04d5a6a5ef778a4de0439fc1"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/guarded.gd",
|
||||
"sha256": "c9deebb39ee643d312a77bae64f74cf6851697bb31036fc56d67dd7c4037ee09"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/measure.gd",
|
||||
"sha256": "95e5967835cb05ad96e8e6f257689d0bd831792aa9c228c022e3af81448ff76a"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/profile_guard.gd",
|
||||
"sha256": "597696c705cc8f0e79a35265ca3ef8df55f7fc3c4e0e5a2ce9a4e20e29e29a6f"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/run-r02/readiness.json",
|
||||
"sha256": "da93aecdd2c1648952e2a80382acb7e3f30cfe48a7e5df982315791161fedde2"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/full-measurement-db9d613-r01/run/profile_guard.gd",
|
||||
"sha256": "597696c705cc8f0e79a35265ca3ef8df55f7fc3c4e0e5a2ce9a4e20e29e29a6f"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/native-restart-954528a-r01/run/profile_guard.gd",
|
||||
"sha256": "597696c705cc8f0e79a35265ca3ef8df55f7fc3c4e0e5a2ce9a4e20e29e29a6f"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-current1299-runtime-scale-build-candidate-native-r01/fixture/reference.variant",
|
||||
"sha256": "eb4f770071eb07cd7e136fe54c3f78768cc8de7a4264e62a9af1f06a8f90f9bd"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-formal-regression-83b170a-r01/all-tests.json",
|
||||
"sha256": "31da4c4672f4321469183996e12fb23064b3772a69e8330052ab3e2ea66044f1"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-formal-regression-83b170a-r01/runner-ended.json",
|
||||
"sha256": "705fb2c12007bab125548742dc8d9b07918e30cae3a63c7d89e965c43b8b5f7b"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p14-03-formal-regression-83b170a-r01/runner-input.json",
|
||||
"sha256": "5bc9760c0c114a80a3c9e93634e733fc8fa93d4eeea5d8bcfda85d8e4eebd094"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json",
|
||||
"sha256": "0ff86a20891da66d84295f0c66ae7517ef586b54a1a668acb7778a450d473cd2"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/RESTART_3AC2CED_FORMAL_DB_JOIN.json",
|
||||
"sha256": "5c9b82a70f6bdb972d720aaa64f5d27aa54130f27f9d1f7cea1c7b02524f84be"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/ROOT_CANDIDATE_3AC2CED_QUALIFICATION.json",
|
||||
"sha256": "5bd8030acddc385eb728441286f6a740ec1ee966125474b60ef4b1efe2a2c343"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/native-restart-954528a-r01/ROOT_RAW_ACCEPTANCE_JOIN.json",
|
||||
"sha256": "6dbb3cbb4ac1838b66034323cab22a9c5a968c1da0efe8d34676073b3f980cfb"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/native-restart-954528a-r01/run/execution.json",
|
||||
"sha256": "c1ec566402896f9f3fdefdfc268de6ed0bc46b8a37a9eb6bfb252b0cc4ebb514"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/independent-review-bc703c9-changes-requested-r01/result.json",
|
||||
"sha256": "f2e67cb2adbef9c9294ec1e879da70b500fa27d2220d9ae1b9fb50c96cbdc3b2"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/restart-independent-review-954528a-r01/DESKTOP_DELIVERY.json",
|
||||
"sha256": "90b6943d86bf370599c3a8629c57a69806374a5863ce18957a7b7d696a5be9fd"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/restart-independent-review-954528a-r01/DESKTOP_RESULT.json",
|
||||
"sha256": "b8fff13e8789797a4a50a5e2900f3d84ef20a6682a8f25feca225edc7fe00300"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/restart-independent-review-954528a-r01/actual-output-after-receiver-timeout/origin.json",
|
||||
"sha256": "90b6943d86bf370599c3a8629c57a69806374a5863ce18957a7b7d696a5be9fd"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/restart-independent-review-954528a-r01/actual-output-after-receiver-timeout/result.json",
|
||||
"sha256": "b8fff13e8789797a4a50a5e2900f3d84ef20a6682a8f25feca225edc7fe00300"
|
||||
}
|
||||
],
|
||||
"basis": [
|
||||
"再利用する各証拠は、それ自身の source・設定・engine・marker・入力・範囲にだけ適用する"
|
||||
],
|
||||
"scope_and_unmet": [
|
||||
"全証拠に共通の入力を仮定しない。各固定証拠とその manifest 参照を個別に検査する。"
|
||||
]
|
||||
}
|
||||
],
|
||||
"original_condition_count": 7,
|
||||
"auxiliary_condition_count": 2,
|
||||
"package_denominator": 30,
|
||||
"deliverable_denominator": 88,
|
||||
"original_completed_before_gate": 22,
|
||||
"package_status": "in_progress",
|
||||
"whole_unit_accepted": false,
|
||||
"g3_decision": "independent_review_required",
|
||||
"g3_material": "(これらは証拠の提示であって、合否はそこから導きません):\n- E7 の最終 review の RS5 の判断(「overload は条件付きの分岐で、実施されていないことを開示し、保持されるとは主張しない」)と、low の1件目(「0でない backlog の保持は実施していない」)。\n- 製品の契約: `scripts/core/simulation_kernel.gd:217-231`(`restore_clock` が `_accumulator` を0に戻し、`_skip_next_process` を立てる)と、snapshot の範囲(`autoload/simulation_runtime.gd:521-583`)。1分未満の accumulator は、保存される状態の外にある。\n- 「発生しなかった」ことは、保存できたことの証明ではありません。独立レビューは、原本の条件「途中保存・再起動で再現」が、overload で止まって backlog が0でない状態の保存と、1分未満の実時間の位相までを要求するかどうかを、原文と上の証拠から判断します。要求すると判断した場合は、未達として changes_requested にします。",
|
||||
"unmet": [
|
||||
"予算の未達: P1 x1 平均 479.210 ms > 4、P1 stall 最大 4809.099 ms > 50、P2 x3 P95 5062.697 ms > 12、P2 stall 最大 5891.466 ms > 50。",
|
||||
"200客での Operations ON(不可)。O の P3/P4 の合格は、小さな入力に限られる。",
|
||||
"GPU 時間は null(gl_compatibility)。R0 と O0 は CPU も null。",
|
||||
"同一の Windows 実機、長期の操作感とバランス、ゲーム全体の受入。",
|
||||
"RESTART の low 3件と、G3 の判断の結果。"
|
||||
],
|
||||
"restart_low_findings": [
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "RS5: non-zero backlog preservation is still unexercised. Both writers had overloads=0 and backlog_at_save=0. This is correctly disclosed (coverage.overload_nonzero_backlog=not_exercised, no persistence claim), and the contract treats overload as conditional. Even so, 'backlog not consumed' has only been shown with backlog 0. Do not cite this run as proof that a stopped state with a non-zero backlog survives a restart."
|
||||
},
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "RS6: the public report includes probe_deadline.engine_elapsed_at_begin_seconds, a wall-clock engine start-up time of 1.5–1.9 s. It is a watchdog diagnostic outside deterministic_step and not a performance or budget claim. However, check_no_metrics and public_has_forbidden only reject the key suffixes *_us/*_ms and a few names, so timing values in seconds pass without check. Consider either listing this field in an explicit allowlist or keeping it only in the sidecar."
|
||||
},
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "RS4: the checker does not prove that at_390.events is complete. It checks that each listed event is in the 390 window and inside (saved_minute, 390]. It does not check that every event in that interval is present, for example that domain_event_counter minus the E index equals the event count, or that the window did not overflow. In this run the raw data is complete (IDs 9–24 are contiguous, counter 24, and the recent window holds 24 of 256), so this is a gap in the checker's strength, not a defect in this run."
|
||||
}
|
||||
],
|
||||
"scopes": {
|
||||
"full9_commit": "db9d6132a8349928560dc4a4a99306d27a81e332",
|
||||
"r02_display": "headless",
|
||||
"r02_operations": "OFF",
|
||||
"r02_speed": "x1",
|
||||
"r02_seed_field_present": false,
|
||||
"r02_seed_provenance": "Input SHA equals E2 R; E2 R report seed 28012026. No seed field is invented in r02.",
|
||||
"r02_marker": "not_applicable: performance observation, not a regression run",
|
||||
"R_registered": [
|
||||
20000,
|
||||
200,
|
||||
50,
|
||||
150
|
||||
],
|
||||
"R_active_facilities": 100,
|
||||
"O_guest_range": [
|
||||
0,
|
||||
2
|
||||
],
|
||||
"P2_warm_minutes": 31,
|
||||
"CPU_measured_phases": [
|
||||
"P1",
|
||||
"P2",
|
||||
"P3",
|
||||
"P4"
|
||||
],
|
||||
"GPU_value": null,
|
||||
"GPU_reason": "zero_from_api_on_gl_compatibility_unsupported_or_unmeasured",
|
||||
"budget_pass": false,
|
||||
"Windows_verified": false,
|
||||
"approval": "Existing delegation; no new human utterance"
|
||||
}
|
||||
}
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
{
|
||||
"status": "draft r02; not registered, dispatched or run. Root commits gate/SPEC.md and gate/TASK_INSTRUCTIONS.md to main and fills base_commit with that actual SHA.",
|
||||
"assignment": {
|
||||
"implementation": {
|
||||
"worker": "codex",
|
||||
"model": "gpt-6-astra",
|
||||
"reason": "Bounded in code size but evidence-heavy: exact SHA/source-map/scope bindings across 7 evidence groups with strong rejection tests. Astra has the full collector/RESTART context and a reliable record here; OpenCode previously failed on tool truncation in this repository."
|
||||
},
|
||||
"independent_review": {
|
||||
"model": "claude-opus-5-5",
|
||||
"context": "fresh",
|
||||
"transport": "separate Desktop session (CLI Claude auth expired); async delivery per REVIEW_DELIVERY_PLAN.md"
|
||||
},
|
||||
"run_decision": "Desktop governor gives the actual RUN decision after registration, in the 3-key commander schema"
|
||||
},
|
||||
"register_project": {
|
||||
"project": "the-tree-p28-01-gate",
|
||||
"repo": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部",
|
||||
"worktrees": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-01-gate",
|
||||
"baseline": [
|
||||
{
|
||||
"id": "P28-01-G0",
|
||||
"requirement_id": "P28-01",
|
||||
"text": "原work: 実Runtimeの20,000マス・200客・50職員・150施設を基準にTick、経路、snapshot、描画、保存を測る。原acceptance: 機種、ビルド、シード、速度、測定区間、平均・P95・最大を残す。",
|
||||
"validation_ids": [
|
||||
"p28-01-gate-unit",
|
||||
"p28-01-gate-verify"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-G1",
|
||||
"requirement_id": "P28",
|
||||
"text": "原本条件とUI操作が一致",
|
||||
"validation_ids": [
|
||||
"p28-01-gate-unit",
|
||||
"p28-01-gate-verify"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-G2",
|
||||
"requirement_id": "P28",
|
||||
"text": "Runtimeの実利用・会計・行動へ接続",
|
||||
"validation_ids": [
|
||||
"p28-01-gate-unit",
|
||||
"p28-01-gate-verify"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-G3",
|
||||
"requirement_id": "P28",
|
||||
"text": "途中保存・再起動で再現",
|
||||
"validation_ids": [
|
||||
"p28-01-gate-unit",
|
||||
"p28-01-gate-verify"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-G4",
|
||||
"requirement_id": "P28",
|
||||
"text": "異常/境界/中断を検証",
|
||||
"validation_ids": [
|
||||
"p28-01-gate-unit",
|
||||
"p28-01-gate-verify"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-G5",
|
||||
"requirement_id": "P28",
|
||||
"text": "関連回帰PASS",
|
||||
"validation_ids": [
|
||||
"p28-01-gate-unit",
|
||||
"p28-01-gate-verify"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-G6",
|
||||
"requirement_id": "P28",
|
||||
"text": "要件別ログとソースハッシュを保存",
|
||||
"validation_ids": [
|
||||
"p28-01-gate-unit",
|
||||
"p28-01-gate-verify"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-U1",
|
||||
"requirement_id": "P28-01",
|
||||
"text": "補助(原本の条件ではない): 未達と、実施していない分岐を、合格に数えずに開示する。",
|
||||
"validation_ids": [
|
||||
"p28-01-gate-unit",
|
||||
"p28-01-gate-verify"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-U2",
|
||||
"requirement_id": "P28-01",
|
||||
"text": "補助(原本の条件ではない): 再利用する各証拠が、それ自身の source・設定・engine・marker・入力・範囲に正確に適用できる。",
|
||||
"validation_ids": [
|
||||
"p28-01-gate-unit",
|
||||
"p28-01-gate-verify"
|
||||
]
|
||||
}
|
||||
],
|
||||
"validations": {
|
||||
"p28-01-gate-unit": {
|
||||
"argv": [
|
||||
"{python}",
|
||||
"docs/plans/original-completion-2026-10-02/execution/p28-01/gate/test_verify_p28_01_evidence.py"
|
||||
]
|
||||
},
|
||||
"p28-01-gate-verify": {
|
||||
"argv": [
|
||||
"{python}",
|
||||
"docs/plans/original-completion-2026-10-02/execution/p28-01/gate/verify_p28_01_evidence.py",
|
||||
"--project-root",
|
||||
"."
|
||||
]
|
||||
}
|
||||
},
|
||||
"validation_note": "p28-01-gate-verify requires the real E1-E7 bundle at $BUILD_OUTPUT_DIR/p28-01-gate-evidence and fails when it or BUILD_OUTPUT_DIR is absent; it writes its per-condition JSON to $BUILD_OUTPUT_DIR/p28-01-gate-verify.json and stdout (no argv placeholder needed). Synthetic unit PASS alone never accepts."
|
||||
},
|
||||
"register_task_contract": {
|
||||
"id": "P28-01-GATE",
|
||||
"title": "原P28-01 whole-unit 受入ゲート(既存証拠の結合)",
|
||||
"requirement_ids": [
|
||||
"P28-01",
|
||||
"P28"
|
||||
],
|
||||
"acceptance_ids": [
|
||||
"P28-01-G0",
|
||||
"P28-01-G1",
|
||||
"P28-01-G2",
|
||||
"P28-01-G3",
|
||||
"P28-01-G4",
|
||||
"P28-01-G5",
|
||||
"P28-01-G6",
|
||||
"P28-01-U1",
|
||||
"P28-01-U2"
|
||||
],
|
||||
"spec_ref": "docs/plans/original-completion-2026-10-02/execution/p28-01/gate/SPEC.md",
|
||||
"base_commit": "TBD: actual main SHA that adds gate/SPEC.md and gate/TASK_INSTRUCTIONS.md",
|
||||
"worker": "codex",
|
||||
"allowed_paths": [
|
||||
"docs/plans/original-completion-2026-10-02/execution/p28-01/gate/**"
|
||||
],
|
||||
"depends_on": [],
|
||||
"resource_locks": [
|
||||
"p28-01-gate"
|
||||
],
|
||||
"validation_ids": [
|
||||
"p28-01-gate-unit",
|
||||
"p28-01-gate-verify"
|
||||
],
|
||||
"max_repair_attempts": null
|
||||
},
|
||||
"evidence_bundle": {
|
||||
"place_under": "$BUILD_OUTPUT_DIR/p28-01-gate-evidence/<repo-relative path>",
|
||||
"files": "SPEC.md §3 E1-E7 artifacts/ entries, byte copies, read-only; docs/ entries are read from the checkout",
|
||||
"required_for": [
|
||||
"validation",
|
||||
"integration",
|
||||
"final"
|
||||
]
|
||||
},
|
||||
"policy_note": "New project: align max_llm_runs to null under the existing owner delegation with the same owner script protocol, recorded honestly.",
|
||||
"credit": "Gate completion is the only step that moves P28-01 to 23/88; P28 package stays in_progress.",
|
||||
"unchanged": [
|
||||
"original 30/88",
|
||||
"budgets",
|
||||
"product source map 1407",
|
||||
"collector Q and RESTART Q bytes",
|
||||
"permissions/auth/billing"
|
||||
]
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
# P28-01-GATE 独立レビューの非同期配送(判断)
|
||||
|
||||
**判断: 採用します。** 推論は1回だけで、Runner 本体、権限、認証、課金は変更しません。
|
||||
|
||||
1. **凍結(受信待ちはまだ始めない)**: 正式な validation が合格した後、Root は Runner の通常の手順で、review の W・.review-material・packet・schema・target を作ります。それらを凍結し、packet の SHA と manifest の SHA を `READY_FOR_DESKTOP_REVIEW.json` に記録します。900秒の受信待ちは、まだ始めません。
|
||||
2. **独立レビュー(1回)**: Desktop の管制が、新しい別セッション(claude-opus-5-5、fresh、読取専用、engine と試験は無し)を起動します。担当は packet と資料の SHA を自分で確かめ、schema に一致する結果だけを `DESKTOP_RESULT.json` に新規保存します。管制は、担当のセッションが終わった後に `DESKTOP_DELIVERY.json` を最後に作ります。
|
||||
3. **受け入れ(1回)**: Root は delivery ができた後で、通常の review の受信を1回だけ実行します。その前に、次の条件を照合します。
|
||||
- 同じ凍結 W であること、資料の全 SHA、packet の bytes、S、schema、target が一致すること。
|
||||
- 未編集の結果を、通常の check_schema → review の判定 → evidence/state に通すこと。
|
||||
- 一致しない場合は取り込まず、管制に戻します。
|
||||
4. **記録**: Desktop の独立セッションの ID と結果の SHA、CLI の認証が切れていたこと(回復とはしない)、受信の失敗があればそれを、事実として記録します。
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"authority": "Existing user instructed complete original plan, actual Desktop Opus266/268 gate design, Root delegated ordinary implementation/acceptance judgment; not new human SHA-specific statement",
|
||||
"governor_session": "local_a7f85421-3eed-4482-8030-c0dca669573a",
|
||||
"model": "claude-opus-5-5",
|
||||
"role": "commander",
|
||||
"implementation_model_chosen": "gpt-6-astra",
|
||||
"adopted_from": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/p28-whole-unit-gate-draft-3ac2ced-r01",
|
||||
"files": [
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p28-01/gate/SPEC.md",
|
||||
"sha256": "e2bff09a16db9e8cf2646eac7fcc48b1fe7704fbf35ace34b7dab67ce5d4fde2"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p28-01/gate/TASK_INSTRUCTIONS.md",
|
||||
"sha256": "246f80d101c27d472e2ebfd21b348ba27fd5a8129385c6fc4fa7fc52dcce43fa"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p28-01/gate/REGISTRATION_PROPOSAL.json",
|
||||
"sha256": "6b154161b3b05c24683ee6a0f92b957be33867b5fa125b20326973dd9bad17c5"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p28-01/gate/REVIEW_DELIVERY_PLAN.md",
|
||||
"sha256": "464fe1135930862bd21315b8649311e0a4042d7ce03f19e5b6c5fdd54d3475cf"
|
||||
}
|
||||
],
|
||||
"original_conditions_unchanged": true,
|
||||
"original_denominator": [
|
||||
30,
|
||||
88
|
||||
],
|
||||
"formal_completed": 22,
|
||||
"partial_credit": false,
|
||||
"baseline_count": 9,
|
||||
"original_work_acceptance_and_cross6": 7,
|
||||
"evidence_quality_support_conditions": 2,
|
||||
"real_bundle_validation_required": true,
|
||||
"synthetic_unit_alone_never_accepts": true,
|
||||
"gate_registered": false,
|
||||
"base_commit": "Registration will resolve actual commit containing these exact files; proposal TBD is not an execution value.",
|
||||
"G3_independent_adjudication_required": true,
|
||||
"no_engine_full9_192_reexecution": true,
|
||||
"spec_draft_label": "Immutable commander draft is adopted by this record without editing its bytes."
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
# P28-01-GATE 仕様(原 P28-01 whole-unit 受入ゲート、spec_ref の草案 r02)
|
||||
|
||||
- 状態: 草案です。Root が原本の条件と受入を照合し、この文書を `docs/plans/original-completion-2026-10-02/execution/p28-01/gate/SPEC.md` として main に commit します。その実 SHA を base にしてから、通常の登録をします。数値や条件を独自に変えるものではありません。
|
||||
- 前提: Desktop の計画(`P28-01_WHOLE_UNIT_GATE_PLAN_DESKTOP_233.md`、`bc222542…`)と、Root の対応表(`P28_WHOLE_UNIT_GATE_BINDING_FOLLOWUP_954528A.json`)を、現在の正式記録に合わせて具体化したものです。
|
||||
|
||||
## 1. 原本の条件(変更しない)
|
||||
|
||||
- 成果単位 P28-01「現HEADの性能基準」(`work_packages.json` の P28.remaining_deliverables[0])
|
||||
- work: 実Runtimeの20,000マス・200客・50職員・150施設を基準にTick、経路、snapshot、描画、保存を測る。
|
||||
- acceptance: 機種、ビルド、シード、速度、測定区間、平均・P95・最大を残す。
|
||||
- P28 の横断条件6件(cross_cutting_acceptance の原文):
|
||||
1. 原本条件とUI操作が一致
|
||||
2. Runtimeの実利用・会計・行動へ接続
|
||||
3. 途中保存・再起動で再現
|
||||
4. 異常/境界/中断を検証
|
||||
5. 関連回帰PASS
|
||||
6. 要件別ログとソースハッシュを保存
|
||||
- 分母は 30/88 で、部分点はありません。このゲートが完了した場合に限り、P28-01 を 22→23 とします。P28 パッケージは in_progress のままです。
|
||||
|
||||
## 2. baseline(9件。原本の原文は G0 と G1〜G6 の7件。U1/U2 は証拠の品質を保つための補助)
|
||||
|
||||
| id | 内容 |
|
||||
|---|---|
|
||||
| G0 | 原 work と原 acceptance(§1 の原文) |
|
||||
| G1〜G6 | 横断条件1〜6(§1 の原文) |
|
||||
| U1 | 未達と、実施していない分岐を、合格に数えずに開示する |
|
||||
| U2 | 再利用する各証拠が、それ自身の source・設定・engine・marker・入力・範囲に正確に適用できること(全証拠に共通の1つの入力を仮定しない) |
|
||||
|
||||
U1 と U2 は原本の条件ではありません。「原本の9条件」として扱ってはいけません。
|
||||
|
||||
## 3. 結合する証拠(path は repository からの相対。SHA256 は完全な値)
|
||||
|
||||
Root は E1〜E7 の全ファイルを、bytes をそのまま `$BUILD_OUTPUT_DIR/p28-01-gate-evidence/<repository からの相対 path>` に読取専用で置きます。`docs/` の下のものは、validation の checkout から直接読みます。
|
||||
|
||||
**E1 collector の正式完了**
|
||||
- `docs/plans/original-completion-2026-10-02/execution/p28-01/COLLECTOR_FORMAL_COMPLETION_0F53A67.json` `2886547ae2ccd000a85cf8374304f292f85ed2899d9ebe9818833600dc9ff55f`
|
||||
- `docs/plans/original-completion-2026-10-02/execution/p28-01/COLLECTOR_INDEPENDENT_REVIEW_5DB97D7.json` `23eed6741996a7b9327fcceda2e852104315954105c9c09f0dfcdf35c88c8d41`
|
||||
- 検査: task が completed で、4つの gate がすべて pass。Q `0f53a67210227207efa63045de88a9a7c6e45409`。collector の4ファイル(`execution/p28-01/collector/*`)が、base の bytes と一致すること。
|
||||
|
||||
**E2 統合版の全9 phase(実際の commit は `db9d6132a8349928560dc4a4a99306d27a81e332`。新しい Q での実測ではない)**
|
||||
- `artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/full-measurement-db9d613-r01/run/execution.json` `a4d10e9b1a75a17b981792265338d64fac61178cb6f87dd646c5b486292db73c`
|
||||
- 同じ dir の `run/admission.json` `c4a3b5ba7a2b6427e55a6f85f037ded08e0c6bfd8c0a766647747ea9cc94378d`
|
||||
- 同じ dir の `ROOT_RAW_ACCEPTANCE_JOIN.json` `a76082f767e0a0d52cb70e2f66fa371b62a88754e9be319da3c08d4b48e78ba0`(`docs/…/INTEGRATED_FULL9_MEASUREMENT_DB9D613.json` も同じ SHA)
|
||||
- 検査:
|
||||
- admission の commit と source map は1407件(`651f7036…`)。
|
||||
- R0/P1/P2/O0/P3/P4 の各 report に、`metadata.machine`、`metadata.build`、seed、speed、区間(開始/終了のゲーム分、wall 時刻、frame 数)、平均・P95・最大・標本数がある。
|
||||
- R の登録は 20,000/200/50/150 で、稼働は100施設。P2 の warm は 31分(150分と書き換えない)。
|
||||
- **描画の CPU は P1〜P4 だけで取得されている**(件数 P1 150、P2 50、P3 2398、P4 745)。R0 と O0 は、CPU も GPU も null で、理由は `zero_from_api_on_gl_compatibility_unsupported_or_unmeasured`。GPU は全 phase で null と同じ理由。
|
||||
|
||||
**E3 r02(経路・snapshot・保存・読込。headless・Ops OFF・x1 の観測だけ)**
|
||||
- dir `artifacts/original-completion-2026-10-02/p14-03-representative-perf-r01/`:
|
||||
- `run-r02/execution.json` `553971f55635e529b48634ef86ad7d4198c8bafe04d5a6a5ef778a4de0439fc1`
|
||||
- `run-r02/readiness.json` `da93aecdd2c1648952e2a80382acb7e3f30cfe48a7e5df982315791161fedde2`
|
||||
- `run-r02/after/evidence/report.json` `f1d447937158a884a1b287c3b5be38b6e73b73c5d748f9576401877402dabe1b`
|
||||
- `run-r02/after/measure.stdout.log` `294730160536fc862ca657654fbae290b874acc65b77a6d24aaccd9a859907f7`
|
||||
- `run-r02/before/evidence/report.json` `a070c061ca783aaec37152bf66ad6b6e6dc69b80d9c4c024eceab916d4c5d4e9`
|
||||
- 実行した script: `run-r02/measure.gd` `95e5967835cb05ad96e8e6f257689d0bd831792aa9c228c022e3af81448ff76a`、`run-r02/guarded.gd` `c9deebb39ee643d312a77bae64f74cf6851697bb31036fc56d67dd7c4037ee09`、`run-r02/profile_guard.gd` `597696c705cc8f0e79a35265ca3ef8df55f7fc3c4e0e5a2ce9a4e20e29e29a6f`、`collect_perf.py` `43f3e42900a670957a01cfcaca7e485809527b79912a358917c935c57ea9c063`
|
||||
- 入力: `artifacts/original-completion-2026-10-02/p28-current1299-runtime-scale-build-candidate-native-r01/fixture/reference.variant` `eb4f770071eb07cd7e136fe54c3f78768cc8de7a4264e62a9af1f06a8f90f9bd`
|
||||
- 検査(1407 の source の一致だけで済ませない):
|
||||
- execution の after/measure phase の report の SHA が `f1d44793…` で、PID が 46264。report は raw の stdout の `P14_03_PERF_REPORT=` の行と同じ。
|
||||
- report の `input_sha256` が入力の SHA と同じ。`prepared_state_sha256`(`9ca8f809…`)、`migrated_input_state_sha256`、`running_state_sha256` を記録する。`counts_prepared` は 20,000/200/50/150。
|
||||
- engine は readiness の `engine_sha256` `c7cccbf8fb143e34e02fd6521e09be2c2b974f0d5db080b19071c9c570718ccf`(4.7.2-stable official)。probe は `95e59678…`、runner は `43f3e429…`。hardware は `Mac16,10`・10・10・25769803776。
|
||||
- source: readiness の after `83b170a4c7176761b0b18d8e9292017d990e80a9`(1407件)の source map が、E4 の selected_source_map と一致し、それが現在の source map とも一致する。
|
||||
- speed は x1(`advance_one_minute_wall_ms_x1`)。区間と標本数は、各統計の count(x1 300、経路 cold 245 など)。統計は平均・P95・最大。
|
||||
- seed: r02 の report には seed の欄がありません。値を作らず、入力の SHA が E2 の R と同じであることと、E2 の R の report の seed(28012026)を根拠として記録します。
|
||||
- marker: r02 は回帰ではないので、marker 契約は適用外。理由を記録します。
|
||||
|
||||
**E4 正式192(関連回帰)**
|
||||
- dir `artifacts/original-completion-2026-10-02/p14-03-formal-regression-83b170a-r01/`: `runner-input.json` `5bc9760c0c114a80a3c9e93634e733fc8fa93d4eeea5d8bcfda85d8e4eebd094`、`runner-ended.json` `705fb2c12007bab125548742dc8d9b07918e30cae3a63c7d89e965c43b8b5f7b`、`all-tests.json` `31da4c4672f4321469183996e12fb23064b3772a69e8330052ab3e2ea66044f1`
|
||||
- marker 契約: `docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json` `0ff86a20891da66d84295f0c66ae7517ef586b54a1a668acb7778a450d473cd2`
|
||||
- 検査: 192/192、22,198 checks、失敗0。input の marker_contract_sha256 と engine_sha256 が一致する。selected_source_map が、validation の checkout の現在の product source map(`tools/run_all_tests.source_refs`)と完全一致する。
|
||||
|
||||
**E5 RESTART の正式完了**
|
||||
- `artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/RESTART_3AC2CED_FORMAL_DB_JOIN.json` `5c9b82a70f6bdb972d720aaa64f5d27aa54130f27f9d1f7cea1c7b02524f84be`
|
||||
- `artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/ROOT_CANDIDATE_3AC2CED_QUALIFICATION.json` `5bd8030acddc385eb728441286f6a740ec1ee966125474b60ef4b1efe2a2c343`
|
||||
- 検査: completed 8/8、4つの gate がすべて pass。S `954528a7c5a0e27e2e50b642373168fa0a550aba`、Q `3ac2cedc8dc977778fd5a3ba152197cc6cf00fa2`、M `3086af2ccd970ddee271a184214dad037bd1d6af`。承認は、既存の委任に基づくもの(新しい人間の発言ではない)。
|
||||
|
||||
**E6 RESTART の native(最終 S)**
|
||||
- `artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/native-restart-954528a-r01/run/execution.json` `c1ec566402896f9f3fdefdfc268de6ed0bc46b8a37a9eb6bfb252b0cc4ebb514`
|
||||
- 同じ dir の `ROOT_RAW_ACCEPTANCE_JOIN.json` `6dbb3cbb4ac1838b66034323cab22a9c5a968c1da0efe8d34676073b3f980cfb`
|
||||
- 検査:
|
||||
- 7 phase とも PASS。writer と reader の PID がすべて異なる。
|
||||
- `restart/probe/*` の4ファイルが、base(= Q `3ac2ced` 以降の main)の bytes と一致する(再利用の条件)。
|
||||
- R/O とも K=30 で、毎分の typed SHA が一致。改ざんは CHECKSUM_MISMATCH で拒否される。O の390分の記録は ID 9〜24 が連番で、counter 24。
|
||||
|
||||
**E7 独立レビュー**
|
||||
- `artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/restart-independent-review-954528a-r01/DESKTOP_RESULT.json` `b8fff13e8789797a4a50a5e2900f3d84ef20a6682a8f25feca225edc7fe00300`(delivery `90b6943d86bf370599c3a8629c57a69806374a5863ce18957a7b7d696a5be9fd`)
|
||||
- 以前の changes_requested: `independent-review-bc703c9-changes-requested-r01/result.json` `f2e67cb2adbef9c9294ec1e879da70b500fa27d2220d9ae1b9fb50c96cbdc3b2`
|
||||
- 検査: 最終が pass であること。low 3件を消さずに記録すること。
|
||||
|
||||
各証拠は、それぞれの source・設定・engine・marker・入力・範囲にだけ適用します。たとえば r02 の値を windowed の値と言い換えたり、full9 を Q での実測と言い換えたりしません。
|
||||
|
||||
## 4. 条件表(verifier は結合を確かめ、whole-unit の独立レビューが判定する)
|
||||
|
||||
| 条件 | 根拠 | 範囲と未達 |
|
||||
|---|---|---|
|
||||
| G0 work | Tick と描画(CPU は P1〜P4)は E2。経路・snapshot・保存・読込は E3 | 稼働していた施設は100。GPU は null。E3 は headless・Ops OFF・x1 |
|
||||
| G0 acceptance | E2 と E3 の各 report の欄 | 記録の項目がそろえば成立する。予算の合否は条件に含まれない |
|
||||
| G1 | collector C1/C9(`_set_speed`→`_process`、main)と RESTART RS1(UI での保存と読込) | 測定の範囲は Mac(Mac16,10)。Windows での最終受入は未達のまま |
|
||||
| G2 | 既定の autoload の世界、O の実際の Ops ON、RESTART の実際の継続 | O は小さな入力(客0〜2)。200客での Ops ON は上限100のため不可 |
|
||||
| G3 | RESTART RS1〜RS5(E5、E6)と、下の明示した証拠 | 下の判断材料を参照。最終的な判定は whole-unit の独立レビューが行う |
|
||||
| G4 | collector の停止契約と phase の失敗の記録、RESTART の改ざん・停止・境界 | 実施していない分岐は U1 で開示する |
|
||||
| G5 | E4 を再利用(source と marker の一致)。collector と RESTART の unit validation | 製品が変わったら、全 roster をやり直す |
|
||||
| G6 | 全 receipt の SHA、Root の結合、verifier の manifest | — |
|
||||
|
||||
**G3 の判断材料**(これらは証拠の提示であって、合否はそこから導きません):
|
||||
- E7 の最終 review の RS5 の判断(「overload は条件付きの分岐で、実施されていないことを開示し、保持されるとは主張しない」)と、low の1件目(「0でない backlog の保持は実施していない」)。
|
||||
- 製品の契約: `scripts/core/simulation_kernel.gd:217-231`(`restore_clock` が `_accumulator` を0に戻し、`_skip_next_process` を立てる)と、snapshot の範囲(`autoload/simulation_runtime.gd:521-583`)。1分未満の accumulator は、保存される状態の外にある。
|
||||
- 「発生しなかった」ことは、保存できたことの証明ではありません。独立レビューは、原本の条件「途中保存・再起動で再現」が、overload で止まって backlog が0でない状態の保存と、1分未満の実時間の位相までを要求するかどうかを、原文と上の証拠から判断します。要求すると判断した場合は、未達として changes_requested にします。
|
||||
|
||||
## 5. U1: 開示する未達(合否には含めない。P28 / P28-02 / P28-03 に残す)
|
||||
|
||||
- 予算の未達: P1 x1 平均 479.210 ms > 4、P1 stall 最大 4809.099 ms > 50、P2 x3 P95 5062.697 ms > 12、P2 stall 最大 5891.466 ms > 50。
|
||||
- 200客での Operations ON(不可)。O の P3/P4 の合格は、小さな入力に限られる。
|
||||
- GPU 時間は null(gl_compatibility)。R0 と O0 は CPU も null。
|
||||
- 同一の Windows 実機、長期の操作感とバランス、ゲーム全体の受入。
|
||||
- RESTART の low 3件と、G3 の判断の結果。
|
||||
|
||||
## 6. 成果物(worker。allowed_paths は `docs/plans/original-completion-2026-10-02/execution/p28-01/gate/**` のみ)
|
||||
|
||||
- `verify_p28_01_evidence.py`: engine を使わない verify-only の検査です。§3 の全証拠を、`EVIDENCE_BUNDLE_MANIFEST.json`(全ファイルの bytes と SHA)と、上に固定した SHA で結合します。
|
||||
- `--project-root .` は必須。`--evidence-root` を省略すると、`$BUILD_OUTPUT_DIR/p28-01-gate-evidence` を使います。どちらも無い場合、または証拠が欠けている場合は、**失敗**にします(スキップしない)。
|
||||
- 条件ごとの結果の JSON を stdout に出力し、`$BUILD_OUTPUT_DIR` が設定されていれば `$BUILD_OUTPUT_DIR/p28-01-gate-verify.json` にも書き出します(`--json <path>` で、書出し先を明示的に指定することもできます)。source の木には書き込みません。
|
||||
- 失敗は、構造化した検査の失敗として返します(traceback で終わらせない)。
|
||||
- `EVIDENCE_BUNDLE_MANIFEST.json`、`GATE_CONDITIONS.json`(§4 と §5 の機械可読版。各条件の根拠になる path と SHA、範囲、未達)。
|
||||
- `test_verify_p28_01_evidence.py`: 合成した証拠を使う正常試験と拒否試験。
|
||||
|
||||
## 7. verifier と試験が必ず拒否するもの
|
||||
|
||||
- manifest の外のファイル、SHA の不一致、証拠の欠落、改ざん。
|
||||
- 範囲を外れた適用:
|
||||
- full9 の commit を Q と表記すること
|
||||
- r02 を windowed の値と言い換えること
|
||||
- r02 の seed を作ること
|
||||
- R0/O0 の CPU を取得済みとすること
|
||||
- 予算の false を合格として扱うこと。GPU の null を0や合格に置き換えること。
|
||||
- R の稼働数(100施設)を登録数として扱うこと。O の小さな入力を「200客」と表記すること。
|
||||
- RESTART の4つの gate のどれかが欠けていること。probe の bytes が Q と一致しないこと。
|
||||
- E4 の source map または marker が、現在のものと一致しないこと。
|
||||
- U1 の項目が欠けていること。U1/U2 を原本の条件として数えること。G3 を「発生しなかった」だけで合格とすること。
|
||||
|
||||
## 8. 対象外
|
||||
|
||||
- engine、全192、full9、collector、RESTART の再実行。
|
||||
- 製品、依存、予算値、原本の分母の変更。Runner 本体、権限、認証、課金の変更。
|
||||
@@ -0,0 +1,13 @@
|
||||
# P28-01-GATE worker 指示
|
||||
|
||||
仕様は同じディレクトリの `SPEC.md` です。この指示と仕様は変更しないでください。
|
||||
|
||||
- 作るもの(allowed_paths `docs/plans/original-completion-2026-10-02/execution/p28-01/gate/**` の中だけ): `verify_p28_01_evidence.py`、`EVIDENCE_BUNDLE_MANIFEST.json`、`GATE_CONDITIONS.json`、`test_verify_p28_01_evidence.py`。
|
||||
- 実装する範囲: SPEC の §3 の全結合、§4 と §5 の条件表と開示、§7 の拒否のすべて(それぞれに拒否試験を置く)。
|
||||
- 証拠は、Root が `$BUILD_OUTPUT_DIR/p28-01-gate-evidence/<repository からの相対 path>` に読取専用で置きます。manifest は、その実際の bundle を読んで作ります。SPEC §3 に固定した SHA と食い違うファイルがあれば、manifest に入れずに blocked にして報告します。
|
||||
- validation は2つです。`p28-01-gate-unit`(合成した証拠の試験)と、`p28-01-gate-verify`(実際の bundle に対して `verify_p28_01_evidence.py --project-root .` を実行するもの。結果の JSON は `$BUILD_OUTPUT_DIR/p28-01-gate-verify.json` と stdout)。実際の bundle が無い場合、verify は必ず失敗します。合成の試験の PASS だけで受入にはなりません。
|
||||
- 試験には、欠損、改ざん、範囲を外れた適用(SPEC §7)を拒否するものを含めます。
|
||||
- 外部の import は標準ライブラリだけにします。製品の Python は import しません。必要なら、`tools/run_all_tests.py` の `source_refs` だけを読取専用で使ってかまいません。
|
||||
- しないこと: engine の起動、全回帰、製品やほかの task のファイルの変更、commit。
|
||||
- 提出前に、engine を使わない `python3 test_verify_p28_01_evidence.py` を局所で実行して全件 PASS を確かめ、生出力をそのまま報告します。一時ディレクトリは `BUILD_OUTPUT_DIR` か `tempfile.gettempdir()` の下に作り、source には書き込みません。
|
||||
- 承認された範囲の中の通常の実装上の選択は、worker の判断でかまいません。決めきれない不足は、具体的な箇所を示した場合に限り blocked にします。
|
||||
+337
@@ -0,0 +1,337 @@
|
||||
"""Synthetic tests; no engine, Runner, or actual evidence bundle required."""
|
||||
import contextlib
|
||||
import copy
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
import verify_p28_01_evidence as v
|
||||
|
||||
|
||||
def measured(count=10):
|
||||
return dict(count=count, mean=1, p95=2, max=3, reason=None)
|
||||
|
||||
|
||||
def unmeasured():
|
||||
return dict(count=0, mean=None, p95=None, max=None, reason=v.NULL_REASON)
|
||||
|
||||
|
||||
def full_fixture():
|
||||
admission = dict(commit=v.FULL_COMMIT, source_map={str(i): "a" * 64 for i in range(1407)})
|
||||
phases = [dict(phase=k, passed=True) for k in ("import", "parse-base", "parse-wrapper")]
|
||||
for name in ("R0", "P1", "P2", "O0", "P3", "P4"):
|
||||
representative = name in ("R0", "P1", "P2")
|
||||
count = dict(walkable_cells=20000, guests=200, staff=50, facilities=150)
|
||||
registered = {k: dict(min=x, max=x) for k, x in count.items()}
|
||||
if not representative:
|
||||
registered["guests"] = dict(min=0, max=2)
|
||||
measurement = dict(start_minute=30, end_minute=150, start_wall=1, end_wall=2, frames=150,
|
||||
frame_observation={"sim_us_per_minute": measured()},
|
||||
render_gpu_ms=unmeasured(),
|
||||
render_cpu_ms=unmeasured() if name in ("R0", "O0") else measured({"P1": 150, "P2": 50, "P3": 2398, "P4": 745}[name]),
|
||||
count_ranges=dict(registered=registered, active=dict(facilities=dict(min=100, max=100))))
|
||||
report = dict(seed="28012026", speed=3 if name in ("P2", "P4") else 1,
|
||||
metadata=dict(machine={"hw.model": "Mac16,10"}, build=dict(project_commit=v.FULL_COMMIT, engine_sha256=v.ENGINE)),
|
||||
measurement=measurement, warmup=dict(actual_minutes=31 if name == "P2" else 30),
|
||||
budget_verdict=dict(simulation=dict(passed=False, value=479210, limit_us=4000)))
|
||||
phases.append(dict(phase=name, passed=True, report=report))
|
||||
return dict(admission=copy.deepcopy(admission), passed=True, phases=phases), admission
|
||||
|
||||
|
||||
class MemoryEvidence:
|
||||
def __init__(self, files):
|
||||
self.files = files
|
||||
|
||||
def raw(self, path):
|
||||
return self.files[path]
|
||||
|
||||
def json(self, path):
|
||||
return json.loads(self.raw(path))
|
||||
|
||||
|
||||
def r02_fixture():
|
||||
report = dict(pid=46264, input_sha256="a" * 64,
|
||||
counts_prepared=dict(walkable_cells=20000, guests=200, staff=50, facilities=150),
|
||||
advance_one_minute_wall_ms_x1=measured(300),
|
||||
prepared_state_sha256="b" * 64, migrated_input_state_sha256="c" * 64,
|
||||
running_state_sha256="d" * 64)
|
||||
for key in ("path_after_tick_ms", "path_after_request_ms", "path_after_cancel_ms", "save_load_ms"):
|
||||
report[key] = {"found_cold": measured(245)}
|
||||
readiness = dict(fixture_sha256="a" * 64, engine_sha256=v.ENGINE,
|
||||
hardware=["Mac16,10", "10", "10", "25769803776"],
|
||||
after="83b170a4c7176761b0b18d8e9292017d990e80a9", after_source_files=1407,
|
||||
probe_sha256=v.digest(b"probe"), runner_sha256=v.digest(b"runner"))
|
||||
raw = json.dumps(report).encode()
|
||||
execution = dict(phases=[dict(variant="after", phase="measure", actual_pid=46264,
|
||||
report=report, report_sha256=v.digest(raw), argv=["engine", "--headless"])])
|
||||
files = {v.PERF + "run-r02/readiness.json": json.dumps(readiness).encode(),
|
||||
v.PERF + "run-r02/execution.json": json.dumps(execution).encode(),
|
||||
v.PERF + "run-r02/after/evidence/report.json": raw,
|
||||
v.PERF + "run-r02/after/measure.stdout.log": b"P14_03_PERF_REPORT=" + raw + b"\n",
|
||||
v.PERF + "run-r02/measure.gd": b"probe", v.PERF + "collect_perf.py": b"runner"}
|
||||
return MemoryEvidence(files), {"R0": dict(input_sha256="a" * 64)}
|
||||
|
||||
|
||||
def restart_fixture():
|
||||
phases = [dict(phase=k, passed=True, process=dict(pid=i))
|
||||
for i, k in enumerate(("import", "parse-base", "parse-wrapper"))]
|
||||
for i, name in enumerate(("R-writer", "R-reader", "O-writer", "O-reader"), 3):
|
||||
at = dict(clock=dict(absolute_minute=390), domain_event_counter=24,
|
||||
events=[dict(event_id="domain_event.%012d" % j) for j in range(9, 25)])
|
||||
report = dict(pid=i, actual_k=30, saved_minute=361,
|
||||
minutes=[dict(after=362+j, sha256=str(j)) for j in range(30)],
|
||||
io=[dict(code="CHECKSUM_MISMATCH")], at_390=at if name.startswith("O") else None)
|
||||
phases.append(dict(phase=name, passed=True, report=report, process=dict(pid=i)))
|
||||
return dict(passed=True, phases=phases)
|
||||
|
||||
|
||||
class EvidenceTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmp = tempfile.TemporaryDirectory(dir=os.environ.get("BUILD_OUTPUT_DIR"))
|
||||
self.addCleanup(self.tmp.cleanup)
|
||||
self.root = Path(self.tmp.name).resolve()
|
||||
self.project = self.root / "project"
|
||||
self.bundle_root = self.root / "evidence"
|
||||
self.project.mkdir()
|
||||
self.bundle_root.mkdir()
|
||||
|
||||
def bundle(self):
|
||||
raw = b'{"passed":true}\n'
|
||||
(self.bundle_root / "receipt.json").write_bytes(raw)
|
||||
manifest = dict(files=[dict(path="receipt.json", original_path="/old/receipt.json", location="bundle", bytes=len(raw), sha256=v.digest(raw))])
|
||||
return v.Bundle(self.project, self.bundle_root, manifest)
|
||||
|
||||
def test_bundle_positive_and_original_path_resolution(self):
|
||||
b = self.bundle()
|
||||
b.verify_all()
|
||||
self.assertEqual(b.json("/old/receipt.json"), dict(passed=True))
|
||||
|
||||
def test_missing_file(self):
|
||||
b = self.bundle()
|
||||
(self.bundle_root / "receipt.json").unlink()
|
||||
with self.assertRaises(v.InvalidEvidence):
|
||||
b.verify_all()
|
||||
|
||||
def test_extra_file(self):
|
||||
b = self.bundle()
|
||||
(self.bundle_root / "extra").write_bytes(b"x")
|
||||
with self.assertRaises(v.InvalidEvidence):
|
||||
b.verify_all()
|
||||
|
||||
def test_file_outside_manifest(self):
|
||||
with self.assertRaises(v.InvalidEvidence):
|
||||
self.bundle().raw("unknown.json")
|
||||
|
||||
def test_same_size_tampering(self):
|
||||
b = self.bundle()
|
||||
p = self.bundle_root / "receipt.json"
|
||||
p.write_bytes(p.read_bytes().replace(b"true", b"null"))
|
||||
with self.assertRaisesRegex(v.InvalidEvidence, "SHA256"):
|
||||
b.verify_all()
|
||||
|
||||
def test_byte_count_tampering(self):
|
||||
b = self.bundle()
|
||||
(self.bundle_root / "receipt.json").write_bytes(b"x")
|
||||
with self.assertRaisesRegex(v.InvalidEvidence, "byte count"):
|
||||
b.verify_all()
|
||||
|
||||
def test_traversal_and_symlink(self):
|
||||
for name in ("../escape", "/absolute"):
|
||||
with self.subTest(name=name), self.assertRaises(v.InvalidEvidence):
|
||||
v.safe_path(self.bundle_root, name)
|
||||
(self.bundle_root / "alias").symlink_to(self.project)
|
||||
with self.assertRaises(v.InvalidEvidence):
|
||||
v.safe_path(self.bundle_root, "alias")
|
||||
|
||||
def test_duplicate_json_keys(self):
|
||||
with self.assertRaises(v.InvalidEvidence):
|
||||
v.load_json('{"passed":false,"passed":true}')
|
||||
|
||||
def test_full9_positive(self):
|
||||
self.assertEqual(len(v.full9(*full_fixture())), 6)
|
||||
|
||||
def test_empty_stall_samples_are_disclosed(self):
|
||||
e, a = full_fixture()
|
||||
for p in e["phases"][-2:]:
|
||||
p["report"]["measurement"]["frame_observation"]["stall_over_50000_us"] = dict(count=0, mean=None, p95=None, max=None, reason="no_samples")
|
||||
v.full9(e, a)
|
||||
e["phases"][-1]["report"]["measurement"]["frame_observation"]["stall_over_50000_us"]["mean"] = 0
|
||||
with self.assertRaises(v.InvalidEvidence):
|
||||
v.full9(e, a)
|
||||
|
||||
def test_probe_bytes_must_match_base(self):
|
||||
p = self.project / "restart_probe.gd"
|
||||
p.write_bytes(b"base/Q probe")
|
||||
pins = {p.name: v.digest(p.read_bytes())}
|
||||
v.verify_base_files(self.project, pins)
|
||||
p.write_bytes(b"modified probe")
|
||||
with self.assertRaises(v.InvalidEvidence):
|
||||
v.verify_base_files(self.project, pins)
|
||||
|
||||
def test_full9_commit_cannot_be_Q(self):
|
||||
e, a = full_fixture()
|
||||
a["commit"] = "3ac2cedc8dc977778fd5a3ba152197cc6cf00fa2"
|
||||
with self.assertRaises(v.InvalidEvidence):
|
||||
v.full9(e, a)
|
||||
|
||||
def test_full9_boundary_and_scope_rejections(self):
|
||||
mutations = {
|
||||
"R0 CPU claimed": lambda r: r["R0"]["measurement"].update(render_cpu_ms=measured()),
|
||||
"O0 CPU claimed": lambda r: r["O0"]["measurement"].update(render_cpu_ms=measured()),
|
||||
"GPU zero": lambda r: r["P1"]["measurement"]["render_gpu_ms"].update(mean=0),
|
||||
"GPU pass": lambda r: r["P1"]["measurement"].update(render_gpu_ms=measured()),
|
||||
"budget false becomes pass": lambda r: r["P1"]["budget_verdict"]["simulation"].update(passed=True),
|
||||
"registered facilities 100": lambda r: r["P1"]["measurement"]["count_ranges"]["registered"].update(facilities=dict(min=100,max=100)),
|
||||
"O 200 guests": lambda r: r["P3"]["measurement"]["count_ranges"]["registered"].update(guests=dict(min=200,max=200)),
|
||||
"warmup 150": lambda r: r["P2"]["warmup"].update(actual_minutes=150),
|
||||
"missing interval": lambda r: r["P1"]["measurement"].pop("start_minute"),
|
||||
"missing P95": lambda r: r["P1"]["measurement"]["render_cpu_ms"].pop("p95"),
|
||||
}
|
||||
for name, mutate in mutations.items():
|
||||
with self.subTest(name=name):
|
||||
e, a = full_fixture()
|
||||
mutate({p["phase"]: p["report"] for p in e["phases"][3:]})
|
||||
with self.assertRaises((v.InvalidEvidence, KeyError)):
|
||||
v.full9(e, a)
|
||||
|
||||
def test_r02_positive(self):
|
||||
ready, report = v.r02(*r02_fixture())
|
||||
self.assertEqual(report["pid"], 46264)
|
||||
|
||||
def test_r02_windowed_and_seed_rejected(self):
|
||||
for mode in ("windowed", "invented seed", "wrong PID", "stdout mismatch", "input mismatch"):
|
||||
with self.subTest(mode=mode):
|
||||
b, reports = r02_fixture()
|
||||
path = v.PERF + "run-r02/execution.json"
|
||||
e = b.json(path)
|
||||
if mode == "windowed":
|
||||
e["phases"][0]["argv"] = ["engine"]
|
||||
elif mode == "wrong PID":
|
||||
e["phases"][0]["actual_pid"] = 7
|
||||
elif mode == "stdout mismatch":
|
||||
b.files[v.PERF + "run-r02/after/measure.stdout.log"] = b"P14_03_PERF_REPORT={}\n"
|
||||
elif mode == "input mismatch":
|
||||
reports["R0"]["input_sha256"] = "0" * 64
|
||||
else:
|
||||
report = e["phases"][0]["report"]
|
||||
report["seed"] = 28012026
|
||||
raw = json.dumps(report).encode()
|
||||
e["phases"][0]["report_sha256"] = v.digest(raw)
|
||||
b.files[v.PERF + "run-r02/after/evidence/report.json"] = raw
|
||||
b.files[v.PERF + "run-r02/after/measure.stdout.log"] = b"P14_03_PERF_REPORT=" + raw
|
||||
b.files[path] = json.dumps(e).encode()
|
||||
with self.assertRaises(v.InvalidEvidence):
|
||||
v.r02(b, reports)
|
||||
|
||||
def test_all_four_normal_gates_required(self):
|
||||
rows = [dict(kind=k, passed=1) for k in ("validation", "review", "integration", "final")]
|
||||
v.four_gates(rows)
|
||||
for i in range(4):
|
||||
with self.subTest(missing=i), self.assertRaises(v.InvalidEvidence):
|
||||
v.four_gates(rows[:i] + rows[i+1:])
|
||||
rows[2]["passed"] = 0
|
||||
with self.assertRaises(v.InvalidEvidence):
|
||||
v.four_gates(rows)
|
||||
|
||||
def test_restart_positive(self):
|
||||
self.assertEqual(len(v.restart(restart_fixture())), 4)
|
||||
|
||||
def test_restart_pid_minutes_tamper_and_boundary(self):
|
||||
mutations = {
|
||||
"PID reuse": lambda p: p[4]["report"].update(pid=3),
|
||||
"K missing": lambda p: p[3]["report"].update(actual_k=29),
|
||||
"typed SHA": lambda p: p[4]["report"]["minutes"][0].update(sha256="bad"),
|
||||
"tamper accepted": lambda p: p[4]["report"].update(io=[dict(code="OK")]),
|
||||
"event omission": lambda p: p[5]["report"]["at_390"]["events"].pop(),
|
||||
"counter wrong": lambda p: p[5]["report"]["at_390"].update(domain_event_counter=23),
|
||||
}
|
||||
for name, mutate in mutations.items():
|
||||
with self.subTest(name=name):
|
||||
e = restart_fixture()
|
||||
mutate(e["phases"])
|
||||
with self.assertRaises(v.InvalidEvidence):
|
||||
v.restart(e)
|
||||
|
||||
def test_regression_positive_and_source_marker_rejection(self):
|
||||
source = {str(i): "a" * 64 for i in range(1407)}
|
||||
tests = [dict(test=str(i), passed=True, failed=False) for i in range(192)]
|
||||
reg = dict(selected_source_map=copy.deepcopy(source), marker_contract_sha256="marker", engine_sha256=v.ENGINE, roster=[r["test"] for r in tests])
|
||||
ended = dict(passed=True, actual_suites=192, failed_suites=0, strict_accepted_checks=22198, actual_all_roster_checks=22198)
|
||||
v.regression(reg, ended, tests, source, "marker")
|
||||
with self.assertRaises(v.InvalidEvidence):
|
||||
v.regression(reg, ended, tests, source, "changed")
|
||||
source["0"] = "changed"
|
||||
with self.assertRaises(v.InvalidEvidence):
|
||||
v.regression(reg, ended, tests, source, "marker")
|
||||
|
||||
def test_disclosures_cannot_be_removed_or_promoted(self):
|
||||
expected = json.loads(Path(v.__file__).with_name("GATE_CONDITIONS.json").read_text())
|
||||
v.verify_conditions(copy.deepcopy(expected), expected)
|
||||
mutations = {
|
||||
"missing U1 disclosure": lambda c: c["unmet"].pop(),
|
||||
"U1 is original": lambda c: c["conditions"][-2].update(original=True),
|
||||
"U2 is original": lambda c: c["conditions"][-1].update(original=True),
|
||||
"G3 absent overload proves pass": lambda c: c.update(g3_decision="pass"),
|
||||
"whole unit self accepted": lambda c: c.update(whole_unit_accepted=True),
|
||||
"low finding omitted": lambda c: c["restart_low_findings"].pop(),
|
||||
"windowed attribution": lambda c: c["scopes"].update(r02_display="windowed"),
|
||||
"seed invented": lambda c: c["scopes"].update(r02_seed_field_present=True),
|
||||
}
|
||||
for name, mutate in mutations.items():
|
||||
with self.subTest(name=name):
|
||||
c = copy.deepcopy(expected)
|
||||
mutate(c)
|
||||
with self.assertRaises(v.InvalidEvidence):
|
||||
v.verify_conditions(c, expected)
|
||||
|
||||
def test_manifest_and_conditions_seals(self):
|
||||
here = Path(v.__file__).parent
|
||||
self.assertEqual(v.digest((here / "EVIDENCE_BUNDLE_MANIFEST.json").read_bytes()), v.MANIFEST_SHA256)
|
||||
self.assertEqual(v.digest((here / "GATE_CONDITIONS.json").read_bytes()), v.CONDITIONS_SHA256)
|
||||
# Also reject ambiguous aliases before any actual bundle is read.
|
||||
v.Bundle(self.project, self.bundle_root, json.loads((here / "EVIDENCE_BUNDLE_MANIFEST.json").read_text()))
|
||||
|
||||
def test_cli_missing_root_and_missing_bundle_are_structured(self):
|
||||
for args in ([], ["--project-root", str(self.project)],
|
||||
["--project-root", str(self.project), "--evidence-root", str(self.root / "missing")]):
|
||||
with self.subTest(args=args), patch.dict(os.environ, {}, clear=True), contextlib.redirect_stdout(io.StringIO()) as out:
|
||||
self.assertEqual(v.main(args), 1)
|
||||
data = json.loads(out.getvalue())
|
||||
self.assertFalse(data["passed"])
|
||||
self.assertEqual(len(data["conditions"]), 9)
|
||||
self.assertTrue(data["failures"])
|
||||
|
||||
def test_source_output_refused(self):
|
||||
output = self.project / "forbidden.json"
|
||||
with contextlib.redirect_stdout(io.StringIO()) as out:
|
||||
self.assertEqual(v.main(["--project-root", str(self.project), "--json", str(output)]), 1)
|
||||
self.assertFalse(output.exists())
|
||||
self.assertFalse(json.loads(out.getvalue())["passed"])
|
||||
|
||||
def test_failure_json_explicit_and_build_default(self):
|
||||
for explicit in (True, False):
|
||||
with self.subTest(explicit=explicit):
|
||||
output = self.root / ("explicit.json" if explicit else "p28-01-gate-verify.json")
|
||||
args = ["--project-root", str(self.project), "--evidence-root", str(self.root / "missing")]
|
||||
if explicit:
|
||||
args += ["--json", str(output)]
|
||||
with patch.dict(os.environ, {"BUILD_OUTPUT_DIR": str(self.root)}), contextlib.redirect_stdout(io.StringIO()) as out:
|
||||
self.assertEqual(v.main(args), 1)
|
||||
self.assertEqual(json.loads(output.read_text()), json.loads(out.getvalue()))
|
||||
|
||||
def test_checkout_docs_are_read_from_project(self):
|
||||
(self.project / "doc.json").write_bytes(b"{}")
|
||||
manifest = dict(files=[dict(path="doc.json", location="checkout", bytes=2, sha256=v.digest(b"{}"))])
|
||||
b = v.Bundle(self.project, self.bundle_root, manifest)
|
||||
b.verify_all()
|
||||
self.assertEqual(b.json("doc.json"), {})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(verbosity=2)
|
||||
+362
@@ -0,0 +1,362 @@
|
||||
"""Read-only evidence join. A successful join is NOT whole-unit acceptance.
|
||||
|
||||
Only the standard library is used. No engine, product module or Runner is run.
|
||||
The manifest is sealed to prevent replacing both evidence and its claimed hash.
|
||||
"""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
BASE = "0aee05e1d987e126ee9c00536aed5d9c75115d00"
|
||||
MANIFEST_SHA256 = "f95f2cd3777fa60c7dbbdfd6777c647bda1165cefc2a186ec87218f3c8f76848"
|
||||
CONDITIONS_SHA256 = "6268646995bef77fe96b6246c90e8d37eba4f1fda3314d2b1aa1e35018bc1a78"
|
||||
DOC = "docs/plans/original-completion-2026-10-02/execution/"
|
||||
ART = "artifacts/original-completion-2026-10-02/"
|
||||
PERF = ART + "p14-03-representative-perf-r01/"
|
||||
FULL = ART + "p28-01-runner-preflight-r01/full-measurement-db9d613-r01/"
|
||||
PRE = ART + "p28-01-runner-preflight-r01/"
|
||||
RESTART = PRE + "native-restart-954528a-r01/"
|
||||
REG = ART + "p14-03-formal-regression-83b170a-r01/"
|
||||
ENGINE = "c7cccbf8fb143e34e02fd6521e09be2c2b974f0d5db080b19071c9c570718ccf"
|
||||
FULL_COMMIT = "db9d6132a8349928560dc4a4a99306d27a81e332"
|
||||
NULL_REASON = "zero_from_api_on_gl_compatibility_unsupported_or_unmeasured"
|
||||
IDS = ["P28-01-" + k for k in ["G0", "G1", "G2", "G3", "G4", "G5", "G6", "U1", "U2"]]
|
||||
|
||||
|
||||
class InvalidEvidence(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
def require(value, message):
|
||||
if not value:
|
||||
raise InvalidEvidence(message)
|
||||
|
||||
|
||||
def digest(raw):
|
||||
return hashlib.sha256(raw).hexdigest()
|
||||
|
||||
|
||||
def load_json(raw):
|
||||
def pairs(items):
|
||||
result = {}
|
||||
for key, value in items:
|
||||
require(key not in result, "duplicate JSON key: " + key)
|
||||
result[key] = value
|
||||
return result
|
||||
return json.loads(raw, object_pairs_hook=pairs,
|
||||
parse_constant=lambda value: (_ for _ in ()).throw(InvalidEvidence(value)))
|
||||
|
||||
|
||||
def safe_path(root, name):
|
||||
p = Path(name)
|
||||
require(not p.is_absolute() and p.parts and ".." not in p.parts,
|
||||
"unsafe evidence path: " + name)
|
||||
target = root / p
|
||||
require(not any(part.is_symlink() for part in [target, *target.parents]),
|
||||
"symlink refused: " + name)
|
||||
require(target.resolve().is_relative_to(root.resolve()), "path escaped root: " + name)
|
||||
return target
|
||||
|
||||
|
||||
class Bundle:
|
||||
def __init__(self, project, root, manifest):
|
||||
self.project, self.root = project, root
|
||||
self.entries = {}
|
||||
self.aliases = {}
|
||||
for row in manifest["files"]:
|
||||
key = row["path"]
|
||||
require(key not in self.entries, "duplicate manifest path: " + key)
|
||||
require(row["location"] in ("bundle", "checkout"), "invalid location")
|
||||
self.entries[key] = row
|
||||
self.aliases[key] = key
|
||||
if row.get("original_path"):
|
||||
alias = row["original_path"]
|
||||
require(alias not in self.aliases or self.aliases[alias] == key,
|
||||
"ambiguous original_path: " + alias)
|
||||
self.aliases[alias] = key
|
||||
|
||||
def raw(self, name):
|
||||
require(name in self.aliases, "file outside manifest: " + name)
|
||||
row = self.entries[self.aliases[name]]
|
||||
root = self.project if row["location"] == "checkout" else self.root
|
||||
data = safe_path(root, row["path"]).read_bytes()
|
||||
require(len(data) == row["bytes"], "byte count mismatch: " + name)
|
||||
require(digest(data) == row["sha256"], "SHA256 mismatch: " + name)
|
||||
return data
|
||||
|
||||
def json(self, name):
|
||||
return load_json(self.raw(name))
|
||||
|
||||
def verify_all(self):
|
||||
require(self.root.is_dir(), "evidence root missing")
|
||||
actual = {p.relative_to(self.root).as_posix() for p in self.root.rglob("*")
|
||||
if p.is_file() or p.is_symlink()}
|
||||
expected = {k for k, v in self.entries.items() if v["location"] == "bundle"}
|
||||
require(actual == expected, "bundle inventory differs: " + repr({
|
||||
"missing": sorted(expected - actual), "extra": sorted(actual - expected)}))
|
||||
for name in self.entries:
|
||||
self.raw(name)
|
||||
|
||||
|
||||
def product_sources(project):
|
||||
# Same selection as tools/run_all_tests.py:source_refs, without importing it.
|
||||
paths = [p for folder in ("autoload", "scripts", "scenes", "data", "tests", "assets", "tools")
|
||||
for p in (project / folder).rglob("*")
|
||||
if p.is_file() and p.name != ".DS_Store" and "__pycache__" not in p.parts]
|
||||
paths += [project / p for p in ("project.godot", "export_presets.cfg", "Makefile", "AGENTS.md")]
|
||||
return {p.relative_to(project).as_posix(): digest(safe_path(project, p.relative_to(project).as_posix()).read_bytes())
|
||||
for p in sorted(paths)}
|
||||
|
||||
|
||||
def normalized_map(mapping):
|
||||
return {k: v["sha256"] if isinstance(v, dict) else v for k, v in mapping.items()}
|
||||
|
||||
|
||||
def verify_base_files(project, pins):
|
||||
for path, pin in pins.items():
|
||||
require(digest(safe_path(project, path).read_bytes()) == pin, "base/probe bytes changed: " + path)
|
||||
|
||||
|
||||
def four_gates(rows):
|
||||
require(len(rows) == 4 and {r["kind"] for r in rows} ==
|
||||
{"validation", "review", "integration", "final"}, "four normal gates required")
|
||||
require(all(r["passed"] == 1 for r in rows), "normal gate failed")
|
||||
|
||||
|
||||
def regression(reg, ended, tests, source, marker):
|
||||
require(len(source) == 1407 and source == reg["selected_source_map"], "E4 current source map mismatch")
|
||||
require(reg["marker_contract_sha256"] == marker, "E4 marker mismatch")
|
||||
require(reg["engine_sha256"] == ENGINE, "E4 engine mismatch")
|
||||
require(ended["passed"] is True and ended["actual_suites"] == 192 and
|
||||
ended["failed_suites"] == 0 and ended["strict_accepted_checks"] ==
|
||||
ended["actual_all_roster_checks"] == 22198, "E4 formal 192/22198 totals")
|
||||
require(len(tests) == 192 and all(t["passed"] is True and t["failed"] is False for t in tests), "E4 failed suite")
|
||||
require([t["test"] for t in tests] == reg["roster"], "E4 roster join")
|
||||
|
||||
|
||||
def stats(value, null=False):
|
||||
require(set(("count", "mean", "p95", "max")) <= value.keys(), "incomplete statistics")
|
||||
if null:
|
||||
require(value["count"] == 0 and all(value[k] is None for k in ("mean", "p95", "max"))
|
||||
and value["reason"] == NULL_REASON, "unmeasured render time must remain null")
|
||||
else:
|
||||
require(type(value["count"]) is int and value["count"] > 0 and
|
||||
all(type(value[k]) in (int, float) and value[k] >= 0 for k in ("mean", "p95", "max")),
|
||||
"invalid measured statistics")
|
||||
|
||||
|
||||
def full9(execution, admission):
|
||||
require(admission["commit"] == FULL_COMMIT, "full9 was not measured at Q")
|
||||
require(len(admission["source_map"]) == 1407, "full9 source count")
|
||||
require(execution["admission"] == admission, "full9 admission join")
|
||||
require(execution["passed"] is True, "full9 execution failed")
|
||||
phases = execution["phases"]
|
||||
require([p["phase"] for p in phases] == ["import", "parse-base", "parse-wrapper", "R0", "P1", "P2", "O0", "P3", "P4"], "full9 phase order")
|
||||
require(all(p["passed"] is True for p in phases), "full9 phase failure")
|
||||
reports = {p["phase"]: p["report"] for p in phases[3:]}
|
||||
for name, r in reports.items():
|
||||
require(r["metadata"]["machine"]["hw.model"] == "Mac16,10", "full9 machine")
|
||||
require(r["metadata"]["build"]["project_commit"] == FULL_COMMIT, "full9 build misattribution")
|
||||
require(r["metadata"]["build"]["engine_sha256"] == ENGINE, "full9 engine")
|
||||
require(r["seed"] and r["speed"] in (1, 3), "seed/speed missing")
|
||||
m = r["measurement"]
|
||||
require(all(k in m for k in ("start_minute", "end_minute", "start_wall", "end_wall", "frames")), "full9 interval missing")
|
||||
require(m["end_minute"] > m["start_minute"] and m["end_wall"] > m["start_wall"] and m["frames"] > 0, "full9 interval invalid")
|
||||
for field, value in m["frame_observation"].items():
|
||||
if field == "stall_over_50000_us" and value["count"] == 0:
|
||||
require(all(value[k] is None for k in ("mean", "p95", "max")) and
|
||||
value["reason"] == "no_samples", "empty stall statistics must remain null/no_samples")
|
||||
else:
|
||||
stats(value)
|
||||
stats(m["render_gpu_ms"], null=True)
|
||||
stats(m["render_cpu_ms"], null=name in ("R0", "O0"))
|
||||
if name in ("P1", "P2", "P3", "P4"):
|
||||
require(m["render_cpu_ms"]["count"] == {"P1": 150, "P2": 50, "P3": 2398, "P4": 745}[name], "CPU sample count")
|
||||
counts = m["count_ranges"]
|
||||
if name in ("R0", "P1", "P2"):
|
||||
for k, v in {"walkable_cells": 20000, "guests": 200, "staff": 50, "facilities": 150}.items():
|
||||
require(counts["registered"][k] == {"min": v, "max": v}, "R registered count: " + k)
|
||||
require(counts["active"]["facilities"] == {"min": 100, "max": 100}, "R active facilities")
|
||||
else:
|
||||
require(counts["registered"]["guests"]["max"] <= 2, "O is only a small input")
|
||||
for verdict in r["budget_verdict"].values():
|
||||
require(verdict["passed"] is (verdict["value"] <= verdict["limit_us"]), "false budget reported as pass")
|
||||
require(reports["P2"]["warmup"]["actual_minutes"] == 31, "P2 warmup is 31 minutes")
|
||||
require(str(reports["R0"]["seed"]) == "28012026", "R seed")
|
||||
return reports
|
||||
|
||||
|
||||
def r02(bundle, reports):
|
||||
readiness = bundle.json(PERF + "run-r02/readiness.json")
|
||||
execution = bundle.json(PERF + "run-r02/execution.json")
|
||||
report = bundle.json(PERF + "run-r02/after/evidence/report.json")
|
||||
phase = [p for p in execution["phases"] if p["variant"] == "after" and p["phase"] == "measure"]
|
||||
require(len(phase) == 1, "r02 after/measure missing or duplicated")
|
||||
p = phase[0]
|
||||
require(p["actual_pid"] == report["pid"] == 46264 and p["report"] == report, "r02 PID/report join")
|
||||
require(p["report_sha256"] == digest(bundle.raw(PERF + "run-r02/after/evidence/report.json")), "r02 report SHA join")
|
||||
require("--headless" in p["argv"] and "seed" not in report, "r02 scope/seed misattribution")
|
||||
lines = [line.split("=", 1)[1] for line in bundle.raw(PERF + "run-r02/after/measure.stdout.log").decode().splitlines()
|
||||
if line.startswith("P14_03_PERF_REPORT=")]
|
||||
require(len(lines) == 1 and load_json(lines[0]) == report, "r02 stdout report join")
|
||||
require(report["input_sha256"] == reports["R0"]["input_sha256"] == readiness["fixture_sha256"], "r02/R input join")
|
||||
require(readiness["engine_sha256"] == ENGINE and readiness["hardware"] == ["Mac16,10", "10", "10", "25769803776"], "r02 engine/hardware")
|
||||
require(readiness["after"] == "83b170a4c7176761b0b18d8e9292017d990e80a9" and readiness["after_source_files"] == 1407, "r02 source provenance")
|
||||
for field, path in (("probe_sha256", PERF + "run-r02/measure.gd"), ("runner_sha256", PERF + "collect_perf.py")):
|
||||
require(readiness[field] == digest(bundle.raw(path)), "r02 script join: " + field)
|
||||
for k, v in {"walkable_cells": 20000, "guests": 200, "staff": 50, "facilities": 150}.items():
|
||||
require(report["counts_prepared"][k] == v, "r02 counts_prepared")
|
||||
require(report["advance_one_minute_wall_ms_x1"]["count"] == 300, "r02 x1 sample count")
|
||||
require(report["path_after_tick_ms"]["found_cold"]["count"] == 245, "r02 path sample count")
|
||||
for field in ("prepared_state_sha256", "migrated_input_state_sha256", "running_state_sha256"):
|
||||
require(len(report[field]) == 64, "r02 state identity missing")
|
||||
for field in ("path_after_tick_ms", "path_after_request_ms", "path_after_cancel_ms", "save_load_ms"):
|
||||
for value in report[field].values():
|
||||
stats(value)
|
||||
stats(report["advance_one_minute_wall_ms_x1"])
|
||||
return readiness, report
|
||||
|
||||
|
||||
def restart(execution):
|
||||
require(execution["passed"] is True, "restart execution failed")
|
||||
phases = execution["phases"]
|
||||
require([p["phase"] for p in phases] == ["import", "parse-base", "parse-wrapper", "R-writer", "R-reader", "O-writer", "O-reader"], "restart seven phases")
|
||||
require(all(p["passed"] is True for p in phases), "restart phase failure")
|
||||
require(len({p["process"]["pid"] for p in phases}) == 7, "restart seven distinct PIDs")
|
||||
reports = {p["phase"]: p["report"] for p in phases[3:]}
|
||||
pids = [r["pid"] for r in reports.values()]
|
||||
require(len(set(pids)) == 4, "restart writer/reader PID reuse")
|
||||
for prefix in ("R", "O"):
|
||||
writer, reader = reports[prefix + "-writer"], reports[prefix + "-reader"]
|
||||
require(writer["actual_k"] == reader["actual_k"] == 30, "restart K must be 30")
|
||||
require(len(writer["minutes"]) == len(reader["minutes"]) == 30, "restart minute count")
|
||||
for i, (w, r) in enumerate(zip(writer["minutes"], reader["minutes"])):
|
||||
require(w["after"] == r["after"] == writer["saved_minute"] + i + 1 and
|
||||
w["sha256"] == r["sha256"], "restart per-minute typed SHA mismatch")
|
||||
require("CHECKSUM_MISMATCH" in json.dumps(reader["io"]), "tampered load rejection missing")
|
||||
for name in ("O-writer", "O-reader"):
|
||||
at = reports[name]["at_390"]
|
||||
require(at["clock"]["absolute_minute"] == 390 and at["domain_event_counter"] == 24, "O boundary counter")
|
||||
require([int(e["event_id"].split(".")[-1]) for e in at["events"]] == list(range(9, 25)), "O boundary events incomplete")
|
||||
require(reports["O-writer"]["at_390"] == reports["O-reader"]["at_390"], "O boundary differs across restart")
|
||||
return reports
|
||||
|
||||
|
||||
def verify_conditions(conditions, expected):
|
||||
# Reject omissions AND additional claims. The expected record is SHA-sealed
|
||||
# independently of the input evidence, including the unresolved G3 decision.
|
||||
require(conditions == expected, "condition scope/disclosure differs from sealed contract")
|
||||
require([r["id"] for r in conditions["conditions"]] == IDS, "condition baseline")
|
||||
require([r["original"] for r in conditions["conditions"]] == [True] * 7 + [False] * 2, "U1/U2 are auxiliary")
|
||||
require(conditions["whole_unit_accepted"] is False and conditions["g3_decision"] == "independent_review_required", "G3 cannot be inferred from absence of overload")
|
||||
|
||||
|
||||
def run(project, evidence_root, manifest, conditions):
|
||||
bundle = Bundle(project, evidence_root, manifest)
|
||||
bundle.verify_all()
|
||||
for path, pin in manifest["spec_pins"].items():
|
||||
require(digest(bundle.raw(path)) == pin, "SPEC fixed SHA mismatch: " + path)
|
||||
require(digest((project / (DOC + "p28-01/gate/SPEC.md")).read_text().strip().encode()) ==
|
||||
"641ce9eaa7acd58e5d7aeab3e3394e798b47c080fd6b463ae1c9eb036d8d7bee", "SPEC contract hash")
|
||||
verify_base_files(project, manifest["base_files"])
|
||||
collector = bundle.json(DOC + "p28-01/COLLECTOR_FORMAL_COMPLETION_0F53A67.json")
|
||||
require(collector["state"] == "completed" and collector["candidate"] == "0f53a67210227207efa63045de88a9a7c6e45409", "collector completion")
|
||||
four_gates(collector["evidence"])
|
||||
execution = bundle.json(FULL + "run/execution.json")
|
||||
admission = bundle.json(FULL + "run/admission.json")
|
||||
reports = full9(execution, admission)
|
||||
readiness, perf_report = r02(bundle, reports)
|
||||
reg = bundle.json(REG + "runner-input.json")
|
||||
source = product_sources(project)
|
||||
require(len(source) == 1407 and source == reg["selected_source_map"] == normalized_map(admission["source_map"]), "E4/current/full9 source map mismatch")
|
||||
require(source == manifest["r02_source_map"] and manifest["r02_source_commit"] == readiness["after"], "r02 after source map mismatch")
|
||||
for key, name in (("runner_input_sha256", "runner-input.json"),
|
||||
("runner_ended_sha256", "runner-ended.json"),
|
||||
("all_tests_sha256", "all-tests.json")):
|
||||
require(readiness["full_run_binding"][key] == digest(bundle.raw(REG + name)), "r02/E4 receipt binding: " + key)
|
||||
regression(reg, bundle.json(REG + "runner-ended.json"), bundle.json(REG + "all-tests.json"),
|
||||
source, digest(bundle.raw(DOC + "completion_marker_contracts.json")))
|
||||
require(reg["engine_sha256"] == readiness["engine_sha256"] == ENGINE, "E4 engine mismatch")
|
||||
require(reg["marker_contract_sha256"] == digest(bundle.raw(DOC + "completion_marker_contracts.json")), "E4 marker mismatch")
|
||||
formal = bundle.json(PRE + "RESTART_3AC2CED_FORMAL_DB_JOIN.json")
|
||||
require(formal["state"] == "completed" and formal["helper_acceptance"] == {"completed": 8, "total": 8}, "restart completion 8/8")
|
||||
four_gates(formal["normal_evidence_join"])
|
||||
require([formal[k] for k in ("S", "Q", "M")] == ["954528a7c5a0e27e2e50b642373168fa0a550aba", "3ac2cedc8dc977778fd5a3ba152197cc6cf00fa2", "3086af2ccd970ddee271a184214dad037bd1d6af"], "restart S/Q/M binding")
|
||||
qualification = bundle.json(PRE + "ROOT_CANDIDATE_3AC2CED_QUALIFICATION.json")
|
||||
require(qualification["qualified"] is True and
|
||||
[qualification[k] for k in ("submitted", "candidate", "main_base")] ==
|
||||
[formal[k] for k in ("S", "Q", "M")], "restart qualification binding")
|
||||
native = bundle.json(RESTART + "run/execution.json")
|
||||
require(source == normalized_map(native["source_before"]) == normalized_map(native["source_after"]), "restart source identity")
|
||||
restart(native)
|
||||
for name, sha in native["worker_before"].items():
|
||||
require(manifest["base_files"][DOC + "p28-01/restart/probe/" + name] == sha, "probe not equal to native/Q")
|
||||
require(native["worker_before"] == native["worker_after"] and len(native["worker_before"]) == 4, "restart four probes preserved")
|
||||
review = bundle.json(PRE + "restart-independent-review-954528a-r01/DESKTOP_RESULT.json")
|
||||
require(review["status"] == "pass" and len(review["findings"]) == 3 and
|
||||
all(f["severity"] == "low" for f in review["findings"]), "independent review/low findings")
|
||||
require(conditions["restart_low_findings"] == review["findings"], "low findings omitted")
|
||||
# The fixed-SHA Root joins bind all raw receipts, source snapshots and formal
|
||||
# regression totals. Recheck their recorded boolean results as well.
|
||||
for path, flag in ((FULL + "ROOT_RAW_ACCEPTANCE_JOIN.json", "all_raw_join_checks_passed"),
|
||||
(RESTART + "ROOT_RAW_ACCEPTANCE_JOIN.json", "passed")):
|
||||
join = bundle.json(path)
|
||||
require(join[flag] is True and all(c["passed"] is True for c in join["checks"]), "Root join failed: " + path)
|
||||
return {"verified_files": len(bundle.entries), "source_files": len(source),
|
||||
"r02_state_hashes": {k: perf_report[k] for k in ("prepared_state_sha256", "migrated_input_state_sha256", "running_state_sha256")}}
|
||||
|
||||
|
||||
class Parser(argparse.ArgumentParser):
|
||||
def error(self, message):
|
||||
raise InvalidEvidence(message)
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
result = {"task_id": "P28-01-GATE", "commit": BASE, "passed": False,
|
||||
"whole_unit_accepted": False, "g3_decision": "independent_review_required",
|
||||
"conditions": [], "failures": []}
|
||||
output = None
|
||||
try:
|
||||
parser = Parser(description=__doc__)
|
||||
parser.add_argument("--project-root", required=True)
|
||||
parser.add_argument("--evidence-root")
|
||||
parser.add_argument("--json")
|
||||
args = parser.parse_args(argv)
|
||||
project = Path(args.project_root).resolve()
|
||||
build = os.environ.get("BUILD_OUTPUT_DIR")
|
||||
root = args.evidence_root or (str(Path(build) / "p28-01-gate-evidence") if build else None)
|
||||
output = Path(args.json) if args.json else (Path(build) / "p28-01-gate-verify.json" if build else None)
|
||||
if output:
|
||||
require(not output.resolve().is_relative_to(project), "JSON output inside source tree refused")
|
||||
require(root is not None, "--evidence-root or BUILD_OUTPUT_DIR is required")
|
||||
here = Path(__file__).resolve().parent
|
||||
mraw = (here / "EVIDENCE_BUNDLE_MANIFEST.json").read_bytes()
|
||||
craw = (here / "GATE_CONDITIONS.json").read_bytes()
|
||||
require(digest(mraw) == MANIFEST_SHA256, "manifest seal mismatch")
|
||||
require(digest(craw) == CONDITIONS_SHA256, "conditions seal mismatch")
|
||||
conditions = load_json(craw)
|
||||
verify_conditions(conditions, load_json(craw))
|
||||
result["disclosures"] = conditions
|
||||
result["details"] = run(project, Path(root).resolve(), load_json(mraw), conditions)
|
||||
result["passed"] = True
|
||||
except (OSError, ValueError, KeyError, TypeError, IndexError) as exc:
|
||||
result["failures"].append({"check": "evidence_join", "error": str(exc), "type": type(exc).__name__})
|
||||
result["conditions"] = [{"id": key, "join_status": "pass" if result["passed"] else "fail",
|
||||
"acceptance_status": "independent_review_required"} for key in IDS]
|
||||
if output is not None:
|
||||
try:
|
||||
# Never create source outputs, including on a failed argument check.
|
||||
require(not output.resolve().is_relative_to(project), "JSON output inside source tree refused")
|
||||
output.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n")
|
||||
except (OSError, ValueError) as exc:
|
||||
result["passed"] = False
|
||||
result["failures"].append({"check": "output", "error": str(exc)})
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
return 0 if result["passed"] else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,123 @@
|
||||
# P28-01-RESTART 最終草案(未登録・未実行)
|
||||
|
||||
- 状態: Desktop 管制による草案です。collector の final が出るまで、登録、worker の起動、engine の実行はしません。
|
||||
- 目的: 原 P28 の横断条件「途中保存・再起動で再現」を、R と O のそれぞれについて、writer と reader を別PIDにした1組で示します。性能の数値はここでは比べません。
|
||||
- 原本の扱い: collector だけでは原本に加算しません(22/88 のまま)。P28-01 の加算には、次の4つがすべて必要です。
|
||||
1. collector の final
|
||||
2. 実測
|
||||
3. RESTART の pair の PASS
|
||||
4. 証拠ゲート
|
||||
- 予算の未達、200客での Operations ON、Windows は、未達のまま残します。
|
||||
|
||||
## 1. 範囲と入力
|
||||
|
||||
| | writer(PID A) | reader(PID B、別の engine プロセス) |
|
||||
|---|---|---|
|
||||
| R(602分、Ops OFF) | SPEC §4.3 と同じ起動(autoload への復元 → main の追加 → `_set_speed(1)`)。実際の clock 経路で10ゲーム分、または `simulation_overloaded` までの短い方を進め、clock が止まった状態で保存する | 新規開始で main を追加(`_ready` の Ops 開始結果をそのまま記録)→ UI の読込で R の保存を読む |
|
||||
| O(新規開始、Ops ON) | 新規開始の main で実際の x1 を進める。最初の**実際の arrival**(下の §4)が起きた後の最初の分で、clock を止めて保存する。上限は120ゲーム分 | R と同じ |
|
||||
|
||||
- 保存には、main の実際の手動保存 `main._save_manual_at_path(path, name)` を使います(main.gd:2243 → `save_to_path` :4195 → `_perform_checkpoint_save`)。
|
||||
- 読込には、main の実際の読込 `main._load_manual_at_path(path, true)` を使います(main.gd:2288 → `load_from_path` :4223)。
|
||||
- 保存先は private user:// だけで、通常 player の保全を前後で照合します。
|
||||
|
||||
## 2. 成否の取り方(`_save_manual_at_path` は bool、`_load_manual_at_path` は void)
|
||||
|
||||
- `_save_manual_at_path`(main.gd:2243)は bool を返し、`_load_manual_at_path`(main.gd:2288)は void です。
|
||||
- 保存の bool 戻り値は記録し、signal の `ok` と一致することを要求します。ただし、bool の true だけで成功とはしません。
|
||||
- 保存も読込も、成否は下の signal、`latest_save_operation_result`、実状態の3つで判定します。
|
||||
|
||||
- 呼出しの前に、`SimulationRuntime.save_operation_completed`(:9)へ受動的に接続しておきます。
|
||||
- 成否は、次の3つがそろったときに成功とします。
|
||||
1. signal の result(ok、code、operation、save_id、saved_absolute_minute)
|
||||
2. 直後に読んだ `latest_save_operation_result`。§3 の IO イベントの payload と一致すること
|
||||
3. 実際の状態。clock の `absolute_minute` が saved_absolute_minute と一致し、§3 の比較が成立すること
|
||||
- 読込の void 戻り値も、保存の bool 戻り値の true も、それだけでは成功とみなしません。signal が来なければ、失敗として原記録を残します。保存の bool と signal の ok が食い違えば、不整合として失敗にします。
|
||||
- `latest_save_operation_result` は `create_snapshot()`(simulation_runtime.gd:521-583)に含まれません。そのため状態比較の対象にはせず、別欄に記録して IO イベントの payload との一致だけを確かめます。
|
||||
|
||||
## 3. IOイベントの狭い対応規約(広い削除は禁止)
|
||||
|
||||
### 3.1 事実
|
||||
|
||||
- snapshot の中の場所
|
||||
- `systems.runtime.domain_events = {version, published_ids, recent_events}`(scripts/core/domain_event_stream.gd:96-112)
|
||||
- `published_ids` は、これまでに公開した全 ID を文字列で sort した配列で、上限はありません。
|
||||
- `recent_events` は直近の最大256件(`RECENT_EVENT_LIMIT`、:6)。append のとき、256件を超えると先頭が1件落ちます(:58-60)。
|
||||
- `systems.runtime.id_generator.counters["domain_event"]`(キーは String、値は int)
|
||||
- 正式な保存と読込の完了時には、`_complete_save_operation`(:4734)が `_publish_actor_event`(:12558)を1回だけ呼びます。このとき採番される ID は1つで、`E = "domain_event.%012d" % (c + 1)` です(id_generator :13-18)。ここで c は package の snapshot P の counter です。
|
||||
- writer のイベントの型は `SAVE_CHECKPOINT_SUCCEEDED`、reader は `SAVE_LOADED` です。
|
||||
- どちらも `source_system=save_service`、`entity_ids=[]`、payload は `_save_event_payload(result)` です。
|
||||
- writer と reader は同じ P から採番するので、E は同じ ID になります。
|
||||
|
||||
### 3.2 期待状態の構成と、全状態の完全一致
|
||||
|
||||
除外して合わせるのではなく、P に IO イベント ev を1件だけ規則どおり適用した期待値 `X(P, ev)` を作り、実際の snapshot 全体と完全一致(typed `var_to_bytes`)させます。
|
||||
|
||||
- `id_generator.counters["domain_event"]` = P の値 + 1。他の counter はすべて P と同じ。
|
||||
- `published_ids` = sort(P.published_ids ∪ {E})。E は P に含まれていてはいけません。
|
||||
- `recent_events` は P.recent_events の長さで分けます。
|
||||
- 256 未満のとき: P.recent_events に ev を末尾に足したもの
|
||||
- 256 のとき: P.recent_events の先頭1件を落とし、ev を末尾に足したもの。落ちた先頭1件は、全内容を原記録に残します。
|
||||
- ev は、event_id が E、型・source・entity_ids・minute は実際の値で、payload は signal の result から作ったもの(= `latest_save_operation_result`)です。
|
||||
- 上記3つのパス以外は、P とバイト単位で同じでなければなりません。
|
||||
|
||||
### 3.3 比較の種類
|
||||
|
||||
| 比較 | 規約 |
|
||||
|---|---|
|
||||
| a. writer の保存直前 `create_snapshot()` と P(`SaveService.load_snapshot(path)` で読んだもの。runtime は変えない) | 完全一致。不一致なら、差のあるパスの全一覧を残して止め、判断に戻す |
|
||||
| b. writer の保存直後と `X(P, ev_save)` | 完全一致 |
|
||||
| c. reader の読込直後と `X(P, ev_load)` | 完全一致 |
|
||||
| d. 継続 K 分の各分 t について、writer_t と reader_t | 全状態の完全一致。違ってよいのは、`recent_events` の中の event_id E の要素1件だけです。E がまだ直近256件の窓の中にある間は、reader 側のその要素を writer 側のものに置き換えた状態が、writer_t と完全一致しなければなりません。窓から落ちた後は、置き換えなしで完全一致です。`published_ids` と counter は、置き換えなしで一致します。置き換えた両方の要素は全内容を記録します |
|
||||
|
||||
- digest は `SimulationDigest.compute` で計算し、上の比較が成立した状態について記録します。digest 同士の一致だけで比較の代わりにはしません。
|
||||
- 規約の外で違う箇所が1つでもあれば失敗です。差のあるパスの一覧を返します。規約を広げるには、新しい判断が必要です。
|
||||
|
||||
## 4. Operations の境界と、実際の進行
|
||||
|
||||
- O の writer は、390分の時点で次のものを記録します。
|
||||
- 実際の clock
|
||||
- `get_operations_report()`(coordinator の current_minute、population)
|
||||
- 処理済みの arrival 分
|
||||
- 実際に来館した guest-auto の数
|
||||
- その間の domain_events
|
||||
- 保存できるのは、実際の arrival(来館数 > 0)が起きた後だけです。境界に到達しただけで、arrival を処理したことにはしません。120分までに arrival が起きなければ `no_arrival_within_cap` とし、Ops 境界の項目は null と理由にします。
|
||||
- 継続 K:
|
||||
- 各側で clock を止めた状態にして、`advance_minutes(1)` を1回ずつ呼びます(止めた状態での手動の stepping、simulation_kernel.gd:104-119)。
|
||||
- 呼出しの前後で `absolute_minute` を照合し、進まなければ `stop_reason=no_tick_advance` で止めます。
|
||||
- 実際の K は、実際に進んだ分の数です。両側の実際の K が一致することを要求し、K=30 のときだけ30分と表記します。
|
||||
|
||||
## 5. 異常と境界(最小限)
|
||||
|
||||
1. **改ざんした保存の原子的な拒否**: package のコピーで `world.bin` の1バイトを反転させます。UI の読込が拒否され、試行の前後で runtime の全 snapshot が完全に一致することを確かめます。
|
||||
2. **止まった状態での保存(R)**: overload で止まった writer の保存は、読込後の reader も止まった状態であること、backlog を消費しないことを確かめます。
|
||||
3. **Ops の境界をまたぐ(O)**: 継続 K の d. の比較が、次の arrival 境界で重複も欠落もないことを示します。
|
||||
|
||||
## 6. 静的な禁止検査(collector の check_probe は丸ごと import しない)
|
||||
|
||||
| 区間 | 許可するもの | 禁止するもの | 用途 |
|
||||
|---|---|---|---|
|
||||
| UI 区間(起動、実際の clock、UI での保存と読込) | `_set_speed`、`_save_manual_at_path`、`_load_manual_at_path`、受動的な接続 | `advance_`、`set_paused(false)`、`clear(`、`spawn_guest`、`SimulationRuntime.new`、`initialize(`、`enable_production_` | 状態と IO の観測だけ。性能値は出さない |
|
||||
| 決定性区間 | 単一の `func deterministic_step()` の中の `GameClock.advance_minutes(1)` だけ。呼び出せるのは UI 区間が終わって clock が止まった後だけ | `Time.get_ticks_usec` と `*_us` の統計欄、および上の UI 区間の禁止 token(許可した1つを除く) | writer と reader の比較だけ。性能・予算・速度には使わない |
|
||||
|
||||
checker の拒否試験は、少なくとも次のものです。
|
||||
|
||||
- `deterministic_step` の外での advance
|
||||
- 決定性区間から出た timing 値
|
||||
- 呼出し回数で K を数えること
|
||||
- 規約外のパスの除外や置き換え
|
||||
- cap が256のときに「末尾1件の除外」として扱うこと
|
||||
- E 以外のイベントの置き換え
|
||||
- 読込の void 戻り値や、保存の bool の true だけで成功とみなすこと
|
||||
- 保存の bool と signal の ok の食い違いを見逃すこと
|
||||
- 境界に到達しただけで arrival を処理したとみなすこと
|
||||
- signal が来ていないのに成功とすること
|
||||
|
||||
## 7. 実装と実行
|
||||
|
||||
- 置き場所: `docs/plans/original-completion-2026-10-02/execution/p28-01/restart/**`(source map の外)。
|
||||
- 前後で source map が1407件のまま一致することを照合し、正式全192の証拠を維持します。製品を変えた場合は全 roster をやり直す必要があり、旧証拠は流用できません。
|
||||
- 登録: collector の baseline C1〜C9 は凍結されているので、別の小さな project と task(`P28-01-RESTART`)として登録します。
|
||||
- spec_ref はこの草案を Git で追跡する文書にしたものです。
|
||||
- worker は通常の Runner worker、レビューは独立した Opus です。
|
||||
- 統合済みの collector の owned-phase の実行、guard、private profile は、読取専用で import します。
|
||||
- 実行: Root が、実測と並べて R に2プロセス、O に2プロセスを、owned PID と raw の結合つきで実行します。
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
{
|
||||
"status": "registration proposal for Root; finalized after full9 raw review. Not registered or run by this Desktop session.",
|
||||
"draft": {
|
||||
"path": "handoffs/p28-01-20261020/RESTART_DRAFT.md",
|
||||
"sha256": "a4a8261bcc14d38de8df8d9024321dd8fdb6c6356964ff43472d2bf84eff1334"
|
||||
},
|
||||
"assignment": {
|
||||
"implementation": {
|
||||
"worker": "codex",
|
||||
"model": "gpt-6-astra",
|
||||
"route": "complex",
|
||||
"reason": "GDScript writer/reader probe, a Python pair driver reusing owned/guard machinery, and an exact IO-event state relation; multi-area with design judgement, not bounded. Not UI implementation, so no Antigravity."
|
||||
},
|
||||
"independent_review": {
|
||||
"model": "claude-opus-5-5",
|
||||
"session": "fresh temporary session, separate from implementation and spec authoring"
|
||||
},
|
||||
"native_execution": "Root only, after RESTART final, on the integrated main (see RESTART_ROOT_NATIVE_PROCEDURE.md)",
|
||||
"quota_fallback": "Only on structured provider quota: once, same role, Antigravity claude-opus-5-5-high. Other failures blocked."
|
||||
},
|
||||
"register_project": {
|
||||
"project": "the-tree-p28-01-restart",
|
||||
"repo": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部",
|
||||
"worktrees": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-01-restart",
|
||||
"baseline": [
|
||||
{
|
||||
"id": "P28-01-RS1",
|
||||
"requirement_id": "P28-01",
|
||||
"text": "保存(bool)・読込(void)の成否を signal・latest_save_operation_result・実状態の3点で判定し、保存の bool と signal の ok の一致を要求する。",
|
||||
"validation_ids": [
|
||||
"p28-01-restart-unit"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-RS2",
|
||||
"requirement_id": "P28-01",
|
||||
"text": "P が保存直前と完全一致し、保存後・読込後が、IO イベント1件を規則どおり適用した X(P, ev)(counter+1、published_ids への E の sort 挿入、recent 上限256の先頭落とし)と全状態で完全一致する。",
|
||||
"validation_ids": [
|
||||
"p28-01-restart-unit"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-RS3",
|
||||
"requirement_id": "P28-01",
|
||||
"text": "実際に進んだ K=30分の各分で writer と reader が全状態で完全一致し、置き換えは窓内の event_id E の1要素だけとする。",
|
||||
"validation_ids": [
|
||||
"p28-01-restart-unit"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-RS4",
|
||||
"requirement_id": "P28-01",
|
||||
"text": "O は実際の arrival が >0 になった後に保存する。120分以内に arrival が無ければ null と理由にし、境界への到達を arrival の処理とみなさない。",
|
||||
"validation_ids": [
|
||||
"p28-01-restart-unit"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-RS5",
|
||||
"requirement_id": "P28-01",
|
||||
"text": "改ざんした保存の原子的な拒否、R の止まった状態での保存と読込後も止まっていること、O の次の arrival 境界に重複・欠落が無いことを検証する。",
|
||||
"validation_ids": [
|
||||
"p28-01-restart-unit"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-RS6",
|
||||
"requirement_id": "P28-01",
|
||||
"text": "UI 区間と決定性区間の静的な禁止と許可を守り(advance は deterministic_step の中だけ)、性能値を出さない。",
|
||||
"validation_ids": [
|
||||
"p28-01-restart-unit"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-RS7",
|
||||
"requirement_id": "P28-01",
|
||||
"text": "private user://・owned PID・guard・cleanup・通常 player の保全・全 raw と SHA の結合を保ち、source map 1407 と collector の4ファイルが前後で不変であることを確かめる。",
|
||||
"validation_ids": [
|
||||
"p28-01-restart-unit"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-01-RS8",
|
||||
"requirement_id": "P28",
|
||||
"text": "P28 の横断条件6件それぞれの適用と理由を記録し、「途中保存・再起動で再現」は RS1〜RS5 で適用する。",
|
||||
"validation_ids": [
|
||||
"p28-01-restart-unit"
|
||||
]
|
||||
}
|
||||
],
|
||||
"validations": {
|
||||
"p28-01-restart-unit": {
|
||||
"argv": [
|
||||
"{python}",
|
||||
"docs/plans/original-completion-2026-10-02/execution/p28-01/restart/probe/test_restart.py"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"register_task_contract": {
|
||||
"id": "P28-01-RESTART",
|
||||
"title": "現HEADの性能基準:途中保存・別PID再開の再現",
|
||||
"requirement_ids": [
|
||||
"P28-01",
|
||||
"P28"
|
||||
],
|
||||
"acceptance_ids": [
|
||||
"P28-01-RS1",
|
||||
"P28-01-RS2",
|
||||
"P28-01-RS3",
|
||||
"P28-01-RS4",
|
||||
"P28-01-RS5",
|
||||
"P28-01-RS6",
|
||||
"P28-01-RS7",
|
||||
"P28-01-RS8"
|
||||
],
|
||||
"spec_ref": "docs/plans/original-completion-2026-10-02/execution/p28-01/restart/SPEC.md",
|
||||
"base_commit": "2132bbe3d39a0f1b69a97c731bb09213bf9d7a91",
|
||||
"worker": "codex",
|
||||
"allowed_paths": [
|
||||
"docs/plans/original-completion-2026-10-02/execution/p28-01/restart/probe/**"
|
||||
],
|
||||
"depends_on": [],
|
||||
"resource_locks": [
|
||||
"p28-01-restart-probe"
|
||||
],
|
||||
"validation_ids": [
|
||||
"p28-01-restart-unit"
|
||||
],
|
||||
"max_repair_attempts": null
|
||||
},
|
||||
"tracked_prerequisites": {
|
||||
"collector_final_Q": "0f53a67210227207efa63045de88a9a7c6e45409",
|
||||
"ledger_main_at_prepare": "db9d6132a8349928560dc4a4a99306d27a81e332",
|
||||
"full9_measurement_receipt": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/full-measurement-db9d613-r01/run/execution.json",
|
||||
"sha256": "a4d10e9b1a75a17b981792265338d64fac61178cb6f87dd646c5b486292db73c",
|
||||
"actual_commit": "db9d6132a8349928560dc4a4a99306d27a81e332",
|
||||
"root_join": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/full-measurement-db9d613-r01/ROOT_RAW_ACCEPTANCE_JOIN.json",
|
||||
"sha256": "a76082f767e0a0d52cb70e2f66fa371b62a88754e9be319da3c08d4b48e78ba0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tracked_imports_allowed": [
|
||||
"tools/run_all_tests.py",
|
||||
"tools/regression/owned_outer.py",
|
||||
"docs/plans/original-completion-2026-10-02/execution/p28-01/collector/collect_p28_01.py"
|
||||
],
|
||||
"policy_note": "New project gets the current default policy at registration; any per-item cap removal follows the existing owner delegation and is recorded honestly, not as a new human utterance.",
|
||||
"credit": "RESTART task completion adds 0. P28-01 credit needs collector final + integrated full9 measurement + RESTART final + Root 4-PID pair PASS + evidence gate.",
|
||||
"unchanged": [
|
||||
"budgets",
|
||||
"original 30/88 and acceptance denominators",
|
||||
"product source map 1407",
|
||||
"IO normalization scope (RESTART_DRAFT §3)",
|
||||
"permissions/billing/models of existing projects"
|
||||
],
|
||||
"spec_sha256_at_base": {
|
||||
"docs/plans/original-completion-2026-10-02/execution/p28-01/restart/SPEC.md": {
|
||||
"bytes_sha256": "ebfbc279c0c8a1dcc23beaef76c2983ac3c9e718bbf47f31935fe59cbc0fd933",
|
||||
"strip_text_sha256": "2eeec95c3cc0ee5ebe86fd4c64a8c2299c5704fee7ecca3358e8578eed406168"
|
||||
},
|
||||
"docs/plans/original-completion-2026-10-02/execution/p28-01/restart/TASK_INSTRUCTIONS.md": {
|
||||
"bytes_sha256": "99df3a0c8df7c202590da7fc2721430f3a55d7442285c045143583763ce7b6fb",
|
||||
"strip_text_sha256": "00aa7ba5f1d9d56634591e386d99c9ce0bc0fffe49a025bceb31bfb8be072c33"
|
||||
},
|
||||
"docs/plans/original-completion-2026-10-02/execution/p28-01/restart/DRAFT.md": {
|
||||
"bytes_sha256": "a4a8261bcc14d38de8df8d9024321dd8fdb6c6356964ff43472d2bf84eff1334",
|
||||
"strip_text_sha256": "83ac2152dd4e0078c6edb60112d2b172f33e3ee1bbdb1c48370e992a90e42487"
|
||||
},
|
||||
"docs/plans/original-completion-2026-10-02/execution/p28-01/restart/ROOT_NATIVE_PROCEDURE.md": {
|
||||
"bytes_sha256": "4fae29f0f0cfce689f04cbf3b5b4d42e2564e8cbfc89ed6f223575d2a211abb4",
|
||||
"strip_text_sha256": "8daf13e8bb5a1040c3863270f2d5d9966adb0038e6a94b860dd81397ada5b05c"
|
||||
}
|
||||
},
|
||||
"task_instructions": "docs/plans/original-completion-2026-10-02/execution/p28-01/restart/TASK_INSTRUCTIONS.md",
|
||||
"run_decision_request": {
|
||||
"action": "run",
|
||||
"task": "P28-01-RESTART",
|
||||
"worker": "codex",
|
||||
"model": "gpt-6-astra",
|
||||
"reviewer": "claude-opus-5-5 (independent fresh session)",
|
||||
"note": "Root passes this contract to the normal commander; dispatch only on the recorded commander decision."
|
||||
},
|
||||
"sequence": [
|
||||
"Root: register project/task with base 2132bbe3d39a0f1b69a97c731bb09213bf9d7a91; check spec_sha256_at_base (bytes and strip) against the base blobs",
|
||||
"Root: normal commander records the RUN decision; read back to Desktop",
|
||||
"dispatch once: codex gpt-6-astra implements restart/probe/** (local engine-free test_restart.py PASS before submit)",
|
||||
"normal validation p28-01-restart-unit (Desktop reads raw log)",
|
||||
"Root: pre-review native per ROOT_NATIVE_PROCEDURE.md (import, parse, 4 PIDs), recorded as diagnostic, then engine-free check_restart.py",
|
||||
"independent Opus full review of RS1-RS8",
|
||||
"prepare-integration / final",
|
||||
"Root: real 4-PID pair on integrated main, then P28-01 evidence gate (22->23 only if all join)"
|
||||
]
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
# P28-01-RESTART:Root の実測4 PID の手順
|
||||
|
||||
RESTART が final に達した後に、統合済みの main で1回だけ実行します。
|
||||
|
||||
## 事前の照合(いずれかが不一致なら起動しない)
|
||||
|
||||
- 実際の main commit を記録します。source map は1407件で、collector の4ファイルは Q と一致し、restart の4ファイルは RESTART の final の S と一致すること。
|
||||
- engine の SHA、代表入力の SHA(`eb4f7700…`)。
|
||||
- 動いている engine が無いこと。空き容量が 1.2 GB 以上あること。
|
||||
- `restart_pair.py --check` の計画の argv を保存し、実行するものと一致することを確かめます。
|
||||
|
||||
## 実行(`restart_pair.py --run`、全体で1回)
|
||||
|
||||
1. import を1回、owned で行います。
|
||||
2. parse を行います。`restart_probe.gd` と wrapper のそれぞれについて `--check-only` を owned で実行します。exit 0 で、SCRIPT ERROR/Parse Error/ERROR が無いことを要求します。
|
||||
3. **R-writer**(PID 1)
|
||||
- autoload への復元 → main の追加 → `_set_speed(1)` の順で始めます。
|
||||
- 実際の clock で10分、または overload まで進めます。
|
||||
- clock を止めた状態で、UI から保存します。
|
||||
- その後、決定性区間で K=30 を進めます。
|
||||
4. driver が package を byte 一致・SHA で照合して、reader の private user:// へ複製します。改ざん用のコピーは、`world.bin` の1バイトを反転したものにします。
|
||||
5. **R-reader**(PID 2)
|
||||
- 新規開始の main で始めます。
|
||||
- 改ざんコピーを UI から読み、拒否されること、状態が完全に不変であることを確かめます。
|
||||
- 正しい保存を UI から読みます。
|
||||
- 決定性区間で K=30 を進めます。
|
||||
6. **O-writer**(PID 3)
|
||||
- 新規開始の main で始め、実際の x1 で進めます。
|
||||
- 実際の arrival が >0 になった後の最初の分で(上限120分)、clock を止めて UI から保存します。
|
||||
- 決定性区間で K=30 を進めます。
|
||||
7. **O-reader**(PID 4): R-reader と同じ手順です。
|
||||
8. 各 PID には、collector と同じ guard の bootstrap、新しい private profile、owned の Popen を使います。phase の上限は 1800 s です。
|
||||
|
||||
## 判定(engine を使わない `check_restart.py`)
|
||||
|
||||
- RS1〜RS7 のすべてと、4 PID の PID・raw の結合。
|
||||
- source、通常 player、stage の inventory、guard、cleanup が前後で一致すること。
|
||||
- 失敗した場合は原記録を残して止め、判断に戻します。そのまま再実行はしません。
|
||||
- R の overload での停止は、実際の値として記録します。O の arrival が無い場合は、null と理由にします。
|
||||
|
||||
## 証拠ゲート(その後)
|
||||
|
||||
collector の final(Q)、統合版の全9 phase の実測、RESTART の final、この4 PID の pair を結合したときに限り、P28-01 を 22→23 とします。予算の未達、200客での Operations ON、Windows は、未達として残します。
|
||||
@@ -0,0 +1,88 @@
|
||||
# P28-01-RESTART 仕様(spec_ref)
|
||||
|
||||
- 状態: 登録用の確定版です。この文書を Git で追跡し、それを含む commit を base にします。
|
||||
- 元の草案: 同じディレクトリの `DRAFT.md`(SHA `a4a8261bcc14d38de8df8d9024321dd8fdb6c6356964ff43472d2bf84eff1334`。Control handoff の `RESTART_DRAFT.md` とバイト一致)。§番号は草案と同じです。この仕様は草案を契約の形にしたもので、規約を広げてはいません。
|
||||
|
||||
## 1. 原本との対応(変更しない)
|
||||
|
||||
- 対象は原 P28 の横断条件6件のうち、「途中保存・再起動で再現」です(`work_packages.json` P28.cross_cutting_acceptance[2])。
|
||||
- 成果単位は P28-01「現HEADの性能基準」です。work と acceptance は collector の SPEC §1 と同じです。
|
||||
- 分母は原30/88で、部分点はありません。この task が完了しても、原本は加算0です。P28-01 の加算(22→23)には、次の4つがすべて必要です。
|
||||
1. collector の final(完了済み、Q `0f53a672…`)
|
||||
2. 統合版で全9 phase を実測すること(完了済み。実際の commit `db9d6132a8349928560dc4a4a99306d27a81e332`、`artifacts/original-completion-2026-10-02/p28-01-runner-preflight-r01/full-measurement-db9d613-r01/run/execution.json` SHA `a4d10e9b1a75a17b981792265338d64fac61178cb6f87dd646c5b486292db73c`、Root の結合 `ROOT_RAW_ACCEPTANCE_JOIN.json` SHA `a76082f767e0a0d52cb70e2f66fa371b62a88754e9be319da3c08d4b48e78ba0`)
|
||||
3. この RESTART の final と、Root の実測4 PID の pair の PASS
|
||||
4. 証拠ゲート
|
||||
- 次のものは、未達のまま残します。
|
||||
- 予算の未達
|
||||
- 200客での Operations ON(製品の上限100)
|
||||
- Windows
|
||||
- 予算値、製品の値、停止契約は変更しません。
|
||||
|
||||
## 2. 成果物(worker、allowed_paths `docs/plans/original-completion-2026-10-02/execution/p28-01/restart/probe/**`)
|
||||
|
||||
| ファイル | 役割 |
|
||||
|---|---|
|
||||
| `restart_pair.py` | Root が実行する driver です。統合済みの collector から、`no_live_engine`、`source_map`、`run_private_engine`、`run_engine` と、guard、private profile、owned phase の仕組みを、読取専用で import して使います。import してよい外部ファイルは `tools/run_all_tests.py`、`tools/regression/owned_outer.py`、統合済みの `execution/p28-01/collector/collect_p28_01.py` だけです。`--check` は計画だけを出力し、`--run` で4 PID を実行します。 |
|
||||
| `restart_probe.gd` | writer と reader の probe(extends SceneTree)です。草案 §1〜§5 を実装します。 |
|
||||
| `check_restart.py` | report と receipt の checker です。§4 の拒否をすべて実装します。 |
|
||||
| `test_restart.py` | engine を使わない unittest です。 |
|
||||
|
||||
## 3. 受入グループ(baseline。有限で、草案の各節に対応する)
|
||||
|
||||
- **RS1(草案§1〜2)保存・読込の成否**
|
||||
- 保存は main の `_save_manual_at_path`(bool)、読込は `_load_manual_at_path(path, true)`(void)で行います。
|
||||
- 成否は、`save_operation_completed` の signal、`latest_save_operation_result`、実際の状態の3つで判定します。
|
||||
- 保存の bool と signal の ok は一致しなければなりません。
|
||||
- **RS2(§3.2、3.3 a〜c)IO の厳密な対応**
|
||||
- a: P が保存直前の状態と完全一致すること。
|
||||
- b: 保存後の状態が X(P, ev_save) と完全一致すること。
|
||||
- c: 読込後の状態が X(P, ev_load) と完全一致すること。
|
||||
- X の構成は §3.2 のとおりで、counter+1、published_ids への E の sort 挿入、recent_events(上限256、溢れたら先頭を1件落として記録)です。
|
||||
- **RS3(§3.3 d、§4)継続の一致**
|
||||
- 実際に進んだ K(=30)分の各分で、writer と reader が全状態で完全一致すること。
|
||||
- 置き換えてよいのは、窓の中にある event_id E の要素1件だけです。
|
||||
- K は実際に進んだ分の数で数えます。
|
||||
- **RS4(§4)Operations の境界**
|
||||
- O は、実際の arrival(>0)が起きた後に保存します。
|
||||
- 120分までに arrival が無ければ、null と理由にします。
|
||||
- 境界に到達しただけで、arrival を処理したことにはしません。
|
||||
- **RS5(§5)異常と境界**
|
||||
- 改ざんした保存の原子的な拒否(reader の PID 内で、正しい保存の前に試します)。
|
||||
- R の止まった状態での保存と、読込後も止まっていること。
|
||||
- O の次の arrival の境界に、重複も欠落もないこと。
|
||||
- **RS6(§6)区間の静的検査**
|
||||
- UI 区間と決定性区間の、それぞれの禁止と許可を守ること。
|
||||
- 性能値を一切出さないこと。
|
||||
- **RS7(§7、collector C6/C7 と同じ)分離**
|
||||
- private user://、owned PID、guard、cleanup、通常 player の保全、全 raw と SHA の結合。
|
||||
- source map が1407件のまま前後で一致すること。
|
||||
- collector の4ファイルが Q と一致すること。
|
||||
- **RS8 横断条件の記録**
|
||||
- 6件の横断条件それぞれについて、適用するかどうかと理由を記録します。
|
||||
- 「途中保存・再起動で再現」は、RS1〜RS5 で適用します。
|
||||
- それ以外の5件は、collector 側の既存の受入を参照し、適用外の理由を記録します。
|
||||
|
||||
## 4. checker が必ず拒否するもの(すべて test_restart.py に拒否試験を置く)
|
||||
|
||||
1. 読込の void 戻り値、または保存の bool の true だけで、成功とすること。保存の bool と signal の ok の食い違いを見逃すこと。signal が無いのに成功とすること。
|
||||
2. X(P, ev) 以外の差を許すこと。たとえば次のものです。
|
||||
- 2件以上のイベントの差
|
||||
- E 以外の ID
|
||||
- counter が+1でない
|
||||
- published_ids の欠落や重複
|
||||
- recent の上限での先頭の落とし方の違い
|
||||
- 規約の外のパスの除外や置き換え
|
||||
- 「末尾1件の除外」として扱うこと
|
||||
3. 継続で、E 以外の要素を置き換えること。窓から落ちた後も置き換えること。digest の一致だけで比較の代わりにすること。
|
||||
4. advance の呼出し回数で K を数えること。writer と reader で実際の K が違うのに合格とすること。
|
||||
5. 境界に到達しただけで arrival を処理したとみなすこと。arrival が0なのに RS4 を合格とすること。
|
||||
6. 改ざんした読込のあとで状態が変わっているのに合格とすること。止まった状態での保存のあとに backlog を消費すること。
|
||||
7. 決定性区間の外での `advance_minutes`。決定性区間の中の `Time.get_ticks_usec` や `*_us`。UI 区間の禁止 token。
|
||||
8. PID、raw、source map、通常 player、guard の不一致。owned folder への書込み。
|
||||
|
||||
## 5. 依存と前提
|
||||
|
||||
- 登録済みの task への depends_on はありません。collector は別 project なので、前提は追跡済みの記録として SHA で固定します。
|
||||
- collector の final の receipt(Q)
|
||||
- 統合版の全9 phase の実測の receipt(上の §1 の2つの SHA)
|
||||
- collector の baseline C1〜C9 は凍結されているので、追加しません。RESTART は別 project として登録します。
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
# SPEC_CLARIFICATION_01(P28-01-RESTART)
|
||||
|
||||
- 対象: base `2132bbe3d39a0f1b69a97c731bb09213bf9d7a91` 上の `SPEC.md` と `DRAFT.md` です。
|
||||
- 変えないもの: 元の受入 RS1〜RS8、baseline、contract、spec hash。この文書はそれらを書き換えません。
|
||||
- きっかけ: run `e98055fc5fef4fe2986d2451f03a0734` が blocked で終わり、2件の指摘がありました(その失敗は保持します)。この文書はそれに答えるものです。
|
||||
- 承認: ユーザーが委任した既存の判断に基づく Root の判断です。新しい人間の発言はありません。
|
||||
|
||||
## C1. 仕様 SHA の2つの算法
|
||||
|
||||
| 算法 | 値 |
|
||||
|---|---|
|
||||
| `SPEC.md` の生 bytes の SHA256 | `ebfbc279c0c8a1dcc23beaef76c2983ac3c9e718bbf47f31935fe59cbc0fd933` |
|
||||
| Runner の契約値 `sha256(text.strip())` | `2eeec95c3cc0ee5ebe86fd4c64a8c2299c5704fee7ecca3358e8578eed406168` |
|
||||
|
||||
- どちらも同じ blob で、差は末尾の改行だけです。
|
||||
- 照合は `contract.spec_sha256 == sha256(text.strip())` で行います。この差を理由に blocked にしません。hash も gate も変えません。
|
||||
|
||||
## C2. 改ざんした読込の判定
|
||||
|
||||
DRAFT §5.1、SPEC §4-6、ROOT_NATIVE_PROCEDURE 手順5 にある「全 snapshot 完全一致」を、次のとおり厳密化します。
|
||||
|
||||
- **製品の事実**: 読込は失敗した場合でも、`_complete_save_operation` で `SAVE_LOAD_FAILED` を1件発行します(`autoload/simulation_runtime.gd:4223-4253`、`:4683-4733`)。失敗時には復元を確定せず、paused を元の値に戻します(`:4715-4716`)。
|
||||
- **要求**: 試行直前の全 snapshot を B とします。DRAFT §3.2 と同じ規則で `X(B, ev_fail)` を作り、試行直後の全 snapshot がそれと typed `var_to_bytes` で完全一致することを要求します。規則は次のとおりです。
|
||||
- counter を+1する。
|
||||
- `published_ids` に E_fail を sort 挿入する。
|
||||
- `recent_events` は末尾に追加する。256件のときは先頭を1件落とし、落とした要素の全内容を記録する。
|
||||
- それ以外のパスは、すべて B と同一である。
|
||||
- **ev_fail**: type `SAVE_LOAD_FAILED`、source `save_service`、`entity_ids []`。payload は `latest_save_operation_result` と同じです。
|
||||
- **signal の result**: 次をすべて満たすことを要求します。
|
||||
- `ok=false`
|
||||
- `code` が OK ではない(実際の値を記録する)
|
||||
- `preload_pause_restored=true`
|
||||
- `loaded_clock_paused` が試行前の paused と同じ
|
||||
- `recovery_report` があれば `restore_committed=false`
|
||||
- migration を試みた場合は、事実として記録する
|
||||
- **禁止**: 監査イベントの無視、削除、除外。規則の外の差が1つでもあれば失敗です。
|
||||
- `_record_runtime_performance` による診断値は snapshot の外にあるので、比較しません。
|
||||
|
||||
## C3. その後の正しい読込
|
||||
|
||||
正しい読込は、P で全体を置き換えます。RS2 c の `X(P, ev_load)` との完全一致は従来どおり要求します。あわせて、直前の失敗イベントが読込後の状態に残っていないことも確かめます。
|
||||
|
||||
## C4. 追加する拒否試験
|
||||
|
||||
- 失敗イベントを除外して一致させること
|
||||
- 2件以上の差
|
||||
- counter が+1でない
|
||||
- E_fail が sort されていない
|
||||
- 256件のときの先頭の落とし方の違い
|
||||
- payload と latest の不一致
|
||||
- `ok=true` または `code=OK` を失敗として扱うこと
|
||||
- paused が元に戻っていない
|
||||
- 成功した読込の後に失敗イベントが残っている
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
# SPEC_CLARIFICATION_02(P28-01-RESTART:依存の閉包と実行契約の確定)
|
||||
|
||||
- 対象: base `2132bbe3d39a0f1b69a97c731bb09213bf9d7a91` 上の SPEC.md / DRAFT.md / TASK_INSTRUCTIONS.md と、SPEC_CLARIFICATION_01 です。
|
||||
- 変えないもの: 元の受入 RS1〜RS8、baseline、contract、spec hash。製品、セキュリティ、権限は変更しません。
|
||||
- この文書の役割: repair1 run `facdc513fdd54a5e9899ecc7e4afb817` が blocked になった件(依存の閉包)に答えます。あわせて、残っている実行上の曖昧さを1回でまとめて確定します。
|
||||
- **これ以外の実装上の選択**(関数の分け方、名前、データ構造、ログの書式など)は、承認された範囲の中での worker の通常の判断です。原本の仕様の不足として blocked にしないでください。
|
||||
|
||||
## D1. import の閉包(完全な列挙。それ以外は禁止)
|
||||
|
||||
import してよい project の Python は、次の6ファイルだけです(Root の読取専用の監査 `RESTART_DEPENDENCY_CLOSURE_READONLY.json`、SHA `06fdecc471a56d0edab9f58a68b33c433b1a5ab9d9db38024bd8e97e66166766` の6件と一致。第7のファイルはありません)。どれも読取専用で使い、変更しません。SHA は base 時点の値で、実行時に照合します。
|
||||
|
||||
| ファイル | SHA256 | 区分 |
|
||||
|---|---|---|
|
||||
| `tools/run_all_tests.py` | `320ca668732b4ff72fc4449998af2327d4e5c527b06896740806aaf0487282b4` | 承認済み |
|
||||
| `tools/regression/owned_outer.py` | `c2e3483b76db1a3ff940f23f88ea0083fc78736e874ebcdb385d766dde8866e7` | 承認済み |
|
||||
| `tools/regression/owned_diagnostic_process.py` | `e31e93c6e2c5ea46a7d954ef8bc0b4caec1c08b86b8aef19e93b52ef24993087` | 推移(owned_outer.py:9-10 が import 時に読み込む) |
|
||||
| `tools/regression/p09_owned_engine_process.py` | `c007e712c281573254faa1233c2ac92e100ab87393256a6dc096b35283e43037` | 推移(owned_diagnostic_process.py:10-14 が SHA を照合してから exec する) |
|
||||
| `docs/plans/original-completion-2026-10-02/execution/p28-01/collector/collect_p28_01.py` | `a43e1f963ef8505ddd5acf8bf747d9bfac22a028cbb805c0b964ee11b9b40a14` | 承認済み |
|
||||
| `docs/plans/original-completion-2026-10-02/execution/p28-01/collector/check_p28_01_report.py` | `6c0851d498ca80bdc120fad047b072255f48713f63114521f3d4dfc902b2512b` | 推移(collect_p28_01.py:16 が読み込む) |
|
||||
|
||||
- **読み込み方**: collector は `importlib.util.spec_from_file_location` で読み込みます。その前に、collector のディレクトリを `sys.path` の先頭に追加します(collect_p28_01.py:16 の `from check_p28_01_report import ...` を解決するため)。run_all_tests.py と owned_outer.py は、collector の `load_tool(EXTERNAL_IMPORTS[i])` で取得してかまいません。
|
||||
- **使わないもの**: run_all_tests.py の `main()` の中で読み込まれる `tools/strict_test_markers.py`、`tools/regression/diagnostic_gate.py`、`ordinary_proof_gate.py`、`audio_evidence_gate.py` は使いません。run_all_tests.py の `main()` も呼びません。
|
||||
- **標準ライブラリ**: 通常どおり import してかまいません。新しいものの導入、third-party の package、ネットワークは禁止です。
|
||||
- **自分の4ファイルは別扱い**: 上の6ファイルは、外部から再利用する依存の閉包です。この task が新しく作る allowed_paths 内の4ファイル(`restart/probe/restart_pair.py`、`restart_probe.gd`、`check_restart.py`、`test_restart.py`)が互いを通常どおり import することは許可します。たとえば `test_restart.py` が `check_restart.py` や `restart_pair.py` を、`restart_pair.py` が `check_restart.py` を import する場合です。これは第7の外部依存ではありません。
|
||||
- **checker の検査**: 上の6ファイルと allowed_paths 内の4ファイル以外の project の `.py` を import しようとしたら、拒否します。拒否試験も置きます。
|
||||
- 方法は2つ組み合わせます。AST で import 文と、`spec_from_file_location` / `exec` の文字列の path を走査することと、実行時に `sys.modules` の `__file__` を照合することです。
|
||||
- 6ファイルの SHA が1つでも違えば、起動の前に拒否します。
|
||||
|
||||
## D2. CLI と入力
|
||||
|
||||
- `restart_pair.py --check|--run --readonly <build/p28-01-readonly> --fixture <reference.variant> --engine <Godot> --output <新しいdir>`。collector と同じ形で、`--check` は計画の argv を出力するだけです。
|
||||
- guard は `<readonly>/run-r02/profile_guard.gd` を使い、wrapper は collector と同じ形(`extends "<probe>"` + `Guard.activate()`)で生成します。Root はこの project の allocation にも、`build/p28-01-readonly` を同じ SHA で置きます。
|
||||
- 実行の順序は固定です: import → parse-base → parse-wrapper → R-writer → R-reader → O-writer → O-reader。
|
||||
- 各 phase は collector の `run_private_engine`(新しい private profile、owned、上限 1800 s)で実行します。
|
||||
- parse は collector の L2 と同じ規則です(`--check-only`、exit 0、SCRIPT ERROR/Parse Error/ERROR が無いこと)。
|
||||
- reader は、対応する writer が成功したときだけ起動します。どこかが失敗したら、そこで run 全体を止めます。
|
||||
- 役割と条件は env で渡します。`THE_TREE_PACK_PROFILE_*` は collector と同じものを使います。追加は `THE_TREE_RESTART_ROLE`、`THE_TREE_RESTART_K=30`、`THE_TREE_RESTART_TRANSFER=<絶対path>`、`THE_TREE_RESTART_TRANSFER_MANIFEST=<json の絶対path>` です。
|
||||
|
||||
## D3. 保存 package の受け渡し(profile を事前に書き換えない)
|
||||
|
||||
- guard は、runtime profile が事前に新しいことと、guard より前の書込みが0であることを要求します。そのため、reader の profile に事前にファイルを置くことは**禁止**です。
|
||||
- writer の probe が正規の UI で保存した package を、driver が engine の終了後に writer の private user:// から `out/<R|O>-transfer/good/` へ複製します。全ファイルの SHA を manifest に記録します。
|
||||
- 改ざん用のコピー `out/<R|O>-transfer/tampered/` は、good の複製の `world.bin` の offset `len//2` の1バイトを XOR 0x01 したものです。offset と、改ざん前後の SHA を記録します。
|
||||
- reader の probe は guard が有効になった後で、transfer を絶対 path から読み、自分の `user://p28_restart/good/` と `user://p28_restart/tampered/` へ書き出します。manifest の SHA と一致しなければ、止めて失敗にします。そのあと、UI の読込で `user://` の path を使います。
|
||||
|
||||
## D4. 一時停止と保存の時点(実際の UI/Clock の経路)
|
||||
|
||||
- 時間は実際の Clock(`_set_speed(1)` → `SimulationKernel._process`)だけで進めます。
|
||||
- 保存の時点は、`GameClock.tick_completed(absolute_minute)`(simulation_kernel.gd:9、:154)に受動接続して判定します。
|
||||
- R の writer: 開始から10分以上進んだ tick。
|
||||
- O の writer: 実際の arrival が >0 になった後の最初の tick。上限は120分です。
|
||||
- その signal の handler の中で `GameClock.set_paused(true)` を呼びます。kernel は tick ごとに paused を確かめるので、catch-up の途中でも止まります。
|
||||
- `set_paused(true)` は時間を進めない一時停止なので、許可します。`set_paused(false)` は従来どおり禁止です。`_toggle_pause` は使いません(Ops の acknowledge に分岐するため)。
|
||||
- overload で先に止まった場合は、その状態のまま保存します(RS5 の止まった状態での保存)。
|
||||
- arrival の判定は、guest_id が `guest-auto-` で始まる客(simulation_runtime.gd:2963、:3421)の実数(>0)と、`get_operations_report()` を併記して記録します。
|
||||
|
||||
## D5. 比較の実行場所と記録(役割分担)
|
||||
|
||||
- 状態を正確に比べるのは probe です(GDScript、同じ engine の中)。
|
||||
- X(P, ev)、X(B, ev_fail)(CLARIFICATION_01)、各分の比較は、`var_to_bytes` の完全一致で判定します。
|
||||
- 一致しなかった場合は、差のある path の一覧(depth ≤ 4)を出力します。
|
||||
- 大きな状態(R は約11 MB)を全分保存することは避けます。記録するのは次のものです。
|
||||
- 全 bytes のファイル: P、writer の保存直後、reader の改ざん試行の前(B)、試行の後、読込直後、継続 K の最後の状態。
|
||||
- 毎分の値: 全状態の `var_to_bytes` の SHA256。
|
||||
- writer と reader の各分の照合: 両側とも、event_id E の要素1件(窓の中にある間)を、同じ正規形 `{"event_id": E, "replaced_for_cross_pid_compare": true}` に置き換えます。その後の全状態 bytes の SHA256 が、全分で一致することを要求します。両側の E の要素の全内容は記録します。この置き換えは DRAFT §3.3 d の規則と同じもので、広げてはいません。
|
||||
- **2つの digest の区別**: PID をまたぐ毎分の照合に使う「完全な typed `var_to_bytes` の SHA256」は、全状態の bytes をすべて覆う値であり、bytes の完全一致を表す方法として認めます。これは、状態の一部しか覆わない SimulationDigest とは別のものです。SimulationDigest は参考の記録にだけ使い、一致の判定には使いません。
|
||||
- 同じ PID の中の比較(X(P, ev)、X(B, ev_fail)、読込直後など)は、従来どおり `var_to_bytes` の bytes そのもので完全一致を判定します。
|
||||
- どちらの方法でも、E の1要素の正規形への置き換え(PID をまたぐ照合の場合だけ)以外の path は、1つも除外も省略もしません。全部が比較の対象に残ります。
|
||||
- これは実行契約の明確化であり、原本の受入 RS1〜RS8 を緩めるものではありません。
|
||||
- Python の checker が確かめるのは、report の形式・矛盾・SHA の結合と、両 PID の毎分の SHA の一致、そして D1/D7 の静的検査です(Godot の値の再構成はしません)。probe の実経路は静的レビューで確かめ、実際の実行は Root の owned PID と raw の結合で確かめます。
|
||||
|
||||
## D6. 終了と診断
|
||||
|
||||
- main を追加した phase は、collector の K1 と同じく `get_root().close_requested.emit()` で終了し、`P28_RESTART_AUDIO_CLOSE` の1行を要求します。
|
||||
- report は stdout の `P28_RESTART_REPORT=<json>` ちょうど1行です。PID の結合、raw の SHA、owned folder へ書き込まないこと、は collector と同じです。
|
||||
- probe の自己停止は 1740 s です。
|
||||
|
||||
## D7. 静的検査(restart probe 用。SPEC §6 を具体化)
|
||||
|
||||
- UI 区間で許可するもの: `_set_speed`、`_save_manual_at_path`、`_load_manual_at_path`、`GameClock.set_paused(true)`(tick 完了の handler の中だけ)、`close_requested.emit()`、受動的な connect。
|
||||
- 決定性区間: 単一の `func deterministic_step()` の中の `GameClock.advance_minutes(1)` だけを許可します。呼んでよいのは、保存か読込が成功し、clock が止まった後だけです。
|
||||
- 禁止: 決定性区間の外の `advance_`、`set_paused(false)`、`clear(`、`spawn_guest`、`SimulationRuntime.new`、`initialize(`、`enable_production_`、`_toggle_pause`、決定性区間の中の `Time.get_ticks_usec`。性能値の欄も禁止です。
|
||||
|
||||
## D8. 試験(test_restart.py)
|
||||
|
||||
- SPEC §4 と CLARIFICATION_01 C4 のすべてに加えて、D1〜D7 それぞれに、正常試験と拒否試験を1つ以上置きます。
|
||||
- 試験は engine を使いません。合成した report と、collector の J2 と同じ「本物の owned_outer に偽の子プロセス」で動かします。
|
||||
- 例: 閉包の外の import、6ファイルの SHA の違い、事前に profile へ書き込むこと、transfer の SHA の不一致、`set_paused(false)` や `_toggle_pause`、毎分の SHA の不一致、E 以外の置き換え、writer の失敗の後に reader を起動すること。
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
# P28-01-RESTART worker 指示
|
||||
|
||||
仕様は同じディレクトリの `SPEC.md` と、その元になった `DRAFT.md` です。この指示と仕様は変更しないでください。
|
||||
|
||||
## 作るもの(allowed_paths `docs/plans/original-completion-2026-10-02/execution/p28-01/restart/probe/**` の中だけ)
|
||||
|
||||
`restart_pair.py`、`restart_probe.gd`、`check_restart.py`、`test_restart.py` の4つです(SPEC §2)。
|
||||
|
||||
## 必須事項
|
||||
|
||||
- RS1〜RS8 を実装し、SPEC §4 の拒否それぞれに拒否試験を置きます。
|
||||
- import してよいのは `tools/run_all_tests.py`、`tools/regression/owned_outer.py`、統合済みの `execution/p28-01/collector/collect_p28_01.py` だけです。いずれも読取専用で使い、変更しません。collector の `check_probe` は丸ごと流用せず、SPEC §4-7 の区間別の検査を自分で実装します。
|
||||
- probe について:
|
||||
- 既定の autoload の世界を使い、main は実際の UI 経路で動かします。
|
||||
- 決定性区間は、単一の `func deterministic_step()` の中の `GameClock.advance_minutes(1)` だけにします。
|
||||
- 性能値は出しません。
|
||||
- 製品の dictionary の key は、collector と同じ `field()` の方式で読みます。
|
||||
- 4 PID の役割は次のとおりです。
|
||||
- R-writer、R-reader、O-writer、O-reader
|
||||
- reader の PID は、正しい保存を読む前に、改ざんしたコピーの読込を試します。
|
||||
- writer の private user:// にある保存 package は、driver が byte 一致を SHA で確かめてから、reader の private user:// へ複製します。
|
||||
- 製品、source map の対象、予算値、停止契約、IO の正規化の規約は変更しません。草案 §3 より広い除外は禁止です。
|
||||
- engine の起動、全回帰、fixture の生成、commit はしません。
|
||||
- 提出前に、engine を使わない `python3 test_restart.py` を局所実行して全件 PASS を確認し、その生出力をそのまま報告します。局所の PASS は、正式な validation の代わりにはなりません。
|
||||
- 決めきれない不足は、具体的な箇所を示した場合に限り blocked にします。
|
||||
|
||||
## 検証
|
||||
|
||||
Runner の validation `p28-01-restart-unit` が `test_restart.py` を実行します。そのあと、独立した Opus が全条件をレビューします。自己申告や終了コード0は、合格の証拠になりません。
|
||||
+735
@@ -0,0 +1,735 @@
|
||||
#!/usr/bin/env python3
|
||||
"""RESTART evidence validation; typed state comparisons execute in Godot."""
|
||||
import ast
|
||||
import copy
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
import re
|
||||
import sys
|
||||
import sysconfig
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
ROOT = HERE.parents[6]
|
||||
BASE = "docs/plans/original-completion-2026-10-02/execution/p28-01/"
|
||||
DEPENDENCIES = {
|
||||
"tools/run_all_tests.py": "320ca668732b4ff72fc4449998af2327d4e5c527b06896740806aaf0487282b4",
|
||||
"tools/regression/owned_outer.py": "c2e3483b76db1a3ff940f23f88ea0083fc78736e874ebcdb385d766dde8866e7",
|
||||
"tools/regression/owned_diagnostic_process.py": "e31e93c6e2c5ea46a7d954ef8bc0b4caec1c08b86b8aef19e93b52ef24993087",
|
||||
"tools/regression/p09_owned_engine_process.py": "c007e712c281573254faa1233c2ac92e100ab87393256a6dc096b35283e43037",
|
||||
BASE + "collector/collect_p28_01.py": "a43e1f963ef8505ddd5acf8bf747d9bfac22a028cbb805c0b964ee11b9b40a14",
|
||||
BASE + "collector/check_p28_01_report.py": "6c0851d498ca80bdc120fad047b072255f48713f63114521f3d4dfc902b2512b",
|
||||
}
|
||||
LOCAL = {BASE + "restart/probe/" + n for n in
|
||||
("restart_pair.py", "check_restart.py", "test_restart.py", "restart_probe.gd")}
|
||||
SIDECAR = "p28_restart/evidence/report_full.json"
|
||||
TIMINGS = ("duration_us", "runtime_duration_us")
|
||||
SAVE_EVENTS = {
|
||||
"SAVE_CHECKPOINT_SUCCEEDED", "SAVE_CHECKPOINT_FAILED", "SAVE_LOADED", "SAVE_LOAD_FAILED",
|
||||
"SAVE_MIGRATION_SUCCEEDED", "SAVE_MIGRATION_FAILED", "SAVE_RECOVERY_LOADED",
|
||||
"SAVE_RECOVERY_CANDIDATE_LOADED", "SAVE_RECOVERY_FAILED",
|
||||
}
|
||||
PHASES = ["import", "parse-base", "parse-wrapper", "R-writer", "R-reader", "O-writer", "O-reader"]
|
||||
CROSS = [
|
||||
{"condition": "原本条件とUI操作が一致", "applicable": False, "reference": "collector C1/C2",
|
||||
"reason": "既存collectorのUI経路受入を参照。このtaskは再開の比較を担当する。"},
|
||||
{"condition": "Runtimeの実利用・会計・行動へ接続", "applicable": False, "reference": "collector C2/C3",
|
||||
"reason": "既存collectorの実Runtime観測を参照。製品の接続は変更しない。"},
|
||||
{"condition": "途中保存・再起動で再現", "applicable": True, "reference": "RS1/RS2/RS3/RS4/RS5",
|
||||
"reason": "別PIDのR/O writerとreaderで適用する。"},
|
||||
{"condition": "異常/境界/中断を検証", "applicable": False, "reference": "collector C5/C6/C7",
|
||||
"reason": "横断受入は既存collectorを参照。再開固有の異常と境界はRS5で検証する。"},
|
||||
{"condition": "関連回帰PASS", "applicable": False, "reference": "collector C8",
|
||||
"reason": "既存collectorの正式回帰証拠を参照。workerは全回帰を実行しない。"},
|
||||
{"condition": "要件別ログとソースハッシュを保存", "applicable": False, "reference": "collector C6/C7/C9",
|
||||
"reason": "既存collectorの証拠契約を参照。再開のraw結合はRS7で保存する。"},
|
||||
]
|
||||
|
||||
|
||||
def require(condition, message):
|
||||
if not condition:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def sha(path):
|
||||
return hashlib.sha256(Path(path).read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def pin(path):
|
||||
path = Path(path)
|
||||
require(path.is_file() and not path.is_symlink(), "missing/symlink evidence")
|
||||
return {"sha256": sha(path), "bytes": path.stat().st_size}
|
||||
|
||||
|
||||
def check_dependencies(root=ROOT):
|
||||
for name, expected in DEPENDENCIES.items():
|
||||
require(sha(root / name) == expected, "dependency SHA: " + name)
|
||||
|
||||
|
||||
def check_imports(text, external=None):
|
||||
tree = ast.parse(text)
|
||||
if external is not None:
|
||||
require(external in DEPENDENCIES, "external AST closure")
|
||||
# The explicitly forbidden run_all_tests.main imports are not executed.
|
||||
tree.body = [node for node in tree.body if not (
|
||||
isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name == "main")]
|
||||
allowed = {Path(p).stem for p in set(DEPENDENCIES) | LOCAL}
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, (ast.Import, ast.ImportFrom)):
|
||||
names = [node.module] if isinstance(node, ast.ImportFrom) else [a.name for a in node.names]
|
||||
for name in names:
|
||||
require(name and (standard_library(name.split(".")[0]) or name in allowed),
|
||||
"import outside closure")
|
||||
if isinstance(node, ast.Call):
|
||||
name = node.func.id if isinstance(node.func, ast.Name) else getattr(node.func, "attr", "")
|
||||
require(name not in ("__import__", "eval", "import_module"), "dynamic code forbidden")
|
||||
require(name != "exec" or external == "tools/regression/owned_diagnostic_process.py",
|
||||
"unapproved exec")
|
||||
if name == "spec_from_file_location":
|
||||
for sub in ast.walk(node):
|
||||
if isinstance(sub, ast.Constant) and isinstance(sub.value, str):
|
||||
for path in re.findall(r"[\w./-]+\.py", sub.value):
|
||||
require(path in DEPENDENCIES or path in LOCAL or
|
||||
path in {Path(p).name for p in DEPENDENCIES}, "loader outside closure")
|
||||
if external and isinstance(node, ast.BinOp):
|
||||
for sub in ast.walk(node):
|
||||
if isinstance(sub, ast.Constant) and isinstance(sub.value, str) and sub.value.endswith(".py"):
|
||||
require(sub.value in DEPENDENCIES or sub.value in {Path(p).name for p in DEPENDENCIES},
|
||||
"external loader path outside closure")
|
||||
|
||||
|
||||
def standard_library(name):
|
||||
# Python 3.9 on the native host has no sys.stdlib_module_names.
|
||||
spec = importlib.util.find_spec(name)
|
||||
if spec is None:
|
||||
return False
|
||||
if spec.origin in ("built-in", "frozen"):
|
||||
return True
|
||||
if not spec.origin:
|
||||
return False
|
||||
path = Path(spec.origin).resolve()
|
||||
return path.is_relative_to(Path(sysconfig.get_path("stdlib")).resolve()) and not any(
|
||||
part in ("site-packages", "dist-packages") for part in path.parts)
|
||||
|
||||
|
||||
def check_modules(modules=None, root=ROOT):
|
||||
for module in (sys.modules if modules is None else modules).values():
|
||||
name = getattr(module, "__file__", None)
|
||||
if name:
|
||||
path = Path(name).resolve()
|
||||
if path.is_relative_to(root):
|
||||
require(path.relative_to(root).as_posix() in set(DEPENDENCIES) | LOCAL,
|
||||
"loaded project module outside closure")
|
||||
|
||||
|
||||
def check_probe(text):
|
||||
# Focused guard for direct Variant inference only, not GDScript compilation.
|
||||
# Root's actual Godot parse remains required.
|
||||
lines = [raw.split("#", 1)[0] for raw in text.splitlines()]
|
||||
variant_names = set()
|
||||
variant_functions = {"field"}
|
||||
for line in lines:
|
||||
declaration = re.match(r"^var\s+(\w+)\s*:\s*Variant\b", line)
|
||||
if declaration:
|
||||
variant_names.add(declaration[1])
|
||||
declaration = re.match(r"^func\s+(\w+)\s*\(.*\)\s*->\s*Variant\b", line)
|
||||
if declaration:
|
||||
variant_functions.add(declaration[1])
|
||||
function = None
|
||||
blocks = []
|
||||
pauses = 0
|
||||
watchdog_calls = 0
|
||||
for raw in text.splitlines():
|
||||
line = raw.split("#", 1)[0]
|
||||
inferred = re.match(r"^\s*var\s+\w+\s*:=\s*(\w+)\b(.*)", line)
|
||||
if inferred:
|
||||
require(not (inferred[1] in variant_names or
|
||||
(inferred[1] in variant_functions and inferred[2].lstrip().startswith("("))),
|
||||
"direct Variant inference requires an explicit type")
|
||||
match = re.match(r"^func (\w+)\(", line)
|
||||
if match:
|
||||
function = match[1]
|
||||
if function == "deterministic_step":
|
||||
blocks.append([])
|
||||
if function == "deterministic_step":
|
||||
blocks[-1].append(line)
|
||||
require(not re.search(r"set_paused\s*\(\s*false|\bclear\s*\(|spawn_guest|SimulationRuntime\.new|\binitialize\s*\(|enable_production_|_toggle_pause", line),
|
||||
"forbidden probe API")
|
||||
if re.search(r"\badvance_", line):
|
||||
require(function == "deterministic_step" and line.strip() == "GameClock.advance_minutes(1)",
|
||||
"advance outside deterministic_step")
|
||||
if "set_paused(" in line:
|
||||
require(function == "on_tick" and line.strip() == "GameClock.set_paused(true)", "pause outside tick")
|
||||
pauses += 1
|
||||
if "Time." in line:
|
||||
require(function == "deadline_timer_seconds" and
|
||||
line.strip() == "return Time.get_ticks_msec() / 1000.0", "timing API outside watchdog")
|
||||
watchdog_calls += 1
|
||||
# Literal names are used only by the finite evidence redaction routine.
|
||||
timing_line = line.strip() == 'for key: String in ["duration_us", "runtime_duration_us"]:'
|
||||
require(not re.search(r"\b\w+_(?:us|ms)\b|Performance\.|get_performance_report", line)
|
||||
or (function == "redact_payload" and timing_line),
|
||||
"performance instrumentation")
|
||||
require(len(blocks) == 1 and pauses == 1 and watchdog_calls == 1, "deterministic/tick/watchdog functions required")
|
||||
block = "\n".join(blocks[0])
|
||||
require(block.count("GameClock.advance_minutes(1)") == 1 and
|
||||
"io_ready" in block and "GameClock.paused" in block, "step admission")
|
||||
|
||||
|
||||
def event_id(counter):
|
||||
require(type(counter) is int and counter >= 0, "counter type")
|
||||
return "domain_event.%012d" % (counter + 1)
|
||||
|
||||
|
||||
def transform(witness, event):
|
||||
"""Consistency oracle for recorded IO paths, not a Godot state decoder."""
|
||||
result = copy.deepcopy(witness)
|
||||
eid = event_id(witness["counter"])
|
||||
ids, recent = witness["published_ids"], witness["recent_events"]
|
||||
require(event["event_id"] == eid, "IO event ID")
|
||||
require(ids == sorted(set(ids)) and eid not in ids and len(recent) <= 256, "event history")
|
||||
require(len({x["event_id"] for x in recent}) == len(recent) and
|
||||
all(x["event_id"] in ids for x in recent), "recent IDs")
|
||||
result["counter"] += 1
|
||||
result["published_ids"] = sorted(ids + [eid])
|
||||
result["recent_events"] = (recent[1:] if len(recent) == 256 else recent) + [event]
|
||||
return result
|
||||
|
||||
|
||||
def exact(row):
|
||||
require(row["method"] == "var_to_bytes" and row["equal"] is True and row["diff_paths"] == [],
|
||||
"typed bytes differ")
|
||||
require(row["excluded_paths"] == [], "state path exclusion")
|
||||
require(re.fullmatch("[0-9a-f]{64}", row["expected_sha256"]) and
|
||||
row["actual_sha256"] == row["expected_sha256"], "typed comparison contradiction")
|
||||
|
||||
|
||||
def check_io(row, kind):
|
||||
require(row["kind"] == kind and row["signal_count"] == 1, "one IO signal required")
|
||||
signal, latest, ev = row["signal_payload"], row["latest"], row["event"]
|
||||
require(signal == latest == ev["payload"], "signal/latest/event mismatch")
|
||||
require(row["signal_projection"] == "_save_event_payload" and row["payload_bytes_equal"] is True,
|
||||
"typed payload comparison")
|
||||
success = kind != "tamper"
|
||||
require(type(signal["ok"]) is bool and signal["ok"] is success and
|
||||
((signal["code"] == "OK") is success), "IO result")
|
||||
require(signal["operation"] == ("save" if kind == "save" else "load"), "operation")
|
||||
require(signal["save_path"].startswith("user://p28_restart/") and ".." not in signal["save_path"],
|
||||
"private IO path")
|
||||
require(row["return_kind"] == ("bool" if kind == "save" else "void"), "return kind")
|
||||
require(row["return_value"] is signal["ok"] if kind == "save" else row["return_value"] is None,
|
||||
"return/signal mismatch")
|
||||
expected_type = {"save": "SAVE_CHECKPOINT_SUCCEEDED", "load": "SAVE_LOADED",
|
||||
"tamper": "SAVE_LOAD_FAILED"}[kind]
|
||||
require(ev["event_type"] == expected_type and ev["source_service"] == "save_service" and
|
||||
ev["entity_ids"] == [] and ev["causation_command_id"] == 0, "event shape")
|
||||
require(ev["absolute_minute"] == row["minute_after"] == row["minute_before"], "IO minute")
|
||||
require(row["after"] == transform(row["before"], ev), "not X(P,event)")
|
||||
require(row["dropped"] == (row["before"]["recent_events"][0]
|
||||
if len(row["before"]["recent_events"]) == 256 else None), "wrong dropped head")
|
||||
exact(row["comparison"])
|
||||
if success:
|
||||
require(signal["saved_absolute_minute"] == row["minute_after"] and bool(signal["save_id"]),
|
||||
"save identity")
|
||||
require(row["paused_after"] is True, "IO clock not paused")
|
||||
else:
|
||||
failure = row["failure"]
|
||||
require(failure["preload_pause_restored"] is True and
|
||||
failure["loaded_clock_paused"] is row["paused_before"] and
|
||||
row["paused_after"] is row["paused_before"], "tamper pause")
|
||||
if failure["recovery_report"] is not None:
|
||||
require(failure["recovery_report"]["restore_committed"] is False, "tamper restore")
|
||||
require(type(failure["migration_attempted"]) is bool, "migration observation")
|
||||
|
||||
|
||||
def save_event(event):
|
||||
return isinstance(event, dict) and event.get("source_service") == "save_service" and \
|
||||
event.get("event_type") in SAVE_EVENTS
|
||||
|
||||
|
||||
def payload_paths(report):
|
||||
"""The complete W2 closure; no recursive payload-name exemption."""
|
||||
result = {}
|
||||
for i, row in enumerate(report["io"]):
|
||||
prefix = "/report/io/%d" % i
|
||||
for name in ("signal_payload", "latest"):
|
||||
result[prefix + "/" + name] = row[name]
|
||||
events = [(prefix + "/event", row["event"]), (prefix + "/dropped", row["dropped"])]
|
||||
for side in ("before", "after"):
|
||||
events.extend((prefix + "/" + side + "/recent_events/%d" % j, event)
|
||||
for j, event in enumerate(row[side]["recent_events"]))
|
||||
for path, event in events:
|
||||
if save_event(event):
|
||||
result[path + "/payload"] = event["payload"]
|
||||
for k, row in enumerate(report["minutes"]):
|
||||
if save_event(row["replacement"]):
|
||||
result["/report/minutes/%d/replacement/payload" % k] = row["replacement"]["payload"]
|
||||
return result
|
||||
|
||||
|
||||
def walk(value, path=""):
|
||||
if isinstance(value, dict):
|
||||
for key, child in value.items():
|
||||
pointer = path + "/" + key.replace("~", "~0").replace("/", "~1")
|
||||
yield pointer, key, child
|
||||
yield from walk(child, pointer)
|
||||
elif isinstance(value, list):
|
||||
for i, child in enumerate(value):
|
||||
yield from walk(child, path + "/" + str(i))
|
||||
|
||||
|
||||
def redact_report(verbatim):
|
||||
"""Also validates the full sidecar report before producing its unique public form."""
|
||||
full = json.loads(json.dumps(verbatim))
|
||||
allowed = payload_paths(full)
|
||||
for path, key, _ in walk(full, "/report"):
|
||||
require(key != "timing_redactions", "reserved marker in verbatim evidence")
|
||||
require(not key.endswith(("_us", "_ms")) or
|
||||
(path.rsplit("/", 1)[0] in allowed and key in TIMINGS), "timing outside W2 closure")
|
||||
require(key not in ("performance", "statistics", "fps", "budget_verdict"),
|
||||
"sidecar performance use")
|
||||
for path, payload in allowed.items():
|
||||
require(isinstance(payload, dict) and all(k in payload for k in TIMINGS), "both timing keys required")
|
||||
payload["timing_redactions"] = [
|
||||
{"original_key": key, "sidecar_ref": path + "/" + key} for key in TIMINGS]
|
||||
for key in TIMINGS:
|
||||
del payload[key]
|
||||
check_no_metrics(full)
|
||||
return full
|
||||
|
||||
|
||||
def resolve_pointer(sidecar, pointer):
|
||||
require(pointer.startswith("/report/"), "pointer outside report")
|
||||
value = sidecar
|
||||
for part in pointer[1:].split("/"):
|
||||
part = part.replace("~1", "/").replace("~0", "~")
|
||||
value = value[int(part)] if isinstance(value, list) else value[part]
|
||||
return value
|
||||
|
||||
|
||||
def json_sha(value):
|
||||
# JSON numbers are represented uniformly across Godot's JSON float parser
|
||||
# and Python. Fixed decimal form is valid JSON; keys and strings remain verbatim.
|
||||
def encode(v):
|
||||
if isinstance(v, dict):
|
||||
require(len({str(k) for k in v}) == len(v), "canonical_key_collision")
|
||||
require(all(type(k) is str for k in v), "canonical JSON requires text keys")
|
||||
return "{" + ",".join(json.dumps(k, ensure_ascii=False) + ":" + encode(v[k])
|
||||
for k in sorted(v)) + "}"
|
||||
if isinstance(v, list):
|
||||
return "[" + ",".join(encode(x) for x in v) + "]"
|
||||
if type(v) in (int, float):
|
||||
require(math.isfinite(v), "nonfinite JSON")
|
||||
return format(v, ".17f")
|
||||
return json.dumps(v, ensure_ascii=False, separators=(",", ":"), allow_nan=False)
|
||||
return hashlib.sha256(encode(value).encode()).hexdigest()
|
||||
|
||||
|
||||
def canonical_event(event, eid):
|
||||
return {"event_id": eid, "replaced_for_cross_pid_compare": True} \
|
||||
if event["event_id"] == eid else event
|
||||
|
||||
|
||||
def check_sidecar(public, sidecar):
|
||||
require(isinstance(sidecar, dict) and set(sidecar) == {"report", "at_390_event_verbatim"},
|
||||
"sidecar root/missing")
|
||||
check_no_metrics(public)
|
||||
full = sidecar["report"]
|
||||
require(full.get("stop_reason") != "canonical_key_collision", "canonical_key_collision")
|
||||
require(public == redact_report(full), "public redaction differs from W2")
|
||||
restored = copy.deepcopy(public)
|
||||
for path, payload in payload_paths(restored).items():
|
||||
expected = [{"original_key": k, "sidecar_ref": path + "/" + k} for k in TIMINGS]
|
||||
require(payload.pop("timing_redactions") == expected, "redaction marker/pointer mismatch")
|
||||
for marker in expected:
|
||||
payload[marker["original_key"]] = resolve_pointer(sidecar, marker["sidecar_ref"])
|
||||
require(restored == full, "entire restored report differs")
|
||||
events = sidecar["at_390_event_verbatim"]
|
||||
require(isinstance(events, list), "at390 verbatim array")
|
||||
observation = full.get("at_390")
|
||||
summaries = observation["events"] if observation is not None else []
|
||||
require(len(events) == len(summaries), "at390 verbatim count")
|
||||
for event, summary in zip(events, summaries):
|
||||
for path, key, _ in walk(event):
|
||||
require(key != "timing_redactions" and key not in
|
||||
("performance", "statistics", "fps", "budget_verdict"), "at390 forbidden field")
|
||||
require(not key.endswith(("_us", "_ms")) or
|
||||
(save_event(event) and path == "/payload/" + key and key in TIMINGS),
|
||||
"at390 timing outside SAVE payload")
|
||||
if save_event(event):
|
||||
require(all(k in event["payload"] for k in TIMINGS), "at390 both timing keys")
|
||||
require(set(summary) == {"event_id", "event_type", "absolute_minute",
|
||||
"raw_event_sha256", "canonical_sha256"}, "at390 summary shape")
|
||||
require(all(event[k] == summary[k] for k in ("event_id", "event_type", "absolute_minute")),
|
||||
"at390 verbatim order/metadata")
|
||||
require(json_sha(event) == summary["raw_event_sha256"] and
|
||||
json_sha(canonical_event(event, full["event_id"])) == summary["canonical_sha256"],
|
||||
"at390 raw/canonical SHA")
|
||||
return full
|
||||
|
||||
|
||||
def check_deadline(value, complete=True):
|
||||
require(set(value) == {"limit_seconds_from_engine_start", "engine_elapsed_at_begin_seconds",
|
||||
"timer_seconds"}, "deadline shape")
|
||||
elapsed, timer = value["engine_elapsed_at_begin_seconds"], value["timer_seconds"]
|
||||
require(type(elapsed) in (int, float) and type(timer) in (int, float) and
|
||||
math.isfinite(elapsed) and math.isfinite(timer) and elapsed >= 0 and
|
||||
value["limit_seconds_from_engine_start"] == 1740 and timer == 1740 - elapsed,
|
||||
"engine elapsed deadline formula")
|
||||
require(not complete or timer > 0, "expired deadline")
|
||||
|
||||
|
||||
def overload_case(report):
|
||||
backlog = report["paused_idle"]["before"]["backlog"]
|
||||
overloads = report["overloads"]
|
||||
require(isinstance(overloads, list) and type(backlog) is int and backlog >= 0, "overload facts")
|
||||
expected = {"exercised": bool(overloads and backlog > 0),
|
||||
"overloads": len(overloads), "backlog_at_save": backlog}
|
||||
require(report["overload_case"] == expected and
|
||||
type(report["overload_case"]["exercised"]) is bool and
|
||||
set(report["overload_case"]) == set(expected), "overload disclosure")
|
||||
for _, key, value in walk(report):
|
||||
if key in ("nonzero_backlog_preserved", "overload_persistence",
|
||||
"across_restart_backlog_persistence_claimed"):
|
||||
require(value is False, "unsupported overload/persistence claim")
|
||||
return expected
|
||||
|
||||
|
||||
def coverage(reports):
|
||||
detail = {r["role"]: overload_case(r) for r in reports if r["role"].endswith("writer")}
|
||||
return {"overload_nonzero_backlog":
|
||||
"exercised_recorded" if detail and all(x["exercised"] for x in detail.values()) else "not_exercised",
|
||||
"per_role": detail, "across_restart_backlog_persistence_claimed": False}
|
||||
|
||||
|
||||
def check_no_arrival_diagnostic(report):
|
||||
require(report["role"] == "O-writer" and report["boundary"] is None and
|
||||
report["boundary_reason"] == report["stop_reason"] == "no_arrival_within_cap",
|
||||
"no-arrival reason/null")
|
||||
require(report["ui_ticks"] and
|
||||
report["ui_ticks"][-1]["clock"] - report["start_minute"] == 120 and
|
||||
all(t["guest_auto_count"] == 0 and t["guest_auto_ids"] == [] for t in report["ui_ticks"]),
|
||||
"no-arrival cap/facts")
|
||||
return True
|
||||
|
||||
|
||||
def check_report(report, sidecar=None):
|
||||
check_no_metrics(report)
|
||||
require(sidecar is not None, "sidecar required")
|
||||
report = check_sidecar(report, sidecar)
|
||||
if report["stop_reason"] == "no_arrival_within_cap":
|
||||
check_no_arrival_diagnostic(report)
|
||||
require(report["schema"] == "p28-restart-v1" and report["stop_reason"] == "complete", "incomplete probe")
|
||||
require(report["role"] in PHASES[3:] and type(report["pid"]) is int and report["pid"] > 0, "role/PID")
|
||||
require(report["main_added"] is True and report["k_target"] == 30 and report["actual_k"] == 30, "main/K")
|
||||
require(report["comparison_scope"] == "complete_typed_var_to_bytes" and report["excluded_paths"] == [],
|
||||
"partial digest/path exclusion")
|
||||
require(report["transfer_before_guard_writes"] == 0 and report["probe_deadline_seconds"] == 1740,
|
||||
"profile/deadline")
|
||||
check_deadline(report["probe_deadline"])
|
||||
writer = report["role"].endswith("writer")
|
||||
kinds = ["save"] if writer else ["tamper", "load"]
|
||||
require(len(report["io"]) == len(kinds), "IO sequence")
|
||||
for row, kind in zip(report["io"], kinds):
|
||||
check_io(row, kind)
|
||||
if writer:
|
||||
overload_case(report)
|
||||
exact(report["package_matches_before"])
|
||||
require(report["package_matches_before"]["actual_sha256"] == report["states"]["P"]["sha256"],
|
||||
"P not package snapshot")
|
||||
else:
|
||||
require(report["failure_event_absent"] is True and report["transfer_verified"] is True,
|
||||
"failed event survived/transfer not verified")
|
||||
io = report["io"][-1]
|
||||
require(report["event_id"] == io["event"]["event_id"] and report["saved_minute"] == io["minute_after"],
|
||||
"continuation IO identity")
|
||||
require(report["paused_idle"]["before"] == report["paused_idle"]["after"], "paused clock consumed backlog")
|
||||
require(report["paused_idle"]["before"]["paused"] is True, "paused observation")
|
||||
exact(report["paused_idle"]["comparison"])
|
||||
rows = report["minutes"]
|
||||
require(len(rows) == 30, "actual K not 30")
|
||||
previous, lost = report["saved_minute"], False
|
||||
for row in rows:
|
||||
require(row["before"] == previous and row["after"] == previous + 1, "K counts actual minute advances")
|
||||
require(row["paused"] is True and row["backlog_before"] == row["backlog_after"], "step consumed backlog")
|
||||
require(row["scope"] == "complete_typed_var_to_bytes" and row["excluded_paths"] == [], "partial state")
|
||||
require(re.fullmatch("[0-9a-f]{64}", row["sha256"]), "minute SHA")
|
||||
if row["replacement"] is not None:
|
||||
require(not lost and row["replacement"] == io["event"] and row["replacement_count"] == 1,
|
||||
"replacement must be unchanged in-window E")
|
||||
else:
|
||||
lost = True
|
||||
require(row["replacement_count"] == 0, "replacement after E left window")
|
||||
require(0 <= row["recent_count"] <= 256, "recent limit")
|
||||
require(len(row["recent_ids"]) == len(set(row["recent_ids"])) == row["recent_count"] and
|
||||
row["recent_ids"].count(report["event_id"]) == row["replacement_count"],
|
||||
"replacement does not match actual window")
|
||||
previous = row["after"]
|
||||
require(report["final_minute"] == previous, "final minute")
|
||||
for name, entry in report["states"].items():
|
||||
require(re.fullmatch("[0-9a-f]{64}", entry["sha256"]) and entry["bytes"] > 0 and
|
||||
entry["path"] == "p28_restart/evidence/" + name + ".bin", "state binding")
|
||||
required = {"P", "after_save", "final"} if writer else {"P", "B", "after_tamper", "after_load", "final"}
|
||||
require(set(report["states"]) == required, "missing complete state files")
|
||||
require(report["states"]["after_save" if writer else "after_load"]["sha256"] ==
|
||||
io["comparison"]["actual_sha256"], "IO state file differs")
|
||||
if not writer:
|
||||
require(report["states"]["after_tamper"]["sha256"] == report["io"][0]["comparison"]["actual_sha256"],
|
||||
"tamper state file differs")
|
||||
if report["role"].startswith("O-"):
|
||||
observation = report["at_390"]
|
||||
require(isinstance(observation, dict) and observation["clock"] == {"absolute_minute": 390},
|
||||
"at390 missing/clock")
|
||||
require(isinstance(observation["operations"], dict) and
|
||||
type(observation["domain_event_counter"]) is int and
|
||||
type(observation["published_ids_count"]) is int and
|
||||
observation["domain_event_counter"] >= observation["published_ids_count"] >= 0,
|
||||
"at390 report/counter")
|
||||
require(isinstance(observation["processed_arrival_minutes"], list) and
|
||||
observation["processed_arrival_minutes"] == sorted(set(observation["processed_arrival_minutes"])) and
|
||||
all(type(m) is int and m <= 390 for m in observation["processed_arrival_minutes"]),
|
||||
"at390 processed minutes")
|
||||
ids = observation["guest_auto_ids"]
|
||||
require(isinstance(ids, list) and ids == sorted(set(ids)) and
|
||||
type(observation["guest_auto_count"]) is int and
|
||||
len(ids) == observation["guest_auto_count"] and
|
||||
all(x.startswith("guest-auto-") for x in ids), "at390 guests")
|
||||
require(all(type(e["absolute_minute"]) is int and
|
||||
report["saved_minute"] < e["absolute_minute"] <= 390
|
||||
for e in observation["events"]), "at390 event window")
|
||||
event_ids = [e["event_id"] for e in observation["events"]]
|
||||
require(len(event_ids) == len(set(event_ids)), "at390 duplicate event")
|
||||
at_rows = [r for r in rows if r["after"] == 390]
|
||||
require(len(at_rows) == 1 and at_rows[0]["operations_observation"] ==
|
||||
{k: v for k, v in observation.items() if k != "events"}, "at390 minute consistency")
|
||||
require(all(e in at_rows[0]["recent_ids"] for e in event_ids), "at390 event window IDs")
|
||||
boundary = report["boundary"]
|
||||
require(boundary is not None and boundary["guest_auto_count"] > 0, "arrival not processed")
|
||||
require(len(boundary["guest_auto_ids"]) == len(set(boundary["guest_auto_ids"])) ==
|
||||
boundary["guest_auto_count"], "arrival IDs")
|
||||
require(all(x.startswith("guest-auto-") for x in boundary["guest_auto_ids"]), "arrival identity")
|
||||
require(boundary["clock"] == report["saved_minute"] and boundary["processed_arrival_minutes"],
|
||||
"arrival boundary not processed")
|
||||
if writer:
|
||||
require(0 < report["saved_minute"] - report["start_minute"] <= 120, "arrival cap")
|
||||
ticks = report["ui_ticks"]
|
||||
require(ticks and ticks[-1] == boundary and all(t["guest_auto_count"] == 0 for t in ticks[:-1]),
|
||||
"not first arrival tick")
|
||||
next_boundary = report["next_boundary"]
|
||||
added = [m for m in next_boundary["after"] if m not in next_boundary["before"]]
|
||||
require(next_boundary["before"] == boundary["processed_arrival_minutes"] and
|
||||
added == [next_boundary["minute"]] and next_boundary["processed_once"] is True and
|
||||
next_boundary["minute"] > boundary["clock"] and
|
||||
next_boundary["minute"] <= report["final_minute"], "next arrival missing/duplicate")
|
||||
minutes = next_boundary["after"]
|
||||
require(minutes == sorted(set(minutes)), "duplicate processed arrivals")
|
||||
require([r["minute"] for r in next_boundary["arrival_reports"]] == minutes,
|
||||
"arrival report duplicates/omissions")
|
||||
else:
|
||||
require(report["boundary"] is None and report["next_boundary"] is None and
|
||||
report["at_390"] is None, "R Operations boundary")
|
||||
return True
|
||||
|
||||
|
||||
def check_no_metrics(value):
|
||||
if isinstance(value, dict):
|
||||
for key, child in value.items():
|
||||
require(not key.endswith(("_us", "_ms")) and key not in
|
||||
("performance", "statistics", "fps", "budget_verdict"), "performance field forbidden")
|
||||
check_no_metrics(child)
|
||||
elif isinstance(value, list):
|
||||
for child in value:
|
||||
check_no_metrics(child)
|
||||
|
||||
|
||||
def canonical_observation(observation, eid):
|
||||
result = copy.deepcopy(observation)
|
||||
if result is not None:
|
||||
result["events"] = [
|
||||
{"event_id": eid, "replaced_for_cross_pid_compare": True,
|
||||
"canonical_sha256": e["canonical_sha256"]} if e["event_id"] == eid else e
|
||||
for e in result["events"]]
|
||||
return result
|
||||
|
||||
|
||||
def check_pair(writer, reader, writer_sidecar=None, reader_sidecar=None):
|
||||
check_report(writer, writer_sidecar)
|
||||
check_report(reader, reader_sidecar)
|
||||
writer, reader = writer_sidecar["report"], reader_sidecar["report"]
|
||||
require(writer["role"].replace("writer", "reader") == reader["role"] and writer["pid"] != reader["pid"],
|
||||
"pair roles/PID")
|
||||
require(writer["event_id"] == reader["event_id"] and writer["saved_minute"] == reader["saved_minute"],
|
||||
"pair IO identity")
|
||||
require(writer["states"]["P"] == reader["states"]["P"], "package P differs")
|
||||
require(writer["io"][0]["before"] == reader["io"][1]["before"], "package history differs")
|
||||
for left, right in zip(writer["minutes"], reader["minutes"]):
|
||||
require(all(left[k] == right[k] for k in
|
||||
("before", "after", "sha256", "scope", "replacement_count", "recent_count")),
|
||||
"cross-PID complete typed state mismatch")
|
||||
require(writer["boundary"] == reader["boundary"] and writer["next_boundary"] == reader["next_boundary"],
|
||||
"Operations boundary differs")
|
||||
require(canonical_observation(writer["at_390"], writer["event_id"]) ==
|
||||
canonical_observation(reader["at_390"], reader["event_id"]), "at390 pair differs")
|
||||
|
||||
|
||||
def bound_raw(folder, row):
|
||||
folder = Path(folder)
|
||||
require(set(row["raw"]) == {"stdout.log", "stderr.log", "engine-native.log"}, "all raw required")
|
||||
for name, expected in {**row["raw"], **row["owned_files"]}.items():
|
||||
require(Path(name).name == name and pin(folder / name) == expected, "raw/owned SHA mismatch")
|
||||
require(set(row["owned_files"]) == {"process.json", "started.json", "ended.json",
|
||||
"adapter-owned-cleanup.json", "outer-owned-finally.json"}, "owned receipts missing")
|
||||
require(json.loads((folder / "process.json").read_text()) == row["process"], "process receipt mismatch")
|
||||
process, capture = row["process"], row["capture"]
|
||||
require(row["passed"] is True and process["exit_code"] == 0 and process["helper_exit_code"] == 0 and
|
||||
process["timed_out"] is False and process["error_lines"] == [] and process["lifecycle_error"] is None and
|
||||
process["external_process_or_group_signals"] == 0 and process["owned_popen_ended"] is True and
|
||||
process["old_helper_engine_ended"] is True and process["start_receipt_written"] is True and
|
||||
process["end_receipt_written"] is True, "owned phase failed")
|
||||
require(capture["passed"] is True and capture["all_captured_ended"] is True and
|
||||
capture["outer_cleanup_errors"] == [] and capture["adapter_exception"] is None and
|
||||
capture["foreign_process_or_group_signals"] == 0 and
|
||||
capture["all_cleanup_before_outer_receipt"] is True and capture["outer_receipt_written"] is True and
|
||||
capture["outer_receipt_error"] is None and len(capture["actual_captured_pids"]) == 1, "outer capture")
|
||||
actual = json.loads((folder / "outer-owned-finally.json").read_text())
|
||||
require(actual == {k: v for k, v in capture.items() if k not in
|
||||
("passed", "outer_receipt_written", "outer_receipt_error")}, "outer raw differs")
|
||||
pid = capture["actual_captured_pids"][0]
|
||||
require(process["pid"] == pid and process["argv"] == row["argv"], "process PID/argv")
|
||||
for filename, key in (("started.json", "actual_start_binding"), ("ended.json", "actual_end_binding")):
|
||||
binding = json.loads((folder / filename).read_text())
|
||||
require(binding == process[key] and binding["pid"] == binding["actual_pid"] == pid and
|
||||
binding["argv"] == binding["command"] == row["argv"] and binding["owner_pid"] > 0,
|
||||
"START/END binding")
|
||||
require(process["actual_start_binding"]["owner_pid"] == process["actual_end_binding"]["owner_pid"],
|
||||
"START/END owners differ")
|
||||
require(json.loads((folder / "adapter-owned-cleanup.json").read_text()) == process["adapter_owned_cleanup"]
|
||||
and process["adapter_owned_cleanup"]["engine_ended"] is True and
|
||||
process["adapter_owned_cleanup"]["errors"] == [], "adapter cleanup binding")
|
||||
require(process["stdout_sha256"] == row["raw"]["stdout.log"]["sha256"] and
|
||||
process["stderr_sha256"] == row["raw"]["stderr.log"]["sha256"], "process raw binding")
|
||||
require(row["timeout_seconds"] == 1800 and row["diagnostics"] == [], "phase timeout/diagnostics")
|
||||
raw = (folder / "stdout.log").read_text()
|
||||
for name in ("stdout.log", "stderr.log", "engine-native.log"):
|
||||
require(not re.search(r"SCRIPT ERROR|Parse Error|ERROR:", (folder / name).read_text()),
|
||||
"raw engine error")
|
||||
if row["phase"] not in PHASES[3:]:
|
||||
require("P28_RESTART_REPORT=" not in raw, "parse/import report")
|
||||
if row["phase"].startswith("parse"):
|
||||
require("--check-only" in row["argv"], "parse not check-only")
|
||||
return None
|
||||
reports = [s.partition("=")[2] for s in raw.splitlines() if s.startswith("P28_RESTART_REPORT=")]
|
||||
closes = [s.partition("=")[2] for s in raw.splitlines() if s.startswith("P28_RESTART_AUDIO_CLOSE=")]
|
||||
require(len(reports) == len(closes) == 1, "report/audio close count")
|
||||
report, close = json.loads(reports[0]), json.loads(closes[0])
|
||||
require(report == row["report"] and report["role"] == row["phase"] and
|
||||
capture["actual_captured_pids"] == [report["pid"]], "raw report/PID binding")
|
||||
require(close.get("coordinator") is True and close.get("passed") is True and
|
||||
close.get("owned_engine_pid") == report["pid"] and close.get("all_owned_targets_released") is True and
|
||||
close.get("deadline_reached") is False and close.get("clock_process_held_stopped") is True and
|
||||
close.get("window_input_held_disabled") is True and close.get("restored_before_quit") is False and
|
||||
close.get("shared_bus_deleted") is False and close.get("settings_mutated") is False, "audio close")
|
||||
check_report(report, bound_sidecar(folder.parent, row))
|
||||
return report
|
||||
|
||||
|
||||
def bound_sidecar(out, row):
|
||||
binding = row["sidecar"]
|
||||
require(set(binding) == {"path", "sha256", "bytes"} and binding["path"] == SIDECAR,
|
||||
"sidecar binding/path")
|
||||
expected = {k: binding[k] for k in ("sha256", "bytes")}
|
||||
path = Path(out) / (row["phase"] + "-states") / "report_full.json"
|
||||
require(pin(path) == expected and pin(Path(row["runtime_profile"]) / SIDECAR) == expected,
|
||||
"sidecar SHA/private profile binding")
|
||||
return json.loads(path.read_text())
|
||||
|
||||
|
||||
def check_receipt(receipt, out):
|
||||
from restart_pair import COLLECTOR_PINS, check_transfer
|
||||
out = Path(out)
|
||||
require(receipt["passed"] is True and receipt["error"] is None and receipt["cleanup_errors"] == [],
|
||||
"execution failed")
|
||||
require(receipt["source_before"] == receipt["source_after"] and len(receipt["source_before"]) == 1407,
|
||||
"source map mismatch")
|
||||
require(receipt["default_player_before"] == receipt["default_player_after"], "ordinary player changed")
|
||||
require(receipt["dependencies_before"] == receipt["dependencies_after"] == DEPENDENCIES, "dependency pins")
|
||||
require(receipt["collector_before"] == receipt["collector_after"] == COLLECTOR_PINS,
|
||||
"collector changed")
|
||||
require(receipt["worker_before"] == receipt["worker_after"] and set(receipt["worker_before"]) ==
|
||||
{"restart_pair.py", "restart_probe.gd", "check_restart.py", "test_restart.py"}, "worker changed")
|
||||
require(receipt["cross_cutting"] == CROSS, "cross-cutting record")
|
||||
require(receipt["original_credit"] == 0 and receipt["windows_verified"] is False and
|
||||
receipt["operations_200_guests_verified"] is False, "scope expanded")
|
||||
rows = receipt["phases"]
|
||||
require([r["phase"] for r in rows] == PHASES, "phase order/incomplete execution")
|
||||
pids = []
|
||||
for row in rows:
|
||||
report = bound_raw(out / row["phase"], row)
|
||||
pids.extend(row["capture"]["actual_captured_pids"])
|
||||
require(row["stage_before"] == row["stage_after"] if row["phase"] != "import"
|
||||
else row["stage_preserved"] is True, "stage changed")
|
||||
require(row["source_preserved"] is True and row["player_preserved"] is True, "phase preservation")
|
||||
startup = row["startup_project"]
|
||||
require(startup["fresh_before"] is True and sha(startup["path"]) == startup["sha256"], "startup profile")
|
||||
if report:
|
||||
guard = row["guard"]
|
||||
expected = {"verified": True, "conditions": [True] * 5, "conditions_checked": 5,
|
||||
"actual_user_data_dir": row["runtime_profile"],
|
||||
"expected_user_data_dir": row["runtime_profile"],
|
||||
"globalized_probe": str(Path(row["runtime_profile"]) / "guard-probe"),
|
||||
"custom_user_dir_name": row["runtime_name"], "fresh_before_parent_initialize": True,
|
||||
"profile_writes_before_guard": 0, "project_settings_persisted": False}
|
||||
require(all(type(guard.get(k)) is type(v) and guard[k] == v for k, v in expected.items()),
|
||||
"guard mismatch")
|
||||
guard_path = out / (row["phase"] + "-guard.json")
|
||||
require(sha(guard_path) == row["guard_sha256"] and json.loads(guard_path.read_text()) == guard,
|
||||
"guard raw binding")
|
||||
for name, evidence in report["states"].items():
|
||||
require(pin(out / (row["phase"] + "-states") / (name + ".bin")) ==
|
||||
{k: evidence[k] for k in ("sha256", "bytes")}, "state bytes SHA")
|
||||
require(receipt["profiles"][row["runtime_profile"]].get(SIDECAR) ==
|
||||
{k: row["sidecar"][k] for k in ("sha256", "bytes")}, "sidecar absent from file map")
|
||||
require(len(set(pids)) == len(pids), "reused PID")
|
||||
require({r["pid"] for r in receipt["cleanup"]} == set(pids) and all(
|
||||
r["engine_ended"] is True and r["outer_cleanup_errors"] == [] and
|
||||
r["foreign_process_or_group_signals"] == 0 for r in receipt["cleanup"]), "cleanup mismatch")
|
||||
for start in (3, 5):
|
||||
check_pair(rows[start]["report"], rows[start + 1]["report"],
|
||||
bound_sidecar(out, rows[start]), bound_sidecar(out, rows[start + 1]))
|
||||
transfer = out / (rows[start]["phase"][0] + "-transfer")
|
||||
manifest_path = transfer / "manifest.json"
|
||||
manifest = json.loads(manifest_path.read_text())
|
||||
check_transfer(transfer, manifest)
|
||||
require(rows[start + 1]["report"]["transfer_manifest_sha256"] == sha(manifest_path),
|
||||
"reader manifest SHA")
|
||||
for phase_index, kinds in ((start, ("good",)), (start + 1, ("good", "tampered"))):
|
||||
profile = rows[phase_index]["runtime_profile"]
|
||||
for kind in kinds:
|
||||
for name, expected in manifest["files"][kind].items():
|
||||
relative = "p28_restart/" + kind + "/" + name
|
||||
require(receipt["profiles"][profile].get(relative) == expected,
|
||||
"private package differs from transfer")
|
||||
for path, expected in receipt["script_pins"].items():
|
||||
require(sha(path) == expected, "executed script changed")
|
||||
check_probe((out / "restart_probe.gd").read_text())
|
||||
require(sha(out / "restart_probe.gd") == receipt["worker_before"]["restart_probe.gd"],
|
||||
"executed probe differs from submitted source")
|
||||
require(receipt["owned_writes"] == [], "probe/driver wrote owned folder")
|
||||
require(receipt["coverage"] == coverage([bound_sidecar(out, r)["report"] for r in rows[3:]]),
|
||||
"overload coverage/persistence claim")
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("execution", type=Path)
|
||||
args = parser.parse_args()
|
||||
check_receipt(json.loads(args.execution.read_text()), args.execution.parent)
|
||||
print("P28_RESTART_CHECK=PASS")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+322
@@ -0,0 +1,322 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Root-only four-PID RESTART driver. --check performs no writes or launches."""
|
||||
import argparse
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
import check_restart as check
|
||||
|
||||
HERE, ROOT = check.HERE, check.ROOT
|
||||
COLLECTOR = check.BASE + "collector/collect_p28_01.py"
|
||||
COLLECTOR_PINS = {
|
||||
"collect_p28_01.py": check.DEPENDENCIES[COLLECTOR],
|
||||
"check_p28_01_report.py": check.DEPENDENCIES[check.BASE + "collector/check_p28_01_report.py"],
|
||||
"measure_p28_01.gd": "4acef88bce4ac77f3f5d2659d766afcc5fd139cee381118839012022772946bb",
|
||||
"test_collect_p28_01.py": "99d86ea723c976c95dbd283acbb85981eabf427b30994e652002ec0e76dc4ab0",
|
||||
}
|
||||
|
||||
|
||||
def load_dependencies():
|
||||
check.check_dependencies()
|
||||
for name in check.DEPENDENCIES:
|
||||
check.check_imports((ROOT / name).read_text(), external=name)
|
||||
for name in ("restart_pair.py", "check_restart.py", "test_restart.py"):
|
||||
check.check_imports((HERE / name).read_text())
|
||||
check.check_modules()
|
||||
sys.path.insert(0, str((ROOT / COLLECTOR).parent))
|
||||
spec = importlib.util.spec_from_file_location("restart_collector", ROOT / COLLECTOR)
|
||||
collector = importlib.util.module_from_spec(spec)
|
||||
sys.modules[spec.name] = collector
|
||||
spec.loader.exec_module(collector)
|
||||
standard = collector.load_tool(collector.EXTERNAL_IMPORTS[0])
|
||||
outer = collector.load_tool(collector.EXTERNAL_IMPORTS[1])
|
||||
check.check_modules()
|
||||
check.check_modules({"collector": collector, "standard": standard, "outer": outer, "native": outer.native})
|
||||
check.require(Path(outer.native.helper["__file__"]).resolve() ==
|
||||
ROOT / "tools/regression/p09_owned_engine_process.py", "exec helper closure")
|
||||
return collector, standard, outer
|
||||
|
||||
|
||||
def collector_pins():
|
||||
actual = {name: check.sha((ROOT / COLLECTOR).parent / name) for name in COLLECTOR_PINS}
|
||||
check.require(actual == COLLECTOR_PINS, "collector differs from Q")
|
||||
return actual
|
||||
|
||||
|
||||
def plan(collector, engine, out):
|
||||
stage, wrapper = out / "stage", out / "guarded.gd"
|
||||
return [collector.argv_for(engine, stage, wrapper, True),
|
||||
collector.parse_argv(engine, stage, out / "restart_probe.gd"),
|
||||
collector.parse_argv(engine, stage, wrapper)] + [
|
||||
collector.argv_for(engine, stage, wrapper) for _ in range(4)]
|
||||
|
||||
|
||||
def schedule(rows):
|
||||
for index, label in enumerate(check.PHASES):
|
||||
check.require(all(row["passed"] is True for row in rows), "previous phase failed")
|
||||
if label.endswith("reader"):
|
||||
check.require(rows and rows[-1]["phase"] == label.replace("reader", "writer") and
|
||||
rows[-1].get("report", {}).get("stop_reason") == "complete",
|
||||
"reader requires successful writer")
|
||||
yield index, label
|
||||
check.require(len(rows) == index + 1 and rows[-1]["phase"] == label and rows[-1]["passed"] is True,
|
||||
"phase did not complete")
|
||||
|
||||
|
||||
def safe_inventory(directory):
|
||||
directory = Path(directory)
|
||||
check.require(directory.is_dir() and not directory.is_symlink(), "package missing/symlink")
|
||||
result = {}
|
||||
for path in sorted(directory.rglob("*")):
|
||||
check.require(not path.is_symlink(), "package symlink")
|
||||
if path.is_file():
|
||||
result[path.relative_to(directory).as_posix()] = check.pin(path)
|
||||
check.require(result, "empty package")
|
||||
return result
|
||||
|
||||
|
||||
def create_transfer(source, destination, write):
|
||||
"""Copy only after writer exit; never pre-populate the reader profile."""
|
||||
before = safe_inventory(source)
|
||||
check.require("world.bin" in before and not destination.exists(), "transfer admission")
|
||||
destination.mkdir()
|
||||
shutil.copytree(source, destination / "good")
|
||||
check.require(safe_inventory(destination / "good") == before, "good copy differs")
|
||||
shutil.copytree(destination / "good", destination / "tampered")
|
||||
world = destination / "tampered/world.bin"
|
||||
data = bytearray(world.read_bytes())
|
||||
check.require(data, "empty world")
|
||||
offset = len(data) // 2
|
||||
data[offset] ^= 1
|
||||
world.write_bytes(data)
|
||||
manifest = {"files": {"good": before, "tampered": safe_inventory(destination / "tampered")},
|
||||
"tamper": {"file": "world.bin", "offset": offset, "xor": 1,
|
||||
"before_sha256": before["world.bin"]["sha256"], "after_sha256": check.sha(world)}}
|
||||
write(destination / "manifest.json", manifest)
|
||||
check_transfer(destination, manifest)
|
||||
check.require(safe_inventory(source) == before, "writer package changed")
|
||||
return manifest
|
||||
|
||||
|
||||
def check_transfer(directory, manifest):
|
||||
check.require(set(manifest["files"]) == {"good", "tampered"}, "transfer kinds")
|
||||
for kind in ("good", "tampered"):
|
||||
check.require(safe_inventory(directory / kind) == manifest["files"][kind], "transfer SHA mismatch")
|
||||
good = (directory / "good/world.bin").read_bytes()
|
||||
bad = (directory / "tampered/world.bin").read_bytes()
|
||||
tamper = manifest["tamper"]
|
||||
check.require(len(good) == len(bad) > 0 and tamper["offset"] == len(good) // 2 and tamper["xor"] == 1,
|
||||
"tamper location")
|
||||
expected = bytearray(good)
|
||||
expected[len(good) // 2] ^= 1
|
||||
check.require(bytes(expected) == bad and tamper["before_sha256"] == check.sha(directory / "good/world.bin")
|
||||
and tamper["after_sha256"] == check.sha(directory / "tampered/world.bin"), "tamper bytes")
|
||||
for name, value in manifest["files"]["good"].items():
|
||||
if name != "world.bin":
|
||||
check.require(manifest["files"]["tampered"].get(name) == value, "extra tamper difference")
|
||||
check.require(set(manifest["files"]["good"]) == set(manifest["files"]["tampered"]), "tamper inventory")
|
||||
|
||||
|
||||
def fresh_profile(path):
|
||||
check.require(not path.exists(), "profile must be fresh; pre-guard writes forbidden")
|
||||
|
||||
|
||||
def committed_source(source, commit):
|
||||
"""Read-only Git operations: the 1407 files must be the actual HEAD bytes."""
|
||||
tracked = subprocess.check_output(["git", "-C", str(ROOT), "ls-tree", "-r", commit], text=True)
|
||||
selected = {}
|
||||
for line in tracked.splitlines():
|
||||
metadata, name = line.split("\t", 1)
|
||||
if name.split("/", 1)[0] in ("autoload", "scripts", "scenes", "data", "tests", "assets", "tools") or \
|
||||
name in ("project.godot", "export_presets.cfg", "Makefile", "AGENTS.md"):
|
||||
if Path(name).name != ".DS_Store" and "__pycache__" not in Path(name).parts:
|
||||
selected[name] = metadata.split()[2]
|
||||
check.require(set(selected) == set(source), "source map differs from HEAD")
|
||||
for name, expected in selected.items():
|
||||
actual = subprocess.check_output(["git", "-C", str(ROOT), "hash-object", str(ROOT / name)], text=True).strip()
|
||||
check.require(actual == expected, "dirty product source: " + name)
|
||||
|
||||
|
||||
def bind_report(out, label, row):
|
||||
text = (out / label / "stdout.log").read_text()
|
||||
reports = [s.partition("=")[2] for s in text.splitlines() if s.startswith("P28_RESTART_REPORT=")]
|
||||
check.require(len(reports) == 1, "one raw report required")
|
||||
row["report"] = json.loads(reports[0])
|
||||
copy_states(out, row)
|
||||
check.bound_raw(out / label, row)
|
||||
return row["report"]
|
||||
|
||||
|
||||
def copy_states(out, row):
|
||||
folder = out / (row["phase"] + "-states")
|
||||
folder.mkdir()
|
||||
profile = Path(row["runtime_profile"])
|
||||
source = profile / check.SIDECAR
|
||||
row["sidecar"] = {"path": check.SIDECAR, **check.pin(source)}
|
||||
shutil.copyfile(source, folder / "report_full.json")
|
||||
check.require(check.pin(source) == check.pin(folder / "report_full.json"), "sidecar copy differs")
|
||||
for name, expected in row["report"]["states"].items():
|
||||
source = profile / expected["path"]
|
||||
check.require(source.resolve().is_relative_to(profile.resolve()) and
|
||||
check.pin(source) == {k: expected[k] for k in ("bytes", "sha256")}, "state binding")
|
||||
shutil.copyfile(source, folder / (name + ".bin"))
|
||||
check.require(check.pin(source) == check.pin(folder / (name + ".bin")), "state copy differs")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
mode = parser.add_mutually_exclusive_group(required=True)
|
||||
mode.add_argument("--check", action="store_true")
|
||||
mode.add_argument("--run", action="store_true")
|
||||
for name in ("readonly", "fixture", "engine", "output"):
|
||||
parser.add_argument("--" + name, type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
collector, standard, outer = load_dependencies()
|
||||
pinned = collector_pins()
|
||||
readonly, fixture, engine, out = (p.resolve() for p in (args.readonly, args.fixture, args.engine, args.output))
|
||||
inputs = collector.inputs(readonly)
|
||||
check.require(check.sha(fixture) == collector.FIXTURE_SHA and check.sha(engine) == collector.ENGINE_SHA,
|
||||
"fixture/engine SHA")
|
||||
check.check_probe((HERE / "restart_probe.gd").read_text())
|
||||
source = collector.source_map(standard, ROOT)
|
||||
check.require(len(source) == 1407, "source count")
|
||||
commit = subprocess.check_output(["git", "-C", str(ROOT), "rev-parse", "HEAD"], text=True).strip()
|
||||
committed_source(source, commit)
|
||||
worker_pins = {name: check.sha(HERE / name) for name in
|
||||
("restart_pair.py", "restart_probe.gd", "check_restart.py", "test_restart.py")}
|
||||
commands = plan(collector, engine, out)
|
||||
admission = {"commit": commit, "source_map": source, "readonly": inputs, "planned_argv": commands,
|
||||
"engine_sha256": check.sha(engine), "fixture_sha256": check.sha(fixture),
|
||||
"dependencies": check.DEPENDENCIES, "collector_Q": pinned,
|
||||
"worker_pins": worker_pins,
|
||||
"phases": check.PHASES, "engines_started": 0}
|
||||
check.require(not out.exists(), "new output required")
|
||||
if args.check:
|
||||
print(json.dumps(admission, ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
check.require(sys.platform == "darwin", "reviewed native platform is macOS")
|
||||
collector.no_live_engine()
|
||||
check.require(shutil.disk_usage(out.parent).free >= 1200000000, "disk space")
|
||||
player = standard.default_player(ROOT)
|
||||
out.mkdir(parents=True)
|
||||
collector.write(out / "admission.json", admission)
|
||||
rows, profiles = [], []
|
||||
error = None
|
||||
scripts = {}
|
||||
receipt = {}
|
||||
try:
|
||||
stage = out / "stage"
|
||||
stage.mkdir()
|
||||
for name in source:
|
||||
target = stage / name
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
standard.copy_source(ROOT / name, target)
|
||||
expected = standard.inventory(stage, stage=True)
|
||||
check.require(expected == source, "stage source differs")
|
||||
probe, guard, wrapper = (out / n for n in ("restart_probe.gd", "profile_guard.gd", "guarded.gd"))
|
||||
probe.write_bytes((HERE / probe.name).read_bytes())
|
||||
guard.write_bytes((readonly / "run-r02/profile_guard.gd").read_bytes())
|
||||
wrapper.write_text("extends " + json.dumps(str(probe)) + "\nconst Guard = preload(" +
|
||||
json.dumps(str(guard)) + ")\nfunc _initialize() -> void:\n"
|
||||
"\tif not Guard.activate():\n\t\tquit(97)\n\t\treturn\n\tsuper._initialize()\n")
|
||||
scripts = {str(p): check.sha(p) for p in (probe, guard, wrapper)}
|
||||
public = (stage / "project.godot").read_bytes()
|
||||
for index, label in schedule(rows):
|
||||
check.check_dependencies()
|
||||
collector_pins()
|
||||
check.check_modules()
|
||||
check.require(collector.source_map(standard, ROOT) == source and standard.default_player(ROOT) == player,
|
||||
"source/player changed before phase")
|
||||
check.require(standard.inventory(stage, stage=True) == expected and
|
||||
all(check.sha(p) == value for p, value in scripts.items()) and
|
||||
all(check.sha(HERE / p) == value for p, value in worker_pins.items()),
|
||||
"stage/scripts changed")
|
||||
startup_name = "TheTree-PCK-tests-" + uuid.uuid4().hex + "-restart-startup"
|
||||
runtime_name = "TheTree-PCK-tests-" + uuid.uuid4().hex + "-restart-runtime"
|
||||
startup, runtime = (standard.profile_path(n) for n in (startup_name, runtime_name))
|
||||
fresh_profile(startup)
|
||||
fresh_profile(runtime)
|
||||
profiles.extend([startup, runtime])
|
||||
transfer = out / (label[0] + "-transfer")
|
||||
env = {"THE_TREE_PACK_PROFILE_NAME": runtime_name, "THE_TREE_PACK_PROFILE_EXPECTED": str(runtime),
|
||||
"THE_TREE_PACK_PROFILE_REPORT": str(out / (label + "-guard.json")),
|
||||
"THE_TREE_RESTART_ROLE": label, "THE_TREE_RESTART_K": "30",
|
||||
"THE_TREE_RESTART_TRANSFER": str(transfer),
|
||||
"THE_TREE_RESTART_TRANSFER_MANIFEST": str(transfer / "manifest.json"),
|
||||
"THE_TREE_P28_FIXTURE": str(fixture), "THE_TREE_P28_FIXTURE_SHA": collector.FIXTURE_SHA}
|
||||
if label.endswith("reader"):
|
||||
check_transfer(transfer, json.loads((transfer / "manifest.json").read_text()))
|
||||
before_stage = collector.digest(expected)
|
||||
row = collector.run_private_engine(standard, outer, commands[index], out / label, env,
|
||||
stage, public, startup_name)
|
||||
row["phase"] = label
|
||||
rows.append(row)
|
||||
check.require(row["passed"], "owned engine failure: " + label)
|
||||
after_stage = standard.inventory(stage, stage=True)
|
||||
collector.stage_gate(expected, after_stage, importing=label == "import")
|
||||
row.update(stage_before=before_stage, stage_after=collector.digest(after_stage), stage_preserved=True,
|
||||
source_preserved=collector.source_map(standard, ROOT) == source,
|
||||
player_preserved=standard.default_player(ROOT) == player)
|
||||
expected = after_stage
|
||||
if label in check.PHASES[3:]:
|
||||
guard_path = out / (label + "-guard.json")
|
||||
observation = json.loads(guard_path.read_text())
|
||||
collector.guard_gate(observation, runtime, runtime_name)
|
||||
row.update(runtime_profile=str(runtime), runtime_name=runtime_name,
|
||||
guard=observation, guard_sha256=check.sha(guard_path))
|
||||
report = bind_report(out, label, row)
|
||||
collector.write(out / (label + "-report.json"), report)
|
||||
if label.endswith("writer"):
|
||||
create_transfer(runtime / "p28_restart/good", transfer, collector.write)
|
||||
else:
|
||||
check.require(report["transfer_manifest_sha256"] == check.sha(transfer / "manifest.json"),
|
||||
"reader transfer manifest binding")
|
||||
check.check_pair(rows[-2]["report"], report, check.bound_sidecar(out, rows[-2]),
|
||||
check.bound_sidecar(out, row))
|
||||
else:
|
||||
check.bound_raw(out / label, row)
|
||||
check.require(row["source_preserved"] and row["player_preserved"] and
|
||||
check.sha(fixture) == collector.FIXTURE_SHA and
|
||||
all(check.sha(p) == value for p, value in scripts.items()), "post-phase preservation")
|
||||
except BaseException as caught:
|
||||
error = repr(caught)
|
||||
finally:
|
||||
cleanup, cleanup_errors = outer.finish_captured(outer.OWNED)
|
||||
receipt = {"admission": admission, "phases": rows, "error": error, "passed": error is None,
|
||||
"source_before": source, "source_after": collector.source_map(standard, ROOT),
|
||||
"default_player_before": player, "default_player_after": standard.default_player(ROOT),
|
||||
"dependencies_before": check.DEPENDENCIES,
|
||||
"dependencies_after": {p: check.sha(ROOT / p) for p in check.DEPENDENCIES},
|
||||
"collector_before": pinned,
|
||||
"collector_after": {p: check.sha((ROOT / COLLECTOR).parent / p) for p in pinned},
|
||||
"script_pins": scripts, "cleanup": cleanup, "cleanup_errors": cleanup_errors,
|
||||
"worker_before": worker_pins,
|
||||
"worker_after": {p: check.sha(HERE / p) for p in worker_pins},
|
||||
"profiles": {str(p): collector.profile_files(p) for p in profiles}, "owned_writes": [],
|
||||
"cross_cutting": check.CROSS, "original_credit": 0,
|
||||
"windows_verified": False, "operations_200_guests_verified": False}
|
||||
try:
|
||||
receipt["coverage"] = check.coverage([
|
||||
check.bound_sidecar(out, row)["report"] for row in rows if "sidecar" in row])
|
||||
except (ValueError, KeyError, TypeError, OSError) as caught:
|
||||
receipt["coverage"] = {"overload_nonzero_backlog": "not_exercised",
|
||||
"error": repr(caught)}
|
||||
receipt.update(passed=False, error=receipt["error"] or "coverage: " + repr(caught))
|
||||
if receipt["passed"]:
|
||||
try:
|
||||
check.check_receipt(receipt, out)
|
||||
except (ValueError, KeyError, TypeError, OSError) as caught:
|
||||
receipt.update(passed=False, error="checker: " + repr(caught))
|
||||
collector.write(out / "execution.json", receipt)
|
||||
return 0 if receipt["passed"] else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+603
@@ -0,0 +1,603 @@
|
||||
extends SceneTree
|
||||
## Root-owned execution only. All files are written after Guard.activate().
|
||||
var GameClock: Variant
|
||||
var runtime: Variant
|
||||
var main: Variant
|
||||
var report: Dictionary = {}
|
||||
var signals: Array = []
|
||||
var key_forms: Dictionary = {}
|
||||
var role := ""
|
||||
var start_minute := 0
|
||||
var selected_tick := false
|
||||
var overloaded := false
|
||||
var io_ready := false
|
||||
var finished := false
|
||||
var E := ""
|
||||
var package: Dictionary = {}
|
||||
var failure_event: Dictionary = {}
|
||||
var boundary: Variant = null
|
||||
var at_390_event_verbatim: Array = []
|
||||
var at_390_pending: Array = []
|
||||
var redaction_ok := true
|
||||
var canonical_key_collision := false
|
||||
const SAVE_EVENTS := ["SAVE_CHECKPOINT_SUCCEEDED", "SAVE_CHECKPOINT_FAILED", "SAVE_LOADED",
|
||||
"SAVE_LOAD_FAILED", "SAVE_MIGRATION_SUCCEEDED", "SAVE_MIGRATION_FAILED",
|
||||
"SAVE_RECOVERY_LOADED", "SAVE_RECOVERY_CANDIDATE_LOADED", "SAVE_RECOVERY_FAILED"]
|
||||
|
||||
func canonical_json(value: Variant) -> String:
|
||||
if value is Dictionary:
|
||||
var keys: Array = value.keys()
|
||||
keys.sort_custom(func(a, b): return str(a) < str(b))
|
||||
for index: int in range(1, keys.size()):
|
||||
if str(keys[index - 1]) == str(keys[index]):
|
||||
canonical_key_collision = true
|
||||
# Unwind serialization before emitting failure evidence.
|
||||
call_deferred("finish", "canonical_key_collision")
|
||||
return ""
|
||||
var parts: PackedStringArray = []
|
||||
for key: Variant in keys:
|
||||
parts.append(JSON.stringify(str(key)) + ":" + canonical_json(value[key]))
|
||||
return "{" + ",".join(parts) + "}"
|
||||
if value is Array:
|
||||
var parts: PackedStringArray = []
|
||||
for child: Variant in value:
|
||||
parts.append(canonical_json(child))
|
||||
return "[" + ",".join(parts) + "]"
|
||||
if value is int or value is float:
|
||||
return "%.17f" % float(value)
|
||||
return JSON.stringify(value)
|
||||
|
||||
func event_summary(event: Dictionary) -> Dictionary:
|
||||
var canonical: Dictionary = {"event_id": E, "replaced_for_cross_pid_compare": true} \
|
||||
if field(event, "event_id") == E else event
|
||||
return {"event_id": field(event, "event_id"), "event_type": field(event, "event_type"),
|
||||
"absolute_minute": field(event, "absolute_minute"),
|
||||
"raw_event_sha256": bytes_hash(canonical_json(event).to_utf8_buffer()),
|
||||
"canonical_sha256": bytes_hash(canonical_json(canonical).to_utf8_buffer())}
|
||||
|
||||
func observe_390() -> void:
|
||||
if not role.begins_with("O-") or GameClock.absolute_minute != 390 or report.at_390 != null:
|
||||
return
|
||||
var observed := operations_observation()
|
||||
var state := witness(runtime.create_snapshot())
|
||||
report.at_390 = {"clock": {"absolute_minute": GameClock.absolute_minute},
|
||||
"operations": observed.operations, "processed_arrival_minutes": observed.processed_arrival_minutes,
|
||||
"guest_auto_ids": observed.guest_auto_ids, "guest_auto_count": observed.guest_auto_count,
|
||||
"published_ids_count": state.published_ids.size(), "domain_event_counter": state.counter,
|
||||
"events": []}
|
||||
at_390_pending = state.recent_events.duplicate(true)
|
||||
if report.has("saved_minute"):
|
||||
bind_390_events()
|
||||
|
||||
func bind_390_events() -> void:
|
||||
if report.at_390 == null:
|
||||
return
|
||||
at_390_event_verbatim = []
|
||||
report.at_390.events = []
|
||||
for event: Dictionary in at_390_pending:
|
||||
var minute: int = field(event, "absolute_minute")
|
||||
if minute > report.saved_minute and minute <= 390:
|
||||
at_390_event_verbatim.append(event.duplicate(true))
|
||||
report.at_390.events.append(event_summary(event))
|
||||
|
||||
func deadline_timer_seconds() -> float:
|
||||
return Time.get_ticks_msec() / 1000.0
|
||||
|
||||
func field(d: Variant, name: String) -> Variant:
|
||||
if d is Dictionary:
|
||||
if d.has(StringName(name)):
|
||||
key_forms[name] = "StringName"
|
||||
return d[StringName(name)]
|
||||
if d.has(String(name)):
|
||||
key_forms[name] = "String"
|
||||
return d[String(name)]
|
||||
key_forms[name] = "missing_product_field"
|
||||
return null
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("begin")
|
||||
|
||||
func bytes_hash(data: PackedByteArray) -> String:
|
||||
var context := HashingContext.new()
|
||||
context.start(HashingContext.HASH_SHA256)
|
||||
context.update(data)
|
||||
return context.finish().hex_encode()
|
||||
|
||||
func runtime_part(snapshot: Dictionary) -> Dictionary:
|
||||
return field(field(snapshot, "systems"), "runtime")
|
||||
|
||||
func witness(snapshot: Dictionary) -> Dictionary:
|
||||
var part := runtime_part(snapshot)
|
||||
var events: Dictionary = field(part, "domain_events")
|
||||
return {"counter": field(field(field(part, "id_generator"), "counters"), "domain_event"),
|
||||
"published_ids": field(events, "published_ids").duplicate(true),
|
||||
"recent_events": field(events, "recent_events").duplicate(true)}
|
||||
|
||||
func different_paths(a: Variant, b: Variant, path := "$", depth := 0) -> Array:
|
||||
var result: Array = []
|
||||
if var_to_bytes(a) == var_to_bytes(b):
|
||||
return result
|
||||
if depth >= 4 or typeof(a) != typeof(b):
|
||||
return [path]
|
||||
if a is Dictionary:
|
||||
for key: Variant in a:
|
||||
if not b.has(key):
|
||||
result.append(path + "." + str(key))
|
||||
else:
|
||||
result.append_array(different_paths(a[key], b[key], path + "." + str(key), depth + 1))
|
||||
for key: Variant in b:
|
||||
if not a.has(key):
|
||||
result.append(path + "." + str(key))
|
||||
elif a is Array and a.size() == b.size():
|
||||
for index in range(a.size()):
|
||||
result.append_array(different_paths(a[index], b[index], path + "[%d]" % index, depth + 1))
|
||||
else:
|
||||
result.append(path)
|
||||
# Typed container/key/order differences can exist without value differences.
|
||||
if result.is_empty():
|
||||
result.append(path)
|
||||
return result
|
||||
|
||||
func compare(expected: Dictionary, actual: Dictionary) -> Dictionary:
|
||||
var a := var_to_bytes(expected)
|
||||
var b := var_to_bytes(actual)
|
||||
return {"method": "var_to_bytes", "equal": a == b, "expected_sha256": bytes_hash(a),
|
||||
"actual_sha256": bytes_hash(b), "excluded_paths": [],
|
||||
"diff_paths": different_paths(expected, actual)}
|
||||
|
||||
func record_state(name: String, value: Dictionary) -> bool:
|
||||
var path := "user://p28_restart/evidence/" + name + ".bin"
|
||||
if DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(path).get_base_dir()) != OK:
|
||||
return false
|
||||
var data := var_to_bytes(value)
|
||||
var file := FileAccess.open(path, FileAccess.WRITE)
|
||||
if file == null:
|
||||
return false
|
||||
file.store_buffer(data)
|
||||
file.close()
|
||||
report.states[name] = {"path": path.trim_prefix("user://"), "bytes": data.size(),
|
||||
"sha256": bytes_hash(data)}
|
||||
return FileAccess.get_sha256(path) == report.states[name].sha256
|
||||
|
||||
func on_save(result: Dictionary) -> void:
|
||||
signals.append(result.duplicate(true))
|
||||
|
||||
func auto_ids() -> Array:
|
||||
var ids: Array = []
|
||||
for guest: Variant in runtime.guest_service.get_guests():
|
||||
if str(guest.guest_id).begins_with("guest-auto-"):
|
||||
ids.append(str(guest.guest_id))
|
||||
ids.sort()
|
||||
return ids
|
||||
|
||||
func operations_observation() -> Dictionary:
|
||||
var ids := auto_ids()
|
||||
var processed: Array = []
|
||||
var arrivals: Array = []
|
||||
if runtime.operations_coordinator != null:
|
||||
var state: Dictionary = runtime.operations_coordinator.create_snapshot()
|
||||
processed = field(state, "processed_arrival_minutes").duplicate()
|
||||
arrivals = runtime.operations_coordinator.get_arrival_reports().duplicate(true)
|
||||
return {"clock": GameClock.absolute_minute, "guest_auto_ids": ids, "guest_auto_count": ids.size(),
|
||||
"operations": runtime.get_operations_report(), "processed_arrival_minutes": processed,
|
||||
"arrival_reports": arrivals}
|
||||
|
||||
func on_tick(minute: int) -> void:
|
||||
# During deterministic stepping the observation is made after advance returns.
|
||||
if not io_ready:
|
||||
observe_390()
|
||||
if io_ready or finished or selected_tick:
|
||||
return
|
||||
var observed := operations_observation()
|
||||
if role == "O-writer":
|
||||
report.ui_ticks.append(observed)
|
||||
var stop: bool = (role == "R-writer" and minute - start_minute >= 10) or \
|
||||
(role == "O-writer" and (observed.guest_auto_count > 0 or minute - start_minute >= 120))
|
||||
if stop:
|
||||
GameClock.set_paused(true)
|
||||
selected_tick = true
|
||||
|
||||
func on_overload(backlog: int) -> void:
|
||||
overloaded = true
|
||||
report.overloads.append({"minute": GameClock.absolute_minute, "backlog": backlog})
|
||||
|
||||
func clock_observation() -> Dictionary:
|
||||
return {"minute": GameClock.absolute_minute, "paused": GameClock.paused,
|
||||
"backlog": GameClock.get_backlog_minutes(),
|
||||
"accumulator_hex": PackedFloat64Array([GameClock.get("_accumulator")]).to_byte_array().hex_encode()}
|
||||
|
||||
func begin() -> void:
|
||||
GameClock = root.get_node("GameClock")
|
||||
runtime = root.get_node("SimulationRuntime")
|
||||
role = OS.get_environment("THE_TREE_RESTART_ROLE")
|
||||
report = {"schema": "p28-restart-v1", "role": role, "pid": OS.get_process_id(),
|
||||
"main_added": false, "k_target": 30, "actual_k": 0, "io": [], "states": {},
|
||||
"minutes": [], "ui_ticks": [], "overloads": [], "boundary": null, "next_boundary": null,
|
||||
"at_390": null,
|
||||
"comparison_scope": "complete_typed_var_to_bytes", "excluded_paths": [],
|
||||
"transfer_before_guard_writes": 0, "transfer_verified": false,
|
||||
"probe_deadline_seconds": 1740}
|
||||
var elapsed := deadline_timer_seconds()
|
||||
var interval := 1740.0 - elapsed
|
||||
report["probe_deadline"] = {"limit_seconds_from_engine_start": 1740,
|
||||
"engine_elapsed_at_begin_seconds": elapsed, "timer_seconds": interval}
|
||||
if interval <= 0:
|
||||
finish("probe_deadline")
|
||||
return
|
||||
create_timer(interval).timeout.connect(func() -> void: finish("probe_deadline"))
|
||||
if OS.get_environment("THE_TREE_RESTART_K") != "30" or role not in ["R-writer", "R-reader", "O-writer", "O-reader"]:
|
||||
finish("invalid_environment")
|
||||
return
|
||||
# No await before main: the default autoload clock cannot process here.
|
||||
if role == "R-writer":
|
||||
var fixture := OS.get_environment("THE_TREE_P28_FIXTURE")
|
||||
if FileAccess.get_sha256(fixture) != OS.get_environment("THE_TREE_P28_FIXTURE_SHA"):
|
||||
finish("fixture_sha")
|
||||
return
|
||||
var file := FileAccess.open(fixture, FileAccess.READ)
|
||||
if file == null:
|
||||
finish("fixture_missing")
|
||||
return
|
||||
var value: Variant = file.get_var(false)
|
||||
file.close()
|
||||
if not value is Dictionary or not runtime.restore_snapshot(value):
|
||||
finish("fixture_restore")
|
||||
return
|
||||
main = load("res://scenes/main/main.tscn").instantiate()
|
||||
root.add_child(main)
|
||||
report.main_added = true
|
||||
report["startup"] = {"start_report": main.get("_operations_start_report").duplicate(true),
|
||||
"operations": runtime.get_operations_report(), "clock": clock_observation()}
|
||||
if role != "R-writer" and not GameClock.paused:
|
||||
finish("main_did_not_pause")
|
||||
return
|
||||
var start_result: Dictionary = report.startup.start_report
|
||||
var operations_enabled: Variant = field(report.startup.operations, "enabled")
|
||||
if role == "R-writer":
|
||||
if operations_enabled != false or field(start_result, "code") != "INVALID_START_TIME":
|
||||
finish("unexpected_R_start")
|
||||
return
|
||||
elif operations_enabled != true or field(start_result, "ok") != true or GameClock.absolute_minute != 360:
|
||||
finish("unexpected_fresh_start")
|
||||
return
|
||||
runtime.save_operation_completed.connect(on_save)
|
||||
GameClock.tick_completed.connect(on_tick)
|
||||
GameClock.simulation_overloaded.connect(on_overload)
|
||||
start_minute = GameClock.absolute_minute
|
||||
report["start_minute"] = start_minute
|
||||
if role.ends_with("writer"):
|
||||
main.call("_set_speed", 1)
|
||||
while not GameClock.paused and not finished:
|
||||
await process_frame
|
||||
if finished:
|
||||
return
|
||||
if not selected_tick and not overloaded:
|
||||
finish("unexpected_ui_stop")
|
||||
return
|
||||
if role == "O-writer" and auto_ids().is_empty():
|
||||
report["boundary"] = null
|
||||
report["boundary_reason"] = "no_arrival_within_cap" if GameClock.absolute_minute - start_minute >= 120 else "stopped_before_arrival"
|
||||
finish(report.boundary_reason)
|
||||
return
|
||||
if not writer_save():
|
||||
finish("save_comparison_failed")
|
||||
return
|
||||
else:
|
||||
if not import_transfer():
|
||||
finish("transfer_failed")
|
||||
return
|
||||
if not reader_load():
|
||||
finish("load_comparison_failed")
|
||||
return
|
||||
report["event_id"] = E
|
||||
report["saved_minute"] = GameClock.absolute_minute
|
||||
bind_390_events()
|
||||
boundary = operations_observation() if role.begins_with("O-") else null
|
||||
report["boundary"] = boundary
|
||||
var idle_before: Dictionary = runtime.create_snapshot()
|
||||
var clock_before := clock_observation()
|
||||
await process_frame
|
||||
await process_frame
|
||||
if finished:
|
||||
return
|
||||
var idle_after: Dictionary = runtime.create_snapshot()
|
||||
report["paused_idle"] = {"before": clock_before, "after": clock_observation(),
|
||||
"comparison": compare(idle_before, idle_after)}
|
||||
if not report.paused_idle.comparison.equal or report.paused_idle.before != report.paused_idle.after:
|
||||
finish("paused_backlog_consumed")
|
||||
return
|
||||
io_ready = true
|
||||
for _index in range(30):
|
||||
if not deterministic_step():
|
||||
finish("no_tick_advance_or_invalid_state")
|
||||
return
|
||||
# Keep the event loop responsive to deadline/audio while remaining paused.
|
||||
await process_frame
|
||||
if finished:
|
||||
return
|
||||
if not record_state("final", runtime.create_snapshot()):
|
||||
finish("state_write_failed")
|
||||
return
|
||||
report["final_minute"] = GameClock.absolute_minute
|
||||
if role.begins_with("O-"):
|
||||
if report.at_390 == null:
|
||||
finish("at_390_not_reached")
|
||||
return
|
||||
var last := operations_observation()
|
||||
var added: Array = []
|
||||
for minute: Variant in last.processed_arrival_minutes:
|
||||
if minute not in boundary.processed_arrival_minutes:
|
||||
added.append(minute)
|
||||
report["next_boundary"] = {"minute": added[0] if added.size() == 1 else null,
|
||||
"processed_once": added.size() == 1, "before": boundary.processed_arrival_minutes,
|
||||
"after": last.processed_arrival_minutes, "arrival_reports": last.arrival_reports,
|
||||
"guest_auto_ids": last.guest_auto_ids}
|
||||
if added.size() != 1:
|
||||
finish("next_arrival_boundary_missing_or_duplicate")
|
||||
return
|
||||
finish("complete")
|
||||
|
||||
func io_proof(base: Dictionary, after: Dictionary, kind: String, returned: Variant) -> bool:
|
||||
var row := {"kind": kind, "signal_count": signals.size(),
|
||||
"return_kind": "bool" if kind == "save" else "void", "return_value": returned}
|
||||
report.io.append(row)
|
||||
if signals.size() != 1:
|
||||
return false
|
||||
var signal_result: Dictionary = signals[0]
|
||||
var payload: Dictionary = runtime.call("_save_event_payload", signal_result)
|
||||
var latest: Dictionary = runtime.latest_save_operation_result.duplicate(true)
|
||||
row["signal_projection"] = "_save_event_payload"
|
||||
row["signal_payload"] = payload
|
||||
row["latest"] = latest
|
||||
row["payload_bytes_equal"] = var_to_bytes(payload) == var_to_bytes(latest)
|
||||
var before := witness(base)
|
||||
var after_witness := witness(after)
|
||||
var eid := "domain_event.%012d" % (int(before.counter) + 1)
|
||||
var found: Array = []
|
||||
for event: Dictionary in after_witness.recent_events:
|
||||
if field(event, "event_id") == eid:
|
||||
found.append(event)
|
||||
if found.size() != 1 or before.published_ids.has(eid):
|
||||
return false
|
||||
var event: Dictionary = found[0]
|
||||
row["event"] = event
|
||||
row["before"] = before
|
||||
row["after"] = after_witness
|
||||
row["minute_before"] = field(field(base, "clock"), "absolute_minute")
|
||||
row["minute_after"] = field(field(after, "clock"), "absolute_minute")
|
||||
row["paused_before"] = field(field(base, "clock"), "paused")
|
||||
row["paused_after"] = field(field(after, "clock"), "paused")
|
||||
row["dropped"] = before.recent_events[0] if before.recent_events.size() == 256 else null
|
||||
var expected := base.duplicate(true)
|
||||
var part := runtime_part(expected)
|
||||
var counters: Dictionary = field(field(part, "id_generator"), "counters")
|
||||
counters["domain_event"] = int(before.counter) + 1
|
||||
var events: Dictionary = field(part, "domain_events")
|
||||
var ids: Array = field(events, "published_ids")
|
||||
ids.append(eid)
|
||||
ids.sort()
|
||||
var recent: Array = field(events, "recent_events")
|
||||
if recent.size() > 256:
|
||||
return false
|
||||
if recent.size() == 256:
|
||||
recent.pop_front()
|
||||
recent.append(event.duplicate(true))
|
||||
row["comparison"] = compare(expected, after)
|
||||
var expected_type := {"save": "SAVE_CHECKPOINT_SUCCEEDED", "load": "SAVE_LOADED", "tamper": "SAVE_LOAD_FAILED"}
|
||||
var ok: bool = row.payload_bytes_equal and row.comparison.equal and \
|
||||
var_to_bytes(field(event, "payload")) == var_to_bytes(payload) and \
|
||||
field(event, "event_type") == expected_type[kind] and field(event, "source_service") == "save_service" and \
|
||||
field(event, "entity_ids").is_empty() and field(event, "causation_command_id") == 0 and \
|
||||
row.minute_before == row.minute_after and field(event, "absolute_minute") == row.minute_after
|
||||
if kind == "tamper":
|
||||
var recovery: Variant = field(signal_result, "recovery_report")
|
||||
var migration: Variant = field(signal_result, "migration_attempted")
|
||||
row["failure"] = {"preload_pause_restored": field(signal_result, "preload_pause_restored"),
|
||||
"loaded_clock_paused": field(signal_result, "loaded_clock_paused"), "recovery_report": recovery,
|
||||
"migration_attempted": migration == true}
|
||||
failure_event = event.duplicate(true)
|
||||
return ok and field(payload, "ok") == false and field(payload, "code") != "OK" and \
|
||||
row.failure.preload_pause_restored == true and row.failure.loaded_clock_paused == row.paused_before and \
|
||||
row.paused_before == row.paused_after and (recovery == null or field(recovery, "restore_committed") == false)
|
||||
E = eid
|
||||
return ok and field(payload, "ok") == true and field(payload, "code") == "OK" and \
|
||||
(returned == true if kind == "save" else returned == null) and row.paused_after == true and \
|
||||
field(payload, "saved_absolute_minute") == row.minute_after
|
||||
|
||||
func writer_save() -> bool:
|
||||
var before: Dictionary = runtime.create_snapshot()
|
||||
var backlog: int = GameClock.get_backlog_minutes()
|
||||
report["overload_case"] = {"exercised": not report.overloads.is_empty() and backlog > 0,
|
||||
"overloads": report.overloads.size(), "backlog_at_save": backlog}
|
||||
signals = []
|
||||
var returned: bool = main.call("_save_manual_at_path", "user://p28_restart/good", "P28 RESTART")
|
||||
var after: Dictionary = runtime.create_snapshot()
|
||||
var loaded := SaveService.load_snapshot("user://p28_restart/good")
|
||||
if field(loaded, "ok") != true:
|
||||
return false
|
||||
package = field(loaded, "snapshot")
|
||||
report["package_matches_before"] = compare(before, package)
|
||||
if not record_state("P", package) or not record_state("after_save", after):
|
||||
return false
|
||||
return report.package_matches_before.equal and io_proof(package, after, "save", returned)
|
||||
|
||||
func import_transfer() -> bool:
|
||||
var transfer := OS.get_environment("THE_TREE_RESTART_TRANSFER")
|
||||
var manifest_path := OS.get_environment("THE_TREE_RESTART_TRANSFER_MANIFEST")
|
||||
if not transfer.is_absolute_path() or not manifest_path.is_absolute_path():
|
||||
return false
|
||||
var manifest: Variant = JSON.parse_string(FileAccess.get_file_as_string(manifest_path))
|
||||
if not manifest is Dictionary:
|
||||
return false
|
||||
for kind: String in ["good", "tampered"]:
|
||||
var target := "user://p28_restart/" + kind
|
||||
if DirAccess.dir_exists_absolute(target):
|
||||
return false
|
||||
for relative: String in manifest.files[kind]:
|
||||
if relative.is_absolute_path() or ".." in relative.split("/") or "\\" in relative:
|
||||
return false
|
||||
var source := transfer.path_join(kind).path_join(relative)
|
||||
var expected: Dictionary = manifest.files[kind][relative]
|
||||
if FileAccess.get_sha256(source) != expected.sha256:
|
||||
return false
|
||||
var data := FileAccess.get_file_as_bytes(source)
|
||||
if data.size() != int(expected.bytes):
|
||||
return false
|
||||
var destination := target.path_join(relative)
|
||||
if DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(destination).get_base_dir()) != OK:
|
||||
return false
|
||||
var file := FileAccess.open(destination, FileAccess.WRITE)
|
||||
if file == null:
|
||||
return false
|
||||
file.store_buffer(data)
|
||||
file.close()
|
||||
if FileAccess.get_sha256(destination) != expected.sha256:
|
||||
return false
|
||||
report["transfer_manifest_sha256"] = FileAccess.get_sha256(manifest_path)
|
||||
report["transfer_verified"] = true
|
||||
return true
|
||||
|
||||
func reader_load() -> bool:
|
||||
var loaded := SaveService.load_snapshot("user://p28_restart/good")
|
||||
if field(loaded, "ok") != true:
|
||||
return false
|
||||
package = field(loaded, "snapshot")
|
||||
var before: Dictionary = runtime.create_snapshot()
|
||||
if not record_state("P", package) or not record_state("B", before):
|
||||
return false
|
||||
signals = []
|
||||
main.call("_load_manual_at_path", "user://p28_restart/tampered", true)
|
||||
var failed: Dictionary = runtime.create_snapshot()
|
||||
if not record_state("after_tamper", failed) or not io_proof(before, failed, "tamper", null):
|
||||
return false
|
||||
signals = []
|
||||
main.call("_load_manual_at_path", "user://p28_restart/good", true)
|
||||
var after: Dictionary = runtime.create_snapshot()
|
||||
if not record_state("after_load", after) or not io_proof(package, after, "load", null):
|
||||
return false
|
||||
report["failure_event_absent"] = true
|
||||
for event: Dictionary in witness(after).recent_events:
|
||||
if var_to_bytes(event) == var_to_bytes(failure_event):
|
||||
report.failure_event_absent = false
|
||||
return report.failure_event_absent
|
||||
|
||||
func deterministic_step() -> bool:
|
||||
if not io_ready or not GameClock.paused:
|
||||
return false
|
||||
var before: int = GameClock.absolute_minute
|
||||
var backlog: int = GameClock.get_backlog_minutes()
|
||||
GameClock.advance_minutes(1)
|
||||
var after: int = GameClock.absolute_minute
|
||||
if after != before + 1:
|
||||
return false
|
||||
observe_390()
|
||||
var state: Dictionary = runtime.create_snapshot()
|
||||
var normalized := state.duplicate(true)
|
||||
var recent: Array = field(field(runtime_part(normalized), "domain_events"), "recent_events")
|
||||
var replacement: Variant = null
|
||||
var count := 0
|
||||
var recent_ids: Array = []
|
||||
for index in range(recent.size()):
|
||||
recent_ids.append(field(recent[index], "event_id"))
|
||||
if field(recent[index], "event_id") == E:
|
||||
replacement = recent[index].duplicate(true)
|
||||
recent[index] = {"event_id": E, "replaced_for_cross_pid_compare": true}
|
||||
count += 1
|
||||
if count > 1:
|
||||
return false
|
||||
report.minutes.append({"before": before, "after": after, "paused": GameClock.paused,
|
||||
"backlog_before": backlog, "backlog_after": GameClock.get_backlog_minutes(),
|
||||
"sha256": bytes_hash(var_to_bytes(normalized)), "scope": "complete_typed_var_to_bytes",
|
||||
"excluded_paths": [], "replacement": replacement, "replacement_count": count,
|
||||
"recent_count": recent.size(), "recent_ids": recent_ids})
|
||||
report.actual_k += after - before
|
||||
if role.begins_with("O-") and after == 390:
|
||||
var observation: Dictionary = report.at_390.duplicate(true)
|
||||
observation.erase("events")
|
||||
report.minutes[-1]["operations_observation"] = observation
|
||||
return GameClock.paused and backlog == GameClock.get_backlog_minutes()
|
||||
|
||||
func is_save_event(event: Variant) -> bool:
|
||||
return event is Dictionary and field(event, "source_service") == "save_service" and \
|
||||
field(event, "event_type") in SAVE_EVENTS
|
||||
|
||||
func redact_payload(payload: Dictionary, pointer: String) -> void:
|
||||
if payload.has("timing_redactions"):
|
||||
redaction_ok = false
|
||||
return
|
||||
var markers: Array = []
|
||||
for key: String in ["duration_us", "runtime_duration_us"]:
|
||||
if not payload.has(key):
|
||||
redaction_ok = false
|
||||
return
|
||||
markers.append({"original_key": key, "sidecar_ref": pointer + "/" + key})
|
||||
payload.erase(key)
|
||||
payload["timing_redactions"] = markers
|
||||
|
||||
func redact_event(event: Variant, pointer: String) -> void:
|
||||
if is_save_event(event):
|
||||
redact_payload(event.payload, pointer + "/payload")
|
||||
|
||||
func public_report(full: Dictionary) -> Dictionary:
|
||||
var result: Dictionary = full.duplicate(true)
|
||||
for i in range(result.io.size()):
|
||||
var row: Dictionary = result.io[i]
|
||||
var pointer := "/report/io/%d" % i
|
||||
for name: String in ["signal_payload", "latest"]:
|
||||
if row.has(name):
|
||||
redact_payload(row[name], pointer + "/" + name)
|
||||
for name: String in ["event", "dropped"]:
|
||||
if row.has(name):
|
||||
redact_event(row[name], pointer + "/" + name)
|
||||
for side: String in ["before", "after"]:
|
||||
if row.has(side):
|
||||
for j in range(row[side].recent_events.size()):
|
||||
redact_event(row[side].recent_events[j], pointer + "/" + side + "/recent_events/%d" % j)
|
||||
for k in range(result.minutes.size()):
|
||||
redact_event(result.minutes[k].replacement, "/report/minutes/%d/replacement" % k)
|
||||
return result
|
||||
|
||||
func public_has_forbidden(value: Variant) -> bool:
|
||||
if value is Dictionary:
|
||||
for key: Variant in value:
|
||||
if str(key).ends_with("_" + "us") or str(key).ends_with("_" + "ms") or \
|
||||
str(key) in ["performance", "statistics", "fps", "budget_verdict"]:
|
||||
return true
|
||||
if public_has_forbidden(value[key]):
|
||||
return true
|
||||
elif value is Array:
|
||||
for child: Variant in value:
|
||||
if public_has_forbidden(child):
|
||||
return true
|
||||
return false
|
||||
|
||||
func finish(reason: String) -> void:
|
||||
if finished:
|
||||
return
|
||||
if canonical_key_collision:
|
||||
reason = "canonical_key_collision"
|
||||
finished = true
|
||||
report["stop_reason"] = reason
|
||||
report["key_forms"] = key_forms
|
||||
var sidecar_path := "user://p28_restart/evidence/report_full.json"
|
||||
DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(sidecar_path).get_base_dir())
|
||||
var file := FileAccess.open(sidecar_path, FileAccess.WRITE)
|
||||
if file == null:
|
||||
quit(1)
|
||||
return
|
||||
file.store_string(JSON.stringify({"report": report, "at_390_event_verbatim": at_390_event_verbatim}, "", true, true))
|
||||
file.close()
|
||||
# Partial failure evidence stays in the sidecar. Never emit a leaking public payload.
|
||||
var public: Dictionary = public_report(report)
|
||||
if not redaction_ok or public_has_forbidden(public) or public.is_empty():
|
||||
print("P28_RESTART_EVIDENCE_FAILURE=" + reason)
|
||||
else:
|
||||
print("P28_RESTART_REPORT=" + JSON.stringify(public, "", true, true))
|
||||
if is_instance_valid(main) and main.is_inside_tree():
|
||||
main.ui_audio_window_close_drained.connect(emit_audio_close, CONNECT_ONE_SHOT)
|
||||
get_root().close_requested.emit()
|
||||
else:
|
||||
quit(1)
|
||||
|
||||
func emit_audio_close(result: Dictionary) -> void:
|
||||
print("P28_RESTART_AUDIO_CLOSE=" + JSON.stringify(result))
|
||||
+876
@@ -0,0 +1,876 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Engine-free positive and adversarial controls. No native acceptance claimed."""
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
from types import SimpleNamespace
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
import check_restart as check
|
||||
import restart_pair as driver
|
||||
|
||||
DATA = b"synthetic typed bytes fixture (not engine evidence)"
|
||||
HASH = hashlib.sha256(DATA).hexdigest()
|
||||
|
||||
|
||||
def comparison():
|
||||
return {"method": "var_to_bytes", "equal": True, "diff_paths": [], "excluded_paths": [],
|
||||
"expected_sha256": HASH, "actual_sha256": HASH}
|
||||
|
||||
|
||||
def history(count=256):
|
||||
ids = ["domain_event.%012d" % n for n in range(1, count + 1)]
|
||||
return {"counter": count, "published_ids": ids,
|
||||
"recent_events": [{"event_id": eid, "event_type": "OLD", "payload": {"n": n}}
|
||||
for n, eid in enumerate(ids)]}
|
||||
|
||||
|
||||
def io(kind, minute=612, count=256):
|
||||
before = history(count)
|
||||
payload = {"ok": kind != "tamper", "code": "OK" if kind != "tamper" else "CORRUPT_WORLD",
|
||||
"operation": "save" if kind == "save" else "load",
|
||||
"save_path": "user://p28_restart/" + ("tampered" if kind == "tamper" else "good"),
|
||||
"save_id": "synthetic", "saved_absolute_minute": minute,
|
||||
"duration_us": 124, "runtime_duration_us": 57}
|
||||
event = {"event_id": check.event_id(count),
|
||||
"event_type": {"save": "SAVE_CHECKPOINT_SUCCEEDED", "load": "SAVE_LOADED",
|
||||
"tamper": "SAVE_LOAD_FAILED"}[kind],
|
||||
"source_service": "save_service", "entity_ids": [], "causation_command_id": 0,
|
||||
"absolute_minute": minute, "payload": copy.deepcopy(payload)}
|
||||
return {"kind": kind, "signal_count": 1, "signal_payload": payload, "latest": copy.deepcopy(payload),
|
||||
"event": event, "signal_projection": "_save_event_payload", "payload_bytes_equal": True,
|
||||
"return_kind": "bool" if kind == "save" else "void",
|
||||
"return_value": True if kind == "save" else None,
|
||||
"before": before, "after": check.transform(before, event),
|
||||
"dropped": before["recent_events"][0] if count == 256 else None,
|
||||
"comparison": comparison(), "minute_before": minute, "minute_after": minute,
|
||||
"paused_before": True, "paused_after": True,
|
||||
"failure": {"preload_pause_restored": True, "loaded_clock_paused": True,
|
||||
"migration_attempted": False, "recovery_report": {"restore_committed": False}}}
|
||||
|
||||
|
||||
def report(role="R-writer", pid=100):
|
||||
writer, ops = role.endswith("writer"), role.startswith("O-")
|
||||
minute = 361 if ops else 612
|
||||
rows = [io("save", minute)] if writer else [io("tamper", 360, 3), io("load", minute)]
|
||||
names = ["P", "after_save", "final"] if writer else ["P", "B", "after_tamper", "after_load", "final"]
|
||||
event = rows[-1]["event"]
|
||||
boundary = {"clock": minute, "guest_auto_ids": ["guest-auto-00000001"], "guest_auto_count": 1,
|
||||
"operations": {"enabled": True}, "processed_arrival_minutes": [360],
|
||||
"arrival_reports": [{"minute": 360}]} if ops else None
|
||||
next_boundary = {"minute": 390, "processed_once": True, "before": [360], "after": [360, 390],
|
||||
"arrival_reports": [{"minute": 360}, {"minute": 390}],
|
||||
"guest_auto_ids": ["guest-auto-00000001", "guest-auto-00000002"]} if ops else None
|
||||
clock = {"minute": minute, "paused": True, "backlog": 121, "accumulator_hex": "00"}
|
||||
result = {"schema": "p28-restart-v1", "role": role, "pid": pid, "stop_reason": "complete",
|
||||
"main_added": True, "k_target": 30, "actual_k": 30, "comparison_scope": "complete_typed_var_to_bytes",
|
||||
"excluded_paths": [], "transfer_before_guard_writes": 0, "probe_deadline_seconds": 1740,
|
||||
"probe_deadline": {"limit_seconds_from_engine_start": 1740,
|
||||
"engine_elapsed_at_begin_seconds": 12.25, "timer_seconds": 1727.75},
|
||||
"overloads": [],
|
||||
"overload_case": {"exercised": False, "overloads": 0, "backlog_at_save": 121},
|
||||
"io": rows, "package_matches_before": comparison(), "failure_event_absent": True,
|
||||
"transfer_verified": True, "event_id": event["event_id"], "saved_minute": minute,
|
||||
"start_minute": 360 if ops else 602, "final_minute": minute + 30,
|
||||
"states": {n: {"path": "p28_restart/evidence/" + n + ".bin", "bytes": len(DATA), "sha256": HASH}
|
||||
for n in names},
|
||||
"paused_idle": {"before": clock, "after": copy.deepcopy(clock), "comparison": comparison()},
|
||||
"boundary": boundary, "next_boundary": next_boundary, "ui_ticks": [copy.deepcopy(boundary)] if ops else [],
|
||||
"minutes": [{"before": minute + n, "after": minute + n + 1, "paused": True,
|
||||
"backlog_before": 121, "backlog_after": 121, "sha256": HASH,
|
||||
"scope": "complete_typed_var_to_bytes", "excluded_paths": [],
|
||||
"replacement": copy.deepcopy(event) if n < 5 else None,
|
||||
"replacement_count": 1 if n < 5 else 0, "recent_count": 256,
|
||||
"recent_ids": history(255)["published_ids"] +
|
||||
([event["event_id"]] if n < 5 else ["domain_event.000000000256"])}
|
||||
for n in range(30)], "at_390": None}
|
||||
if ops:
|
||||
result["at_390"] = {"clock": {"absolute_minute": 390}, "operations": {"enabled": True},
|
||||
"processed_arrival_minutes": [360],
|
||||
"guest_auto_ids": ["guest-auto-00000001"], "guest_auto_count": 1,
|
||||
"domain_event_counter": 300, "published_ids_count": 300, "events": []}
|
||||
result["minutes"][28]["operations_observation"] = {
|
||||
k: copy.deepcopy(v) for k, v in result["at_390"].items() if k != "events"}
|
||||
return result
|
||||
|
||||
|
||||
def evidence(full, events=None):
|
||||
sidecar = {"report": copy.deepcopy(full), "at_390_event_verbatim": copy.deepcopy(events or [])}
|
||||
return check.redact_report(full), sidecar
|
||||
|
||||
|
||||
def validate_report(full):
|
||||
public, sidecar = evidence(full)
|
||||
return check.check_report(public, sidecar)
|
||||
|
||||
|
||||
def validate_pair(writer, reader):
|
||||
public_writer, sw = evidence(writer)
|
||||
public_reader, sr = evidence(reader)
|
||||
return check.check_pair(public_writer, public_reader, sw, sr)
|
||||
|
||||
|
||||
def summarize(event, eid):
|
||||
return {**{k: event[k] for k in ("event_id", "event_type", "absolute_minute")},
|
||||
"raw_event_sha256": check.json_sha(event),
|
||||
"canonical_sha256": check.json_sha(check.canonical_event(event, eid))}
|
||||
|
||||
|
||||
def write(path, value):
|
||||
with Path(path).open("x") as stream:
|
||||
json.dump(value, stream)
|
||||
|
||||
|
||||
def audio(pid):
|
||||
return {"coordinator": True, "passed": True, "owned_engine_pid": pid,
|
||||
"all_owned_targets_released": True, "deadline_reached": False,
|
||||
"clock_process_held_stopped": True, "window_input_held_disabled": True,
|
||||
"restored_before_quit": False, "shared_bus_deleted": False, "settings_mutated": False}
|
||||
|
||||
|
||||
def synthetic_receipt(out):
|
||||
rows = []
|
||||
profiles = {}
|
||||
source_package = out / "synthetic-package"
|
||||
source_package.mkdir()
|
||||
(source_package / "world.bin").write_bytes(DATA)
|
||||
(source_package / "manifest.json").write_text("{}")
|
||||
transfers = {kind: driver.create_transfer(source_package, out / (kind + "-transfer"), write)
|
||||
for kind in ("R", "O")}
|
||||
for index, label in enumerate(check.PHASES):
|
||||
folder = out / label
|
||||
folder.mkdir()
|
||||
pid = 100 + index
|
||||
process = {"exit_code": 0, "helper_exit_code": 0, "timed_out": False, "error_lines": [],
|
||||
"lifecycle_error": None, "external_process_or_group_signals": 0,
|
||||
"owned_popen_ended": True, "old_helper_engine_ended": True,
|
||||
"start_receipt_written": True, "end_receipt_written": True}
|
||||
capture = {"passed": True, "all_captured_ended": True, "actual_captured_pids": [pid],
|
||||
"outer_cleanup_errors": [], "adapter_exception": None, "foreign_process_or_group_signals": 0,
|
||||
"all_cleanup_before_outer_receipt": True, "outer_receipt_written": True,
|
||||
"outer_receipt_error": None, "cleanup": []}
|
||||
write(folder / "outer-owned-finally.json", {k: v for k, v in capture.items() if k not in
|
||||
("passed", "outer_receipt_written", "outer_receipt_error")})
|
||||
for filename, key in (("started.json", "actual_start_binding"), ("ended.json", "actual_end_binding")):
|
||||
binding = {"pid": pid, "actual_pid": pid, "owner_pid": 99,
|
||||
"argv": ["fake", "--check-only"], "command": ["fake", "--check-only"]}
|
||||
write(folder / filename, binding)
|
||||
process[key] = binding
|
||||
process["adapter_owned_cleanup"] = {"engine_ended": True, "errors": []}
|
||||
write(folder / "adapter-owned-cleanup.json", process["adapter_owned_cleanup"])
|
||||
process.update(pid=pid, argv=["fake", "--check-only"])
|
||||
raw = ""
|
||||
row = {"phase": label, "passed": True, "process": process, "capture": capture,
|
||||
"timeout_seconds": 1800, "diagnostics": [], "argv": ["fake", "--check-only"],
|
||||
"stage_before": HASH, "stage_after": HASH, "stage_preserved": True,
|
||||
"source_preserved": True, "player_preserved": True}
|
||||
startup = out / (label + "-startup")
|
||||
startup.write_bytes(DATA)
|
||||
row["startup_project"] = {"fresh_before": True, "path": str(startup), "sha256": HASH}
|
||||
if index >= 3:
|
||||
observed = report(label, pid)
|
||||
if label.endswith("reader"):
|
||||
observed["transfer_manifest_sha256"] = check.sha(out / (label[0] + "-transfer/manifest.json"))
|
||||
public, sidecar = evidence(observed)
|
||||
row["report"] = public
|
||||
raw = "P28_RESTART_REPORT=" + json.dumps(public) + "\nP28_RESTART_AUDIO_CLOSE=" + json.dumps(audio(pid)) + "\n"
|
||||
profile = str(out / (label + "-private"))
|
||||
row.update(runtime_profile=profile, runtime_name=label)
|
||||
profiles[profile] = {}
|
||||
for kind in (("good",) if label.endswith("writer") else ("good", "tampered")):
|
||||
for name, value in transfers[label[0]]["files"][kind].items():
|
||||
profiles[profile]["p28_restart/" + kind + "/" + name] = value
|
||||
guard = {"verified": True, "conditions": [True] * 5, "conditions_checked": 5,
|
||||
"actual_user_data_dir": profile, "expected_user_data_dir": profile,
|
||||
"globalized_probe": str(Path(profile) / "guard-probe"), "custom_user_dir_name": label,
|
||||
"fresh_before_parent_initialize": True, "profile_writes_before_guard": 0,
|
||||
"project_settings_persisted": False}
|
||||
guard_path = out / (label + "-guard.json")
|
||||
write(guard_path, guard)
|
||||
row.update(guard=guard, guard_sha256=check.sha(guard_path))
|
||||
states = out / (label + "-states")
|
||||
states.mkdir()
|
||||
sidecar_path = Path(profile) / check.SIDECAR
|
||||
sidecar_path.parent.mkdir(parents=True)
|
||||
write(sidecar_path, sidecar)
|
||||
(states / "report_full.json").write_bytes(sidecar_path.read_bytes())
|
||||
row["sidecar"] = {"path": check.SIDECAR, **check.pin(sidecar_path)}
|
||||
profiles[profile][check.SIDECAR] = check.pin(sidecar_path)
|
||||
for name in observed["states"]:
|
||||
(states / (name + ".bin")).write_bytes(DATA)
|
||||
for name, content in (("stdout.log", raw), ("stderr.log", ""), ("engine-native.log", "native\n")):
|
||||
(folder / name).write_text(content)
|
||||
row["raw"] = {n: check.pin(folder / n) for n in ("stdout.log", "stderr.log", "engine-native.log")}
|
||||
process.update(stdout_sha256=row["raw"]["stdout.log"]["sha256"],
|
||||
stderr_sha256=row["raw"]["stderr.log"]["sha256"])
|
||||
write(folder / "process.json", process)
|
||||
row["owned_files"] = {n: check.pin(folder / n) for n in
|
||||
("process.json", "started.json", "ended.json", "adapter-owned-cleanup.json", "outer-owned-finally.json")}
|
||||
rows.append(row)
|
||||
probe = out / "restart_probe.gd"
|
||||
probe.write_bytes((check.HERE / probe.name).read_bytes())
|
||||
source = {str(i): {"bytes": 1, "sha256": HASH} for i in range(1407)}
|
||||
worker = {n: check.sha(check.HERE / n) for n in
|
||||
("restart_pair.py", "restart_probe.gd", "check_restart.py", "test_restart.py")}
|
||||
return {"passed": True, "error": None, "cleanup_errors": [], "phases": rows,
|
||||
"source_before": source, "source_after": copy.deepcopy(source),
|
||||
"default_player_before": {}, "default_player_after": {},
|
||||
"dependencies_before": check.DEPENDENCIES.copy(), "dependencies_after": check.DEPENDENCIES.copy(),
|
||||
"collector_before": driver.COLLECTOR_PINS.copy(), "collector_after": driver.COLLECTOR_PINS.copy(),
|
||||
"worker_before": worker, "worker_after": worker.copy(), "profiles": profiles,
|
||||
"cross_cutting": copy.deepcopy(check.CROSS), "original_credit": 0,
|
||||
"windows_verified": False, "operations_200_guests_verified": False,
|
||||
"coverage": check.coverage([report("R-writer"), report("O-writer")]),
|
||||
"script_pins": {str(probe): check.sha(probe)}, "owned_writes": [],
|
||||
"cleanup": [{"pid": 100 + n, "engine_ended": True, "outer_cleanup_errors": [],
|
||||
"foreign_process_or_group_signals": 0} for n in range(7)]}
|
||||
|
||||
|
||||
class RestartTests(unittest.TestCase):
|
||||
@staticmethod
|
||||
def here_inventory():
|
||||
return {str(path.relative_to(check.HERE)): hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
for path in check.HERE.rglob("*") if path.is_file()}
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.inventory_before = cls.here_inventory()
|
||||
|
||||
def setUp(self):
|
||||
# Check after every test, including failures, independently of test order.
|
||||
self.addCleanup(lambda: self.assertEqual(self.here_inventory(), self.inventory_before))
|
||||
|
||||
def test_zz_here_inventory_unchanged(self):
|
||||
# unittest's default ordering places this after all twelve existing tests.
|
||||
self.assertIn("test_restart.py", self.inventory_before)
|
||||
self.assertEqual(self.here_inventory(), self.inventory_before)
|
||||
|
||||
def actual_canonical_sidecar(self):
|
||||
relative = Path("native-e524ff2-canonical-failure-r01/report_full.json")
|
||||
roots = [check.ROOT / "build",
|
||||
check.ROOT.parent / ".runtime" / "P28-01-RESTART" / "build"]
|
||||
if os.environ.get("BUILD_OUTPUT_DIR"):
|
||||
roots.insert(0, Path(os.environ["BUILD_OUTPUT_DIR"]))
|
||||
path = next((root / relative for root in roots if (root / relative).is_file()), None)
|
||||
self.assertIsNotNone(path, "required retained native-e524ff2 sidecar is missing")
|
||||
raw = path.read_bytes()
|
||||
self.addCleanup(lambda: self.assertEqual(path.read_bytes(), raw))
|
||||
return json.loads(raw)
|
||||
|
||||
def test_z1_actual_text_key_order_hash(self):
|
||||
sidecar = self.actual_canonical_sidecar()
|
||||
events = sidecar["at_390_event_verbatim"]
|
||||
self.assertEqual(len(events), 16)
|
||||
first = events[0]
|
||||
self.assertEqual(first["event_id"], "domain_event.000000000009")
|
||||
self.assertEqual(first["absolute_minute"], 362)
|
||||
self.assertEqual(first["payload"],
|
||||
{"old_state": 1, "reason": "ENTRY_COMPLETED", "state": 2})
|
||||
self.assertEqual(check.json_sha(first),
|
||||
"9ca80bec0c605ecd288f7671952d30fe66c67f291c5a618ea54623f07394edb9")
|
||||
for key in ("raw_event_sha256", "canonical_sha256"):
|
||||
self.assertEqual(sidecar["report"]["at_390"]["events"][0][key],
|
||||
"f28afa0899d7efea59ab99ae06276ba354779a73cac705fa4801162f27c592d7")
|
||||
with self.assertRaisesRegex(ValueError, "at390 raw/canonical SHA"):
|
||||
check.check_sidecar(check.redact_report(sidecar["report"]), sidecar)
|
||||
|
||||
def test_z1_all_actual_event_bindings(self):
|
||||
original = self.actual_canonical_sidecar()
|
||||
sidecar = copy.deepcopy(original)
|
||||
full = sidecar["report"]
|
||||
self.assertEqual(len(sidecar["at_390_event_verbatim"]), 16)
|
||||
self.assertEqual(len(full["at_390"]["events"]), 16)
|
||||
for event, summary in zip(sidecar["at_390_event_verbatim"], full["at_390"]["events"]):
|
||||
summary["raw_event_sha256"] = check.json_sha(event)
|
||||
summary["canonical_sha256"] = check.json_sha(
|
||||
check.canonical_event(event, full["event_id"]))
|
||||
self.assertEqual(sidecar["at_390_event_verbatim"], original["at_390_event_verbatim"])
|
||||
self.assertEqual(check.check_sidecar(check.redact_report(full), sidecar), full)
|
||||
for index in range(16):
|
||||
with self.subTest(index=index):
|
||||
changed = copy.deepcopy(sidecar)
|
||||
changed["report"]["at_390"]["events"][index]["raw_event_sha256"] = "0" * 64
|
||||
with self.assertRaisesRegex(ValueError, "at390 raw/canonical SHA"):
|
||||
check.check_sidecar(check.redact_report(changed["report"]), changed)
|
||||
|
||||
def test_z2_collision_rejected(self):
|
||||
for ambiguous in ({1: "numeric", "1": "text"},
|
||||
{"nested": [{1: "numeric", "1": "text"}]}):
|
||||
with self.subTest(value=ambiguous), self.assertRaisesRegex(
|
||||
ValueError, "canonical_key_collision"):
|
||||
check.json_sha(ambiguous)
|
||||
full = report()
|
||||
full["stop_reason"] = "canonical_key_collision"
|
||||
public, sidecar = evidence(full)
|
||||
with self.assertRaisesRegex(ValueError, "canonical_key_collision"):
|
||||
check.check_report(public, sidecar)
|
||||
public["stop_reason"] = "complete"
|
||||
with self.assertRaises(ValueError):
|
||||
check.check_report(public, sidecar)
|
||||
|
||||
def rejects(self, original, mutations, validator):
|
||||
for name, mutation in mutations.items():
|
||||
with self.subTest(rejection=name):
|
||||
value = copy.deepcopy(original)
|
||||
mutation(value)
|
||||
with self.assertRaises((ValueError, KeyError, TypeError)):
|
||||
validator(value)
|
||||
|
||||
def test_rs1_signal_bool_void(self):
|
||||
for kind in ("save", "load", "tamper"):
|
||||
check.check_io(io(kind), kind)
|
||||
self.rejects(io("save"), {
|
||||
"true only": lambda x: x.pop("signal_count"),
|
||||
"no signal": lambda x: x.update(signal_count=0),
|
||||
"duplicate signal": lambda x: x.update(signal_count=2),
|
||||
"bool differs": lambda x: x.update(return_value=False),
|
||||
"latest differs": lambda x: x["latest"].update(code="FAILED"),
|
||||
"payload typed mismatch": lambda x: x.update(payload_bytes_equal=False),
|
||||
"state mismatch": lambda x: x["comparison"].update(equal=False),
|
||||
}, lambda x: check.check_io(x, "save"))
|
||||
self.rejects(io("load"), {
|
||||
"void success": lambda x: x.update(return_value=True),
|
||||
"void coerced bool": lambda x: x.update(return_kind="bool"),
|
||||
"missing signal": lambda x: x.update(signal_count=0),
|
||||
}, lambda x: check.check_io(x, "load"))
|
||||
|
||||
def test_rs2_exact_event_transform(self):
|
||||
for kind in ("save", "load", "tamper"):
|
||||
for count in (0, 255, 256):
|
||||
check.check_io(io(kind, count=count), kind)
|
||||
self.rejects(io(kind), {
|
||||
"two events": lambda x: x["after"]["recent_events"].append(copy.deepcopy(x["event"])),
|
||||
"different E": lambda x: x["event"].update(event_id="domain_event.000009999999"),
|
||||
"counter": lambda x: x["after"].update(counter=x["before"]["counter"] + 2),
|
||||
"missing ID": lambda x: x["after"]["published_ids"].pop(0),
|
||||
"duplicate ID": lambda x: x["after"]["published_ids"].append(x["event"]["event_id"]),
|
||||
"unsorted IDs": lambda x: x["after"]["published_ids"].reverse(),
|
||||
"drop tail": lambda x: x["after"].update(recent_events=x["before"]["recent_events"][:-1] + [x["event"]]),
|
||||
"wrong dropped record": lambda x: x.update(dropped=x["before"]["recent_events"][-1]),
|
||||
"excluded path": lambda x: x["comparison"].update(excluded_paths=["systems.runtime.economy"]),
|
||||
"diff hidden": lambda x: x["comparison"].update(diff_paths=["$.clock"]),
|
||||
"digest only": lambda x: x["comparison"].update(method="SimulationDigest"),
|
||||
"bytes SHA disagreement": lambda x: x["comparison"].update(actual_sha256="f" * 64),
|
||||
}, lambda x: check.check_io(x, kind))
|
||||
|
||||
def test_clarification01_tamper(self):
|
||||
self.rejects(io("tamper"), {
|
||||
"failure event ignored": lambda x: x.update(after=x["before"]),
|
||||
"failure ok": lambda x: x["signal_payload"].update(ok=True),
|
||||
"failure OK code": lambda x: x["signal_payload"].update(code="OK"),
|
||||
"not restored": lambda x: x["failure"].update(preload_pause_restored=False),
|
||||
"paused differs": lambda x: x.update(paused_after=False),
|
||||
"loaded pause differs": lambda x: x["failure"].update(loaded_clock_paused=False),
|
||||
"restore committed": lambda x: x["failure"]["recovery_report"].update(restore_committed=True),
|
||||
"migration unrecorded": lambda x: x["failure"].pop("migration_attempted"),
|
||||
}, lambda x: check.check_io(x, "tamper"))
|
||||
self.rejects(report("R-reader"), {
|
||||
"failed event retained": lambda x: x.update(failure_event_absent=False),
|
||||
"backlog consumed": lambda x: x["paused_idle"]["after"].update(backlog=120),
|
||||
"reader not paused": lambda x: x["io"][1].update(paused_after=False),
|
||||
}, validate_report)
|
||||
|
||||
def test_rs3_actual_k_full_typed_cross_pid(self):
|
||||
writer, reader = report(), report("R-reader", 101)
|
||||
validate_pair(writer, reader)
|
||||
self.rejects(reader, {
|
||||
"K call count": lambda x: x["minutes"][0].update(after=x["saved_minute"]),
|
||||
"different K": lambda x: x.update(actual_k=29),
|
||||
"missing minute": lambda x: x["minutes"].pop(),
|
||||
"partial digest": lambda x: x["minutes"][0].update(scope="SimulationDigest"),
|
||||
"minute SHA": lambda x: x["minutes"][0].update(sha256="f" * 64),
|
||||
"wrong replacement": lambda x: x["minutes"][0]["replacement"].update(event_id="other"),
|
||||
"two replacements": lambda x: x["minutes"][0].update(replacement_count=2),
|
||||
"after window": lambda x: x["minutes"][6].update(replacement=x["io"][-1]["event"], replacement_count=1),
|
||||
"excluded path": lambda x: x["minutes"][0].update(excluded_paths=["clock"]),
|
||||
"same PID": lambda x: x.update(pid=100),
|
||||
}, lambda x: validate_pair(writer, x))
|
||||
|
||||
def test_rs4_rs5_arrival_boundary(self):
|
||||
writer, reader = report("O-writer"), report("O-reader", 101)
|
||||
validate_pair(writer, reader)
|
||||
self.rejects(writer, {
|
||||
"boundary only": lambda x: x["boundary"].update(guest_auto_count=0),
|
||||
"null arrival": lambda x: x.update(boundary=None),
|
||||
"unprocessed": lambda x: x["boundary"].update(processed_arrival_minutes=[]),
|
||||
"not first tick": lambda x: x["ui_ticks"].insert(0, copy.deepcopy(x["boundary"])),
|
||||
"duplicate guest": lambda x: x["boundary"]["guest_auto_ids"].append("guest-auto-00000001"),
|
||||
"next missing": lambda x: x["next_boundary"].update(processed_once=False),
|
||||
"next duplicate": lambda x: x["next_boundary"]["after"].append(420),
|
||||
"cap exceeded": lambda x: x.update(start_minute=200),
|
||||
}, validate_report)
|
||||
|
||||
def test_d1_import_closure(self):
|
||||
check.check_dependencies()
|
||||
check.check_modules()
|
||||
for name in ("restart_pair.py", "check_restart.py", "test_restart.py"):
|
||||
check.check_imports((check.HERE / name).read_text())
|
||||
for text in ("import forbidden_project_module", "from tools.strict_test_markers import main",
|
||||
"importlib.util.spec_from_file_location('x', 'tools/evil.py')",
|
||||
"__import__('tools.evil')", "exec('import tools.evil')"):
|
||||
with self.subTest(text=text), self.assertRaises(ValueError):
|
||||
check.check_imports(text)
|
||||
with self.assertRaises(ValueError):
|
||||
check.check_modules({"bad": SimpleNamespace(__file__=str(check.ROOT / "tools/evil.py"))})
|
||||
for path in check.DEPENDENCIES:
|
||||
original = check.sha
|
||||
with self.subTest(pin=path), patch.object(check, "sha", side_effect=lambda p:
|
||||
"0" * 64 if Path(p) == check.ROOT / path else original(p)), self.assertRaises(ValueError):
|
||||
check.check_dependencies()
|
||||
|
||||
def test_d2_order_and_check_plan(self):
|
||||
collector, _, _ = driver.load_dependencies()
|
||||
commands = driver.plan(collector, Path("/engine"), Path("/out"))
|
||||
self.assertEqual(len(commands), 7)
|
||||
self.assertIn("--import", commands[0])
|
||||
self.assertIn("--check-only", commands[1])
|
||||
self.assertIn("--check-only", commands[2])
|
||||
rows = []
|
||||
for _, label in driver.schedule(rows):
|
||||
rows.append({"phase": label, "passed": True, "report": {"stop_reason": "complete"}})
|
||||
self.assertEqual([r["phase"] for r in rows], check.PHASES)
|
||||
rows = []
|
||||
iterator = driver.schedule(rows)
|
||||
for _ in range(4):
|
||||
_, label = next(iterator)
|
||||
rows.append({"phase": label, "passed": True, "report": {"stop_reason": "complete"}})
|
||||
rows[-1]["passed"] = False
|
||||
with self.assertRaises(ValueError):
|
||||
next(iterator)
|
||||
rows[-1]["passed"] = True
|
||||
rows[-1]["report"]["stop_reason"] = "failed"
|
||||
iterator = driver.schedule([])
|
||||
next(iterator)
|
||||
with self.assertRaises(ValueError):
|
||||
next(iterator)
|
||||
|
||||
def test_d3_transfer_and_fresh_profiles(self):
|
||||
with tempfile.TemporaryDirectory(dir=os.environ.get("BUILD_OUTPUT_DIR") or None) as temporary:
|
||||
base = Path(temporary)
|
||||
source = base / "source"
|
||||
source.mkdir()
|
||||
(source / "world.bin").write_bytes(b"0123456789")
|
||||
(source / "manifest.json").write_text("{}")
|
||||
target = base / "transfer"
|
||||
manifest = driver.create_transfer(source, target, write)
|
||||
driver.check_transfer(target, manifest)
|
||||
self.assertEqual((target / "tampered/world.bin").read_bytes(), b"0123446789")
|
||||
driver.fresh_profile(base / "new-profile")
|
||||
with self.assertRaises(ValueError):
|
||||
driver.fresh_profile(source)
|
||||
self.rejects(manifest, {
|
||||
"SHA": lambda x: x["files"]["good"]["world.bin"].update(sha256="f" * 64),
|
||||
"offset": lambda x: x["tamper"].update(offset=0),
|
||||
"xor": lambda x: x["tamper"].update(xor=2),
|
||||
}, lambda x: driver.check_transfer(target, x))
|
||||
(target / "tampered/manifest.json").write_text("changed")
|
||||
with self.assertRaises(ValueError):
|
||||
driver.check_transfer(target, manifest)
|
||||
|
||||
def test_d4_d5_d7_probe_static(self):
|
||||
text = (check.HERE / "restart_probe.gd").read_text()
|
||||
check.check_probe(text)
|
||||
snippets = ["GameClock.advance_minutes(1)", "GameClock.set_paused(false)",
|
||||
"x.clear()", "spawn_guest()", "SimulationRuntime.new()", "initialize()",
|
||||
"enable_production_checkpoint()", "main._toggle_pause()", "GameClock.set_paused(true)",
|
||||
"var elapsed_us = 1", "Time.get_ticks_usec()"]
|
||||
for snippet in snippets:
|
||||
with self.subTest(snippet=snippet), self.assertRaises(ValueError):
|
||||
check.check_probe(text + "\nfunc bad():\n\t" + snippet + "\n")
|
||||
for replacement in ("GameClock.advance_minutes(2)", "Time.get_ticks_usec()",
|
||||
"GameClock.advance_one_minute()"):
|
||||
with self.subTest(step=replacement), self.assertRaises(ValueError):
|
||||
check.check_probe(text.replace("GameClock.advance_minutes(1)", replacement))
|
||||
|
||||
def test_y2_direct_variant_inference_guard(self):
|
||||
text = (check.HERE / "restart_probe.gd").read_text()
|
||||
check.check_probe(text)
|
||||
snippets = [
|
||||
"var x := GameClock.get_backlog_minutes()",
|
||||
'var y := field(d,"k")',
|
||||
"var x := runtime.create_snapshot()",
|
||||
"var x := main",
|
||||
"var x := other_variant.value",
|
||||
"var x := other_field(d)",
|
||||
]
|
||||
declarations = "\nvar other_variant: Variant\nfunc other_field(d) -> Variant:\n\treturn d\n"
|
||||
for snippet in snippets:
|
||||
with self.subTest(snippet=snippet), self.assertRaisesRegex(ValueError, "direct Variant inference"):
|
||||
check.check_probe(text + declarations + "\nfunc bad():\n\t" + snippet + "\n")
|
||||
for snippet in (
|
||||
"var x: int = GameClock.get_backlog_minutes()",
|
||||
'var y: Variant = field(d,"k")',
|
||||
"var x := 1",
|
||||
"var x := witness(d)",
|
||||
"var x := GameClock_extra.get_backlog_minutes()",
|
||||
):
|
||||
with self.subTest(allowed=snippet):
|
||||
check.check_probe(text + "\nfunc allowed():\n\t" + snippet + "\n")
|
||||
|
||||
def test_rs7_rs8_full_receipt_rejections(self):
|
||||
with tempfile.TemporaryDirectory(dir=os.environ.get("BUILD_OUTPUT_DIR") or None) as temporary:
|
||||
out = Path(temporary)
|
||||
receipt = synthetic_receipt(out)
|
||||
check.check_receipt(receipt, out)
|
||||
self.rejects(receipt, {
|
||||
"source map": lambda x: x["source_after"].pop("0"),
|
||||
"ordinary player": lambda x: x["default_player_after"].update(changed=True),
|
||||
"guard": lambda x: x["phases"][3]["guard"].update(profile_writes_before_guard=1),
|
||||
"raw SHA": lambda x: x["phases"][3]["raw"]["stdout.log"].update(sha256="f" * 64),
|
||||
"PID": lambda x: x["phases"][3]["capture"].update(actual_captured_pids=[999]),
|
||||
"owned write": lambda x: x["owned_writes"].append("R-writer/report.json"),
|
||||
"cleanup": lambda x: x["cleanup"][0].update(engine_ended=False),
|
||||
"stage": lambda x: x["phases"][1].update(stage_after="f" * 64),
|
||||
"collector": lambda x: x["collector_after"].pop("measure_p28_01.gd"),
|
||||
"dependency": lambda x: x["dependencies_after"].update({"tools/run_all_tests.py": "f" * 64}),
|
||||
"cross-cutting": lambda x: x["cross_cutting"].pop(),
|
||||
"source count": lambda x: (x["source_after"].pop("1"), x["source_before"].pop("1")),
|
||||
"phase order": lambda x: x["phases"].reverse(),
|
||||
"foreign signal": lambda x: x["phases"][0]["capture"].update(foreign_process_or_group_signals=1),
|
||||
}, lambda x: check.check_receipt(x, out))
|
||||
(out / "R-reader-states/B.bin").write_bytes(b"changed")
|
||||
with self.assertRaises(ValueError):
|
||||
check.check_receipt(receipt, out)
|
||||
|
||||
def test_d6_raw_report_and_audio(self):
|
||||
with tempfile.TemporaryDirectory(dir=os.environ.get("BUILD_OUTPUT_DIR") or None) as temporary:
|
||||
out = Path(temporary)
|
||||
receipt = synthetic_receipt(out)
|
||||
row = receipt["phases"][3]
|
||||
folder = out / "R-writer"
|
||||
original = (folder / "stdout.log").read_text()
|
||||
for text in ("", original + original, original.split("P28_RESTART_AUDIO_CLOSE=")[0],
|
||||
original.replace('"owned_engine_pid": 103', '"owned_engine_pid": 999')):
|
||||
(folder / "stdout.log").write_text(text)
|
||||
row["raw"]["stdout.log"] = check.pin(folder / "stdout.log")
|
||||
with self.assertRaises(ValueError):
|
||||
check.bound_raw(folder, row)
|
||||
|
||||
def test_d2_d6_real_owned_outer_fake_child(self):
|
||||
collector, _, outer = driver.load_dependencies()
|
||||
child = (
|
||||
"import os,sys,json,time,pathlib\n"
|
||||
"mode=sys.argv[1]\n"
|
||||
"identity={'pid':os.getpid(),'ppid':os.getppid(),'pgid':os.getpgrp(),"
|
||||
"'args':' '.join([sys.executable]+sys.argv)}\n"
|
||||
"path=pathlib.Path(sys.argv[0]).parent/(mode+'-identity')\n"
|
||||
"tmp=path.with_suffix('.tmp'); tmp.write_text(json.dumps(identity)); tmp.replace(path)\n"
|
||||
"pathlib.Path(sys.argv[sys.argv.index('--log-file')+1]).write_text('native raw\\n')\n"
|
||||
"print('Godot Engine v4.7.2.stable.official.ed1daf0bf - https://godotengine.org',flush=True)\n"
|
||||
"time.sleep(0.15)\n"
|
||||
"if mode=='timeout': time.sleep(5)\n"
|
||||
"sys.exit(3 if mode=='exit' else 0)\n")
|
||||
with tempfile.TemporaryDirectory(dir=os.environ.get("BUILD_OUTPUT_DIR") or None) as temporary:
|
||||
base = Path(temporary)
|
||||
script = base / "fake-child"
|
||||
script.write_text(child)
|
||||
def diagnostic(command, folder, timeout, env, **kwargs):
|
||||
def probe(pid):
|
||||
path = base / (command[2] + "-identity")
|
||||
deadline = time.monotonic() + 2
|
||||
while time.monotonic() < deadline:
|
||||
if path.exists():
|
||||
identity = json.loads(path.read_text())
|
||||
if identity["pid"] == pid:
|
||||
return identity
|
||||
time.sleep(.005)
|
||||
raise RuntimeError("fake child identity unavailable")
|
||||
return outer.native.run_owned_diagnostic(command, folder, timeout, env, probe_fn=probe, **kwargs)
|
||||
for mode in ("ok", "exit", "timeout"):
|
||||
folder = base / mode
|
||||
def run_phase(command, destination, limit, env, **kwargs):
|
||||
self.assertEqual(limit, 1800)
|
||||
return outer.run_phase(command, destination, .3 if mode == "timeout" else 5,
|
||||
env, diagnostic_fn=diagnostic, **kwargs)
|
||||
def guarded_write(path, value):
|
||||
self.assertFalse(Path(path).is_relative_to(folder), "driver writes owned folder")
|
||||
write(path, value)
|
||||
def real_phase(*args, **kwargs):
|
||||
kwargs["dump_fn"] = write
|
||||
return run_phase(*args, **kwargs)
|
||||
with patch.object(collector, "live_engines", return_value=[]), \
|
||||
patch.object(collector, "write", side_effect=guarded_write):
|
||||
row = collector.run_engine(SimpleNamespace(run_phase=real_phase),
|
||||
[sys.executable, str(script), mode], folder, {})
|
||||
row["phase"] = "import"
|
||||
self.assertEqual(row["passed"], mode == "ok", row)
|
||||
self.assertTrue(row["capture"]["all_captured_ended"])
|
||||
if mode == "ok":
|
||||
check.bound_raw(folder, row)
|
||||
else:
|
||||
with self.assertRaises(ValueError):
|
||||
check.bound_raw(folder, row)
|
||||
_, errors = outer.finish_captured(outer.OWNED)
|
||||
self.assertEqual(errors, [])
|
||||
|
||||
def test_v1_at390_required_and_pair_consistency(self):
|
||||
writer, reader = report("O-writer"), report("O-reader", 101)
|
||||
validate_pair(writer, reader)
|
||||
self.rejects(writer, {
|
||||
"absent": lambda x: x.pop("at_390"),
|
||||
"null": lambda x: x.update(at_390=None),
|
||||
"not reached": lambda x: x.update(stop_reason="at_390_not_reached"),
|
||||
"clock": lambda x: x["at_390"]["clock"].update(absolute_minute=391),
|
||||
"counter type": lambda x: x["at_390"].update(domain_event_counter="300"),
|
||||
"row differs": lambda x: x["minutes"][28]["operations_observation"].update(guest_auto_count=2),
|
||||
}, validate_report)
|
||||
for mutate in (lambda x: x.pop("at_390"), lambda x: x.update(at_390=None)):
|
||||
w, r = copy.deepcopy(writer), copy.deepcopy(reader)
|
||||
mutate(w)
|
||||
mutate(r)
|
||||
with self.assertRaises((ValueError, KeyError)):
|
||||
validate_pair(w, r)
|
||||
different = copy.deepcopy(reader)
|
||||
different["at_390"]["operations"]["population"] = 3
|
||||
different["minutes"][28]["operations_observation"]["operations"]["population"] = 3
|
||||
validate_report(different)
|
||||
with self.assertRaises(ValueError):
|
||||
validate_pair(writer, different)
|
||||
with self.assertRaises((ValueError, KeyError)):
|
||||
validate_pair(writer, {**reader, "at_390": None})
|
||||
|
||||
def test_v2_all_seven_families_restore_verbatim(self):
|
||||
full = report()
|
||||
# T4 and T6 are conditional: an earlier SAVE event is the dropped head.
|
||||
old = io("save", minute=600, count=0)["event"]
|
||||
row = full["io"][0]
|
||||
row["before"]["recent_events"][0] = old
|
||||
row["dropped"] = copy.deepcopy(old)
|
||||
row["after"] = check.transform(row["before"], row["event"])
|
||||
public, sidecar = evidence(full)
|
||||
check.check_report(public, sidecar)
|
||||
paths = check.payload_paths(public)
|
||||
for path in ("/report/io/0/signal_payload", "/report/io/0/latest",
|
||||
"/report/io/0/event/payload", "/report/io/0/before/recent_events/0/payload",
|
||||
"/report/io/0/after/recent_events/255/payload", "/report/io/0/dropped/payload",
|
||||
"/report/minutes/0/replacement/payload"):
|
||||
self.assertEqual(paths[path]["timing_redactions"], [
|
||||
{"original_key": key, "sidecar_ref": path + "/" + key} for key in check.TIMINGS])
|
||||
self.assertNotIn("duration_us", paths[path])
|
||||
self.assertEqual(public["io"][0]["after"]["recent_events"][0],
|
||||
full["io"][0]["after"]["recent_events"][0])
|
||||
self.assertEqual(check.check_sidecar(public, sidecar), full)
|
||||
for kind in ("save", "load", "tamper"):
|
||||
full_row = io(kind)
|
||||
check.check_io(full_row, kind)
|
||||
self.rejects(sidecar, {
|
||||
"root extra": lambda x: x.update(extra={}),
|
||||
"performance use": lambda x: x["report"].update(statistics={"average": 1}),
|
||||
"outside closure": lambda x: x["report"].update(elapsed_ms=1),
|
||||
"nested timing": lambda x: x["report"]["io"][0]["event"]["payload"].update(nested={"latency_us": 1}),
|
||||
"missing timing": lambda x: x["report"]["io"][0]["latest"].pop("duration_us"),
|
||||
"tamper value": lambda x: x["report"]["io"][0]["latest"].update(code="CHANGED"),
|
||||
"nonSAVE": lambda x: x["report"]["io"][0]["before"]["recent_events"][0].update(event_type="OLD"),
|
||||
"wrong service": lambda x: x["report"]["io"][0]["before"]["recent_events"][0].update(source_service="other"),
|
||||
}, lambda x: check.check_sidecar(public, x))
|
||||
self.rejects(public, {
|
||||
"leaked key": lambda x: x["io"][0]["signal_payload"].update(duration_us=124),
|
||||
"hidden payload leak": lambda x: x.update(payload={"hidden_ms": 1}),
|
||||
"extra marker": lambda x: x["io"][0]["latest"]["timing_redactions"].append(
|
||||
{"original_key": "other", "sidecar_ref": "/report/other"}),
|
||||
"missing marker": lambda x: x["io"][0]["latest"].pop("timing_redactions"),
|
||||
"one marker": lambda x: x["io"][0]["latest"]["timing_redactions"].pop(),
|
||||
"wrong pointer": lambda x: x["io"][0]["latest"]["timing_redactions"][0].update(
|
||||
sidecar_ref="/report/io/0/signal_payload/duration_us"),
|
||||
"unresolved pointer": lambda x: x["io"][0]["latest"]["timing_redactions"][0].update(
|
||||
sidecar_ref="/report/io/999/latest/duration_us"),
|
||||
"extra marker field": lambda x: x["io"][0]["latest"]["timing_redactions"][0].update(extra=1),
|
||||
"nonSAVE marker": lambda x: x["io"][0]["after"]["recent_events"][0]["payload"].update(
|
||||
timing_redactions=copy.deepcopy(x["io"][0]["latest"]["timing_redactions"])),
|
||||
"restore differs": lambda x: x.update(pid=999),
|
||||
}, lambda x: check.check_sidecar(x, sidecar))
|
||||
with self.assertRaises(ValueError):
|
||||
check.check_report(public)
|
||||
# The raw equality check must see differences hidden by public redaction.
|
||||
tampered = copy.deepcopy(sidecar)
|
||||
tampered["report"]["io"][0]["latest"]["duration_us"] += 1
|
||||
self.assertEqual(check.redact_report(tampered["report"]), public)
|
||||
with self.assertRaises(ValueError):
|
||||
check.check_report(public, tampered)
|
||||
|
||||
def test_v2_at390_full_array_binding(self):
|
||||
full = report("O-writer")
|
||||
events = [
|
||||
{"event_id": "domain_event.%012d" % n, "event_type": "ARRIVAL",
|
||||
"absolute_minute": 380 + i, "source_service": "operations", "payload": {"count": i + 1}}
|
||||
for i, n in enumerate((250, 251))]
|
||||
full["at_390"]["events"] = [summarize(e, full["event_id"]) for e in events]
|
||||
public, sidecar = evidence(full, events)
|
||||
check.check_report(public, sidecar)
|
||||
self.rejects(sidecar, {
|
||||
"missing array": lambda x: x.pop("at_390_event_verbatim"),
|
||||
"missing entry": lambda x: x["at_390_event_verbatim"].pop(),
|
||||
"extra entry": lambda x: x["at_390_event_verbatim"].append(copy.deepcopy(events[0])),
|
||||
"reordered": lambda x: x["at_390_event_verbatim"].reverse(),
|
||||
"raw differs": lambda x: x["at_390_event_verbatim"][0]["payload"].update(count=7),
|
||||
"nonSAVE timing": lambda x: x["at_390_event_verbatim"][0]["payload"].update(duration_us=1),
|
||||
}, lambda x: check.check_sidecar(public, x))
|
||||
for field in ("raw_event_sha256", "canonical_sha256"):
|
||||
changed = copy.deepcopy(full)
|
||||
changed["at_390"]["events"][0][field] = "f" * 64
|
||||
pub, sc = evidence(changed, events)
|
||||
with self.assertRaises(ValueError):
|
||||
check.check_sidecar(pub, sc)
|
||||
changed = copy.deepcopy(full)
|
||||
changed["at_390"]["events"][0]["canonical_sha256"] = check.json_sha(
|
||||
{"event_id": events[0]["event_id"], "replaced_for_cross_pid_compare": True})
|
||||
pub, sc = evidence(changed, events)
|
||||
with self.assertRaises(ValueError):
|
||||
check.check_sidecar(pub, sc)
|
||||
# D5 operates on an entire E element, including type and timing differences.
|
||||
# This exercises the binding independently of the strict > saved_minute filter.
|
||||
canonical_observations = []
|
||||
raw_shas = []
|
||||
for kind in ("save", "load"):
|
||||
raw_event = io(kind, 361)["event"]
|
||||
raw_event["payload"]["duration_us"] += 1 if kind == "load" else 0
|
||||
value = copy.deepcopy(full)
|
||||
value["at_390"]["events"] = [summarize(raw_event, value["event_id"])]
|
||||
pub, sc = evidence(value, [raw_event])
|
||||
check.check_sidecar(pub, sc)
|
||||
canonical_observations.append(check.canonical_observation(value["at_390"], value["event_id"]))
|
||||
raw_shas.append(value["at_390"]["events"][0]["raw_event_sha256"])
|
||||
with self.assertRaises(ValueError):
|
||||
check.check_report(pub, sc) # E at saved_minute is outside this observation's window.
|
||||
self.assertNotEqual(raw_shas[0], raw_shas[1])
|
||||
self.assertEqual(*canonical_observations)
|
||||
# All other event fields and canonical hashes remain significant cross PID.
|
||||
reader = report("O-reader", 101)
|
||||
reader["at_390"]["events"] = copy.deepcopy(full["at_390"]["events"])
|
||||
rp, rs = evidence(reader, events)
|
||||
check.check_pair(public, rp, sidecar, rs)
|
||||
changed_events = copy.deepcopy(events)
|
||||
changed_events[0]["payload"]["count"] = 9
|
||||
reader["at_390"]["events"] = [summarize(e, reader["event_id"]) for e in changed_events]
|
||||
rp, rs = evidence(reader, changed_events)
|
||||
check.check_report(rp, rs)
|
||||
with self.assertRaises(ValueError):
|
||||
check.check_pair(public, rp, sidecar, rs)
|
||||
|
||||
def test_v2_receipt_sidecar_binding_rejections(self):
|
||||
with tempfile.TemporaryDirectory(dir=os.environ.get("BUILD_OUTPUT_DIR") or None) as temporary:
|
||||
out = Path(temporary)
|
||||
receipt = synthetic_receipt(out)
|
||||
check.check_receipt(receipt, out)
|
||||
self.rejects(receipt, {
|
||||
"unbound": lambda x: x["phases"][3].pop("sidecar"),
|
||||
"wrong SHA": lambda x: x["phases"][3]["sidecar"].update(sha256="f" * 64),
|
||||
"missing map": lambda x: x["profiles"][x["phases"][3]["runtime_profile"]].pop(check.SIDECAR),
|
||||
"wrong path": lambda x: x["phases"][3]["sidecar"].update(path="other.json"),
|
||||
"coverage omitted": lambda x: x.pop("coverage"),
|
||||
"false coverage": lambda x: x["coverage"].update(overload_nonzero_backlog="exercised_recorded"),
|
||||
"persistence claim": lambda x: x["coverage"].update(across_restart_backlog_persistence_claimed=True),
|
||||
}, lambda x: check.check_receipt(x, out))
|
||||
path = out / "R-writer-states/report_full.json"
|
||||
saved = path.read_bytes()
|
||||
path.write_bytes(b"{}")
|
||||
with self.assertRaises(ValueError):
|
||||
check.check_receipt(receipt, out)
|
||||
path.unlink()
|
||||
with self.assertRaises(ValueError):
|
||||
check.check_receipt(receipt, out)
|
||||
path.write_bytes(saved)
|
||||
private = Path(receipt["phases"][3]["runtime_profile"]) / check.SIDECAR
|
||||
private.write_bytes(b"{}")
|
||||
with self.assertRaises(ValueError):
|
||||
check.check_receipt(receipt, out)
|
||||
|
||||
def test_v3_overload_is_observation_not_persistence(self):
|
||||
for count, backlog in ((0, 0), (0, 121), (1, 0), (1, 121)):
|
||||
full = report()
|
||||
full["overloads"] = [{"minute": 612, "backlog": backlog}] * count
|
||||
full["paused_idle"]["before"]["backlog"] = backlog
|
||||
full["paused_idle"]["after"]["backlog"] = backlog
|
||||
full["overload_case"] = {"exercised": bool(count and backlog > 0),
|
||||
"overloads": count, "backlog_at_save": backlog}
|
||||
validate_report(full)
|
||||
observed = check.coverage([full])
|
||||
self.assertEqual(observed["overload_nonzero_backlog"],
|
||||
"exercised_recorded" if count and backlog else "not_exercised")
|
||||
self.assertFalse(observed["across_restart_backlog_persistence_claimed"])
|
||||
self.rejects(full, {
|
||||
"missing disclosure": lambda x: x.pop("overload_case"),
|
||||
"wrong exercised": lambda x: x["overload_case"].update(exercised=not bool(count and backlog)),
|
||||
"wrong count": lambda x: x["overload_case"].update(overloads=count + 1),
|
||||
"wrong backlog": lambda x: x["overload_case"].update(backlog_at_save=backlog + 1),
|
||||
"consumption": lambda x: x["paused_idle"]["after"].update(backlog=backlog + 1),
|
||||
"persistence claim": lambda x: x.update(across_restart_backlog_persistence_claimed=True),
|
||||
"nonzero claim": lambda x: x.update(nonzero_backlog_preserved=True),
|
||||
}, validate_report)
|
||||
writer, reader = report(), report("R-reader", 101)
|
||||
reader["paused_idle"]["before"]["backlog"] = 0
|
||||
reader["paused_idle"]["after"]["backlog"] = 0
|
||||
for row in reader["minutes"]:
|
||||
row.update(backlog_before=0, backlog_after=0)
|
||||
validate_pair(writer, reader) # No across-restart accumulator persistence requirement.
|
||||
|
||||
def test_v4_no_arrival_is_diagnostic_failure(self):
|
||||
full = report("O-writer")
|
||||
full.update(boundary=None, boundary_reason="no_arrival_within_cap",
|
||||
stop_reason="no_arrival_within_cap", next_boundary=None, io=[], minutes=[], actual_k=0)
|
||||
full["ui_ticks"] = [{"clock": n, "guest_auto_ids": [], "guest_auto_count": 0}
|
||||
for n in range(361, 481)]
|
||||
self.assertTrue(check.check_no_arrival_diagnostic(full))
|
||||
with self.assertRaisesRegex(ValueError, "incomplete probe"):
|
||||
validate_report(full)
|
||||
self.rejects(full, {
|
||||
"missing reason": lambda x: x.pop("boundary_reason"),
|
||||
"wrong reason": lambda x: x.update(boundary_reason="complete"),
|
||||
"not null": lambda x: x.update(boundary={}),
|
||||
"short cap": lambda x: x["ui_ticks"].pop(),
|
||||
}, check.check_no_arrival_diagnostic)
|
||||
with tempfile.TemporaryDirectory(dir=os.environ.get("BUILD_OUTPUT_DIR") or None) as temporary:
|
||||
out = Path(temporary)
|
||||
receipt = synthetic_receipt(out)
|
||||
row = receipt["phases"][5]
|
||||
full["pid"] = row["report"]["pid"]
|
||||
public, sidecar = evidence(full)
|
||||
row["report"] = public
|
||||
text = "P28_RESTART_REPORT=" + json.dumps(public) + "\nP28_RESTART_AUDIO_CLOSE=" + json.dumps(audio(full["pid"])) + "\n"
|
||||
(out / "O-writer/stdout.log").write_text(text)
|
||||
row["raw"]["stdout.log"] = check.pin(out / "O-writer/stdout.log")
|
||||
row["process"]["stdout_sha256"] = row["raw"]["stdout.log"]["sha256"]
|
||||
(out / "O-writer/process.json").write_text(json.dumps(row["process"]))
|
||||
row["owned_files"]["process.json"] = check.pin(out / "O-writer/process.json")
|
||||
for path in (out / "O-writer-states/report_full.json",
|
||||
Path(row["runtime_profile"]) / check.SIDECAR):
|
||||
path.write_text(json.dumps(sidecar))
|
||||
binding = check.pin(out / "O-writer-states/report_full.json")
|
||||
row["sidecar"] = {"path": check.SIDECAR, **binding}
|
||||
receipt["profiles"][row["runtime_profile"]][check.SIDECAR] = binding
|
||||
with self.assertRaisesRegex(ValueError, "incomplete probe"):
|
||||
check.check_receipt(receipt, out)
|
||||
|
||||
def test_v5_engine_elapsed_deadline(self):
|
||||
for elapsed in (0, .25, 37, 1739.5):
|
||||
full = report()
|
||||
full["probe_deadline"].update(engine_elapsed_at_begin_seconds=elapsed, timer_seconds=1740-elapsed)
|
||||
validate_report(full)
|
||||
value = report()["probe_deadline"]
|
||||
self.rejects(value, {
|
||||
"old relative timer": lambda x: x.update(timer_seconds=1740),
|
||||
"wrong limit": lambda x: x.update(limit_seconds_from_engine_start=1800),
|
||||
"negative elapsed": lambda x: x.update(engine_elapsed_at_begin_seconds=-1, timer_seconds=1741),
|
||||
"missing elapsed": lambda x: x.pop("engine_elapsed_at_begin_seconds"),
|
||||
"expired": lambda x: x.update(engine_elapsed_at_begin_seconds=1740, timer_seconds=0),
|
||||
"nonfinite": lambda x: x.update(engine_elapsed_at_begin_seconds=float("inf")),
|
||||
}, check.check_deadline)
|
||||
check.check_deadline({**value, "engine_elapsed_at_begin_seconds": 1750, "timer_seconds": -10}, complete=False)
|
||||
text = (check.HERE / "restart_probe.gd").read_text()
|
||||
for altered in (text + "\nfunc forbidden():\n\tTime.get_ticks_msec()\n",
|
||||
text.replace("deadline_timer_seconds() ->", "other_watchdog() ->"),
|
||||
text.replace("GameClock.advance_minutes(1)", "Time.get_ticks_msec()"),
|
||||
text + "\nfunc forbidden():\n\tTime.get_unix_time_from_system()\n"):
|
||||
with self.assertRaises(ValueError):
|
||||
check.check_probe(altered)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(verbosity=2)
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
OPUS_D2_SCOPE_CLARIFICATION(P28-02-DIAG。base 8c06c3b44235a52f35898b1f6263e8ee064af256 の SPEC §3.1・§3.3、D2 の「live の全状態」の境界を確定する。原本の条件、SPEC、TASK_INSTRUCTIONS、baseline は変更しない。TASK_INSTRUCTIONS の全指示は従来どおり守る)
|
||||
|
||||
この確定の位置づけ
|
||||
- 「全状態」を、製品が保存する状態(S 層)と、それに関わる、名前を挙げた有限の内部状態(K 層・C 層)の和として定める。対象を独自に除外することも、無差別な走査を足すこともしない。比較は、tick の外の各計測(snapshot の変換、scratch の restore・save・load・validator)の前と後で行う。
|
||||
- 製品の値や仕様の変更の承認ではない。診断の観測の副作用が無いことを確かめるための、比較の範囲の具体化である。
|
||||
|
||||
S 層: 製品が保存する状態(必須)
|
||||
- SimulationRuntime.create_snapshot() が返す Dictionary の全体を、var_to_bytes で完全一致させる(SimulationDigest だけでは不可)。
|
||||
- 差がある場合は、RESTART の修正8で確定した typed の正規表現(文字順の key、衝突は拒否、typeof・配列の順序・値を保つ)で path を特定して報告する。dict の列挙の順序だけの差も、path つきで記録する。
|
||||
|
||||
K 層: kernel の、保存されない内部状態(名前を挙げたものだけ。get() で受動的に読む)
|
||||
- GameClock: absolute_minute、speed、paused、snapshot_version、_accumulator(f64 の hex)、_skip_next_process、_pause_request_serial、_abort_current_tick_requested、_last_tick_advanced。
|
||||
- _phase_handlers: phase ごとの handler ID の集合を、文字で並べた一覧として比べる。Callable の同一性は比べない。
|
||||
- performance_budget: _sample_counts、_totals、_samples(metric ごとの配列)。tick の外の計測でこれらが変われば、失敗にする。
|
||||
- 前後で完全一致を要求する。
|
||||
|
||||
C 層: Runtime の内部キャッシュ(create_snapshot が書き換えうるもの。autoload/simulation_runtime.gd:134-136、:11183-11200)
|
||||
- _mvp_manifest_cache_generation、_mvp_manifest_cache_catalog の同一性(get_instance_id)、_mvp_manifest_cache の var_to_bytes。
|
||||
- 規則:
|
||||
- (a) 計測の前に cache が満たされていること(generation が現在の catalog の get_manifest_generation() と一致)を記録する。満たされていなければ、計測の外で create_snapshot() を1回呼んで満たし、その事実を記録する(cache が埋まったのは、計測より前の準備としてである)。
|
||||
- (b) 計測の区間の中で cache miss(generation か catalog の変化、または cache の bytes の変化)が起きたら、その計測の標本に cache_miss_in_sample=true を付けて記録する。値は捨てず、cache の再構築を含む inclusive の値として扱う。黙って除外したり、平均に混ぜて隠したりしない。
|
||||
- (c) 計測の後の C 層が、計測の前と完全一致すること。(b) の miss があった場合は、その後の cache が、現在の catalog から作った manifest と一致することを確かめる。古い値や他の値が残っていれば失敗にする。
|
||||
- これは、P28-02-OPT の E4(キャッシュの古さ)と E5(所有権)の診断側の前提である。create_snapshot() が cache の deep copy を返すことも記録する(:11185-11199)。
|
||||
|
||||
observer の副作用の検査(有限)
|
||||
- 診断が接続する signal(tick_started、tick_phase_started、tick_completed、path_query_measured)について、接続の前と、切断した後で、各 signal の get_connections() の件数と、接続先の object と method の一覧が一致すること(診断の分は除く)。
|
||||
- 診断で DomainEventStream に subscribe した場合は、unsubscribe の後に、subscriber の一覧が元に戻っていること。
|
||||
|
||||
scratch の参照の漏れの検査
|
||||
- scratch に渡すのは、live の snapshot の deep copy だけ。scratch の操作の後で、S 層・K 層・C 層が完全一致すること。scratch の Runtime や関連の object を解放した後に、それらの instance ID が is_instance_id_valid で無効になっていること。scratch の path は private user:// の下に限る。
|
||||
|
||||
起動の構成(未指定だった点の確定)
|
||||
- 代表入力 R は、collector の SPEC §4.3 と同じ順序で既定の autoload に復元し、main を追加する(headless)。こうすることで、同期の受け手(state_updated などに接続する main 側の処理)が、実際のゲームと同じように I11 の inclusive の区間に入る。保存の policy による autosave が区間の中で起きたら、事実として記録する。
|
||||
|
||||
対象の外(理由つき)
|
||||
- Object の参照や循環の無差別な走査、engine・描画・OS の状態、node の木の全体: 製品の永続状態でも、P28-02 の計測で変わりうる内部状態でもないため対象外とする。上の K 層・C 層・observer・scratch の検査で、診断の副作用は検出できる。
|
||||
|
||||
報告
|
||||
- 各計測の前後の S/K/C 層の SHA と一致の結果、cache の状態、observer の接続の件数、scratch の解放を、report に記録する。測れない項目は、理由つきの未測定にする(0 で補わない)。上記で決めきれない不足は、具体的な箇所を示した場合に限り blocked にする。
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
{
|
||||
"at_jst": "2026-10-21T05:57:42.235969+09:00",
|
||||
"project": "the-tree-p28-02-diag",
|
||||
"task": "P28-02-DIAG",
|
||||
"task_state": "implementing",
|
||||
"base": "8c06c3b44235a52f35898b1f6263e8ee064af256",
|
||||
"W": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-diag/P28-02-DIAG-implement",
|
||||
"repairs": 1,
|
||||
"S": null,
|
||||
"real_runs": [
|
||||
{
|
||||
"id": "0be902e004054787bbad4f34fd7d5ca4",
|
||||
"role": "implementer",
|
||||
"service": "codex",
|
||||
"status": "finished",
|
||||
"pid": 42489,
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/0be902e004054787bbad4f34fd7d5ca4",
|
||||
"started": 1792529217.1272,
|
||||
"finished": 1792529389.367724
|
||||
},
|
||||
{
|
||||
"id": "b8f62f42be4d4be6a2b38cbdcda8eca4",
|
||||
"role": "implementer",
|
||||
"service": "codex",
|
||||
"status": "running",
|
||||
"pid": 51050,
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/b8f62f42be4d4be6a2b38cbdcda8eca4",
|
||||
"started": 1792529753.966966,
|
||||
"finished": null
|
||||
}
|
||||
],
|
||||
"actual_argv_model": "gpt-6-astra",
|
||||
"root_exec_session": 6910,
|
||||
"first_scope_question_run_preserved_as_failure": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/diag-initial-D2-scope-return-0be902e-r01/MANIFEST.json",
|
||||
"bytes": 5736,
|
||||
"sha256": "00b4c5b56685da3cc0967abe2d1ef6404bde0fd0ccdab8b6e10cd41c73efd876"
|
||||
},
|
||||
"actual_Opus_scope_clarification": {
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p28-02/CLARIFICATION_D2_ACTUAL_OPUS_282.txt",
|
||||
"bytes": 5598,
|
||||
"sha256": "5775e7b512d9e90dbcfbcb181f5b64dc0fa8207e0947c0a4a6a5c9195565f9bd"
|
||||
},
|
||||
"actual_Opus_repair_decision": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/DESKTOP_DIAG_D2_REPAIR_DECISION.json",
|
||||
"bytes": 1086,
|
||||
"sha256": "2fdbc51675c59325c1ae6723d08c7aa357e12451c624b6663835dfed44b5d2d9"
|
||||
},
|
||||
"actual_Opus_conditional_RUN_decision": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/DESKTOP_DIAG_D2_RUN_DECISION.json",
|
||||
"bytes": 1943,
|
||||
"sha256": "63f201d597974e6afa8565c4c651161ce171e0301d87ced8104da918d2b9fc84"
|
||||
},
|
||||
"normal_actual_repair_record": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/actual-desktop-D2-REPAIR-input/RECORDED.json",
|
||||
"bytes": 2328,
|
||||
"sha256": "33bd222a78060bc2b089f58c36d1b7ace12fa6badb4e8101ce3b6512c138fee0"
|
||||
},
|
||||
"normal_actual_RUN_record": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/actual-desktop-D2-RUN-input/RECORDED.json",
|
||||
"bytes": 3183,
|
||||
"sha256": "adbde7aac89197d8bf6de6cc7e57749a0d2550c8d3f82c8e4fdf0a4142999238"
|
||||
},
|
||||
"normal_input_context": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/diag-D2-owner-run-input/RUN_STARTED.json",
|
||||
"bytes": 921,
|
||||
"sha256": "863eda41392fc63435b1ee41761966cfff9e45243047a9d3a1f9de7f25a14a52"
|
||||
},
|
||||
"original_packet_all_fields_and_text_retained_plus_one_actual_Opus_context_key": true,
|
||||
"registered_SPEC_TASK_baseline_unchanged": true,
|
||||
"engine_runs": 0,
|
||||
"formal_validation_started": false,
|
||||
"whole_original_unit_complete": false,
|
||||
"original_credit": 0,
|
||||
"original_completed": 23,
|
||||
"original_total": 88,
|
||||
"packages_completed": 4,
|
||||
"packages_total": 30,
|
||||
"partial_credit": false,
|
||||
"next": "Follow same DB run b8f62f42be4d4be6a2b38cbdcda8eca4/PID51050 and Root exec6910, do not restart on observation timeout. On S submit read real driver, Root frozenS native import/parse/3PID, fullraw join/bundle, formal unit+nativeverify, fresh separate Opus all4 review, normal prepare/exactQ qualifier/Root existing delegated approval/final. DIAG alone adds0; later OPT and whole P28-02 acceptance required."
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"action":"run","task_id":"P28-02-EQ","reason":"Use only after Root verifies through the normal gates: repair_ready, repairs 4, no S submitted, no active run, and the S1 files still present unchanged in the W. Run repair 4 of P28-02-EQ once on the same W from base 2549cf7d6992560b850bbd11934e3c14b39a1988 with the single worker codex gpt-6-astra, doing only stage S2 of EQ_STAGED_IMPLEMENTATION_PLAN r01 and r02. Start by reading eq/PROGRESS.md and eq/IMPLEMENTATION.md, and reuse the internal types, paths, record schema and API recorded there and in compare_versions.py without redesign. Do not delete or rewrite S1 code, except for fixes a failing test requires. Keep the S1 unit failure history in PROGRESS.md and UNIT_OUTPUT. S2 scope: (1) eq_probe.gd startup per the six-stage lifecycle reused from DIAG; (2) the r02 A1 typed tree encode/decode; (3) the A2 Godot roundtrip proof recording raw_bytes_sha, tree_sha and roundtrip_ok per checkpoint; (4) A3 export of the full B1 tree and exact per-tick diff operations for S and latest_snapshot to private user:// evidence/; (5) the B event record, with to_dictionary trees, original_field_types, start counter and published_ids count; (6) binding of the profile root to the guard actual_user_data_dir, with the value read live and never assumed; and (7) the single-line P28_02_EQ_REPORT plus close_requested.emit(), with the report fields matching what compare_versions.compare_reports already consumes. Also add the check_eq.py format, contradiction and static probe checks: no autoload class_name reference, no state-changing call outside the allowed preparation and stepping, no := inference from a Variant, textual StringName ordering, no SimulationDigest-only judgment. Add synthetic tests for these to test_eq.py. The E3/E4/E5 scenarios are S3 and must not be started now. Run the engine-free python3 test_eq.py until every existing test passes, save the raw output, update PROGRESS.md, and end S2 with formal status blocked and the finding 'stage S2 complete (not a failure; next is S3)'. Do not submit. Deliver as the one supplementary key the full texts of both plans and this decision; clarifications r01, r02 and r03 stay read-only in build/eq-staged-readonly-r04 with path and SHA. No engine, no git write or commit, no product change, no full regression; SPEC, TASK, eq/references and eq/registration stay unchanged. Ordinary implementation choices are recorded without stopping; block only for undecided matters affecting original product spec, values, permissions, billing or original acceptance. After S3 and S4, Root runs native on the frozen S_eq in 7 phases and builds the bundle; then both validations are mandatory, followed by a fresh independent Opus review of all 6 conditions and the normal integration and final. No original credit (23/88, 4/30). Quota fallback only on structured provider quota, once, same role, Antigravity claude-opus-5-5-high; any other failure is blocked. Decided by Claude Opus 5.5 in the Desktop control session (message 324); not a CLI commander result."}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"action":"request-repair","task_id":"P28-02-EQ","reason":"Stage S2 run 558b96b3fe5e462296e85bc591622d1c ended as planned after 846.159 s: formal status blocked with the low finding 'stage S2 complete, next S3'. This is a staged checkpoint, not a failure. No S, repairs 4, no active run. Root preserved 30 files and 6 raw pins (retained-s2-558b96b-r01/RETURN.json SHA 6f4f2b8ce8be7c4236368ffde9bbbd2805748a894b95ea8f42385169fdc78129) and joined the local results (ROOT_S2_LOCAL_JOIN_558B96B.json SHA bbe7975d20c08f4af6dde4ba87f30ec7e3c80bf86ca321fc6d2a6a73cc8dde84). The unit runs went 68, then 71, then 72 tests, each with exit code 0, and the final output SHA 9d323abda9bfffa02faf3a9d5cb56aa87e647017c991eb55b4807cf734386698 matches the raw stdout. The S1 comparator, all 43 S1 test bodies, the failure history and the frozen materials are unchanged; tracked source is unchanged and every change is inside eq/. The 72 local tests are not acceptance. A read-only static check, confirmed by Root against the source, found two checker false-PASS defects for S3 to fix. First, check_eq.py:109 accepts an empty or partial phase-key set. Second, check_eq.py:147-150 accepts a single snapshot_published/state_updated line instead of the actual per-minute count and order. Because S2 took 846 s, close to the unchanged 900-second limit, S3 is split into S3a and S3b in EQ_S3_STAGE_PLAN.txt without changing any requirement. S3a is the two checker fixes with negative tests plus E3 rejection. S3b is E4 cache staleness and E5 ownership. SPEC, TASK, the 6 conditions, eq/references, eq/registration and clarifications r01, r02 and r03 are unchanged. Request the normal repair so the same W continues with S3a. Decided by Claude Opus 5.5 in the Desktop control session (message 328); not a CLI commander result."}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"action":"run","task_id":"P28-02-EQ","reason":"Use only after Root verifies through the normal gates: repair_ready, repairs 5, no S submitted, no active run, and the S1 and S2 files still present unchanged in the W. Run repair 5 of P28-02-EQ once on the same W from base 2549cf7d6992560b850bbd11934e3c14b39a1988 with the single worker codex gpt-6-astra, doing only stage S3a of EQ_S3_STAGE_PLAN.txt, under EQ_STAGED_IMPLEMENTATION_PLAN r01 and r02. Start by reading eq/PROGRESS.md and eq/IMPLEMENTATION.md and reuse the S1/S2 types, paths, record schema, API and notes without redesign. Do not delete or rewrite S1/S2 code or test bodies, except for the two checker fixes and fixes a failing test requires. Keep the failure history. S3a scope: (1) check_eq.py:109 must require each checkpoint's immutable-snapshot phase keys to be exactly {0..10}, with negative tests for empty, missing, out-of-range and duplicate representations. (2) check_eq.py:147-150 must check, for every minute of the 30 measured ticks, the actual count and order of snapshot_published then state_updated emitted by _publish_snapshot (simulation_runtime.gd:12616-12621), bound to that minute's checkpoint. The expected count comes from the recorded actual connection state, because state_updated alone is emitted when snapshot_published has no connection (:12619). Scenario signals are kept in a separate interval record. Add negative tests for missing, partial, duplicated, reordered and shifted minutes and for scenario-signal contamination. (3) E3 rejection in a dedicated scenario interval for both R and O: restore_snapshot, SimulationSnapshot.validate and load_from_path each receive at least one input with a missing key, a wrong type, an out-of-range value, a duplicate ID (published_ids and facility ID) and a world.bin tamper under r03 D3. The inputs are built from a deep copy of B1 by fixed rules, with no randomness or time. Record the return value and code, the before/after full S/K/C, and the event difference and count: a direct restore emits 0 SAVE_* events; a failed load emits exactly 1 SAVE_LOAD_FAILED equal to X(B, ev_fail) after r03 normalization. A product failure is recorded as an existing defect, never as PASS. Add checker rules and tests for these. State-changing calls are allowed only inside the listed scenario interval, and check_eq.py's static allowance is limited to that. Run the engine-free python3 test_eq.py until every existing test passes, save the raw output, update PROGRESS.md, and end S3a with formal status blocked and the finding 'stage S3a complete (not a failure; next is S3b)'. Do not submit. Deliver as the one supplementary key EQ_S3_STAGE_PLAN.txt, both staged plans and this decision; clarifications r01, r02 and r03 stay read-only in build/eq-staged-readonly-r04. No engine, no git write or commit, no product change, no full regression; SPEC, TASK, eq/references and eq/registration stay unchanged. Ordinary implementation choices are recorded without stopping; block only for undecided matters affecting original product spec, values, permissions, billing or original acceptance. The remaining order is S3b, then S4 and submission, then Root native in 7 phases, then both validations, then a fresh independent Opus review of all 6 conditions, then the normal integration and final. No original credit (23/88, 4/30); the 900-second timeout is unchanged. Quota fallback only on structured provider quota, once, same role, Antigravity claude-opus-5-5-high; any other failure is blocked. Decided by Claude Opus 5.5 in the Desktop control session (message 328); not a CLI commander result."}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"action":"run","task_id":"P28-02-EQ","reason":"Supersedes DESKTOP_EQ_S3A_RUN_DECISION.json, which is kept unchanged as history. DESKTOP_EQ_S3A_REPAIR_DECISION.json stays valid as written: its facts (S2 checkpoint, the two checker defects, the S3a/S3b split) are unchanged by this correction. Use only after Root verifies through the normal gates: repair_ready, repairs 5, no S submitted, no active run, and the S1 and S2 files still present unchanged in the W. Run repair 5 of P28-02-EQ once on the same W from base 2549cf7d6992560b850bbd11934e3c14b39a1988 with the single worker codex gpt-6-astra, doing only stage S3a as defined in EQ_S3_STAGE_PLAN.txt and replaced in part by EQ_S3_STAGE_PLAN_R02.txt, under EQ_STAGED_IMPLEMENTATION_PLAN r01 and r02. Start by reading eq/PROGRESS.md and eq/IMPLEMENTATION.md and reuse the S1/S2 types, paths, schema, API and notes without redesign. Do not delete or rewrite S1/S2 code or test bodies, except for the two checker fixes and fixes a failing test requires. Keep the failure history. S3a scope: (1) check_eq.py:109 must require the exact phase-key set {0..10}. (2) check_eq.py:147-150 must require exactly one snapshot_published then state_updated pair for each of the 30 measured minutes, bound to the checkpoint minute and to a recorded connection inventory that contains the probe's own snapshot_published connection. A missing probe connection fails; it is never downgraded to a state-only expectation. The product's unconnected case is out of this interval's scope, and scenario signals are kept separate. (3) E3 uses the source-backed table. restore_snapshot returns its bool with code null and reason 'API returns bool only'. validate returns its full {ok, errors} with code null and reason 'API returns ok/errors only'. load_from_path returns a real code, and a missing code there is a failure. Missing key, wrong type and out-of-range are applied to all three APIs by fixed rules (clock removal, string absolute_minute and speed 9 for the Dictionary APIs; checksum_world removal, string format_version and an unsupported format_version for the package). Duplicate published_ids is applied to restore and, if SaveService.save_snapshot accepts it, to load; it is NA for envelope validate. Duplicate facility is applied in R and is NA in O when B1 actually has 0 facilities, recorded with the count and with no construction, no R substitution and no invented values. world.bin tamper is load only, by r03 D3. Record full before/after S/K/C, the event difference and count (0 for restore and validate; exactly 1 SAVE_LOAD_FAILED equal to X(B, ev_fail) for failed loads), and the applied/NA table. Mismatches are kept as existing-defect candidates; envelope validate accepting an internal duplicate is not called a product defect. Add the listed checker rules and negative tests. State-changing calls are allowed only inside the listed scenario interval. Run the engine-free python3 test_eq.py until every existing test passes, save the raw output, update PROGRESS.md, and end S3a with formal status blocked and the finding 'stage S3a complete (not a failure; next is S3b)'. Do not submit. Deliver as the one supplementary key both S3 plans, both staged plans and this decision; clarifications r01, r02 and r03 stay read-only in build/eq-staged-readonly-r04. No engine, no git write or commit, no product change, no full regression; SPEC, TASK, eq/references and eq/registration stay unchanged. Ordinary implementation choices are recorded without stopping; block only for undecided matters affecting original product spec, values, permissions, billing or original acceptance. The remaining order is S3b, then S4 and submission, then Root native in 7 phases, then both validations, then a fresh independent Opus review of all 6 conditions, then the normal integration and final. No original credit (23/88, 4/30); the 900-second timeout is unchanged. Quota fallback only on structured provider quota, once, same role, Antigravity claude-opus-5-5-high; any other failure is blocked. Decided by Claude Opus 5.5 in the Desktop control session (message 330); not a CLI commander result."}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"action":"request-repair","task_id":"P28-02-EQ","reason":"Stage S3a run 32288e966a844f5baec4dc931bc281a5 ended as planned after 726.041 s: formal status blocked with the low finding 'stage S3a complete, next S3b'. This is a staged checkpoint, not a failure. All 6 conditions are still pending; repairs 5; no S; no active run. Root preserved 39 files and 6 raw files (retained-s3a-32288e9-r01/RETURN.json SHA 3752c3ee5880ac82f2585a1b94e1c7ef0adaf02c406125c9fc9eada1bae83af3) and joined the local results (ROOT_S3A_LOCAL_JOIN_32288E9.json SHA db712aea5390fd6fe9313954a1f77ce07df49556f21633437f8baa1c1a6f0701). Unit runs of 72, 85, 91 and 91 tests all embedded exit code 0, with final output SHA 02221ae41e5dd40ecdac0ec0d7ffc657f265d3194c3817583c2b88ad375f132b. Runs 03 and 04 saved full stdout to a file with its SHA while the provider printed only the tail, and they are joined on that basis, without claiming full provider-stdout equality. All 72 earlier test bodies are unchanged and 19 tests were added. The S1 comparator, the frozen materials and the earlier failure raw are unchanged, tracked product source is unchanged, and every change is inside eq/. Root's two incorrect join attempts are preserved as failures. A limited read-only check found no high or medium false PASS; this is not the formal review. Request the normal repair so the same W continues with stage S3b under EQ_S3B_STAGE_PLAN.txt. SPEC, TASK, the 6 conditions, eq/references, eq/registration, clarifications r01, r02 and r03 and the staged and S3 plans are unchanged. Decided by Claude Opus 5.5 in the Desktop control session (message 334); not a CLI commander result."}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"action":"request-repair","task_id":"P28-02-EQ","reason":"Stage S3b run 7da027a85548460bb008b562a79a52ae (worker PID 32210 exit 0, Root exec 63195 exit 2) returned a formal blocked status that is not the planned checkpoint: EQ_S3B_STAGE_PLAN.txt E4-1 named mvp_content_catalog as the facility enumeration source, but scripts/data/mvp_content_catalog.gd:26-31 DEFAULT_PATHS lists only 4 person kinds, so a scan would yield an empty list and a false not-placeable NA. Root confirmed in source that facilities are registered into runtime.definition_registry at autoload/simulation_runtime.gd:446-447 from the FacilityCatalog paths, that scripts/core/definition_registry.gd get_facility_ids() -> Array[StringName] and get_all_facilities() are the real API, and that preview_build_facility uses the same registry. The worker correctly refused to prove NA from an empty list. This is a reference error in the Desktop plan, not a product or spec change and not a worker failure. Nothing was implemented and no tests were added; the only change is a PROGRESS addition, and all code, the 91 tests and earlier raw are unchanged. Root preserved 39 files and 6 raw files at retained-s3b-7da027a-r01/RETURN.json SHA 4961d36957bf14b9ad05d3fcbc162ad30e971e7dde2bae18fce6bc5eb18b3e1e; earlier successes are not counted as this run's tests. State: blocked, repairs 6, no S, no active run. Request the normal repair (blocked, repairs 6, to repair_ready, repairs 7) so the same W continues S3b under EQ_S3B_STAGE_PLAN.txt as corrected in E4-1 only by EQ_S3B_STAGE_PLAN_R02.txt. SPEC, TASK, the 6 conditions, eq/references, eq/registration, clarifications r01, r02 and r03, the staged plans, the S3 plans and the rest of S3b are unchanged; the old plan, decisions and run are kept as history. Decided by Claude Opus 5.5 in the Desktop control session (message 336); not a CLI commander result."}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"action":"run","task_id":"P28-02-EQ","reason":"Use only after Root verifies through the normal gates: repair_ready, repairs 6, no S submitted, no active run, and the S1, S2 and S3a files still present unchanged in the W. Run repair 6 of P28-02-EQ once on the same W from base 2549cf7d6992560b850bbd11934e3c14b39a1988 with the single worker codex gpt-6-astra, doing only stage S3b. Deliver EQ_S3B_STAGE_PLAN.txt in full as part of the one supplementary key, together with both S3 plans, both staged plans and this decision; clarifications r01, r02 and r03 stay read-only in build/eq-staged-readonly-r04. Start by reading eq/PROGRESS.md and eq/IMPLEMENTATION.md, including the S4 binding requirements at the end, and reuse the S1/S2/S3a types, paths, schema, API and code without redesign. Do not delete or rewrite E3, the 91 existing test bodies or the failure history, except for fixes a failing test requires. S3b scope: E4 in R and O over five paths in order, in dedicated scenario intervals. (1) A state-changing build_facility command through submit_command (simulation_runtime.gd:476), with definition and origin chosen by a fixed scan using the product's public placement check, or NA with a reason when nothing is placeable; record after submit and after one advance_minutes(1). (2) One tick. (3) A direct restore_snapshot(P), with exact P reproduction after normalization and 0 SAVE events. (4) A load_from_path success from a fixed-path, fixed-metadata SaveService.save_snapshot, equal to X(P, ev_load) with exactly 1 SAVE_LOADED. (5) A rejected build_facility with an empty definition_id plus one tick, with all bookkeeping changes recorded. Each path records create_snapshot() and latest_snapshot as full typed trees. latest_snapshot staying unchanged after a path without a tick is a recorded product fact, not cache staleness; staleness is judged later by cross-version comparison. E5 covers 4 targets (create_snapshot() return value, latest_snapshot, snapshot_published argument, state_updated argument) by nested dict (systems.runtime.id_generator.counters gains '__p28_eq_probe': 1) and nested array (systems.runtime.domain_events.published_ids gains '__p28_eq_probe'), in both directions. Signal arguments are held from the probe's existing connection and mutated outside the handler. Outside to inside compares internal create_snapshot, latest_snapshot and the pre-read latest_digest before and after. Inside to outside checks the returned value against its deep copy after one tick. The expected aliasing of latest_snapshot and the signal arguments (:12617-12621) is recorded as an existing defect with every changed path, value and digest effect, never as PASS and never as an unauthorized harness change; checks then resume from a new baseline after one tick. Add check_eq.py rules and negative tests as listed in the plan. Run the engine-free python3 test_eq.py until every existing test passes, save the raw output, update PROGRESS.md, and end S3b with formal status blocked and the finding 'stage S3b complete (not a failure; next is S4)'. Do not submit. No engine, no git write or commit, no product change, no full regression; SPEC, TASK, eq/references and eq/registration stay unchanged. Ordinary implementation choices are recorded without stopping; block only for undecided matters affecting original product spec, values, permissions, billing or original acceptance. The remaining order is S4 (driver and verify) and submission, then Root native on the exact S in 7 phases, then both validations, then a fresh independent Opus review of all 6 conditions, then the normal prepare, candidate qualification, approval, integration and final; the OPT whole-unit gate follows later. No original credit (23/88, 4/30); the 900-second timeout is unchanged. Quota fallback only on structured provider quota, once, same role, Antigravity claude-opus-5-5-high; any other failure is blocked. Decided by Claude Opus 5.5 in the Desktop control session (message 334); not a CLI commander result."}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"action":"run","task_id":"P28-02-EQ","reason":"Supersedes DESKTOP_EQ_S3B_RUN_DECISION.json, which is kept unchanged as history. Use only after Root verifies through the normal gates: repair_ready, repairs 7, no S submitted, no active run, and the S1, S2 and S3a files still present unchanged in the W. Run repair 7 of P28-02-EQ once on the same W from base 2549cf7d6992560b850bbd11934e3c14b39a1988 with the single worker codex gpt-6-astra, doing only stage S3b as defined in EQ_S3B_STAGE_PLAN.txt and corrected in E4-1 only by EQ_S3B_STAGE_PLAN_R02.txt. Deliver both S3b plans in full as part of the one supplementary key, together with both S3 plans, both staged plans and this decision; clarifications r01, r02 and r03 stay read-only in build/eq-staged-readonly-r04. Start by reading eq/PROGRESS.md, including the run 7da027a entry, and eq/IMPLEMENTATION.md, including the S4 binding requirements, and reuse the S1/S2/S3a types, paths, schema, API and code without redesign. E4-1 correction: in both R and O, take candidates from that PID's runtime.definition_registry.get_facility_ids(), sorted by text ID, scan cells in (x, y, z) lexical order with rotation 0, judge each pair only with the public preview_build_facility, and submit the first placeable pair through submit_command(&\"build_facility\", {definition_id, origin, rotation}). Save the raw enumerated list and count, the sorted list and rule, the cell range rule, every tried candidate with its full preview result, the chosen candidate with its submit and command result, and any non-application reason. NA is allowed only when the real list is non-empty and every pair is not placeable; an empty list is a failure, and a false NA from an empty or wrong source is forbidden. NA is never PASS, no facility values are invented, and nothing is built outside this path. Everything else in S3b is unchanged: E4 paths 2 to 5, E5 over all 4 targets by 2 locations by 2 directions, full typed state and event records, the latest_snapshot aliasing (:12617-12621) recorded as an existing defect and never as PASS, the listed check_eq.py rules and negative tests plus the r02 E4-1 rejections, and keeping E3, the 91 existing test bodies and the failure history except for fixes a failing test requires. Run the engine-free python3 test_eq.py until every test passes, save the raw output, update PROGRESS.md, and end S3b with formal status blocked and the finding 'stage S3b complete (not a failure; next is S4)'. Do not submit. No engine, no git write or commit, no product change, no full regression; SPEC, TASK, eq/references and eq/registration stay unchanged. Ordinary implementation choices are recorded without stopping; block only for undecided matters affecting original product spec, values, permissions, billing or original acceptance. The remaining order is S4 (driver and verify) and submission, then Root native on the exact S in 7 phases, then both validations, then a fresh independent Opus review of all 6 conditions, then the normal prepare, candidate qualification, approval, integration and final; the OPT whole-unit gate follows later. No original credit (23/88, 4/30); the 900-second timeout is unchanged. Quota fallback only on structured provider quota, once, same role, Antigravity claude-opus-5-5-high; any other failure is blocked. Decided by Claude Opus 5.5 in the Desktop control session (message 336); not a CLI commander result."}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"action":"request-repair","task_id":"P28-02-EQ","reason":"Stage S3b r02 run c056b928052a493db734593026d471b4 (worker PID 34943 exit 0 after 710.903 s, Root exec 18378 exit 2) ended at the planned checkpoint: formal status blocked with the low finding 'stage S3b complete, next S4'. This is a staged checkpoint, not a failure; all 6 conditions remain pending. State: blocked, repairs 7, no S, no active run. Root preserved 58 files and 6 raw files at retained-s3b-c056b92-r01/RETURN.json SHA 251752724519d048db99c84824e38d5d738e38b64f116dd7a6f52242377eb467 and joined the 9 unit attempts at ROOT_S3B_LOCAL_JOIN_C056B92.json SHA 5e3e67d306d4d0ef067754855df7664bfeb0e804f23f1224435e16665ff46a61, with counts [91,null,114,114,114,118,1,119,119] and exits [0,1,1,0,0,0,1,1,0]. The syntax, digest, dictionary-order and fixture failures of attempts 02, 03, 07 and 08 are kept. The final run passed 119 tests in 32.034 s, output SHA 8360a2065b303099d5c546b3e4aecc0cc0ab0c2c1e5120b8034b6a1a64317030. All 91 earlier test bodies are unchanged by AST and 28 were added. Frozen materials, earlier raw and supplements are unchanged, tracked product source is unchanged, and every change is inside eq/. A limited independent read-only check found no concrete high or medium false PASS and no immediate native API mismatch. The compare_versions digest exception is bound only to the enumerated E5 outside-in operation point with the full proof, aliasing is kept as an existing defect, and E4 uses the real registry with full preview records. That check is not the formal fresh Opus review. Root also fixed the frozen dependency API notes at S4_FROZEN_DEPENDENCY_API_NOTES_R01.json SHA 682a0d9a04978411691e9a8291b46a9ddc6eeb621d38c0f699a756e47be0084b. Request the normal repair (blocked, repairs 7, to repair_ready, repairs 8) so the same W continues with the final stage S4 under EQ_S4_STAGE_PLAN.txt. SPEC, TASK, the 6 conditions, eq/references, eq/registration, clarifications r01, r02 and r03, and the staged, S3 and S3b plans are unchanged; the earlier 7da027a plan error stays recorded. Decided by Claude Opus 5.5 in the Desktop control session (message 340); not a CLI commander result."}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"action":"request-repair","task_id":"P28-02-EQ","reason":"S4 run 20dc4135cc3041dbb82fcc2b2a6b0cbe reached the unchanged 900-second limit and ended with exit 124. Provider PID 43572 and its child unit processes ended, and Root exec 51860 ended. Runner state: blocked, repairs 8, S null, no active run. Root preserved all 70 files with matching SHAs at retained-s4-20dc413-r01, RETURN SHA a2c0d5ffd42b67ef340d5624dd299e294b867168bd1270933aec458a9cde1dc5, joined at ROOT_S4_TIMEOUT_JOIN_20DC413.json SHA 945fec6bcfa431d794ac74513219cce8eab8dccade517c6a6faeb391f86e05d7. All 119 earlier test AST bodies and the earlier unit history are kept, and tracked product source is unchanged. run_eq.py and verify_eq.py are implemented. Unit attempt 05 passed 132 tests in 194.460 s with embedded exit 0, a fact for that version only. Two tests were added afterwards, so there are now 134. Attempt 06 started but produced no output before the limit, so a 134 PASS is not claimed. The failure raws of attempts 01 (syntax), 02 (added unit) and 04 (recompute mismatch) and the failed S4_SUPPLEMENTARY literal-reconstruction assert are all kept. The docs still describe S3b. A limited separate read-only check, confirmed by Root in source, found one medium false PASS: run_eq.py:257 and verify_eq.py:144 ignore the return value of check_probe, which returns {ok, errors} rather than raising (check_eq.py:384). No other collector API mismatch was found within that scope; this is not the formal all-6 review. Root placed the r09 ACTUAL_OPUS_TASK_CONTEXT.json as original-bytes read-only JSON in the same W runtime. Request the normal repair (blocked, repairs 8, to repair_ready, repairs 9) so the same W finishes S4 on the retained implementation under EQ_S4_STAGE_PLAN_R03.txt, without reimplementation and without reducing the S4 deliverable. SPEC, TASK, the 6 conditions, eq/references, eq/registration, clarifications r01, r02 and r03 and all earlier plans are unchanged. Decided by Claude Opus 5.5 in the Desktop control session (message 346); not a CLI commander result."}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"action":"run","task_id":"P28-02-EQ","reason":"Use only after Root verifies through the normal gates: repair_ready, repairs 8, no S submitted, no active run, and all 58 current S1 to S3b files still present unchanged in the W. Run repair 8 of P28-02-EQ once on the same W from base 2549cf7d6992560b850bbd11934e3c14b39a1988 with the single worker codex gpt-6-astra, doing only the final stage S4 as defined in EQ_S4_STAGE_PLAN.txt. Deliver EQ_S4_STAGE_PLAN.txt in full as part of the one supplementary key, together with both S3b plans, both S3 plans, both staged plans, S4_FROZEN_DEPENDENCY_API_NOTES_R01.json and this decision; clarifications r01, r02 and r03 stay read-only in build/eq-staged-readonly-r04. Start by reading eq/PROGRESS.md and eq/IMPLEMENTATION.md, list every existing 'S4 must' requirement and the closing S4 binding requirements, and reuse the existing code and schemas (join_report, check_report, check_observation, check_lifecycle, strict_json, compare_reports) without redesign. S4 scope: (1) eq/run_eq.py with an engine-free --check and a --run over the 7 phases import, parse-base, parse-wrapper, R-A, R-B, O-A and O-B, using the read-only collector API as frozen. run_private_engine takes the original project.godot bytes as public, the startup profile name as name and a not-yet-created folder. guard_gate takes the runtime profile name. Gates return None or raise and are never treated as bool. inputs(readonly) checks the original manifest and its 9 original destinations, never a copied root. An empty profile_files result is never proof. (2) After each owned process ends, copy and hash every private evidence file (report, lifecycle with measurement, e3, e3_b1 and e45, audio-close, events, observation, e3-00 to e3-17, e45-00 to e45-20 and package evidence), using strict_json. Call check_observation, check_lifecycle and check_report with lifecycle.e45, require 39 scenarios (18 E3 plus 21 E4/E5), call join_report before compare_reports, and keep raw, joined, join_facts sidecars, proofs and checker outcomes separate with SHAs. Compare R-A against R-B and O-A against O-B separately, requiring zero difference. Keep existing defects, including aliasing, as defects. (3) Bind raw3, owned5, actual PID start and end, strict diagnostics, guard, role to phase to PID, the full 1407-file source map and all pins (fixture, engine, source map, machine, EQ files, project, settings, marker, policy). Keep the finally cleanup with 0 remaining. Write the manifest and the seal last, only on full success, and label the result S_eq native, not Q measurement. On failure, keep raw, build no bundle and exit nonzero. (4) eq/verify_eq.py reads $BUILD_OUTPUT_DIR/p28-02-eq-evidence, fails when it is absent, recomputes every check from raw and binds the pins to the actual files. (5) Add bundle normal and rejection unit tests as listed in the plan, keeping all 119 test bodies and the full failure history except fixes a failing test requires. Run the engine-free python3 -B test_eq.py until every test passes, save every raw output with SHAs, update PROGRESS.md and IMPLEMENTATION.md with the requirement-to-code table, and submit formally as local unit evidence only, not native, accepted S, Q measurement or product PASS. Do not trial-run either formal validation or run_eq.py --run to make them fail; the bundle is joined once after the real native. No engine, no git write or commit, no product change, no full regression; the Runner commits and gates normally. SPEC, TASK, eq/references and eq/registration stay unchanged. Ordinary implementation choices are recorded without stopping; block only for undecided matters affecting original product spec, values, permissions, billing or original acceptance. The remaining order after submission: Root native on the exact S in 7 phases, the bundle placed at $BUILD_OUTPUT_DIR/p28-02-eq-evidence, both validations (p28-02-eq-unit, p28-02-eq-native-verify), a fresh independent Opus review of all 6 conditions, then the normal prepare, candidate qualification, approval, integration and final, then the OPT and whole-unit gates. No original credit (23/88, 4/30); the 900-second timeout is unchanged. Quota fallback only on structured provider quota, once, same role, Antigravity claude-opus-5-5-high; any other failure is blocked. Decided by Claude Opus 5.5 in the Desktop control session (message 340); not a CLI commander result."}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"action":"run","task_id":"P28-02-EQ","reason":"Supersedes DESKTOP_EQ_S4_RUN_DECISION.json, which was never dispatched and is kept unchanged as history. DESKTOP_EQ_S4_REPAIR_DECISION.json stays valid and usable as written: its facts (the S3b r02 checkpoint, the retained evidence and the S4 continuation) are unchanged by this correction. Use only after Root verifies through the normal gates: repair_ready, repairs 8, no S submitted, no active run, and all 58 current S1 to S3b files still present unchanged in the W. Run repair 8 of P28-02-EQ once on the same W from base 2549cf7d6992560b850bbd11934e3c14b39a1988 with the single worker codex gpt-6-astra, doing the whole final stage S4 as defined in EQ_S4_STAGE_PLAN.txt and corrected by EQ_S4_STAGE_PLAN_R02.txt: run_eq.py, verify_eq.py, bundle normal and rejection unit tests, full local PASS and the formal submission, in this one run, with no reduction to a smaller deliverable. Deliver both S4 plans in full as part of the one supplementary key, together with both S3b plans, both S3 plans, both staged plans, S4_FROZEN_DEPENDENCY_API_NOTES_R01.json and this decision; clarifications r01, r02 and r03 stay read-only in build/eq-staged-readonly-r04. The r02 corrections are as follows. Import the collector read-only inside the frozen SPEC §1 closure only; restart_pair.py belongs to the RESTART task, is not imported, and copy_states is read as reference only. EQ implements its own evidence copy and inventory in eq/ and rejects out-of-closure imports. raw3 is the collector's actual stdout.log, stderr.log and engine-native.log (collect_p28_01.py:217,225). The exit code is read from the owned receipts (for example process.json), never from raw3, and engine-native.log is required. Call check_report(raw, measurement, e3_evidence, b1_record, e45_evidence) with lifecycle.measurement, e3, e3_b1 and e45, and call check_lifecycle(raw, lifecycle, guard). The exact evidence names are checkpoint-00..30, observation-00..30, e3-00..17, e45-00..20, events, report, lifecycle and audio-close, all .json, plus the existing package evidence. The final worker JSON status must be exactly 'submitted' (core.py:1015), not pass or blocked, with commit set to the base. The worker writes no git, and Runner.submit commits normally. Formal validation runs with a read-only checkout (core.py:1064 writable=()) and BUILD_OUTPUT_DIR assigned to the runtime build area (core.py:827), so bundle-synthesis unit tests write only under that BUILD_OUTPUT_DIR or tempfile.gettempdir(), and verify_eq.py only reads. Nothing writes into the source tree, and environment and permissions are unchanged. Everything else in S4 r01 is unchanged: all S4 bindings, raw recomputation in verify, the 7 phases, pins and the 1407-file source map, seal only on success, the S_eq-native-not-Q label, all 119 test bodies and the full failure history, raw outputs with SHAs, and PROGRESS and IMPLEMENTATION updates. No engine, no run_eq.py --run, no trial formal validation, no git write, no product change, no full regression; SPEC, TASK, eq/references and eq/registration stay unchanged. Ordinary implementation choices are recorded without stopping; block only for undecided matters affecting original product spec, values, permissions, billing or original acceptance. After submission: Root native on the exact S in 7 phases, the bundle placed at $BUILD_OUTPUT_DIR/p28-02-eq-evidence, both validations, a fresh independent Opus review of all 6 conditions, then the normal prepare, candidate qualification, approval, integration and final, then the OPT and whole-unit gates. No original credit (23/88, 4/30); the 900-second timeout is unchanged. Quota fallback only on structured provider quota, once, same role, Antigravity claude-opus-5-5-high; any other failure is blocked. Decided by Claude Opus 5.5 in the Desktop control session (message 342); not a CLI commander result."}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"action":"run","task_id":"P28-02-EQ","reason":"Use only after Root verifies through the normal gates: repair_ready, repairs 9, no S submitted, no active run, all 70 retained files present unchanged in the W (RETURN SHA a2c0d5ffd42b67ef340d5624dd299e294b867168bd1270933aec458a9cde1dc5), and the r09 ACTUAL_OPUS_TASK_CONTEXT.json placed read-only with its original bytes. Run repair 9 of P28-02-EQ once on the same W from base 2549cf7d6992560b850bbd11934e3c14b39a1988 with the single worker codex gpt-6-astra, finishing stage S4 on the retained implementation as defined in EQ_S4_STAGE_PLAN_R03.txt, with EQ_S4_STAGE_PLAN.txt and EQ_S4_STAGE_PLAN_R02.txt still in force. Do not reimplement, re-audit or reduce S4 to a smaller deliverable. Remaining work, all in this one run: (1) fix the medium false PASS at run_eq.py:257 and verify_eq.py:144 to static = check.check_probe(...) and require(static['ok'], message with all errors), fix any other ignored check return in those two files the same way, and add rejection tests proving that both the driver and the verifier reject ok:false and accept ok:true; (2) add any remaining S4 tests; (3) update PROGRESS.md and IMPLEMENTATION.md to S4, with the requirement-to-code table covering every 'S4 must' item, the S4 binding requirements and the r02 corrections; (4) write S4_SUPPLEMENTARY.json by loading Root's read-only r09 ACTUAL_OPUS_TASK_CONTEXT.json, plus this r03 plan and decision as Root places them, without hand reconstruction, recording and checking the source and per-plan SHAs, and keep the earlier assert failure; (5) check fixes with targeted class runs, saving their raw, then run the full python3 -B test_eq.py once at the end over all 134 tests plus every test added in r03, starting with at least 300 seconds left. Attempt 05's 132 PASS covers that version only, so the final full PASS must include every later test. A further fix with related rechecks and one more full run is allowed if recorded. If time runs short, return a normal blocked status with all raw kept, and never claim an unproven PASS. (6) Submit the formal JSON with status exactly 'submitted' and commit 2549cf7d6992560b850bbd11934e3c14b39a1988, as local unit evidence only, not native, accepted S, Q measurement or product PASS. Keep the 119 earlier test bodies, all raw outputs and failures, every S4 r01 and r02 binding and raw recomputation, the 6 conditions and both validations. No engine, no run_eq.py --run, no trial formal validation, no git write, no product change, no full regression, and no API fallback. SPEC, TASK, eq/references and eq/registration stay unchanged. Ordinary implementation choices are recorded without stopping; block only for undecided matters affecting original product spec, values, permissions, billing or original acceptance. After submission: Root native on the exact S in 7 phases, the bundle at $BUILD_OUTPUT_DIR/p28-02-eq-evidence, both validations, a fresh independent Opus review of all 6 conditions, then the normal qualification, approval, integration and final, then the OPT and whole-unit gates. No original credit (23/88, 4/30); the 900-second timeout and permissions are unchanged. Quota fallback only on structured provider quota, once, same role, Antigravity claude-opus-5-5-high; any other failure is blocked. Decided by Claude Opus 5.5 in the Desktop control session (message 346); not a CLI commander result."}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"action":"run","task_id":"P28-02-EQ","reason":"Supersedes DESKTOP_EQ_STAGED_RUN_DECISION.json, which is kept unchanged as history. The request-repair decision DESKTOP_EQ_STAGED_REPAIR_DECISION.json stays valid as written. Use only after Root verifies through the normal gates: repair_ready, repairs 3, no S submitted, and no active run. Run repair 3 of P28-02-EQ once on the same W from base 2549cf7d6992560b850bbd11934e3c14b39a1988 with the single worker codex gpt-6-astra, doing only stage S1. Follow EQ_STAGED_IMPLEMENTATION_PLAN.txt (process G1-G6 and stages S1-S4) as replaced in part by EQ_STAGED_IMPLEMENTATION_PLAN_R02.txt. R02 fixes the typed tree transport: every node is [typeof, payload]; floats as IEEE754 hex; dictionaries in insertion order with typed info; typed arrays with typed info; String and StringName distinct. The probe will prove the transport lossless in Godot by an encode/decode roundtrip with matching var_to_bytes and record raw_bytes_sha, tree_sha and roundtrip_ok per checkpoint; Python recomputes tree_sha. The full B1 tree plus exact per-tick path diff operations reconstruct every checkpoint of S and latest_snapshot with no depth limit. E2 events are carried as typed trees of the product to_dictionary output plus original field types. Comparison is exact typed tree equality first, then a textual-key fallback that records every dict-order-only path. normalize_events and normalize_tree return exactly (normalized, sidecar), where sidecar has the fixed keys entries, violations and profile_root. Violations outside the r03 closed list are kept unnormalized and do not fail by mere existence; trying to normalize them is rejected, and a real cross-side difference in them is a failure. S1 implements compare_versions.py (comparison, the four normalize/restore functions, diff reconstruction, tree_sha recomputation, check_source_closure) with synthetic-tree tests in test_eq.py, plus PROGRESS.md and IMPLEMENTATION.md. Never delete created files. Run the engine-free python3 test_eq.py until every existing test passes, save the raw output to UNIT_OUTPUT.txt, and end S1 with formal status blocked and the finding 'stage S1 complete (not a failure; next is S2)'; only S4 submits. Deliver as the one supplementary key the full texts of both plans and this decision; clarifications r01, r02 and r03 are placed read-only in build/ with path and SHA, and eq/references are read from the W. No engine, no git write or commit, no product change, no full regression; SPEC, TASK, eq/references and eq/registration stay unchanged. Ordinary implementation choices are recorded without stopping; block only for undecided matters affecting original product spec, values, permissions, billing or original acceptance. After S4, Root runs native on the frozen S_eq in 7 phases and builds the bundle; then both validations are mandatory, followed by a fresh independent Opus review and the normal integration and final. All 6 conditions are kept; no original credit (23/88, 4/30). Quota fallback only on structured provider quota, once, same role, Antigravity claude-opus-5-5-high; any other failure is blocked. Decided by Claude Opus 5.5 in the Desktop control session (message 320); not a CLI commander result."}
|
||||
+341
@@ -0,0 +1,341 @@
|
||||
{
|
||||
"at_jst": "2026-10-21T07:10:02.150187+09:00",
|
||||
"project": "the-tree-p28-02-diag",
|
||||
"task": "P28-02-DIAG",
|
||||
"state": "completed",
|
||||
"S": "d6ce6ddb95e38ef987f37af98f02f0531483008f",
|
||||
"Q": "fdb309f3bc81529135915429d2432a6644696311",
|
||||
"M": "a25ee83441a967f9979e59b1c019f8b4d9ab6e22",
|
||||
"all_four_normal_gates_passed": true,
|
||||
"acceptance": [
|
||||
{
|
||||
"id": "P28-02-D1",
|
||||
"requirement_id": "P28-02",
|
||||
"text": "現在の source 1407 で、代表入力 R の tick を、tick ID で結びつけた inclusive の wall 区間 I0〜I11 として、受動的な観測だけで別 PID の3回で測る(observer の費用は別に記録し、差し引かない)。",
|
||||
"validation_ids": [
|
||||
"p28-02-diag-unit",
|
||||
"p28-02-diag-native-verify"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-02-D2",
|
||||
"requirement_id": "P28-02",
|
||||
"text": "tick の外で、snapshot の変換を独立の inclusive 計測(N=5、合算しない)で、restore・save・load・validator を使い捨ての scratch でだけ測り、live の全状態の一致と参照の漏れの無さを確かめる。測れない粒度は理由つきの未測定にする。",
|
||||
"validation_ids": [
|
||||
"p28-02-diag-unit",
|
||||
"p28-02-diag-native-verify"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-02-D3",
|
||||
"requirement_id": "P28-02",
|
||||
"text": "経路は path_query_measured の件数と時間だけを区間に結びつけ、connector の費用とは呼ばない。machine・build・seed・RNG state・入力・区間・平均・P95・最大・標本数を記録し、欠損を 0 で補わない。",
|
||||
"validation_ids": [
|
||||
"p28-02-diag-unit",
|
||||
"p28-02-diag-native-verify"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-02-D4",
|
||||
"requirement_id": "P28",
|
||||
"text": "worker の提出した S を Root が隔離した stage で native 実行し(import・parse・3 PID)、owned PID・private profile・guard・cleanup・source map 1407・全 raw と SHA を結合する。最終候補 Q で測ったとは表記しない。",
|
||||
"validation_ids": [
|
||||
"p28-02-diag-unit",
|
||||
"p28-02-diag-native-verify"
|
||||
]
|
||||
}
|
||||
],
|
||||
"normal_gates": [
|
||||
{
|
||||
"kind": "validation",
|
||||
"commit": "d6ce6ddb95e38ef987f37af98f02f0531483008f",
|
||||
"receipt": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/052a7f7671c34228938b3a5ffcda8723-validation.json",
|
||||
"bytes": 616,
|
||||
"sha256": "2222eaa7d85b2d17a115e9f21e0e1250922f009bebd7165054b7212bc3e68c01"
|
||||
},
|
||||
"checks": [
|
||||
{
|
||||
"id": "p28-02-diag-unit",
|
||||
"exit_code": 0,
|
||||
"raw": {
|
||||
"command.json": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/3c6308e284d444ffb999c7ee5de2be84/command.json",
|
||||
"bytes": 725,
|
||||
"sha256": "12d665768693abcd00df5eb5c1acde89936b0e9dc45bd440d312aadb427a1506"
|
||||
},
|
||||
"stdout.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/3c6308e284d444ffb999c7ee5de2be84/stdout.log",
|
||||
"bytes": 61,
|
||||
"sha256": "5a0a7607403c4fb2bf309b678414c030a1852f7d3ef00640dfa08b3c263eaeeb"
|
||||
},
|
||||
"stderr.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/3c6308e284d444ffb999c7ee5de2be84/stderr.log",
|
||||
"bytes": 2139,
|
||||
"sha256": "4f3aa4e343f8ac766a1508daca44f37612ae4d1fea33383736213298ca69b8ec"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "p28-02-diag-native-verify",
|
||||
"exit_code": 0,
|
||||
"raw": {
|
||||
"command.json": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/b8811a05af03429d9c579d7c37c107b0/command.json",
|
||||
"bytes": 758,
|
||||
"sha256": "c1491f7bad23953db88b418651957b72aa98744132c6f65865c085a15ce2d41d"
|
||||
},
|
||||
"stdout.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/b8811a05af03429d9c579d7c37c107b0/stdout.log",
|
||||
"bytes": 19889,
|
||||
"sha256": "40d6c5ea04dc9717be2d76e4c3540499a947e7d2c174a4f10667d9cf6ede8fe2"
|
||||
},
|
||||
"stderr.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/b8811a05af03429d9c579d7c37c107b0/stderr.log",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "review",
|
||||
"commit": "d6ce6ddb95e38ef987f37af98f02f0531483008f",
|
||||
"receipt": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/diag-independent-review-d6ce6dd-r01/actual-independent-desktop-output/result.json",
|
||||
"bytes": 5723,
|
||||
"sha256": "00d38f98ef950fd14aeb6f3453ac0cab058212a7631df833bb7842cd4c840f6f"
|
||||
},
|
||||
"findings": [
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "inter_tick_frame_wall の最初の測定 tick(tick 31)の frame_wall_before_us には、B1 の ensure_cache と layers()(create_snapshot、約80 ms)による診断の作業が入っています。diag-1 では 596708 us で、これがこの run の最大値です。phase には帰属していませんが、最大値が診断の作業で膨らんでいることは注記されていません。OPT で frame の値を使うときは、この標本を区別してください。"
|
||||
},
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "scratch の SaveService.save_snapshot と load_snapshot は、プロセス全体で共有する静的な SaveService._performance_budget に record_sample します。S/K/C の外にある状態です。IMPLEMENTATION.md に開示されていて、管制が範囲の外として採用済みで、製品にそれを読む箇所はありません(grep で確認)。ただし、計測の前後の値は report に記録されていません。"
|
||||
},
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "main の _ready による save policy の設定は、設定後の値(report.startup.save_policy)だけが記録されています。設定前の値は記録されていません。S の SHA は main の追加の前後で一致しています。"
|
||||
},
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "検証と restore の実測は envelope のもの(SimulationSnapshot.validate と restore。数〜数十 us)だけです。Runtime 全体の restore と内部の validator は、理由つきの未測定です。SaveService の load と save の inclusive の値(約235 ms と約170 ms)には、package の検証が含まれます。OPT の対象を選ぶときに、envelope の値を Runtime の検証の費用として扱わないでください。"
|
||||
},
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "RNG の状態(typed hex)は、測定が終わった後の分 662 に1回だけ記録されています。測定の開始時点(分 632)の RNG は記録されていません。seed と、PID 間の RNG の一致は確認できます。"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "integration",
|
||||
"commit": "fdb309f3bc81529135915429d2432a6644696311",
|
||||
"receipt": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/9d2a0477dd644e3d95e50429aabfea42-integration.json",
|
||||
"bytes": 617,
|
||||
"sha256": "8e1a367415d7a1b14c6e26d880abd038e08ac0ca9ddf092cc486665b4ff0236c"
|
||||
},
|
||||
"checks": [
|
||||
{
|
||||
"id": "p28-02-diag-unit",
|
||||
"exit_code": 0,
|
||||
"raw": {
|
||||
"command.json": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/2882e333b26e4e19966b92fed2311248/command.json",
|
||||
"bytes": 726,
|
||||
"sha256": "bfc9f305a54f2a3955cb9a2d032952b91ac64c3e68c025ce0a7e1e8fcfe58b65"
|
||||
},
|
||||
"stdout.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/2882e333b26e4e19966b92fed2311248/stdout.log",
|
||||
"bytes": 61,
|
||||
"sha256": "96c24d2529b7235a768427f4ad6ff161db12a1319845ac39ac1f214200b72041"
|
||||
},
|
||||
"stderr.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/2882e333b26e4e19966b92fed2311248/stderr.log",
|
||||
"bytes": 2139,
|
||||
"sha256": "32cbae529b357842a04b256fd70620465f40bb00fef29b87e9eaf4cbfb28108b"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "p28-02-diag-native-verify",
|
||||
"exit_code": 0,
|
||||
"raw": {
|
||||
"command.json": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/dedc2a26e33d48ef964dda06700545ee/command.json",
|
||||
"bytes": 759,
|
||||
"sha256": "22d8863ed04c822d5554f439e80db97e018449dcc6b0a9b234d84166bcba73e9"
|
||||
},
|
||||
"stdout.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/dedc2a26e33d48ef964dda06700545ee/stdout.log",
|
||||
"bytes": 19889,
|
||||
"sha256": "40d6c5ea04dc9717be2d76e4c3540499a947e7d2c174a4f10667d9cf6ede8fe2"
|
||||
},
|
||||
"stderr.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/dedc2a26e33d48ef964dda06700545ee/stderr.log",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "final",
|
||||
"commit": "fdb309f3bc81529135915429d2432a6644696311",
|
||||
"receipt": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/8ee802edc437462b852d76e09905b22c-final.json",
|
||||
"bytes": 611,
|
||||
"sha256": "1333c5e4b95840685d6525e7e15b7d9cade6b6802f47eb147937c17b01c273df"
|
||||
},
|
||||
"checks": [
|
||||
{
|
||||
"id": "p28-02-diag-unit",
|
||||
"exit_code": 0,
|
||||
"raw": {
|
||||
"command.json": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/b366acde4390434b900e4f0a316204fa/command.json",
|
||||
"bytes": 720,
|
||||
"sha256": "46b2fee761a8af338b3ba93f55e82dca71c97cc01fb994c7164ab835421e0781"
|
||||
},
|
||||
"stdout.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/b366acde4390434b900e4f0a316204fa/stdout.log",
|
||||
"bytes": 61,
|
||||
"sha256": "e7ab4a4864135eda18fb062c3d95f11ceb533ba94ed9d44688d5de5a1f7ce45c"
|
||||
},
|
||||
"stderr.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/b366acde4390434b900e4f0a316204fa/stderr.log",
|
||||
"bytes": 2139,
|
||||
"sha256": "55f512a407a7814b1ce136fce796bb675c50312a268b03bdfb2b4033a67e04ca"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "p28-02-diag-native-verify",
|
||||
"exit_code": 0,
|
||||
"raw": {
|
||||
"command.json": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/139959117af94b56bd09e073bcc067fa/command.json",
|
||||
"bytes": 753,
|
||||
"sha256": "bf6ed61fd9ec888c91d2ef773a5fd14e1f1791a16cbba71dd7b6dd6d9568489a"
|
||||
},
|
||||
"stdout.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/139959117af94b56bd09e073bcc067fa/stdout.log",
|
||||
"bytes": 19889,
|
||||
"sha256": "40d6c5ea04dc9717be2d76e4c3540499a947e7d2c174a4f10667d9cf6ede8fe2"
|
||||
},
|
||||
"stderr.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/runs/139959117af94b56bd09e073bcc067fa/stderr.log",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"traceability": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-diag/traceability/P28-02-DIAG.json",
|
||||
"bytes": 2996,
|
||||
"sha256": "d7f3beda2f6ef111baab28f6f53537345ef6f66b9eec753bb29b548d08ce63c1"
|
||||
},
|
||||
"candidate_qualification": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/ROOT_DIAG_CANDIDATE_FDB309F_QUALIFICATION.json",
|
||||
"bytes": 242987,
|
||||
"sha256": "f04afc857ab4c9529f2f14370668655c9a7f5cc25ef213a0c2695b3ec2ff8e4b"
|
||||
},
|
||||
"review": {
|
||||
"model": "claude-opus-5-5",
|
||||
"session_id": "local_d0a7d3d0-aaef-4e3d-8761-185e62a3ac6b",
|
||||
"context": "fresh",
|
||||
"actual_result": {
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p28-02/DIAG_INDEPENDENT_REVIEW_D6CE6DD.json",
|
||||
"bytes": 5723,
|
||||
"sha256": "00d38f98ef950fd14aeb6f3453ac0cab058212a7631df833bb7842cd4c840f6f"
|
||||
},
|
||||
"delivery": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/diag-independent-review-d6ce6dd-r01/DESKTOP_DELIVERY.json",
|
||||
"bytes": 1822,
|
||||
"sha256": "3c58b811fc5353082bc1271f750a0afc2cb74e90858af2b26e147132843a49c4"
|
||||
},
|
||||
"original_null_delivery_preserved": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/diag-independent-review-d6ce6dd-r01/DESKTOP_DELIVERY.null-result-sha.preserved.json",
|
||||
"bytes": 1675,
|
||||
"sha256": "a3afbc7f6e8da0e4f2e3a6efcaa1d78391cbb8dbcff72945be311638e82b3be3"
|
||||
},
|
||||
"high_medium": 0,
|
||||
"low": 5,
|
||||
"findings": [
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "inter_tick_frame_wall の最初の測定 tick(tick 31)の frame_wall_before_us には、B1 の ensure_cache と layers()(create_snapshot、約80 ms)による診断の作業が入っています。diag-1 では 596708 us で、これがこの run の最大値です。phase には帰属していませんが、最大値が診断の作業で膨らんでいることは注記されていません。OPT で frame の値を使うときは、この標本を区別してください。"
|
||||
},
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "scratch の SaveService.save_snapshot と load_snapshot は、プロセス全体で共有する静的な SaveService._performance_budget に record_sample します。S/K/C の外にある状態です。IMPLEMENTATION.md に開示されていて、管制が範囲の外として採用済みで、製品にそれを読む箇所はありません(grep で確認)。ただし、計測の前後の値は report に記録されていません。"
|
||||
},
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "main の _ready による save policy の設定は、設定後の値(report.startup.save_policy)だけが記録されています。設定前の値は記録されていません。S の SHA は main の追加の前後で一致しています。"
|
||||
},
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "検証と restore の実測は envelope のもの(SimulationSnapshot.validate と restore。数〜数十 us)だけです。Runtime 全体の restore と内部の validator は、理由つきの未測定です。SaveService の load と save の inclusive の値(約235 ms と約170 ms)には、package の検証が含まれます。OPT の対象を選ぶときに、envelope の値を Runtime の検証の費用として扱わないでください。"
|
||||
},
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "RNG の状態(typed hex)は、測定が終わった後の分 662 に1回だけ記録されています。測定の開始時点(分 632)の RNG は記録されていません。seed と、PID 間の RNG の一致は確認できます。"
|
||||
}
|
||||
]
|
||||
},
|
||||
"actual_native": {
|
||||
"measured_source": "S",
|
||||
"candidate_Q_measured": false,
|
||||
"execution": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/native-d6ce6dd-r01/execution.json",
|
||||
"bytes": 87245363,
|
||||
"sha256": "d2d13503d1b84b12872b693942809921140e019a7f32ef3f60c156291a649f58"
|
||||
},
|
||||
"bundle": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/native-d6ce6dd-r01/bundle.json",
|
||||
"bytes": 10496,
|
||||
"sha256": "6ea47083b338a59505e8347e2f556180bf9b21a8769298a58e5b91ce0a92d4d1"
|
||||
},
|
||||
"transfer": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/NATIVE_D6CE6DD_BUNDLE_TRANSFER.json",
|
||||
"bytes": 879,
|
||||
"sha256": "5a14d50e19707a0ac2a2d5571ba7e3301836ee8d64af01b8062163008cd1cd44"
|
||||
},
|
||||
"source_files": 1407,
|
||||
"all_six_owned_processes_ended": true,
|
||||
"source_and_player_unchanged": true,
|
||||
"cleanup_errors": []
|
||||
},
|
||||
"failed_ecbbb6b_preserved": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/ROOT_NATIVE_FAILURE_ECBBB6B.json",
|
||||
"bytes": 4650,
|
||||
"sha256": "1229e120ff3838285b9e4c18809e8ffaa890ed351a52bc2e99789c3df096ad96"
|
||||
},
|
||||
"timeout_failure_preserved": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/diag-repair2-timeout-09ac15a-r01/MANIFEST.json",
|
||||
"bytes": 7892,
|
||||
"sha256": "54d300efbb70f1b4d9a786ee09b287be25ec7393b1df9379db7f76bddaa83da5"
|
||||
},
|
||||
"original_completed": 23,
|
||||
"original_total": 88,
|
||||
"packages_completed": 4,
|
||||
"packages_total": 30,
|
||||
"partial_credit": false,
|
||||
"original_credit": 0,
|
||||
"scope": "DIAG support stage only. Original P28-02 remains in_progress until actual improvement, E1-E5, repeat comparisons, required regressions/restart, whole-unit review, integration/final and ledger.",
|
||||
"remaining": [
|
||||
"P28-02 EQ freeze and T1 OPT implementation/acceptance",
|
||||
"Six original performance budget failures",
|
||||
"200 guest Operations ON maximum scene",
|
||||
"Same Windows build native-device acceptance",
|
||||
"Long normal play and all original game/content/material acceptance"
|
||||
]
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"task_id": "P28-02-DIAG",
|
||||
"commit": "d6ce6ddb95e38ef987f37af98f02f0531483008f",
|
||||
"status": "pass",
|
||||
"summary": "独立レビュー(読取専用)。整合性: packet a3a5b01b…、schema 04a4823f…、manifest 80348632… が一致し、117件すべてが manifest の SHA と bytes に一致。S は base 8c06c3b からの差分で、diag/** の8ファイルだけ。D1: probe は tick_started・tick_phase_started・tick_completed に受動的に接続し、tick ID と13本の境界を記録する。checker は phase0 を m、それ以外を m+1 として検査し、分ではなく ID で結びつける。I0〜I11 は隣り合う境界の差で、kernel の _run_phase(emit の後に handler)と照合して SPEC の定義どおり。observer の費用は callback ごとに別に記録し、差し引かない(semantics.observer_subtracted=false)。tick の間の frame は frame_wall_before_us の別欄。native の3 PID(74945・75718・76472)すべてで、warm 30・測定30・errors 0。D2: S/K/C を各標本の前後で bytes 比較する(K と C は S の観測より前に読み、観測の後に再確認する)。C 層は、規則 (a) を cache_preparations の restored・B1・outside_tick で記録。規則 (b)(c) は、miss のときに cache_miss_in_sample とし、公開の catalog 読取から再構築して比較する。前後の完全一致も要求する。native では、全標本、outside_total、manifest_detached が S・K・C ともに一致し、miss は0。scratch には live の deep copy を渡し、保存先は user://p28_diag/scratch/ の下。detached mutation の後も S/K/C は一致し、3つの instance ID は解放後に無効。observer の接続数は before が connected−1、disconnected が before と同じ。N=5 の独立した inclusive の値で、合算や100%への換算は無い。runtime_restore・runtime_validation_internal・connector_rebuild は samples=null と理由。Runtime の initialize が autoload の GameClock/GameState に結びつくことを source で確認し、理由は妥当。管制が採用済み。起動: 準備の状態変更は set_paused×2・get_var・restore_snapshot・main の add_child と2 frame だけで、checker が静的に強制する。B0/B1/outside の S SHA、K の6項目、C、Ops=INVALID_START_TIME は3 PID で一致。D3: path は query の件数と時間だけで、connector の値は null と理由。machine Mac16,10、engine の SHA と version、commit、seed、RNG 9本の typed hex、入力 eb4f7700… を記録し、各区間の平均・P95・最大・N を出す。D4: import・parse-base・parse-wrapper(--check-only)・3 PID は、exit 0、ERROR と WARNING が0、owned cleanup 済み、PID と profile は重複なし、guard 一致、source map 1407、bundle の72ファイルが SHA 一致。measured_source は S で、candidate_Q_measured は false。正式 validation は unit 20件 OK と native-verify verified。順位は上位3(I10・I4・I11)が3回とも一致する一方、run3 の I2 と I7 の入れ替わりで rankings_agree=false。これは raw と verify の出力にそのまま記録されていて、隠されていない。ecbbb6b の parse の失敗は別に保全され、成功扱いにはなっていない。原本の加算は0(23/88、4/30)。P28-02 全体や OPT の合否は判定していない。指摘は low の注記だけ。",
|
||||
"acceptance_ids": [
|
||||
"P28-02-D1",
|
||||
"P28-02-D2",
|
||||
"P28-02-D3",
|
||||
"P28-02-D4"
|
||||
],
|
||||
"findings": [
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "inter_tick_frame_wall の最初の測定 tick(tick 31)の frame_wall_before_us には、B1 の ensure_cache と layers()(create_snapshot、約80 ms)による診断の作業が入っています。diag-1 では 596708 us で、これがこの run の最大値です。phase には帰属していませんが、最大値が診断の作業で膨らんでいることは注記されていません。OPT で frame の値を使うときは、この標本を区別してください。"
|
||||
},
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "scratch の SaveService.save_snapshot と load_snapshot は、プロセス全体で共有する静的な SaveService._performance_budget に record_sample します。S/K/C の外にある状態です。IMPLEMENTATION.md に開示されていて、管制が範囲の外として採用済みで、製品にそれを読む箇所はありません(grep で確認)。ただし、計測の前後の値は report に記録されていません。"
|
||||
},
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "main の _ready による save policy の設定は、設定後の値(report.startup.save_policy)だけが記録されています。設定前の値は記録されていません。S の SHA は main の追加の前後で一致しています。"
|
||||
},
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "検証と restore の実測は envelope のもの(SimulationSnapshot.validate と restore。数〜数十 us)だけです。Runtime 全体の restore と内部の validator は、理由つきの未測定です。SaveService の load と save の inclusive の値(約235 ms と約170 ms)には、package の検証が含まれます。OPT の対象を選ぶときに、envelope の値を Runtime の検証の費用として扱わないでください。"
|
||||
},
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "RNG の状態(typed hex)は、測定が終わった後の分 662 に1回だけ記録されています。測定の開始時点(分 632)の RNG は記録されていません。seed と、PID 間の RNG の一致は確認できます。"
|
||||
}
|
||||
]
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
EQ_ENVIRONMENT_FIELDS_CLARIFICATION r03(P28-02-EQ repair 2。base 2549cf7d6992560b850bbd11934e3c14b39a1988。EQ_S_TIMING_CLARIFICATION r01 と r02 の全文は、そのまま有効で、書き換えない。r02 の T1(閉じた対象)を、ここで閉じた一覧の F1〜F3 に広げて確定する。SPEC、TASK、6条件、eq/references、eq/registration、製品、原本の 30/88、権限、課金は変更しない)
|
||||
|
||||
範囲(静的に一括して点検したもの)
|
||||
- Runtime: restore_snapshot(:587)、load_from_path(:4223)→ _finish_load_operation(:4683-4733)→ _complete_save_operation(:4734)→ _save_event_payload(:4745-4768)→ _publish_actor_event → domain_events(S の systems.runtime.domain_events.recent_events、E2 の記録)。
|
||||
- SimulationSnapshot.validate。
|
||||
- SaveService: save_snapshot(:29)、load_snapshot(:49)、_resolve_safe_save_path(:1684-1706)、_load_snapshot_absolute_v1(:946-1042)、_write_package_directory の manifest(:901-917)、_complete_timed_operation(:1751-1761)、_failure(:1956)。
|
||||
|
||||
根拠表(源泉 → 返却 → payload → S/E2)
|
||||
|
||||
| 欄 | 源泉 | S/E2 に入るか | 決定性 | 扱い |
|
||||
|---|---|---|---|---|
|
||||
| duration_us | SaveService._complete_timed_operation(:1756-1760) | 入る(payload.duration_us) | 実時間 | F1 |
|
||||
| runtime_duration_us | Runtime._finish_load_operation(:4717) | 入る(payload.runtime_duration_us) | 実時間 | F2 |
|
||||
| message(失敗の場合) | SaveService._failure(code, message)。target_path・world_path・manifest_path(:957、:963、:1011 ほか)と、"symbolic link at package read boundary: %s" などに、_resolve_safe_save_path が globalize した private profile の絶対 path が入る | 入る(payload.message) | PID ごとに profile の root が違う | F3 |
|
||||
| message(その他) | error_string(...)、検証の errors の結合、format_version の文言、Runtime の RESTORE_FAILED の message(= 渡した save_path) | 入る | 決定的(path を固定すれば) | 正規化しない |
|
||||
| save_path | Runtime の _with_operation_context の save_path(渡した user:// の文字列) | 入る | 決定的(下の D1) | 正規化しない |
|
||||
| save_id | manifest の save_id = metadata.save_id か logical_name(:905) | 入る(load の成功の場合) | 決定的(下の D1・D2) | 正規化しない |
|
||||
| saved_absolute_minute | manifest の absolute_minute | 入る | 決定的 | 正規化しない |
|
||||
| ok、code、operation、reason、request_id、checkpoint_absolute_minute、loaded_clock_paused、preload_pause_restored | Runtime と SaveService の戻り値 | 入る(一部は payload の外の result だけ) | 決定的 | 正規化しない |
|
||||
| created_at_real、updated_at_real | manifest の now = Time.get_datetime_string_from_system(:901、:907-908) | **入らない**(_save_event_payload は manifest から save_id と absolute_minute だけを取る) | 実時刻 | S/E2 の比較の対象外。package のファイルの記録として保持する |
|
||||
| manifest の他の欄、thumbnail、journal | package のファイル | 入らない | — | S/E2 の比較の対象外。E3 の改ざんのファイルとして SHA を記録する |
|
||||
| SaveService._performance_budget | 静的な診断 | 入らない(S/K/C の外。DIAG の low の注記) | 実時間 | PID ごとの前後の値を記録し、比べない |
|
||||
| latest_save_operation_result | Runtime の変数 | 入らない(create_snapshot の外) | F1〜F3 を含む | 同じ PID の中で payload との一致を確かめる |
|
||||
|
||||
閉じた一覧(r02 の T1 を置き換える。これ以外は正規化しない)
|
||||
- 対象の event: source_service が "save_service" で、event_type が9種(SAVE_CHECKPOINT_SUCCEEDED、SAVE_CHECKPOINT_FAILED、SAVE_LOADED、SAVE_LOAD_FAILED、SAVE_MIGRATION_SUCCEEDED、SAVE_MIGRATION_FAILED、SAVE_RECOVERY_LOADED、SAVE_RECOVERY_CANDIDATE_LOADED、SAVE_RECOVERY_FAILED)のいずれか。その payload の中だけ。
|
||||
- F1 duration_us、F2 runtime_duration_us: r02 の T2 のとおり、typed の marker に置き換えて、sidecar に値を記録する。
|
||||
- F3 message(文字列)の中の、private profile の絶対 root:
|
||||
- root は、その PID の ProjectSettings.globalize_path("user://").simplify_path() の値。これが、その PID の guard の receipt の actual_user_data_dir と一致することを確かめる。一致しなければ失敗にする。
|
||||
- message の中の、その root の文字列(末尾の "/" を含む prefix として現れる箇所すべて)を、固定の token "{PRIVATE_USER_ROOT}/" に置き換える。元の message の全体を、sidecar に記録する。
|
||||
- 逆の復元: token をその PID の root に戻すと、元の message と完全一致すること。
|
||||
- 置き換えた後の message は、PID の間・版の間で完全一致すること。
|
||||
- root 以外の部分(相対 path、code、文言)は、変更しない。
|
||||
|
||||
決定性の前提(harness の側で守ること。製品は変えない)
|
||||
- D1: E3/E4/E5 が使う user:// の path は、全て固定の相対名にする(例: user://p28_eq/r/good、user://p28_eq/r/tampered_world)。uuid、時刻、PID、乱数を名前に入れない。
|
||||
- D2: save に渡す metadata は、固定の値か空にする(save_id、display_name、created_at_real を時刻や乱数から作らない)。
|
||||
- D3: 改ざんの方法は、固定の規則にする(RESTART と同じく、world.bin の offset len//2 の1バイトを XOR 0x01)。
|
||||
|
||||
検査と試験
|
||||
- 同じ PID の中: F1〜F3 の正規化の前後で可逆であること。正規化の対象が、閉じた一覧の中だけであること。
|
||||
- PID の間と版の間: 正規化の後の S、latest_snapshot、E2 の全イベントが完全一致すること。F1・F2 の値と、F3 の元の root は比べない(理由と各側の実際の値を記録する)。
|
||||
- 閉じた一覧の外で PID の間に差があった場合は、正規化を広げない。失敗として、その path・型・両側の値を全て記録し、判断に戻す(native の実測で初めて分かる場合も、同じ扱い)。
|
||||
- 試験(test_eq.py)を加える:
|
||||
- F3 の正常(root の置き換えと復元)
|
||||
- root が guard の actual_user_data_dir と一致しない場合の拒否
|
||||
- root 以外の部分を置き換える場合の拒否
|
||||
- 閉じた一覧の外の欄や event の正規化の拒否
|
||||
- D1 の違反(uuid や時刻を使った path)の拒否
|
||||
- manifest の時刻を S の比較に入れる場合の拒否
|
||||
- 復元できない sidecar の拒否
|
||||
- 製品の状態、RNG、ID の counter、意味のある値は、正規化しない。
|
||||
|
||||
worker への手順
|
||||
- 前回までは実装0なので、SPEC・TASK・r01・r02・この r03 に従い、eq/** に6ファイルを実装する。SPEC、TASK、eq/registration、eq/references は変更しない。
|
||||
- 静的に分かる環境由来の差は、この表で閉じている。新しい欄に気づいた場合も、短く止めずに、上の「閉じた一覧の外」の規則(失敗として記録して判断に戻す)を harness に実装して進める。blocked にしてよいのは、原本の仕様・値・権限・課金・受入に影響する未確定事項がある場合だけ。
|
||||
- engine を使わない test_eq.py の全件 PASS を局所で確かめ、生出力を報告し、正式な JSON で提出する。engine の起動、git への書込み、製品の変更、全回帰はしない。
|
||||
+412
@@ -0,0 +1,412 @@
|
||||
{
|
||||
"at_jst": "2026-10-22T10:14:42.989122+09:00",
|
||||
"project": "the-tree-p28-02-eq",
|
||||
"task": "P28-02-EQ",
|
||||
"state": "completed",
|
||||
"S": "9adbbee334548ac36cd49d68efc021abb7acd003",
|
||||
"Q": "4e34e5209706e3e16a4860e65e52b90287722c36",
|
||||
"M": "54e07ca98093901e6a53d1bdad86b6c81fcc2db5",
|
||||
"all_four_normal_gates_passed": true,
|
||||
"acceptance": [
|
||||
{
|
||||
"id": "P28-02-EQ1",
|
||||
"requirement_id": "P28-02",
|
||||
"text": "E1 全状態: 観測の区間では、同じ PID の中で完全な S/K/C の前後の一致を確かめる(SPEC §3.3 (a))。版の間では、S 層・latest_snapshot・latest_digest と、SPEC §3.2 に列挙した K/C の欄を比べる。比べない欄は理由と実際の値を記録し、SimulationDigest だけで判定しない。",
|
||||
"validation_ids": [
|
||||
"p28-02-eq-unit",
|
||||
"p28-02-eq-native-verify"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-02-EQ2",
|
||||
"requirement_id": "P28-02",
|
||||
"text": "E2 全イベント: DomainEventStream.subscribe('*') で B1 の後から30分の最後まで、全件を上限なく記録し、開始の counter が一致する場合だけ、event ID をそのまま対応させて全ての欄を比べる。",
|
||||
"validation_ids": [
|
||||
"p28-02-eq-unit",
|
||||
"p28-02-eq-native-verify"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-02-EQ3",
|
||||
"requirement_id": "P28-02",
|
||||
"text": "E3 失敗時の拒否: restore_snapshot・SimulationSnapshot.validate・load_from_path に、欠けた key・違う型・範囲外・重複した ID・改ざんした world.bin を与え、code と、拒否の前後の S/K/C と全イベントを記録して検査する。",
|
||||
"validation_ids": [
|
||||
"p28-02-eq-unit",
|
||||
"p28-02-eq-native-verify"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-02-EQ4",
|
||||
"requirement_id": "P28-02",
|
||||
"text": "E4 キャッシュの古さと E5 所有権: SPEC §3.3 (b) の専用の scenario の区間で、列挙した操作(コマンド・tick・restore・load・状態を変えないコマンド、返した値の入れ子の変更)だけを行い、前後の変化と双方向の非干渉を実際の値として記録して検査する。前の版での失敗(たとえば E5 の aliasing)は、既存の不具合として記録する(PASS で補わない。harness の無断の変更とは区別する)。列挙の外の状態の変更と、hook の差し替えは禁止。",
|
||||
"validation_ids": [
|
||||
"p28-02-eq-unit",
|
||||
"p28-02-eq-native-verify"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-02-EQ5",
|
||||
"requirement_id": "P28-02",
|
||||
"text": "harness の正しさ: R と O の、同じ版の2 PID の間で、版の間の比較の差が0であること。compare_versions.py が、before の source map と OPT の許可された差分の集合を完全に固定し、追加・欠落・登録されていない差分を拒否すること。",
|
||||
"validation_ids": [
|
||||
"p28-02-eq-unit",
|
||||
"p28-02-eq-native-verify"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "P28-02-EQ6",
|
||||
"requirement_id": "P28",
|
||||
"text": "提出された S_eq の native(import、parse 2つ、R-A・R-B・O-A・O-B)を Root が実行し、owned PID・private profile・guard・cleanup・source map 1407・全 raw と SHA を結合する。Q で測ったとは表記しない。",
|
||||
"validation_ids": [
|
||||
"p28-02-eq-unit",
|
||||
"p28-02-eq-native-verify"
|
||||
]
|
||||
}
|
||||
],
|
||||
"normal_gates": [
|
||||
{
|
||||
"kind": "validation",
|
||||
"commit": "9adbbee334548ac36cd49d68efc021abb7acd003",
|
||||
"receipt": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-eq/runs/c8dcaba67e4b4b35a6e6fed4bb8ffb28-validation.json",
|
||||
"bytes": 606,
|
||||
"sha256": "a41a00bd7d3bcf03dd872743a569fa342b3cdc576d9725ffa6481c40382eca1d"
|
||||
},
|
||||
"checks": [
|
||||
{
|
||||
"id": "p28-02-eq-unit",
|
||||
"exit_code": 0,
|
||||
"raw": {
|
||||
"command.json": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-eq/runs/4a82530174d04af988e119a2174315c2/command.json",
|
||||
"bytes": 705,
|
||||
"sha256": "d5a7de0e62bd09d106db0236370f4a3ca57256bf567db32d40fe0df0ee67f778"
|
||||
},
|
||||
"stdout.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-eq/runs/4a82530174d04af988e119a2174315c2/stdout.log",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
"stderr.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-eq/runs/4a82530174d04af988e119a2174315c2/stderr.log",
|
||||
"bytes": 18803,
|
||||
"sha256": "d8cd1523614ce6c40b064ff3523cca193fa32fc558eaf00b89b5a7f8ba12d525"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "p28-02-eq-native-verify",
|
||||
"exit_code": 0,
|
||||
"raw": {
|
||||
"command.json": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-eq/runs/59e0ac4afc8b4b3a9579ec014679f4a1/command.json",
|
||||
"bytes": 738,
|
||||
"sha256": "7e6a69803ae02e322e58b7a7b35f084af8d7d617b08e09eb1f885010b134f9b2"
|
||||
},
|
||||
"stdout.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-eq/runs/59e0ac4afc8b4b3a9579ec014679f4a1/stdout.log",
|
||||
"bytes": 172036886,
|
||||
"sha256": "832815c72df0ac3c5a5849a3cb392ea64fcf6cba5847cf2bacbbc83d31ac23f1"
|
||||
},
|
||||
"stderr.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-eq/runs/59e0ac4afc8b4b3a9579ec014679f4a1/stderr.log",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "review",
|
||||
"commit": "9adbbee334548ac36cd49d68efc021abb7acd003",
|
||||
"receipt": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/eq-independent-review-9adbbee-r01/actual-independent-desktop-output/result.json",
|
||||
"bytes": 8067,
|
||||
"sha256": "cec1c65dc496f1de812caedbd5dff3dd92a4314458cb0f13e67c738c9d84cf95"
|
||||
},
|
||||
"findings": [
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "verify_eq.compare_bundles(verify_eq.py:318-357)は、各版の全シナリオの中身を版の間で比べ、差が 1 件でもあれば ok を false にし、--compare-versions の exit を 1 にする。OPT_CONTRACT_T1 §3 では E5 は版ごとの絶対の検査で、OPT は before の existing-defect(E5 の latest/signal の aliasing など)を直すことが求められる。そのため、正しく直した after の版でも、E5 outside_in の変更直後の観測(observations[2])の latest と digest の欄に差が出て、ok は false になる。差は path つきで全て返り、existing_defects と after_all_e_pass もあるので、誤った合格は生じない。OPT の受入では、差が before で existing-defect だった scenario とその原因の欄に限られることを確かめる規則を、先に決めておくとよい。"
|
||||
},
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "S6Bundles の docstring(test_eq.py:2489-2492)は「no part mocked」と書き、PROGRESS.md も「recompute of a real synthetic after bundle」を本物の経路に数えている。しかし、s6_sealed_bundle(test_eq.py:2354)と S4NativeBundleLogic.recompute(test_eq.py:1533-1535)は、standard.profile_path を patch して recompute を動かしている。合成の束の profile の場所という外部入力の差し替えなので、本物の経路という区分は変わらない。ただし、記述にはその差し替えを書くべきである。"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "integration",
|
||||
"commit": "4e34e5209706e3e16a4860e65e52b90287722c36",
|
||||
"receipt": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-eq/runs/1535669cdc254d05ab041224099b1832-integration.json",
|
||||
"bytes": 607,
|
||||
"sha256": "014c86ec40e21cb49fb9952d647f2f40b5d491bbae3fbdd7b40f3e7829151f74"
|
||||
},
|
||||
"checks": [
|
||||
{
|
||||
"id": "p28-02-eq-unit",
|
||||
"exit_code": 0,
|
||||
"raw": {
|
||||
"command.json": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-eq/runs/f29d52f82b184037a50f12c6503e1e5d/command.json",
|
||||
"bytes": 706,
|
||||
"sha256": "c3fb9c3d59997e9fdce02cbbfbf6b9027f44d9453d791dfb30526ca4f315051f"
|
||||
},
|
||||
"stdout.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-eq/runs/f29d52f82b184037a50f12c6503e1e5d/stdout.log",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
"stderr.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-eq/runs/f29d52f82b184037a50f12c6503e1e5d/stderr.log",
|
||||
"bytes": 18803,
|
||||
"sha256": "456f4644b12cd4c835c8d30c8b97cfb8a21f7c62ed70f42c1a59a5c6390c5224"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "p28-02-eq-native-verify",
|
||||
"exit_code": 0,
|
||||
"raw": {
|
||||
"command.json": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-eq/runs/074578697e174c9bac87a95425a4d53a/command.json",
|
||||
"bytes": 739,
|
||||
"sha256": "e3592e5a534c5c8f0f61a53ff02f9ddd3e34efcebea14836fec50c11791545d2"
|
||||
},
|
||||
"stdout.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-eq/runs/074578697e174c9bac87a95425a4d53a/stdout.log",
|
||||
"bytes": 172036886,
|
||||
"sha256": "832815c72df0ac3c5a5849a3cb392ea64fcf6cba5847cf2bacbbc83d31ac23f1"
|
||||
},
|
||||
"stderr.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-eq/runs/074578697e174c9bac87a95425a4d53a/stderr.log",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "final",
|
||||
"commit": "4e34e5209706e3e16a4860e65e52b90287722c36",
|
||||
"receipt": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-eq/runs/ca9bf226c4744d5a8f6c38208fafe495-final.json",
|
||||
"bytes": 601,
|
||||
"sha256": "77da7c1d82d702a3e7dfe8ad9c8fa810cad8d2b0e8d612e6f0e33db97e9b6fc7"
|
||||
},
|
||||
"checks": [
|
||||
{
|
||||
"id": "p28-02-eq-unit",
|
||||
"exit_code": 0,
|
||||
"raw": {
|
||||
"command.json": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-eq/runs/79d7b4c5b09b4edc8224898537212774/command.json",
|
||||
"bytes": 712,
|
||||
"sha256": "3e7e05fb1d83acb23b3cd885b4653b8b5d9d355562252040adbbb88cc26aa03b"
|
||||
},
|
||||
"stdout.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-eq/runs/79d7b4c5b09b4edc8224898537212774/stdout.log",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
"stderr.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-eq/runs/79d7b4c5b09b4edc8224898537212774/stderr.log",
|
||||
"bytes": 18803,
|
||||
"sha256": "f8f0d5ceae54db418c3e2f69909b4cc25a4b50a3d7994bfda0efadaa0d7a91a7"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "p28-02-eq-native-verify",
|
||||
"exit_code": 0,
|
||||
"raw": {
|
||||
"command.json": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-eq/runs/c9e5c8816df24332bd24ce5a72396c8b/command.json",
|
||||
"bytes": 745,
|
||||
"sha256": "f2c3268fe841ed1483ae0e3f2bfbe17bf99e7beeda5e913a3fbe98b659a271ca"
|
||||
},
|
||||
"stdout.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-eq/runs/c9e5c8816df24332bd24ce5a72396c8b/stdout.log",
|
||||
"bytes": 172036886,
|
||||
"sha256": "832815c72df0ac3c5a5849a3cb392ea64fcf6cba5847cf2bacbbc83d31ac23f1"
|
||||
},
|
||||
"stderr.log": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-eq/runs/c9e5c8816df24332bd24ce5a72396c8b/stderr.log",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"traceability": {
|
||||
"path": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-eq/traceability/P28-02-EQ.json",
|
||||
"bytes": 4227,
|
||||
"sha256": "67fc9fafe5ced85051a82abd3d430ad4faa41b333b4ae1f26e2949503286c49b"
|
||||
},
|
||||
"candidate_qualification": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/ROOT_EQ_CANDIDATE_4E34E52_QUALIFICATION.json",
|
||||
"bytes": 276847,
|
||||
"sha256": "def3bbcc62a674e81845681df283a434f1cc6d76952eb621c32afde16719d663"
|
||||
},
|
||||
"root_candidate_recheck": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/ROOT_EQ_CANDIDATE_4E34E52_ROOT_RECHECK_R01.json",
|
||||
"bytes": 9837,
|
||||
"sha256": "a9a978f6db86d6531c53ef4ff9da55f1dcdb9a1abcc4794ce57db31e74302d68"
|
||||
},
|
||||
"root_final_db_join": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/ROOT_EQ_4E34E52_FINAL_DB_JOIN_R01.json",
|
||||
"bytes": 6085,
|
||||
"sha256": "cd5ae808e16eb046bae331f6d4cf09b3855381fc58f01ca999092c382e20a9e5"
|
||||
},
|
||||
"approval": {
|
||||
"owner": "Root applying the existing user explicit authorization to update main and continue delegated development; exact Q 4e34e5209706e3e16a4860e65e52b90287722c36 and M 54e07ca98093901e6a53d1bdad86b6c81fcc2db5 independently qualified by ROOT_EQ_CANDIDATE_4E34E52_ROOT_RECHECK_R01.json a9a978f6db86d6531c53ef4ff9da55f1dcdb9a1abcc4794ce57db31e74302d68. No new SHA-specific human statement is asserted.",
|
||||
"candidate": "4e34e5209706e3e16a4860e65e52b90287722c36",
|
||||
"main_base": "54e07ca98093901e6a53d1bdad86b6c81fcc2db5",
|
||||
"root_cli_record": [
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/ROOT_APPROVE_4E34E52.stdout",
|
||||
"bytes": 3975,
|
||||
"sha256": "525d9f66bd5e6bd1f3512071684b25fa100dbd411e5e98b624c8017bd7cb1367"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/ROOT_APPROVE_4E34E52.stderr",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/ROOT_APPROVE_4E34E52.exit",
|
||||
"bytes": 2,
|
||||
"sha256": "9a271f2a916b0b6ee6cecb2426f0b3206ef074578be55d9bc94f6f3fe3ab86aa"
|
||||
}
|
||||
],
|
||||
"note": "The governor's own approve calls were refused by the Claude Code auto-mode classifier as self-approval; the normal owner CLI approve was then run by Root (exit 0, approved)."
|
||||
},
|
||||
"integrate_job": {
|
||||
"id": "b90091f0ce2d42599382b086049b9a8f",
|
||||
"started_by": "Root",
|
||||
"state": "succeeded",
|
||||
"exit_code": 0
|
||||
},
|
||||
"review": {
|
||||
"model": "claude-opus-5-5",
|
||||
"session_id": "local_e84f7c9a-4a59-4b51-9eda-7574f6a41012",
|
||||
"context": "fresh",
|
||||
"actual_result": {
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p28-02/EQ_INDEPENDENT_REVIEW_9ADBBEE.json",
|
||||
"bytes": 8067,
|
||||
"sha256": "cec1c65dc496f1de812caedbd5dff3dd92a4314458cb0f13e67c738c9d84cf95"
|
||||
},
|
||||
"delivery": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/eq-independent-review-9adbbee-r01/DESKTOP_DELIVERY.json",
|
||||
"bytes": 1941,
|
||||
"sha256": "1f35b3ebacd1a9f963ee7ca72c5b133bd88e5c28cddca25b1f2dccda3ce607dd"
|
||||
},
|
||||
"high_medium": 0,
|
||||
"low": 2,
|
||||
"findings": [
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "verify_eq.compare_bundles(verify_eq.py:318-357)は、各版の全シナリオの中身を版の間で比べ、差が 1 件でもあれば ok を false にし、--compare-versions の exit を 1 にする。OPT_CONTRACT_T1 §3 では E5 は版ごとの絶対の検査で、OPT は before の existing-defect(E5 の latest/signal の aliasing など)を直すことが求められる。そのため、正しく直した after の版でも、E5 outside_in の変更直後の観測(observations[2])の latest と digest の欄に差が出て、ok は false になる。差は path つきで全て返り、existing_defects と after_all_e_pass もあるので、誤った合格は生じない。OPT の受入では、差が before で existing-defect だった scenario とその原因の欄に限られることを確かめる規則を、先に決めておくとよい。"
|
||||
},
|
||||
{
|
||||
"severity": "low",
|
||||
"message": "S6Bundles の docstring(test_eq.py:2489-2492)は「no part mocked」と書き、PROGRESS.md も「recompute of a real synthetic after bundle」を本物の経路に数えている。しかし、s6_sealed_bundle(test_eq.py:2354)と S4NativeBundleLogic.recompute(test_eq.py:1533-1535)は、standard.profile_path を patch して recompute を動かしている。合成の束の profile の場所という外部入力の差し替えなので、本物の経路という区分は変わらない。ただし、記述にはその差し替えを書くべきである。"
|
||||
}
|
||||
],
|
||||
"low_2_record_correction": "Review low 2 (S6Bundles docstring and PROGRESS say no part mocked, while standard.profile_path is patched for the synthetic bundle profile location) is corrected here: those tests replace that external input only. The EQ bytes stay frozen for the OPT after-version run (OPT_CONTRACT_T1 §2), so the text is not edited.",
|
||||
"low_1_carried_to_OPT": "Before OPT acceptance, fix the rule that cross-version differences are limited to scenarios that were before-version existing defects and their cause fields (compare_bundles returns every difference with its path; ok stays false on any difference)."
|
||||
},
|
||||
"prior_review_changes_requested_preserved": {
|
||||
"S": "a46418d064ebf8c8e03043bae50c29ac174b5472",
|
||||
"result": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/eq-independent-review-a46418d-r01/DESKTOP_RESULT.json",
|
||||
"bytes": 8060,
|
||||
"sha256": "c08eedb2ff81fd308e0771aeb4330076389561255a2bb964567be28f478592cd"
|
||||
},
|
||||
"repair_decision": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/DESKTOP_EQ_INDEPENDENT_REVIEW_REPAIR_DECISION_A46418D.json",
|
||||
"bytes": 1718,
|
||||
"sha256": "49e70371967a0103aeba140cbdee7876c0fc9a268bf465d435b85d4fb3e466b8"
|
||||
},
|
||||
"plan": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/EQ_S6_AFTER_VERSION_PLAN.txt",
|
||||
"bytes": 8074,
|
||||
"sha256": "e51da52336489d069c0adf2c70639755389a049334f5816804fea51a86c25197"
|
||||
}
|
||||
},
|
||||
"prior_failures_preserved": [
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/NORMAL_NATIVE_FAILURE_INTAKE_6F75C9A.json",
|
||||
"bytes": 1870,
|
||||
"sha256": "38f7947dc4b9808f24cd0d362c8e74761841ca4c3ac50d9b9ab3658f10aa69d7"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/FORMAL_VALIDATE_B2DBDA8.stdout",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
{
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/DESKTOP_EQ_VERIFY_SANDBOX_REPAIR_DECISION_B2DBDA8.json",
|
||||
"bytes": 1438,
|
||||
"sha256": "fbd3b12f153e030a1f4a656ad6a507a07ee90be721ab2c669c96ad4b68c2a25b"
|
||||
}
|
||||
],
|
||||
"actual_native": {
|
||||
"measured_source": "S",
|
||||
"candidate_Q_measured": false,
|
||||
"version": "before",
|
||||
"execution": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/native-9adbbee-r01/execution.json",
|
||||
"bytes": 6388559,
|
||||
"sha256": "4aeb4fdd9627ca5a885620d80b3d576dd60b68f0682942dec0cc881c31ba2891"
|
||||
},
|
||||
"bundle": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/native-9adbbee-r01/bundle.json",
|
||||
"bytes": 1153368,
|
||||
"sha256": "4c54b665fc23fdf6b4a8d107aa363bbf7ac827c6cabe52e14445a9a2c12053d6"
|
||||
},
|
||||
"seal": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/native-9adbbee-r01/seal.json",
|
||||
"bytes": 125,
|
||||
"sha256": "d9b827729e028d356a759ab4d5aedf448c207a47df102d5eafb6ac05bcd7b115"
|
||||
},
|
||||
"placement": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/EQ_BUNDLE_PLACEMENT_9ADBBEE.json",
|
||||
"bytes": 774,
|
||||
"sha256": "c2c8a0b50cb45f6d06b89a0702619bc8d73790eb16f46ea8f83cb789ac7a257d"
|
||||
},
|
||||
"compression_before_placement": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/EQ_9ADBBEE_NATIVE_COMPRESSION_BEFORE_PLACEMENT.json",
|
||||
"bytes": 2738,
|
||||
"sha256": "b226b8510318cc30be7f2bad7d021d342c7d3050ad8c120000a7f4d575ff1f15"
|
||||
},
|
||||
"source_files": 1407,
|
||||
"phases": 7,
|
||||
"owned_pids": 7,
|
||||
"cleanup_errors": [],
|
||||
"source_and_player_unchanged": true
|
||||
},
|
||||
"before_record_for_OPT": "SPEC §5: the S_eq native above is the before record of the OPT comparison; Q_eq 4e34e52 matches it in product source map, engine, inputs, settings and EQ files (candidate_qualification).",
|
||||
"implementer": "Claude Opus 5.5 Desktop governor (local_a7f85421-3eed-4482-8030-c0dca669573a) by user delegation; contract worker field codex unchanged; commits by Runner submit",
|
||||
"original_completed": 23,
|
||||
"original_total": 88,
|
||||
"packages_completed": 4,
|
||||
"packages_total": 30,
|
||||
"partial_credit": false,
|
||||
"original_credit": 0,
|
||||
"scope": "EQ support task only. Original P28-02 remains in_progress until T1 OPT implementation, EQ after record, same-condition comparisons, RESTART, exact final roster, whole-unit review, integration/final and ledger.",
|
||||
"remaining": [
|
||||
"P28-02-OPT registration and implementation (allowed existing product files only, OPT_CONTRACT_T1 §2)",
|
||||
"EQ after record and (a)(b)(c)(d) on the OPT S",
|
||||
"OPT validation and whole-unit independent review",
|
||||
"Six original performance budget failures",
|
||||
"200 guest Operations ON maximum scene",
|
||||
"Same Windows build native-device acceptance",
|
||||
"Long normal play and all original game/content/material acceptance"
|
||||
]
|
||||
}
|
||||
+24
File diff suppressed because one or more lines are too long
+248
@@ -0,0 +1,248 @@
|
||||
{
|
||||
"schema": "p28-02-owner-observation/v1",
|
||||
"observed_at_jst": "2026-10-22T01:45:37.125591+09:00",
|
||||
"source_main_before_docs": "ac1a115e3f87b1e31cc74852fa5a0f5ba21129cc",
|
||||
"scope": "Current and next P28-02 acceptance observation; docs-only, not game acceptance",
|
||||
"original_scope": {
|
||||
"packages": 30,
|
||||
"deliveries": 88,
|
||||
"completed_deliveries": 23,
|
||||
"completed_packages": 4,
|
||||
"new_original_credit": 0,
|
||||
"partial_credit": false
|
||||
},
|
||||
"original_unit": {
|
||||
"id": "P28-02",
|
||||
"state": "in_progress",
|
||||
"work": "失敗時拒否を含む同等性を先に固定し、snapshot変換・検証・コピー・経路接続の実測上位から改善する。",
|
||||
"acceptance": "同条件の反復比較と全状態・全イベント一致を確認し、キャッシュの古さ・所有権・不正入力を検証する。",
|
||||
"package_dependencies": [
|
||||
"P27"
|
||||
],
|
||||
"cross_cutting_acceptance": [
|
||||
"原本条件とUI操作が一致",
|
||||
"Runtimeの実利用・会計・行動へ接続",
|
||||
"途中保存・再起動で再現",
|
||||
"異常/境界/中断を検証",
|
||||
"関連回帰PASS",
|
||||
"要件別ログとソースハッシュを保存"
|
||||
]
|
||||
},
|
||||
"backend": {
|
||||
"project": "the-tree-p28-02-eq",
|
||||
"task": "P28-02-EQ",
|
||||
"state": "repair_ready",
|
||||
"repairs": 10,
|
||||
"submitted": "6f75c9a0513aafe5bd6ae7ed46b0ffda6386ac50",
|
||||
"candidate": null,
|
||||
"worktree": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement",
|
||||
"unsubmitted_Opus_changes": true,
|
||||
"contract": {
|
||||
"id": "P28-02-EQ",
|
||||
"title": "意味を保つ軽量化:同等性の先行固定(harness)",
|
||||
"requirement_ids": [
|
||||
"P28-02",
|
||||
"P28"
|
||||
],
|
||||
"acceptance_ids": [
|
||||
"P28-02-EQ1",
|
||||
"P28-02-EQ2",
|
||||
"P28-02-EQ3",
|
||||
"P28-02-EQ4",
|
||||
"P28-02-EQ5",
|
||||
"P28-02-EQ6"
|
||||
],
|
||||
"spec_ref": "docs/plans/original-completion-2026-10-02/execution/p28-02/eq/SPEC.md",
|
||||
"base_commit": "2549cf7d6992560b850bbd11934e3c14b39a1988",
|
||||
"worker": "codex",
|
||||
"allowed_paths": [
|
||||
"docs/plans/original-completion-2026-10-02/execution/p28-02/eq/**"
|
||||
],
|
||||
"depends_on": [],
|
||||
"resource_locks": [
|
||||
"p28-02-eq"
|
||||
],
|
||||
"validation_ids": [
|
||||
"p28-02-eq-unit",
|
||||
"p28-02-eq-native-verify"
|
||||
],
|
||||
"max_repair_attempts": null,
|
||||
"spec_sha256": "cf75af22a803bf492e30f8a04a39f5187ece5b94cd54207aad8ece1683d4bc93"
|
||||
},
|
||||
"delegation_event": {
|
||||
"id": 84,
|
||||
"task_id": "P28-02-EQ",
|
||||
"event": "owner_delegated_implementer",
|
||||
"data": {
|
||||
"at": 1792593136.070688,
|
||||
"implementer_service": "claude",
|
||||
"implementer_model": "claude-opus-5-5",
|
||||
"implementer_session": "local_a7f85421-3eed-4482-8030-c0dca669573a (Claude Desktop, Code tab)",
|
||||
"policy_allowed_services_includes_claude": true,
|
||||
"user_statement": "Codexの使用量上限いきそうなので、作業をひきとって",
|
||||
"user_statement_relayed_addendum": "ユーザーはこのチャットでも『実装担当含めOpusに委任』と明示済み。指揮と実装をOpus5.5で続け、Codex/Astraの新runは開始しない",
|
||||
"contract_worker_field_unchanged": "codex",
|
||||
"note": "Contract is immutable and keeps worker codex as registered; the actual implementer for repair 10 onward is Claude Opus 5.5 in the Desktop session, submitted through the normal Runner.submit gate. The independent review stays a separate fresh Opus session.",
|
||||
"no_new_codex_run": true
|
||||
},
|
||||
"created": 1792593136.0740118
|
||||
},
|
||||
"old_codex_run_terminal_record": {
|
||||
"id": "92b50d1b750e42a5bde6c325dfa1dfc1",
|
||||
"task_id": "P28-02-EQ",
|
||||
"role": "implementer",
|
||||
"service": "codex",
|
||||
"status": "finished",
|
||||
"pid": 52772,
|
||||
"started": 1792544334.2423732,
|
||||
"finished": 1792545039.5378282,
|
||||
"exit_code": 0,
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-eq/runs/92b50d1b750e42a5bde6c325dfa1dfc1",
|
||||
"api_cost_usd": null,
|
||||
"estimated_cost_usd": null,
|
||||
"subscription_usage": null,
|
||||
"usage_json": "{\"input_tokens\": 2989342, \"cached_input_tokens\": 2890752, \"cache_write_input_tokens\": 0, \"output_tokens\": 16685, \"reasoning_output_tokens\": 1313}"
|
||||
}
|
||||
},
|
||||
"frozen_spec_sha256_stripped": "cf75af22a803bf492e30f8a04a39f5187ece5b94cd54207aad8ece1683d4bc93",
|
||||
"frozen_acceptance_sha256": "84628bc21205387f8a7143031bdc50c9f68a767d350e6c34b32e0ba1c7059337",
|
||||
"all_backend_acceptance_rows": [
|
||||
{
|
||||
"acceptance": {
|
||||
"id": "P28-02-EQ1",
|
||||
"requirement_id": "P28-02",
|
||||
"text": "E1 全状態: 観測の区間では、同じ PID の中で完全な S/K/C の前後の一致を確かめる(SPEC §3.3 (a))。版の間では、S 層・latest_snapshot・latest_digest と、SPEC §3.2 に列挙した K/C の欄を比べる。比べない欄は理由と実際の値を記録し、SimulationDigest だけで判定しない。",
|
||||
"validation_ids": [
|
||||
"p28-02-eq-unit",
|
||||
"p28-02-eq-native-verify"
|
||||
]
|
||||
},
|
||||
"formal_status": "not_yet_accepted",
|
||||
"proof_required": "exact final-S registered validations, independent all6 review and normal final"
|
||||
},
|
||||
{
|
||||
"acceptance": {
|
||||
"id": "P28-02-EQ2",
|
||||
"requirement_id": "P28-02",
|
||||
"text": "E2 全イベント: DomainEventStream.subscribe('*') で B1 の後から30分の最後まで、全件を上限なく記録し、開始の counter が一致する場合だけ、event ID をそのまま対応させて全ての欄を比べる。",
|
||||
"validation_ids": [
|
||||
"p28-02-eq-unit",
|
||||
"p28-02-eq-native-verify"
|
||||
]
|
||||
},
|
||||
"formal_status": "not_yet_accepted",
|
||||
"proof_required": "exact final-S registered validations, independent all6 review and normal final"
|
||||
},
|
||||
{
|
||||
"acceptance": {
|
||||
"id": "P28-02-EQ3",
|
||||
"requirement_id": "P28-02",
|
||||
"text": "E3 失敗時の拒否: restore_snapshot・SimulationSnapshot.validate・load_from_path に、欠けた key・違う型・範囲外・重複した ID・改ざんした world.bin を与え、code と、拒否の前後の S/K/C と全イベントを記録して検査する。",
|
||||
"validation_ids": [
|
||||
"p28-02-eq-unit",
|
||||
"p28-02-eq-native-verify"
|
||||
]
|
||||
},
|
||||
"formal_status": "not_yet_accepted",
|
||||
"proof_required": "exact final-S registered validations, independent all6 review and normal final"
|
||||
},
|
||||
{
|
||||
"acceptance": {
|
||||
"id": "P28-02-EQ4",
|
||||
"requirement_id": "P28-02",
|
||||
"text": "E4 キャッシュの古さと E5 所有権: SPEC §3.3 (b) の専用の scenario の区間で、列挙した操作(コマンド・tick・restore・load・状態を変えないコマンド、返した値の入れ子の変更)だけを行い、前後の変化と双方向の非干渉を実際の値として記録して検査する。前の版での失敗(たとえば E5 の aliasing)は、既存の不具合として記録する(PASS で補わない。harness の無断の変更とは区別する)。列挙の外の状態の変更と、hook の差し替えは禁止。",
|
||||
"validation_ids": [
|
||||
"p28-02-eq-unit",
|
||||
"p28-02-eq-native-verify"
|
||||
]
|
||||
},
|
||||
"formal_status": "not_yet_accepted",
|
||||
"proof_required": "exact final-S registered validations, independent all6 review and normal final"
|
||||
},
|
||||
{
|
||||
"acceptance": {
|
||||
"id": "P28-02-EQ5",
|
||||
"requirement_id": "P28-02",
|
||||
"text": "harness の正しさ: R と O の、同じ版の2 PID の間で、版の間の比較の差が0であること。compare_versions.py が、before の source map と OPT の許可された差分の集合を完全に固定し、追加・欠落・登録されていない差分を拒否すること。",
|
||||
"validation_ids": [
|
||||
"p28-02-eq-unit",
|
||||
"p28-02-eq-native-verify"
|
||||
]
|
||||
},
|
||||
"formal_status": "not_yet_accepted",
|
||||
"proof_required": "exact final-S registered validations, independent all6 review and normal final"
|
||||
},
|
||||
{
|
||||
"acceptance": {
|
||||
"id": "P28-02-EQ6",
|
||||
"requirement_id": "P28",
|
||||
"text": "提出された S_eq の native(import、parse 2つ、R-A・R-B・O-A・O-B)を Root が実行し、owned PID・private profile・guard・cleanup・source map 1407・全 raw と SHA を結合する。Q で測ったとは表記しない。",
|
||||
"validation_ids": [
|
||||
"p28-02-eq-unit",
|
||||
"p28-02-eq-native-verify"
|
||||
]
|
||||
},
|
||||
"formal_status": "not_yet_accepted",
|
||||
"proof_required": "exact final-S registered validations, independent all6 review and normal final"
|
||||
}
|
||||
],
|
||||
"versioned_evidence": [
|
||||
{
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/ROOT_R3_COMPLETED_DIAGNOSTIC_AND_TIME_RESULT_R01.json",
|
||||
"bytes": 7753,
|
||||
"sha256": "1e15070629f3f3d44529990cd974f5d53cbff58b6ec538352bef5a0dd2f28f18"
|
||||
},
|
||||
{
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/ROOT_OPUS_S5_LOCAL_UNIT_OBSERVATION_R01.json",
|
||||
"bytes": 4322,
|
||||
"sha256": "9a766614fb3e59baa8220966d91e792a627c12c3c5a621e73bb1afdf6b2a9b00"
|
||||
},
|
||||
{
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/ROOT_OPUS_FULL_R_DIAGNOSTIC_R2_OBSERVATION.json",
|
||||
"bytes": 4685,
|
||||
"sha256": "bd094ed53c2781aa016d9e784d13676b9500d63a5cdb4d437e76bcf7333bf13f"
|
||||
},
|
||||
{
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/ROOT_EQ_OWNER_SUBMIT_API_FINDING_R01.json",
|
||||
"bytes": 2475,
|
||||
"sha256": "2bc358a48bec6cc30d53682e5e5befac1befa22fbf3188f7a46ae632a6dffcb6"
|
||||
},
|
||||
{
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/ROOT_S5_SIGNAL_COMPLETENESS_STATIC_RECHECK_R01.json",
|
||||
"bytes": 2542,
|
||||
"sha256": "1ad7f3535cfdc36d1c9365e913900af1ddbc82f5ba63f9d230398526f55ddd17"
|
||||
},
|
||||
{
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/opus-s5-reader-reference-source-r01/RETENTION.json",
|
||||
"bytes": 3831,
|
||||
"sha256": "ce0de86ffd6be7c7448a5fe5cfe7177db20356608cd894508c29aee1543b14b0"
|
||||
},
|
||||
{
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/EQ_S5_SCALE_PLAN_R02.txt",
|
||||
"bytes": 6569,
|
||||
"sha256": "ebee0ce76561a677b4454c59a091bafef242a406f7099485a508620dccfbb739"
|
||||
},
|
||||
{
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/EQ_S5_SCALE_PLAN_R03.txt",
|
||||
"bytes": 3740,
|
||||
"sha256": "8417c9044f87825453a66bae45f10cdf90428198bd2c5c44c7bce01b18de9f89"
|
||||
}
|
||||
],
|
||||
"current_summary": "原P28-02未完、補助EQも未受入。旧S6f75c9aは146局所試験後に通常提出済みだが正確S nativeのmkdir失敗を保全。実Runner repair_ready10、旧Codex run92b50は終了済み。ユーザー委任をevent84へ記録し、Desktop Opus5.5超高が同WのS5を指揮・実装中。R/O双方E1〜E5・39行を維持、信号のコマンド/通常公開をsource因果で完全照合。旧156局所試験211.416秒の後にも修正があり、新版合格に流用しない。scratch R3はGodot942秒/exit0、Pythonのcheck_report/自己比較True・実exit0だが全体1270.3秒、lifecycle300.6秒で正式verify300秒未達。NA/既存不具合をPASS補完せず全raw保持。Opusが保持R3 rawでcProfileと結果同値のreader高速化を進行、Rootは重複実装/engine/試験を起動しない。最終Sのnative7・両正式validation・fresh独立Opus全6・通常Q資格/委任承認/統合final、続くOPT T1/同条件3反復/RESTART/正確最終全192/原work・ACと横断6のwhole-unit受入は未達。原30/88・正式23/88、4/30・部分点なし、補助EQの原加算0。最大場面性能、完成資産、同一Windows実機、長期通し、全ゲームの未達を維持。",
|
||||
"remaining_conditions": [
|
||||
"S5 reader optimization preserving every typed state/event/check: completed R3 diagnostic took 1270.3s; whole registered verify cap remains 300s; new exact-source local tests required after changes",
|
||||
"Normal owner submission of actual Opus retained implementation; exact final-S native7 with full raw/owned/guard/PID/source/player/cleanup bundle, both registered validations",
|
||||
"Fresh independent Opus all6 review, normal prepare-integration, exact candidate qualification, honest delegated owner approval, integration/final; EQ support adds zero original units",
|
||||
"Original P28-02 T1 product optimization, same-condition three-run comparisons, RESTART, exact final full192 roster, whole-unit original work/acceptance plus cross-cutting6 review/final"
|
||||
],
|
||||
"reuse_conditions": "Reuse only where source/settings/input/policy/marker/engine/environment/application conditions match. Scratch diagnostic is not final-S normal native acceptance; prior local pass does not cover later changes.",
|
||||
"limits": {
|
||||
"unit_and_native_verify_timeout_seconds": 300,
|
||||
"registered_worker_timeout_seconds": 900,
|
||||
"api_fallback": false,
|
||||
"individual_repairs_unlimited": true
|
||||
},
|
||||
"new_engine_runs_by_this_docs_update": 0,
|
||||
"production_game_source_edits_by_this_docs_update": 0,
|
||||
"original_contract_projection_sha256": "6ff614a9526274e677038f05150243561489f1b065bcf1f77b384bcf0930d287"
|
||||
}
|
||||
+329
@@ -0,0 +1,329 @@
|
||||
{
|
||||
"at_jst": "2026-10-21T08:11:37.415587+09:00",
|
||||
"plan_id": "P28-02",
|
||||
"support_project": "the-tree-p28-02-eq",
|
||||
"support_task": "P28-02-EQ",
|
||||
"base": "2549cf7d6992560b850bbd11934e3c14b39a1988",
|
||||
"actual_run": "3325e7da9d064d2792fd178f400bc492",
|
||||
"actual_PID": 6380,
|
||||
"PID_ended": true,
|
||||
"stage": "S1 local readiness only",
|
||||
"retained_stage_return": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s1-3325e7d-r01/RETURN.json",
|
||||
"bytes": 19341,
|
||||
"sha256": "6d39676231b899c8e82bbe2643f14ee99d1605b2ced71cb249218ff99ddaf03f"
|
||||
},
|
||||
"root_raw_unit_join": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/ROOT_S1_LOCAL_JOIN_3325E7D.json",
|
||||
"bytes": 779,
|
||||
"sha256": "ed4c2d0e3d84d22b85e1377ebc19a238a6bb2b4f4f2ff99c9ea4440aa0f9256f"
|
||||
},
|
||||
"local_unit": {
|
||||
"at_jst": "2026-10-21T08:08:55.750391+09:00",
|
||||
"stage": "S1",
|
||||
"actual_run": "3325e7da9d064d2792fd178f400bc492",
|
||||
"same_base": "2549cf7d6992560b850bbd11934e3c14b39a1988",
|
||||
"retained_return_sha256": "6d39676231b899c8e82bbe2643f14ee99d1605b2ced71cb249218ff99ddaf03f",
|
||||
"unit_output_sha256": "cdca0cdd551ce76c6100c4dd90e04559551f9d88c054a19a2bc6a77e6572ae15",
|
||||
"executed_unit_exit_codes": [
|
||||
1,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"final_unit_checks": 43,
|
||||
"original_failed_unit_preserved": true,
|
||||
"all_changes_in_allowed_scope": true,
|
||||
"all_tracked_source_unchanged": true,
|
||||
"formal_EQ_accepted": false,
|
||||
"all6_acceptance_pending": true,
|
||||
"original_credit": 0,
|
||||
"scope": "Local S1 readiness evidence only; no test rerun, no engine, no formal independent review substituted."
|
||||
},
|
||||
"actual_Opus_materials": [
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p28-02/EQ_ENVIRONMENT_FIELDS_CLARIFICATION_R03.txt",
|
||||
"bytes": 7637,
|
||||
"sha256": "12b27f984ef5843d98327200ad261dccaf64968402192b08ad55b15ed5454ea2"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p28-02/EQ_STAGED_IMPLEMENTATION_PLAN.txt",
|
||||
"bytes": 6779,
|
||||
"sha256": "eb7b4831e2164c6fc983fa6f9825e7f3e2993b4c0ebf2f90aac8ad10662666d2"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p28-02/EQ_STAGED_IMPLEMENTATION_PLAN_R02.txt",
|
||||
"bytes": 7768,
|
||||
"sha256": "128f42f18e168e9299995296ac7ad119f47d1647799d472663282e47e8ac9fdd"
|
||||
},
|
||||
{
|
||||
"path": "docs/plans/original-completion-2026-10-02/execution/p28-02/DESKTOP_EQ_STAGED_RUN_DECISION_R02.json",
|
||||
"bytes": 3238,
|
||||
"sha256": "12b3b425638946920fa04867ba67d7a78c4e948ffba130c43bab55014b1989dc"
|
||||
}
|
||||
],
|
||||
"source_files": [
|
||||
{
|
||||
"relative": "IMPLEMENTATION.md",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/IMPLEMENTATION.md",
|
||||
"bytes": 9822,
|
||||
"sha256": "1bb87ce220648545ffd3bb72b0fc5d47ed83031c896946bb2a93d858c629b3da"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s1-3325e7d-r01/eq/IMPLEMENTATION.md",
|
||||
"bytes": 9822,
|
||||
"sha256": "1bb87ce220648545ffd3bb72b0fc5d47ed83031c896946bb2a93d858c629b3da"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "PROGRESS.md",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/PROGRESS.md",
|
||||
"bytes": 2210,
|
||||
"sha256": "aeb0d96bb909bdff2130b0ef0985bc691e9ddab76ea528b7b2ba05ff3e694e3d"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s1-3325e7d-r01/eq/PROGRESS.md",
|
||||
"bytes": 2210,
|
||||
"sha256": "aeb0d96bb909bdff2130b0ef0985bc691e9ddab76ea528b7b2ba05ff3e694e3d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "SPEC.md",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/SPEC.md",
|
||||
"bytes": 10693,
|
||||
"sha256": "3791ccdcb87e8d71fe9205e1e692ebca9e0f974ea3b5fbc4fbb3943882131f1c"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s1-3325e7d-r01/eq/SPEC.md",
|
||||
"bytes": 10693,
|
||||
"sha256": "3791ccdcb87e8d71fe9205e1e692ebca9e0f974ea3b5fbc4fbb3943882131f1c"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "TASK_INSTRUCTIONS.md",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/TASK_INSTRUCTIONS.md",
|
||||
"bytes": 2961,
|
||||
"sha256": "f9937c3c7dff5a25ada453365d26c87fecf009d29518724f0c1e67d2b18afff3"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s1-3325e7d-r01/eq/TASK_INSTRUCTIONS.md",
|
||||
"bytes": 2961,
|
||||
"sha256": "f9937c3c7dff5a25ada453365d26c87fecf009d29518724f0c1e67d2b18afff3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "UNIT_OUTPUT.sha256",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT.sha256",
|
||||
"bytes": 82,
|
||||
"sha256": "87e22105a54771c30fca8e520c87dfabb216dc101dd471c088a5cdf8625751d4"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s1-3325e7d-r01/eq/UNIT_OUTPUT.sha256",
|
||||
"bytes": 82,
|
||||
"sha256": "87e22105a54771c30fca8e520c87dfabb216dc101dd471c088a5cdf8625751d4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "UNIT_OUTPUT.txt",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT.txt",
|
||||
"bytes": 2839,
|
||||
"sha256": "cdca0cdd551ce76c6100c4dd90e04559551f9d88c054a19a2bc6a77e6572ae15"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s1-3325e7d-r01/eq/UNIT_OUTPUT.txt",
|
||||
"bytes": 2839,
|
||||
"sha256": "cdca0cdd551ce76c6100c4dd90e04559551f9d88c054a19a2bc6a77e6572ae15"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "UNIT_OUTPUT_ATTEMPT_01.sha256",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_ATTEMPT_01.sha256",
|
||||
"bytes": 93,
|
||||
"sha256": "eed78af7775a79838e65ea3cb07e9fe09822d2d7a23007f294cbf0a0c98d1c7c"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s1-3325e7d-r01/eq/UNIT_OUTPUT_ATTEMPT_01.sha256",
|
||||
"bytes": 93,
|
||||
"sha256": "eed78af7775a79838e65ea3cb07e9fe09822d2d7a23007f294cbf0a0c98d1c7c"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "UNIT_OUTPUT_ATTEMPT_01.txt",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_ATTEMPT_01.txt",
|
||||
"bytes": 3368,
|
||||
"sha256": "307486177b76602cbe68acae24c9560d501b3b866136d8e69802ec24c28e8be8"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s1-3325e7d-r01/eq/UNIT_OUTPUT_ATTEMPT_01.txt",
|
||||
"bytes": 3368,
|
||||
"sha256": "307486177b76602cbe68acae24c9560d501b3b866136d8e69802ec24c28e8be8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "UNIT_OUTPUT_ATTEMPT_02.sha256",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_ATTEMPT_02.sha256",
|
||||
"bytes": 93,
|
||||
"sha256": "1de0c0052e66cc60668690bf43e29eb01808afd023525e517ba94beaab0db669"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s1-3325e7d-r01/eq/UNIT_OUTPUT_ATTEMPT_02.sha256",
|
||||
"bytes": 93,
|
||||
"sha256": "1de0c0052e66cc60668690bf43e29eb01808afd023525e517ba94beaab0db669"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "UNIT_OUTPUT_ATTEMPT_02.txt",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_ATTEMPT_02.txt",
|
||||
"bytes": 2699,
|
||||
"sha256": "07144a7f1941162227f54fefd1961ed0f209e97282432723bbda534079582e4e"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s1-3325e7d-r01/eq/UNIT_OUTPUT_ATTEMPT_02.txt",
|
||||
"bytes": 2699,
|
||||
"sha256": "07144a7f1941162227f54fefd1961ed0f209e97282432723bbda534079582e4e"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "compare_versions.py",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/compare_versions.py",
|
||||
"bytes": 32313,
|
||||
"sha256": "058f3fef57c2ebe6951a48d85d570a110b982e08a13c805c244fec7d5e9a0fe8"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s1-3325e7d-r01/eq/compare_versions.py",
|
||||
"bytes": 32313,
|
||||
"sha256": "058f3fef57c2ebe6951a48d85d570a110b982e08a13c805c244fec7d5e9a0fe8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "references/OPT_CONTRACT_T1.md",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/references/OPT_CONTRACT_T1.md",
|
||||
"bytes": 13952,
|
||||
"sha256": "ad9f3d76f3500b39263fb898d7c64588b4b47c9e11b456f938195261fd054d16"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s1-3325e7d-r01/eq/references/OPT_CONTRACT_T1.md",
|
||||
"bytes": 13952,
|
||||
"sha256": "ad9f3d76f3500b39263fb898d7c64588b4b47c9e11b456f938195261fd054d16"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "references/OPUS_D2_SCOPE_CLARIFICATION.txt",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/references/OPUS_D2_SCOPE_CLARIFICATION.txt",
|
||||
"bytes": 5598,
|
||||
"sha256": "5775e7b512d9e90dbcfbcb181f5b64dc0fa8207e0947c0a4a6a5c9195565f9bd"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s1-3325e7d-r01/eq/references/OPUS_D2_SCOPE_CLARIFICATION.txt",
|
||||
"bytes": 5598,
|
||||
"sha256": "5775e7b512d9e90dbcfbcb181f5b64dc0fa8207e0947c0a4a6a5c9195565f9bd"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "references/OPUS_STARTUP_LIFECYCLE_CLARIFICATION.txt",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/references/OPUS_STARTUP_LIFECYCLE_CLARIFICATION.txt",
|
||||
"bytes": 5996,
|
||||
"sha256": "967aebf27ff412f7a16ce771ef7d68abf8cfde0bb76ed19cf6dbf2817ca7acc6"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s1-3325e7d-r01/eq/references/OPUS_STARTUP_LIFECYCLE_CLARIFICATION.txt",
|
||||
"bytes": 5996,
|
||||
"sha256": "967aebf27ff412f7a16ce771ef7d68abf8cfde0bb76ed19cf6dbf2817ca7acc6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "registration/ACCEPTANCE_BASELINE.json",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/registration/ACCEPTANCE_BASELINE.json",
|
||||
"bytes": 2981,
|
||||
"sha256": "84628bc21205387f8a7143031bdc50c9f68a767d350e6c34b32e0ba1c7059337"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s1-3325e7d-r01/eq/registration/ACCEPTANCE_BASELINE.json",
|
||||
"bytes": 2981,
|
||||
"sha256": "84628bc21205387f8a7143031bdc50c9f68a767d350e6c34b32e0ba1c7059337"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "registration/CONFIG_PROPOSAL.json",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/registration/CONFIG_PROPOSAL.json",
|
||||
"bytes": 821,
|
||||
"sha256": "775fbbb97cda8b4d4991defdf7871b0fc79f489f4dea34c1733d4830426cf1df"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s1-3325e7d-r01/eq/registration/CONFIG_PROPOSAL.json",
|
||||
"bytes": 821,
|
||||
"sha256": "775fbbb97cda8b4d4991defdf7871b0fc79f489f4dea34c1733d4830426cf1df"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "registration/REGISTRATION_PROPOSAL.json",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/registration/REGISTRATION_PROPOSAL.json",
|
||||
"bytes": 4733,
|
||||
"sha256": "9590fc853a4df8d50cc0d269d23bd9c844dc6931590ff6f24ebf95bb54a8d135"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s1-3325e7d-r01/eq/registration/REGISTRATION_PROPOSAL.json",
|
||||
"bytes": 4733,
|
||||
"sha256": "9590fc853a4df8d50cc0d269d23bd9c844dc6931590ff6f24ebf95bb54a8d135"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "registration/ROOT_PROPOSAL_BYTE_TRANSFER.json",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/registration/ROOT_PROPOSAL_BYTE_TRANSFER.json",
|
||||
"bytes": 4778,
|
||||
"sha256": "63197f650849084f769e487a76458361886a37090042f1e351851c75b0384da2"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s1-3325e7d-r01/eq/registration/ROOT_PROPOSAL_BYTE_TRANSFER.json",
|
||||
"bytes": 4778,
|
||||
"sha256": "63197f650849084f769e487a76458361886a37090042f1e351851c75b0384da2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "test_eq.py",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/test_eq.py",
|
||||
"bytes": 21350,
|
||||
"sha256": "f1ff2a7aa99161527e68c0ebaea0f0db275fde219320b74874f8675c075ad2b1"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s1-3325e7d-r01/eq/test_eq.py",
|
||||
"bytes": 21350,
|
||||
"sha256": "f1ff2a7aa99161527e68c0ebaea0f0db275fde219320b74874f8675c075ad2b1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"stage_implementation_committed": false,
|
||||
"formal_EQ_accepted": false,
|
||||
"all6_registered_acceptance_pending": true,
|
||||
"remaining": [
|
||||
"S2 Godot typed encode/decode/roundtrip, all checkpoints/events and actual guard binding, checker",
|
||||
"S3 rejection/cache/ownership scenarios and absolute checks",
|
||||
"S4 native driver and bundle verifier, full local units and formal submission",
|
||||
"Frozen S native7, both registered validations, fresh independent Opus all6, normal candidate qualification/integration/final",
|
||||
"Original P28-02 T1 optimization, repeat comparison, restart, exact final full roster, whole-unit review/final and ledger"
|
||||
],
|
||||
"original_completed": 23,
|
||||
"original_total": 88,
|
||||
"packages_completed": 4,
|
||||
"packages_total": 30,
|
||||
"original_credit": 0,
|
||||
"partial_credit": false
|
||||
}
|
||||
+477
@@ -0,0 +1,477 @@
|
||||
{
|
||||
"at_jst": "2026-10-21T08:46:08.565024+09:00",
|
||||
"plan_id": "P28-02",
|
||||
"support_project": "the-tree-p28-02-eq",
|
||||
"support_task": "P28-02-EQ",
|
||||
"base": "2549cf7d6992560b850bbd11934e3c14b39a1988",
|
||||
"stage": "S2 local evidence only",
|
||||
"actual_run": {
|
||||
"id": "558b96b3fe5e462296e85bc591622d1c",
|
||||
"task_id": "P28-02-EQ",
|
||||
"role": "implementer",
|
||||
"service": "codex",
|
||||
"status": "finished",
|
||||
"pid": 12205,
|
||||
"started": 1792537978.637717,
|
||||
"finished": 1792538824.796691,
|
||||
"exit_code": 0,
|
||||
"artifact": "/Users/korizatou114/Library/Application Support/agent-control/the-tree-p28-02-eq/runs/558b96b3fe5e462296e85bc591622d1c",
|
||||
"api_cost_usd": null,
|
||||
"estimated_cost_usd": null,
|
||||
"subscription_usage": null,
|
||||
"usage_json": "{\"input_tokens\": 2214984, \"cached_input_tokens\": 2007168, \"cache_write_input_tokens\": 0, \"output_tokens\": 41770, \"reasoning_output_tokens\": 4112}"
|
||||
},
|
||||
"retained_return": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s2-558b96b-r01/RETURN.json",
|
||||
"bytes": 28328,
|
||||
"sha256": "6f4f2b8ce8be7c4236368ffde9bbbd2805748a894b95ea8f42385169fdc78129"
|
||||
},
|
||||
"local_raw_join": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/ROOT_S2_LOCAL_JOIN_558B96B.json",
|
||||
"bytes": 1496,
|
||||
"sha256": "bbe7975d20c08f4af6dde4ba87f30ec7e3c80bf86ca321fc6d2a6a73cc8dde84"
|
||||
},
|
||||
"local_unit": {
|
||||
"at_jst": "2026-10-21T08:29:18.393150+09:00",
|
||||
"stage": "S2",
|
||||
"actual_run": "558b96b3fe5e462296e85bc591622d1c",
|
||||
"retained_return_sha256": "6f4f2b8ce8be7c4236368ffde9bbbd2805748a894b95ea8f42385169fdc78129",
|
||||
"unit_output_sha256": "9d323abda9bfffa02faf3a9d5cb56aa87e647017c991eb55b4807cf734386698",
|
||||
"actual_unit_counts": [
|
||||
68,
|
||||
71,
|
||||
72
|
||||
],
|
||||
"embedded_test_exit_codes": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"final_unit_checks": 72,
|
||||
"all43_prior_S1_test_bodies_preserved": true,
|
||||
"S1_final_output_preserved": true,
|
||||
"frozen_and_historical_files_unchanged": [
|
||||
"UNIT_OUTPUT_ATTEMPT_01.sha256",
|
||||
"UNIT_OUTPUT_ATTEMPT_02.sha256",
|
||||
"compare_versions.py",
|
||||
"SPEC.md",
|
||||
"UNIT_OUTPUT_ATTEMPT_02.txt",
|
||||
"TASK_INSTRUCTIONS.md",
|
||||
"UNIT_OUTPUT_ATTEMPT_01.txt",
|
||||
"registration/CONFIG_PROPOSAL.json",
|
||||
"registration/ACCEPTANCE_BASELINE.json",
|
||||
"registration/ROOT_PROPOSAL_BYTE_TRANSFER.json",
|
||||
"registration/REGISTRATION_PROPOSAL.json",
|
||||
"references/OPUS_D2_SCOPE_CLARIFICATION.txt",
|
||||
"references/OPUS_STARTUP_LIFECYCLE_CLARIFICATION.txt",
|
||||
"references/OPT_CONTRACT_T1.md"
|
||||
],
|
||||
"all_tracked_source_unchanged": true,
|
||||
"all_changes_in_allowed_scope": true,
|
||||
"formal_EQ_accepted": false,
|
||||
"all6_acceptance_pending": true,
|
||||
"original_credit": 0,
|
||||
"remaining_source_findings": "Two read-only pre-S3 findings: require full phase-handler key set and exact core snapshot/state signal coverage. Actual Opus judgment is required for the repair batch."
|
||||
},
|
||||
"all30_retained_files": [
|
||||
{
|
||||
"relative": "IMPLEMENTATION.md",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/IMPLEMENTATION.md",
|
||||
"bytes": 16705,
|
||||
"sha256": "382945c558161d5b83b5afb355d883d0741bb774eaca485b3a0c2052ed5348c4"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s2-558b96b-r01/eq/IMPLEMENTATION.md",
|
||||
"bytes": 16705,
|
||||
"sha256": "382945c558161d5b83b5afb355d883d0741bb774eaca485b3a0c2052ed5348c4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "PROGRESS.md",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/PROGRESS.md",
|
||||
"bytes": 3911,
|
||||
"sha256": "c461104c60775d3f9728a7c6e5e19554b33700578aead88c208741c5b2d4d052"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s2-558b96b-r01/eq/PROGRESS.md",
|
||||
"bytes": 3911,
|
||||
"sha256": "c461104c60775d3f9728a7c6e5e19554b33700578aead88c208741c5b2d4d052"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "S2_SUPPLEMENTARY.json",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/S2_SUPPLEMENTARY.json",
|
||||
"bytes": 19208,
|
||||
"sha256": "26f23bd8650a2495fd93f4364cbb17cc34f9eb1568758d406bec12ae2dab83a7"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s2-558b96b-r01/eq/S2_SUPPLEMENTARY.json",
|
||||
"bytes": 19208,
|
||||
"sha256": "26f23bd8650a2495fd93f4364cbb17cc34f9eb1568758d406bec12ae2dab83a7"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "SPEC.md",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/SPEC.md",
|
||||
"bytes": 10693,
|
||||
"sha256": "3791ccdcb87e8d71fe9205e1e692ebca9e0f974ea3b5fbc4fbb3943882131f1c"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s2-558b96b-r01/eq/SPEC.md",
|
||||
"bytes": 10693,
|
||||
"sha256": "3791ccdcb87e8d71fe9205e1e692ebca9e0f974ea3b5fbc4fbb3943882131f1c"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "TASK_INSTRUCTIONS.md",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/TASK_INSTRUCTIONS.md",
|
||||
"bytes": 2961,
|
||||
"sha256": "f9937c3c7dff5a25ada453365d26c87fecf009d29518724f0c1e67d2b18afff3"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s2-558b96b-r01/eq/TASK_INSTRUCTIONS.md",
|
||||
"bytes": 2961,
|
||||
"sha256": "f9937c3c7dff5a25ada453365d26c87fecf009d29518724f0c1e67d2b18afff3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "UNIT_OUTPUT.sha256",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT.sha256",
|
||||
"bytes": 82,
|
||||
"sha256": "b1ca8148217f769c3d3933f74592d5f012972f956562f0ffb5283f34bfd46d98"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s2-558b96b-r01/eq/UNIT_OUTPUT.sha256",
|
||||
"bytes": 82,
|
||||
"sha256": "b1ca8148217f769c3d3933f74592d5f012972f956562f0ffb5283f34bfd46d98"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "UNIT_OUTPUT.txt",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT.txt",
|
||||
"bytes": 4693,
|
||||
"sha256": "9d323abda9bfffa02faf3a9d5cb56aa87e647017c991eb55b4807cf734386698"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s2-558b96b-r01/eq/UNIT_OUTPUT.txt",
|
||||
"bytes": 4693,
|
||||
"sha256": "9d323abda9bfffa02faf3a9d5cb56aa87e647017c991eb55b4807cf734386698"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "UNIT_OUTPUT_ATTEMPT_01.sha256",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_ATTEMPT_01.sha256",
|
||||
"bytes": 93,
|
||||
"sha256": "eed78af7775a79838e65ea3cb07e9fe09822d2d7a23007f294cbf0a0c98d1c7c"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s2-558b96b-r01/eq/UNIT_OUTPUT_ATTEMPT_01.sha256",
|
||||
"bytes": 93,
|
||||
"sha256": "eed78af7775a79838e65ea3cb07e9fe09822d2d7a23007f294cbf0a0c98d1c7c"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "UNIT_OUTPUT_ATTEMPT_01.txt",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_ATTEMPT_01.txt",
|
||||
"bytes": 3368,
|
||||
"sha256": "307486177b76602cbe68acae24c9560d501b3b866136d8e69802ec24c28e8be8"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s2-558b96b-r01/eq/UNIT_OUTPUT_ATTEMPT_01.txt",
|
||||
"bytes": 3368,
|
||||
"sha256": "307486177b76602cbe68acae24c9560d501b3b866136d8e69802ec24c28e8be8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "UNIT_OUTPUT_ATTEMPT_02.sha256",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_ATTEMPT_02.sha256",
|
||||
"bytes": 93,
|
||||
"sha256": "1de0c0052e66cc60668690bf43e29eb01808afd023525e517ba94beaab0db669"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s2-558b96b-r01/eq/UNIT_OUTPUT_ATTEMPT_02.sha256",
|
||||
"bytes": 93,
|
||||
"sha256": "1de0c0052e66cc60668690bf43e29eb01808afd023525e517ba94beaab0db669"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "UNIT_OUTPUT_ATTEMPT_02.txt",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_ATTEMPT_02.txt",
|
||||
"bytes": 2699,
|
||||
"sha256": "07144a7f1941162227f54fefd1961ed0f209e97282432723bbda534079582e4e"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s2-558b96b-r01/eq/UNIT_OUTPUT_ATTEMPT_02.txt",
|
||||
"bytes": 2699,
|
||||
"sha256": "07144a7f1941162227f54fefd1961ed0f209e97282432723bbda534079582e4e"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "UNIT_OUTPUT_S1_FINAL.sha256",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S1_FINAL.sha256",
|
||||
"bytes": 91,
|
||||
"sha256": "91d9d408ed8d012f38ff892fa6712d129f4849d0ce0b579eaeb7cb1a3f84b704"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s2-558b96b-r01/eq/UNIT_OUTPUT_S1_FINAL.sha256",
|
||||
"bytes": 91,
|
||||
"sha256": "91d9d408ed8d012f38ff892fa6712d129f4849d0ce0b579eaeb7cb1a3f84b704"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "UNIT_OUTPUT_S1_FINAL.txt",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S1_FINAL.txt",
|
||||
"bytes": 2839,
|
||||
"sha256": "cdca0cdd551ce76c6100c4dd90e04559551f9d88c054a19a2bc6a77e6572ae15"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s2-558b96b-r01/eq/UNIT_OUTPUT_S1_FINAL.txt",
|
||||
"bytes": 2839,
|
||||
"sha256": "cdca0cdd551ce76c6100c4dd90e04559551f9d88c054a19a2bc6a77e6572ae15"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "UNIT_OUTPUT_S2_ATTEMPT_01.sha256",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S2_ATTEMPT_01.sha256",
|
||||
"bytes": 96,
|
||||
"sha256": "579254585137adf932f5e4a0eaa2b0ecd67d275ece6c7199bdafbee4b48f4f5d"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s2-558b96b-r01/eq/UNIT_OUTPUT_S2_ATTEMPT_01.sha256",
|
||||
"bytes": 96,
|
||||
"sha256": "579254585137adf932f5e4a0eaa2b0ecd67d275ece6c7199bdafbee4b48f4f5d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "UNIT_OUTPUT_S2_ATTEMPT_01.txt",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S2_ATTEMPT_01.txt",
|
||||
"bytes": 4395,
|
||||
"sha256": "25953dfc5f8bc3759ae66f6403b162100fb0217c4476f308616b8ab78a2cc67e"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s2-558b96b-r01/eq/UNIT_OUTPUT_S2_ATTEMPT_01.txt",
|
||||
"bytes": 4395,
|
||||
"sha256": "25953dfc5f8bc3759ae66f6403b162100fb0217c4476f308616b8ab78a2cc67e"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "UNIT_OUTPUT_S2_ATTEMPT_02.sha256",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S2_ATTEMPT_02.sha256",
|
||||
"bytes": 96,
|
||||
"sha256": "f4094cc3a8924b79316f473b8b4027a4743036d9d9cf8b2ad0c492ff7b581756"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s2-558b96b-r01/eq/UNIT_OUTPUT_S2_ATTEMPT_02.sha256",
|
||||
"bytes": 96,
|
||||
"sha256": "f4094cc3a8924b79316f473b8b4027a4743036d9d9cf8b2ad0c492ff7b581756"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "UNIT_OUTPUT_S2_ATTEMPT_02.txt",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S2_ATTEMPT_02.txt",
|
||||
"bytes": 4617,
|
||||
"sha256": "e26c986dda41b83143723e4c1c5b2ff1051a36b9a947b319341a2239cc3188de"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s2-558b96b-r01/eq/UNIT_OUTPUT_S2_ATTEMPT_02.txt",
|
||||
"bytes": 4617,
|
||||
"sha256": "e26c986dda41b83143723e4c1c5b2ff1051a36b9a947b319341a2239cc3188de"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "UNIT_OUTPUT_S2_ATTEMPT_03.sha256",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S2_ATTEMPT_03.sha256",
|
||||
"bytes": 96,
|
||||
"sha256": "d8f8f7a9be6b2fb1618a1ff03f9af5aa0e397373c5e8f9716dc47befa6e3bb83"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s2-558b96b-r01/eq/UNIT_OUTPUT_S2_ATTEMPT_03.sha256",
|
||||
"bytes": 96,
|
||||
"sha256": "d8f8f7a9be6b2fb1618a1ff03f9af5aa0e397373c5e8f9716dc47befa6e3bb83"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "UNIT_OUTPUT_S2_ATTEMPT_03.txt",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/UNIT_OUTPUT_S2_ATTEMPT_03.txt",
|
||||
"bytes": 4693,
|
||||
"sha256": "9d323abda9bfffa02faf3a9d5cb56aa87e647017c991eb55b4807cf734386698"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s2-558b96b-r01/eq/UNIT_OUTPUT_S2_ATTEMPT_03.txt",
|
||||
"bytes": 4693,
|
||||
"sha256": "9d323abda9bfffa02faf3a9d5cb56aa87e647017c991eb55b4807cf734386698"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "check_eq.py",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/check_eq.py",
|
||||
"bytes": 17595,
|
||||
"sha256": "59e303d6dc3e14ec22b290b3fa102a6249eec68363565c070cca6e9e9f24cac8"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s2-558b96b-r01/eq/check_eq.py",
|
||||
"bytes": 17595,
|
||||
"sha256": "59e303d6dc3e14ec22b290b3fa102a6249eec68363565c070cca6e9e9f24cac8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "compare_versions.py",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/compare_versions.py",
|
||||
"bytes": 32313,
|
||||
"sha256": "058f3fef57c2ebe6951a48d85d570a110b982e08a13c805c244fec7d5e9a0fe8"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s2-558b96b-r01/eq/compare_versions.py",
|
||||
"bytes": 32313,
|
||||
"sha256": "058f3fef57c2ebe6951a48d85d570a110b982e08a13c805c244fec7d5e9a0fe8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "eq_probe.gd",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/eq_probe.gd",
|
||||
"bytes": 23575,
|
||||
"sha256": "7ad4556885b2c44a57f1857f978edc35a9bc02467f9f5d852d0220ecabbcb5fe"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s2-558b96b-r01/eq/eq_probe.gd",
|
||||
"bytes": 23575,
|
||||
"sha256": "7ad4556885b2c44a57f1857f978edc35a9bc02467f9f5d852d0220ecabbcb5fe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "references/OPT_CONTRACT_T1.md",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/references/OPT_CONTRACT_T1.md",
|
||||
"bytes": 13952,
|
||||
"sha256": "ad9f3d76f3500b39263fb898d7c64588b4b47c9e11b456f938195261fd054d16"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s2-558b96b-r01/eq/references/OPT_CONTRACT_T1.md",
|
||||
"bytes": 13952,
|
||||
"sha256": "ad9f3d76f3500b39263fb898d7c64588b4b47c9e11b456f938195261fd054d16"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "references/OPUS_D2_SCOPE_CLARIFICATION.txt",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/references/OPUS_D2_SCOPE_CLARIFICATION.txt",
|
||||
"bytes": 5598,
|
||||
"sha256": "5775e7b512d9e90dbcfbcb181f5b64dc0fa8207e0947c0a4a6a5c9195565f9bd"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s2-558b96b-r01/eq/references/OPUS_D2_SCOPE_CLARIFICATION.txt",
|
||||
"bytes": 5598,
|
||||
"sha256": "5775e7b512d9e90dbcfbcb181f5b64dc0fa8207e0947c0a4a6a5c9195565f9bd"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "references/OPUS_STARTUP_LIFECYCLE_CLARIFICATION.txt",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/references/OPUS_STARTUP_LIFECYCLE_CLARIFICATION.txt",
|
||||
"bytes": 5996,
|
||||
"sha256": "967aebf27ff412f7a16ce771ef7d68abf8cfde0bb76ed19cf6dbf2817ca7acc6"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s2-558b96b-r01/eq/references/OPUS_STARTUP_LIFECYCLE_CLARIFICATION.txt",
|
||||
"bytes": 5996,
|
||||
"sha256": "967aebf27ff412f7a16ce771ef7d68abf8cfde0bb76ed19cf6dbf2817ca7acc6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "registration/ACCEPTANCE_BASELINE.json",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/registration/ACCEPTANCE_BASELINE.json",
|
||||
"bytes": 2981,
|
||||
"sha256": "84628bc21205387f8a7143031bdc50c9f68a767d350e6c34b32e0ba1c7059337"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s2-558b96b-r01/eq/registration/ACCEPTANCE_BASELINE.json",
|
||||
"bytes": 2981,
|
||||
"sha256": "84628bc21205387f8a7143031bdc50c9f68a767d350e6c34b32e0ba1c7059337"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "registration/CONFIG_PROPOSAL.json",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/registration/CONFIG_PROPOSAL.json",
|
||||
"bytes": 821,
|
||||
"sha256": "775fbbb97cda8b4d4991defdf7871b0fc79f489f4dea34c1733d4830426cf1df"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s2-558b96b-r01/eq/registration/CONFIG_PROPOSAL.json",
|
||||
"bytes": 821,
|
||||
"sha256": "775fbbb97cda8b4d4991defdf7871b0fc79f489f4dea34c1733d4830426cf1df"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "registration/REGISTRATION_PROPOSAL.json",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/registration/REGISTRATION_PROPOSAL.json",
|
||||
"bytes": 4733,
|
||||
"sha256": "9590fc853a4df8d50cc0d269d23bd9c844dc6931590ff6f24ebf95bb54a8d135"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s2-558b96b-r01/eq/registration/REGISTRATION_PROPOSAL.json",
|
||||
"bytes": 4733,
|
||||
"sha256": "9590fc853a4df8d50cc0d269d23bd9c844dc6931590ff6f24ebf95bb54a8d135"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "registration/ROOT_PROPOSAL_BYTE_TRANSFER.json",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/registration/ROOT_PROPOSAL_BYTE_TRANSFER.json",
|
||||
"bytes": 4778,
|
||||
"sha256": "63197f650849084f769e487a76458361886a37090042f1e351851c75b0384da2"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s2-558b96b-r01/eq/registration/ROOT_PROPOSAL_BYTE_TRANSFER.json",
|
||||
"bytes": 4778,
|
||||
"sha256": "63197f650849084f769e487a76458361886a37090042f1e351851c75b0384da2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relative": "test_eq.py",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p28-02-eq/P28-02-EQ-implement/docs/plans/original-completion-2026-10-02/execution/p28-02/eq/test_eq.py",
|
||||
"bytes": 35903,
|
||||
"sha256": "ad33d971c545cabf367aa6f231fd1c5b21cec4ba7cf531f36ba8ed2a2bf57f62"
|
||||
},
|
||||
"retained": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p28-02-root-control-r01/retained-s2-558b96b-r01/eq/test_eq.py",
|
||||
"bytes": 35903,
|
||||
"sha256": "ad33d971c545cabf367aa6f231fd1c5b21cec4ba7cf531f36ba8ed2a2bf57f62"
|
||||
}
|
||||
}
|
||||
],
|
||||
"remaining_checker_findings": {
|
||||
"path": "artifacts/original-completion-2026-10-02/p28-02-root-control-r01/ROOT_S2_PRE_S3_SOURCE_NOTES.json",
|
||||
"bytes": 1322,
|
||||
"sha256": "02b175327af0170e3188e6909a85efb987fbf016278b71bbec963572174629ec"
|
||||
},
|
||||
"stage_code_committed": false,
|
||||
"formal_EQ_accepted": false,
|
||||
"original_credit": 0,
|
||||
"scope": "Local unit/raw/source-preservation evidence. The two checker omissions require correction in S3a; native and all formal gates remain pending."
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user