diff --git a/gogobee_branching_zones_plan.md b/gogobee_branching_zones_plan.md index 0faae8e..f7c094e 100644 --- a/gogobee_branching_zones_plan.md +++ b/gogobee_branching_zones_plan.md @@ -310,18 +310,11 @@ func compileLegacyZoneGraph(z ZoneDefinition) ZoneGraph { - Flip `GOGOBEE_BRANCHING_ZONES=1` for that zone. - Remove env gate when all zones converted. -### G9 — Retire linear model +### G9 — Retire linear model ✅ -- Drop `room_seq_json` and `current_room` columns once all zones have registered graphs and old runs are completed. -- Idempotent purge gate (mirror `purgeLegacyAdvCharacterTable` shape from L5): -```go -func purgeLegacyZoneRunColumns() { - if os.Getenv("GOGOBEE_BRANCHING_PURGE") != "1" { return } - // ALTER TABLE dnd_zone_run DROP COLUMN current_room - // ALTER TABLE dnd_zone_run DROP COLUMN room_seq_json -} -``` -- Wire into Init, gated, idempotent. Operator flips after final soak. +- **G9a** — `GOGOBEE_BRANCHING_ZONES` POC gate removed; graph mode is the only runtime path. +- **G9b** — `current_room` / `room_seq_json` no longer persisted. `CurrentRoom` derived from `len(VisitedNodes)-1` on read; `RoomSeq` is in-memory only and unused at runtime. +- **G9c** — Columns dropped from `CREATE TABLE dnd_zone_run`. `purgeLegacyZoneRunColumns()` in `cleanup_g9.go` mirrors the L5 shape (gated by `GOGOBEE_BRANCHING_PURGE=1`, idempotent, runs in adventure init). Operator flips after final soak. --- diff --git a/internal/db/db.go b/internal/db/db.go index a02c49c..cb8c351 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -1730,16 +1730,16 @@ CREATE TABLE IF NOT EXISTS adventure_characters_pre_dnd ( -- A single in-progress or completed dungeon run. One row per run; players -- may have at most one row with completed_at IS NULL AND abandoned = 0 -- (enforced in code, not via constraint, to keep migrations simple). --- room_seq_json is a JSON array of RoomType strings generated at run start; --- current_room indexes into that array. rooms_cleared is a JSON array of --- room indices the player has resolved (combat won / trap survived / etc.). +-- rooms_cleared is a JSON array of room indices the player has resolved +-- (combat won / trap survived / etc.). The zone-graph columns +-- (current_node, visited_nodes, node_choices) are added in Phase G1's +-- columnMigrations; the legacy linear current_room / room_seq_json +-- columns retired in G9. CREATE TABLE IF NOT EXISTS dnd_zone_run ( run_id TEXT PRIMARY KEY, user_id TEXT NOT NULL, zone_id TEXT NOT NULL, - current_room INTEGER NOT NULL DEFAULT 0, total_rooms INTEGER NOT NULL, - room_seq_json TEXT NOT NULL DEFAULT '[]', rooms_cleared TEXT NOT NULL DEFAULT '[]', boss_defeated INTEGER NOT NULL DEFAULT 0, abandoned INTEGER NOT NULL DEFAULT 0, diff --git a/internal/plugin/adventure.go b/internal/plugin/adventure.go index 611aa13..d4edadf 100644 --- a/internal/plugin/adventure.go +++ b/internal/plugin/adventure.go @@ -200,6 +200,10 @@ func (p *AdventurePlugin) Init() error { // Phase L5 close-out — drop the now-cold adventure_characters table when // the operator sets GOGOBEE_LEGACY_PURGE=1. Default off; idempotent. purgeLegacyAdvCharacterTable() + // Phase G9 close-out — drop the now-cold current_room / room_seq_json + // columns from dnd_zone_run when the operator sets + // GOGOBEE_BRANCHING_PURGE=1. Default off; idempotent. + purgeLegacyZoneRunColumns() // Phase R1 orphan-archive used to run here on every Init, but it // over-archived: it treats any active dnd_zone_run row not linked to // an active expedition as a legacy `!adventure dungeon` orphan, which diff --git a/internal/plugin/cleanup_g9.go b/internal/plugin/cleanup_g9.go new file mode 100644 index 0000000..9e34c1e --- /dev/null +++ b/internal/plugin/cleanup_g9.go @@ -0,0 +1,37 @@ +package plugin + +import ( + "log/slog" + "os" + "strings" + + "gogobee/internal/db" +) + +// purgeLegacyZoneRunColumns drops the dnd_zone_run.current_room and +// dnd_zone_run.room_seq_json columns when the operator opts in via +// GOGOBEE_BRANCHING_PURGE=1. Phase G9 close-out: G9a removed the +// runtime gate, G9b stopped persisting the columns, and this is the +// final step. +// +// Idempotent: skips silently when the columns are already gone (or +// never existed on a fresh install — the schema in db.go no longer +// creates them). +func purgeLegacyZoneRunColumns() { + if os.Getenv("GOGOBEE_BRANCHING_PURGE") != "1" { + return + } + d := db.Get() + for _, col := range []string{"current_room", "room_seq_json"} { + if _, err := d.Exec("ALTER TABLE dnd_zone_run DROP COLUMN " + col); err != nil { + msg := err.Error() + // "no such column" → already dropped (or fresh install). + if strings.Contains(msg, "no such column") { + continue + } + slog.Error("G9 purge: drop column failed", "col", col, "err", err) + return + } + slog.Warn("G9 purge: dnd_zone_run column dropped (GOGOBEE_BRANCHING_PURGE=1)", "col", col) + } +}