# 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 executors** —
  `do_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.md`
   → `docs/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.md` → `docs/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.
