25 KiB
25 KiB
P16-01-DISMISS 実装の記録(実 Opus の実装担当、2026-10-24、修正 1 と修正 2 は 2026-10-25)
- 仕様: 同じフォルダの
SPEC.md(登録版 r04。main0b59d7cに固定。ファイルの 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)。
- high: S
- 新しい拒否や、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 の中で直すものは無い。
- medium(F9・§4.3 の通常の入力): S
1. 命令と判定の順(SPEC §4.1)
SimulationRuntimeに通常の commanddismiss_staff(payloadstaff_id)を足した(_execute_dismiss_staff)。- (a) 運営の終了(
BANKRUPT)と (b) 同じ key の再送(DUPLICATE_IDEMPOTENCY_KEY)は、今のsubmit_commandとCommandQueue.enqueueのまま受付の前・受付で止まる(R5・R6、5.b)。 - (c) executor は、状態を変える前に次の順で確かめる。どれかに当たれば、その理由で失敗の結果を返す(5.c)。
- payload に
staff_idが無い →INVALID_PAYLOAD - 職員が居ない →
NOT_FOUND(R4) UNIQUE→UNIQUE(R1。今のremove_staffと同じ code と文)FREE_HIREでない(ADULT_CONTRACT)→NOT_GENERAL_EMPLOYEE(R7)- 外部が持つ作業(仕入れの物流の受け取り・配達・返送)→
EXTERNAL_TASK(R2。今のremove_staffと同じ code と文) - 持っている作業の予約を解けない →
RESERVATION_RELEASE_FAILED(R3。今のremove_staffと同じ code と文)- 修正 1 で、変える前の判定にした(
_staff_task_reservations_releasable)。在庫の解放は、予約が無い時と消費済みの時に失敗する(InventoryService.release_reservation)。それを、読むだけのget_reservationで前もって見る。 remove_staffの同じ拒否は、最後の守りとして残す。そこで止まった時も、全体を巻き戻す。
- 修正 1 で、変える前の判定にした(
- 終わっていない襲撃の防衛者 →
DEFENSE_LOCKED(R9。Root の一時の境界) - 表金と裏金の合計が退職金に足りない →
INSUFFICIENT_FUNDS(R8)
- payload に
- (d) 全て通れば、次を1つの操作として行う。
- 乗ったままの救護の組なら、解雇する人の座席を解く(§3)。
- 既存の削除の契約
StaffService.remove_staff(staff_id, &"DISMISSED", true, 分)。 - 退職金の記帳(§2)。
- 救護の後始末(§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(reasonDISMISSED)は、今の_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の動き)。試験は、プレイヤーと同じく「従業員」のボタンで戻る。 - どれも試験の押し方の問題で、画面の部品や配置は変えていない。
- Main は基準の大きさ 1440×810 から縮めて表示される(窓 1280×720 で倍率 0.889)。修正 1 の診断は
- これは viewport の入力で、OS の入力ではない。Windows の実機・IME・OS の倍率・同じ候補での実機の受入は、SPEC §10「Windows 実機は後」の後続の条件として、実施しておらず未達のまま残す。
- キー(雇用の候補を開く H)は
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)の作業の開き直しと、別の清掃係による完了。
- 元の予約・解雇・別の職員の再予約を同じ分で起こし、別の職員が終えるまで。同じ分の派遣は、StaffService の公開の
- 既存の試験は、この操作 ID の文字列を見ていない(期待の変更なし)。
6. 見つけた既存の不具合と直し(S b06fec1 で直したもの、変更なし)
- 締めの後に税・利息の未払いが残ったゲームの保存が読めなかった(P05-02 からの不具合)。
- 保存を読む時の Operations の照合(
_parse_operations_runtime_snapshot)は、締めた未払いを「給与 + 維持費」だけで数えていた。実行中の照合(_operations_closed_arrears_total)が足している税・利息の未払い(_finance_arrears_through)を、数えていなかった。 - 読む時の照合を、実行中と同じ式にそろえた。照合を緩めたのではない。
- 試験: 帳簿の日の流れが、税の未払い 30 を確かめてから締めの後の保存を復元する。同じ process(修正 1 で全ての保存点)と別の process でも読む。
- 保存を読む時の Operations の照合(
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 の他の処理なしに処理する。
- その命令は、Runtime 自身の命令の段(
- 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 の比べ方で、組が乗ったままであることも確かめる。
- Main の画面の場面を、viewport の入力で操作する形に置き換えた(§4)。
- 再開の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): 求人募集、奴隷市場・破産客からの獲得、人身売買、勤務・休息・気絶・救護の横断、解雇の経路の外の退職時の参照の除去。