Files
rdbms-playground/docs/handoff/20260525-handoff-39.md
claude@clouddev1 a2fc3c9e57 docs: 4f plan + session handoff 39
- docs/plans/20260525-adr-0035-sql-ddl-4f.md — ALTER TABLE ALTER COLUMN
  TYPE plan (/runda'd; forks resolved). Advanced lossy = ForceConversion
  (reuse do_change_column_type; existing transformed_lossy note); the
  internal-__rdbms_* guard folds into do_change_column_type both surfaces
  (user-confirmed); int->serial is ALLOWED (ADR-0018 §8), only non-int
  ->serial / blob / date<->datetime are static-refused.
- docs/handoff/20260525-handoff-39.md — 4d/4e shipped; 4f is next
  (plan ready).
2026-05-25 20:26:34 +00:00

11 KiB

Session handoff — 2026-05-25 (39)

Thirty-ninth handover. This session shipped ADR-0035 Phase-4 sub-phases 4d and 4e (each test-first, with a /runda planning gate and a /runda finished-slice critique, all green) and then fully prepared 4f — the plan is written, /runda'd, and forks resolved, ready for a fresh session to implement. The next session implements 4f — ALTER TABLE … ALTER COLUMN TYPE from docs/plans/20260525-adr-0035-sql-ddl-4f.md (no open decisions).

⚠️ Read §1.1 first — there is a git divergence to reconcile.

§1. State at handoff

Branch: main. Tests: 1854 passing, 0 failing, 0 skipped, 1 ignored (the unchanged friendly/mod.rs ```ignore doctest). Clippy: clean (cargo clippy --all-targets -- -D warnings).

This session's commits (oldest → newest, local):

701217d feat: ADR-0035 4d — CREATE [UNIQUE] INDEX / DROP INDEX   (amended)
bbc2e34 feat: ADR-0035 4e — ALTER TABLE add/drop/rename column

Uncommitted in the working tree: docs/plans/…-4f.md, docs/handoff/…-39.md (this file) — to be committed with user approval.

§1.1 ⚠️ Git divergence (origin/main vs local 4d) — user action needed

origin/main is at 3247e3c — the pre-amend 4d commit. The 4d work was first committed as 3247e3c and pushed; then, during the 4d finished-slice /runda, the user chose "amend 3247e3c" to fold in one extra test (the unnamed CREATE INDEX IF NOT EXISTS skip) + the internal-__rdbms_* guard, producing 701217d. So the pushed 4d (3247e3c) and the local 4d (701217d) are sibling commits (same parent, different content) — the histories diverged at 4d.

Consequence: a plain git push will be rejected (non-fast-forward). The delta between 3247e3c and 701217d is exactly the two /runda additions (one test + the do_add_index internal-table guard + their doc lines). Reconciliation is the user's call (this is a single-dev repo, so a force-push to replace 3247e3c with 701217d+bbc2e34 is the likely intent) — agents must not push or force-push. Options for the user:

  • git push --force-with-lease to replace the pushed 4d with the amended history (single-dev branch → low risk); or
  • cherry-pick only the amend delta as a new commit on top of 3247e3c if a clean linear forward-only history is preferred.

Do not re-amend or rebase further without deciding this first.

§2. What shipped this session

  • 4d — CREATE [UNIQUE] INDEX / DROP INDEX [IF EXISTS] (701217d). Reuses do_add_index / do_drop_index. CREATE UNIQUE INDEX admitted in advanced mode (ADR-0025 Amendment 1) via an additive IndexSchema.unique flag (round-trips project.yaml, survives rebuild, [unique] markers in the structure view + items panel); simple add unique index stays deferred. IF [NOT] EXISTS reuses the 4c skip path. create/drop each gained a second advanced node (all-candidates dispatch). do_add_index gained an internal-__rdbms_* guard (both surfaces).
  • 4e — ALTER TABLE add/drop/rename column (bbc2e34). alter is a new advanced-only entry word; SqlAlterTable { AlterTableAction } is runtime-decomposed to the existing do_add_column / do_drop_column / do_rename_column (one undo step each — no new worker layer). do_add_column extended to consume raw default_sql/check_sql so ADD COLUMN reaches CREATE-TABLE constraint parity. Drop/rename refuse a column any CHECK references — table-level and column-level (incl. a column's own self-check on rename), via the check_references_column tokenizer in the shared executors (both surfaces) — fixing a latent rename-drift bug. SQL DROP COLUMN refuses an index-covered column (no --cascade SQL spelling). The column executors + do_add_index carry the internal-__rdbms_* guard.

ADR-0035 stays Accepted; README + requirements.md Q1 + ADR §13 updated each slice; ADR-0025 Amendment 1 for the unique index.

§3. The NEXT job — 4f (ALTER TABLE … ALTER COLUMN TYPE)

Plan: docs/plans/20260525-adr-0035-sql-ddl-4f.md — read it; it is /runda'd and all forks are resolved. Headlines:

  • A fourth AlterTableAction::AlterColumnType { column, ty }; ALTER TABLE <T> ALTER COLUMN <col> TYPE <type> → a fourth action Choice branch (AT_ALTER_COLUMN, leads alter), builder discriminated by the type keyword (contains_word("type"), checked first — unique to this action; ADD COLUMN's type is an ident). Reuse super::sql_create_table::SQL_TYPE for the type slot.
  • Runtime-decompose to database.change_column_type(table, column, ty, ChangeColumnMode::ForceConversion, src)CommandOutcome::ChangeColumn (the existing simple-change column path — surfaces the client-side lossy note). No new mode, no new note, no new persistence.
  • ForceConversion IS the §7 advanced policy (verified in code): incompatible cells refuse; lossy cells are performed (the dry-run skips the refuse only for ForceConversion, db.rs:4575) and counted → the existing engine-neutral client_side.transformed_lossy note fires.
  • Static refusals (verified type_change::static_refusal): same-type; non-int → serial (only int → serial allowed); any ↔ blob; direct date ↔ datetime; not-in-matrix. ⚠️ int → serial is ALLOWED (auto-fill nulls + UNIQUE, ADR-0018 §8) — ADR-0035 §7's "static-refused →serial" summary is looser than the code. Test int → serial as supported, text → serial / ↔ blob as refused. Do not "fix" int→serial to refuse.
  • Fold the internal-__rdbms_* guard into do_change_column_type (user-confirmed 2026-05-25, both surfaces) — closes the simple change column exposure. (do_add_constraint / do_add_relationship stay a 4g follow-up.)
  • Out: SET DATA TYPE synonym, Postgres USING, any SQL spelling of --force-conversion / --dont-convert (ADR §7/§12).

Implementation sequence + the full checklist are in the plan §9/§3. Test-first; mirror the 4e slices (tests/sql_alter_table.rs e2e via run_replay; tests/column_op_guards.rs for the simple-surface guard; the sql_alter_table_tests in ddl.rs for parse).

§4. Everything else remaining in Phase 4 (ADR-0035 §13)

In order after 4f:

  • 4g — ALTER TABLE add/drop constraint, add FK. The add-FK path reuses the 4b shared relationship helpers; named CHECK adds a name column to __rdbms_playground_table_checks. Fold the internal-table guard into do_add_constraint / do_add_relationship here (the remaining executors of the 4d/4e/4f guard class).
  • 4h — ALTER TABLE … RENAME TO (the §6 new low-level op: rename table + data/<t>.csv + relationship metadata + table_checks rows).
  • 4i — Verification sweep (the running deferral tail — §5 below).

§5. The 4i deferral tail (canonical: ADR-0035 §13 4i)

Unchanged from handoff-38 except as noted: (a) CREATE TABLE help/usage skeleton refresh — 4d/4e/4f index/ALTER forms carry their own help/usage (only the CREATE TABLE skeleton refresh remains); (b) describe of table-level constraints (composite UNIQUE + table CHECK) — the unique-index marker shipped in 4d, so only table-level constraint display remains; (c) 4b self-ref FK pre-submit indicator; (d) shared-entry-word completion merge — widened by 4d (create/ drop now have two advanced nodes each); (e) the simple/advanced completion-colour UX discussion (user flag).

§6. Tracked follow-ups (not lost)

  • Internal-__rdbms_* guard on the remaining executorsdo_change_column_type is closed in 4f; do_add_constraint / do_add_relationship ride in 4g. (The pattern: a reject_internal_table_name(table)? at the top of each user-facing schema-mutation executor; do_add_index + the three column executors already have it.)
  • H1 friendly wording for the CHECK-guard refusals (4e) and the type-conversion diagnostics — the current messages are engine-neutral but terse.
  • The §8 items carried from handoff-38 (app-lifecycle-command runtime-failure journalling (A); M4 execution-time mode side-channel; blob value literal; CI/TT5; DSL→SQL teaching echo) remain open.

§7. Patterns the implementer must not forget

  1. Reuse low-level executors. Sql* DDL wraps the existing helpers (4f wraps change_column_type with ForceConversion); they do not fork. The runtime decomposes SqlAlterTable per action.
  2. Two DDL generators stay in sync (do_create_table / schema_to_ddl) — not directly touched by 4f, but the rebuild path is the round-trip net for the type change.
  3. Grammar leading-Optional trap — each Choice branch leads on a concrete keyword (4f's AT_ALTER_COLUMN leads alter).
  4. Exhaustive matches: a new AlterTableAction variant forces arms in runtime.rs (the inner match action) and app.rs (build_translate_context's inner match). The Command-level matches (verb/target_table/typing-surface) are unchanged (the variant is internal to SqlAlterTable). The compiler finds them.
  5. Catalog lockstep + vocab audit — 4f refreshes the existing sql_alter_table help/usage (no new keys); keep wording engine-neutral.
  6. Probe, don't reason — verify the type-keyword discriminator and the SQL_TYPE extraction (mirror build_alter_add_column_spec) with a parse test before wiring execution.

§8. Process pins (unchanged, still binding)

  • Confirm every commit. Propose the message; wait for the go-ahead.
  • Push is the user's step. Never push; never force-push. See §1.1.
  • No AI attribution in commits.
  • Escalate ambiguity / new cost. 4f's one fork (the do_change_column_type internal guard) was surfaced + user-confirmed 2026-05-25; the §7 lossy policy + the no-force/no-USING exclusions are ADR-settled.
  • DA hat each slice; /runda at the planning exit AND on the finished slice. Both 4d and 4e had a planning /runda and a finished-slice /runda; each finished-slice pass found a real gap (4d: unnamed-skip coverage; 4e: column-level CHECK rename-drift) that was fixed before commit. Budget for the same on 4f.
  • Keep docs lockstep — ADR status/§13 + README + requirements.md in the same edit.

§9. How to take over

  1. Resolve §1.1 (the git divergence) with the user before any new commit work.
  2. Read, in order: this file → docs/plans/20260525-adr-0035-sql-ddl-4f.mddocs/adr/0035-advanced-mode-sql-ddl.md (§4 ALTER row, §7 conversion, §13 4f) → docs/adr/0017-column-type-change-compatibility.md (the matrix + static refusals) → CLAUDE.mddocs/requirements.md (Q1).
  3. Baseline:
    cargo test    # expect 1854 passing / 0 failing / 0 skipped / 1 ignored
    cargo clippy --all-targets -- -D warnings   # clean
    
  4. Implement 4f per the plan §9 (test-first): internal guard first, then command/grammar/builder (probe the type discriminator), then runtime + catalog, then the full sweep + docs + /runda + commit proposal.