adventure-table

M03-G Closeout Checklist

M03-G — Full M03 Integration & Closeout closeout scope:

G.1 六條路徑的證據對照

實作規格路徑 測試
1. W → S 完整 tests/test_m03g_roundtrip.py::test_web_to_standalone_roundtrip_preserves_live_state_and_history
2. S → W 完整 tests/test_m03g_roundtrip.py::test_standalone_to_web_roundtrip_preserves_payload
3. W → S 缺 pack tests/test_m03g_import_edges.py::test_missing_xge_lands_as_persisted_builder_draft
4. State-only 缺 ref tests/test_m03g_import_edges.py::test_state_only_missing_ref_lands_as_history_loss_draft
5. 重複匯入偵測 tests/test_m03g_import_edges.py::test_duplicate_hint_does_not_block_second_character
6. Legacy character 缺 pack 拒絕 tests/test_m03g_import_edges.py::test_legacy_missing_pack_rejection_is_atomic_in_standalone_sqlite

第 1 條的斷言涵蓋 M03-G 想證明的完整往返:網頁版建 Lv5 Multiclass(fixture_multiclass_mixed.json)後先改動 live state(HP −3、prepared spell、新增一件不在 Starting Equipment 的 inventory item),匯出後於單機版 dry-run 得 landing_mode="character"unresolved_ref_count == 0,commit 後由單機版再匯出一次,斷言 payload 與網頁版匯出完全相等envelope.source_app.channel 各自為 web / standalone、version chain 為 [1, 2]。第 6 條在拒絕前後比對 table_counts(engine),證明零副作用。

交付內容

檔案 內容
apps/server/tests/m03g_support.py 以真 launcher migration + file-backed SQLite 起 app.standalone 的 context manager,與 export / commit 輔助
apps/server/tests/test_m03g_roundtrip.py G.1 第 1、2 條:W ↔ S 完整往返與 payload 相等性
apps/server/tests/test_m03g_import_edges.py G.1 第 3~6 條:缺 pack、state-only 缺 ref、重複匯入、legacy 拒絕原子性
apps/server/tests/test_m03g_standalone_runtime.py frozen runtime 行為:/docs /redoc /openapi.json 落到 SPA fallback;launcher KeyboardInterrupt 回收路徑
.github/workflows/m03g-non-e2e.yml.github/m03g-non-e2e.trigger backend / frontend / windows-standalone / compose-config 四個 job 的 review-gated 回歸
docs/M03/M03-G_CLOSEOUT.md 本檔
PROJECT_BRIEF.md M03 closeout 與 Subphase 進度更新
AGENTS.md 補上 M03 已交付與 standalone boundary 的常駐約束
規格企劃.md 第五章 Character Workshop 段補「單機版已出貨」現況

本 Subphase 未新增任何產品程式碼,diff 僅涵蓋 apps/server/tests/.github/ 與文件。

順帶結清的 M03-E 遺留項

docs/M03/M03-E_CLOSEOUT.md 留下的四點中,第 1、2、3 點現已結清:

僅第 4 點(Settings() import-time 快照跨測試污染,tests/conftest.py 仍未加 session autouse fixture)維持未結清。

M03 已知限制

  1. Character JSON schema 於 M03 期間仍是 unstableschema_status 鎖為 "unstable",要到 P2 才 lock。這個版本匯出的 JSON 不保證未來版本讀得進去;可靠的原始資料是 SQLite 檔本身。
  2. Windows-only,且是 portable zip 而非 installer;沒有自動更新、沒有 code signing、沒有 SmartScreen reputation。
  3. Draft 落地會捨棄舊版 chain(見實作規格 3.3、C.6)。landing_modedraftdraft_with_history_loss 時,補洞後 Confirm 產生的是新的 Version 1,原本的 version chain 與 Current State 不會進來。
  4. Legacy 角色(builder_provenance = NULL)於缺 pack 情境無法匯入,一律回 400 draft_reconstruction_unavailable。這類角色只能在 pack 齊全的環境匯入。
  5. 匯入不做合併。同一份 export 匯入兩次會產生兩個獨立角色;duplicate_hint 只提示,不阻擋。
  6. 單機版無帳號、無同步、無 Room / Campaign / Session / Seat / Combat / Timeline / AI Actor
  7. 關閉瀏覽器分頁不會停 server(刻意行為)。要整個關閉必須在 console 按 Ctrl+C 或關掉 console 視窗;README-standalone 兩語都寫明。
  8. port 每次啟動動態挑選,重開後網址會變,以 console 印出的 listening URL 為準。
  9. Settings() 的 import-time 快照仍會跨測試污染(M03-D 記錄、M03-E 重申,至今未處理)。只影響測試撰寫方式,不影響 runtime。
  10. standalone import boundary 的 forbidden regex 依賴字根命名。P2 引入多人模組時若命名不落在 room / session / seat / campaign / party_roster(含複數)內,gate 會靜默失效;P2 第一個 Subphase 必須同步擴充該 regex 與 EXACT_PROTECTED_MODULES

後續補驗紀錄

測試指南 E.9 / G.3 的乾淨 Windows 11 冷啟動已於 2026-09-06 補驗完成。 M03-E → M03-F → M03-G 原始 closeout 過程中,這條因當時沒有符合條件的乾淨機器而一直沒有取得證據;因此歷史文件中會看到它曾被列為 pending。

2026-09-06,使用者確認已在符合 E.9 條件的乾淨 Windows 11 環境完成實際冷啟動驗證。這是後續人工驗收證據,不是當時 M03-G 自動化 run 的一部分,也未宣稱有新的 CI artifact。自此 E.9 / G.3 不再是 M03 的未結清驗收項。

G.1 人工 UI 流程以自動化取代的理由

測試指南 G.1 原本要求六條流程在「最新網頁版 + 最新單機版 zip」上人工執行並保留截圖/錄影。本次改以 API 層 pytest 取得等價證據,理由:

原本唯一沒有被這個自動化取代涵蓋的是 frozen exe 上的人工冷啟動,也就是 E.9;該項已於 2026-09-06 由使用者後續人工補驗完成。

驗收方式與分層 gate

M03-G 是 Phase 關門,依 AGENTS.md 分層 gate 跑全套。實際執行:

M03-G 未涵蓋(依實作規格「本 Subphase 不要求」)

留給後續 Phase 的建議

  1. Settings() import-time 快照污染:建議於 apps/server/tests/conftest.py 加 session autouse fixture 把 settings.database_path 釘成 None,不動 runtime。
  2. P2 第一個 Subphase 必須擴充 import boundary 的 forbidden regex 與 EXACT_PROTECTED_MODULES,否則新命名的多人模組會讓 gate 靜默放行。
  3. P2 lock JSON schema 時,需一併決定既有 unstable 匯出檔的處置(拒絕、或提供一次性 upgrade path)。
  4. seed_p0_fighter_wizard.py 仍直接 create_engine(settings.database_url)(M03-E 遺留),只影響 dev seed 腳本,未收斂到 create_database_engine()