# Session handoff — 2026-05-25 (40)

Fortieth handover. This session **shipped ADR-0035 Phase-4 sub-phases
4f and 4g** (each test-first, with planning escalation and a *combined*
`/runda` finished-slice critique at the end) **plus a `/runda`-found
rebuild fix**. The next session **implements 4h —
`ALTER TABLE … RENAME TO`** (the §6 new low-level op), then **4i — the
verification sweep**. After 4h+4i, Phase 4 (ADR-0035) is complete.

## §1. State at handoff

**Branch:** `main`. **HEAD `50a889e`.** **Tests: 1885 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):

```
5b76315 feat: ADR-0035 4f — ALTER TABLE … ALTER COLUMN TYPE
6ff97f6 feat: ADR-0035 4g — ALTER TABLE add/drop constraint + add FK
50a889e fix: ADR-0035 4g — reconstruct table-CHECK metadata on rebuild
```

**Git state — no divergence.** `origin/main` is at **`5b76315`** (4f),
so **`6ff97f6` (4g) + `50a889e` (the fix) are unpushed** — a normal
working state. (The earlier `a2fc3c9` docs commit + `5b76315` 4f were
pushed at some point this session.) Push is the user's step; nothing
blocks. Working tree clean.

## §2. What shipped this session

- **4f — `ALTER TABLE <T> ALTER COLUMN <col> TYPE <type>`** (`5b76315`).
  A fourth `AlterTableAction::AlterColumnType`, **runtime-decomposed to
  `change_column_type(…, ChangeColumnMode::ForceConversion)`** — which
  **is** the ADR-0035 §7 advanced policy (lossy converts *with a note*,
  no force flag; incompatible + the ADR-0017 static refusals still
  refuse; **`int → serial` is *allowed*** per ADR-0018 §8 — the §7
  "→serial refused" summary is looser than the code). Builder
  discriminates the branch by the **`type` keyword** (unique — ADD
  COLUMN's type is an ident; checked **first** because `alter column …`
  also contains `column`). Internal-`__rdbms_*` guard folded into
  `do_change_column_type`.
- **4g — `ALTER TABLE` add/drop constraint + add FK** (`6ff97f6`).
  `ADD [CONSTRAINT <name>] (CHECK | composite UNIQUE | FOREIGN KEY)` +
  `DROP CONSTRAINT <name>`. **User-confirmed scope:** ADD = CHECK + FK +
  composite UNIQUE; `ADD PRIMARY KEY` and a *named* UNIQUE are refused
  (composite UNIQUE is anonymous in our model). Each ADD reuses a
  low-level path with a dry-run guard (table-CHECK/UNIQUE rebuild; FK →
  `do_add_relationship`, bare `REFERENCES <P>` → parent single PK,
  `create_fk = false`). `DROP CONSTRAINT` resolves the name to a named
  table-CHECK then a child-side named FK, else refuses. **Named-CHECK
  round-trip:** a nullable `name` column on
  `__rdbms_playground_table_checks` (**rebuild-only** arrival) **and** a
  `project.yaml` `check_constraints` extension to a `{expr, name}`
  mapping (the bare-string form still reads — back-compatible). The
  internal-`__rdbms_*` guard was folded into `do_add_constraint` /
  `do_add_relationship`, **completing the 4d/4e/4f guard class**. ADD
  CHECK also refuses a name colliding with an existing CHECK *or*
  relationship (keeps DROP unambiguous). **Grammar restructured:** the
  walker's `Choice` does **not** backtrack between two same-leading-
  keyword branches, so the action `Choice` keeps **one branch per verb**
  (`add`/`drop`/`rename`/`alter`) and `add`/`drop` fan out to an **inner
  `Choice`** whose branches each lead on a *distinct* second keyword
  (column / constraint / check / unique / foreign / primary).
- **Rebuild fix** (`50a889e`, `/runda`-found). `do_rebuild_from_text`
  re-emitted table-CHECKs into the recreated DDL (so enforced) but
  **never repopulated `CHECK_TABLE`** — so a *fresh* rebuild (missing
  `.db`, reconstructed from `project.yaml`) lost the CHECK metadata
  (incl. the name): `DROP CONSTRAINT` / `describe` / a later save would
  drop it. In-place rebuilds only worked because the wipe never touched
  the table. (Latent since 4a.3 for unnamed checks.) Rebuild now wipes +
  repopulates `CHECK_TABLE` from yaml (name + seq + expr), like
  META/REL, and adds the `name` column if a pre-4g table predates it
  (the rebuild-only migration). Regression test:
  `e2e_named_check_metadata_survives_a_fresh_rebuild`.

ADR-0035 stays **Accepted**; README + `requirements.md` Q1 + ADR §13
(4f, 4g) updated each slice.

## §3. The NEXT job — 4h (`ALTER TABLE … RENAME TO`)

**No plan written yet** — 4h needs its own plan (Phase 1→3 + a planning
`/runda`/written-DA gate) before building, because it is the **one
genuinely new low-level op** in Phase 4 (ADR-0035 §6), not a reuse.

**Spec (ADR-0035 §6):** `ALTER TABLE <old> RENAME TO <new>` is
**advanced-mode only** (no simple-mode rename-table verb). Within **one
transaction**:
1. Rename the table in the database.
2. Rename its `data/<old>.csv` → `data/<new>.csv` (persistence layer).
3. Update **every** metadata row that names it:
   - `__rdbms_playground_columns` rows (`table_name`),
   - **both ends** of any `__rdbms_playground_relationships` row
     (`parent_table` *and* `child_table`),
   - **`__rdbms_playground_table_checks` rows** (`table_name`, added in
     4a.3, keyed by table name).
4. Name validation + `__rdbms_*` rejection apply to the **target**.

Closes the rename half of `C1` for the advanced surface.

**Grammar:** a new `AlterTableAction::RenameTo { new: String }` (or
similar) — a **fifth** action branch in `AT_ACTION_CHOICES`. It leads on
`rename` — **but `rename column` already leads on `rename`** (the walker
`Choice` does not backtrack same-led branches; see §7.4). So `rename`
must follow the §2 pattern: one `rename` branch with an inner `Choice`
on the distinct second token (`column` → rename-column; `to` → rename-
table). Mirror the `add`/`drop` restructure 4g did. The builder keys on
`contains_word("column")` (already the §4f/§4g discriminator order) vs a
bare `rename … to`.

**Executor:** a genuinely new `do_rename_table` (no existing reuse) — the
CSV rename is a persistence-layer concern (cf. how `do_drop_table` cleans
CSV + metadata; ADR-0015 §6 ordering, commit-db-last). One undo step
(snapshot is whole-project). Internal-`__rdbms_*` guard on the target
(and refuse renaming an internal table as source — `reject_internal_
table_name` on both).

**Tests:** Tier-1 parse (rename-table vs rename-column dispatch); Tier-3
e2e via `run_replay` — rename a table that has rows (CSV follows), a
relationship (both ends update), and a table-level CHECK (the
`table_checks` rows follow); **survives rebuild** (the renamed table +
its metadata round-trip through yaml); one undo step; refuse rename to an
existing name / to an `__rdbms_*` name.

## §4. Then 4i — verification sweep (the running deferral tail)

Canonical list: ADR-0035 §13 4i. Unchanged from handoff-39 except 4f/4g
now carry their own help/usage (so the *CREATE TABLE* skeleton refresh is
the only help/usage debt left). The tail:
- **(a)** Refresh the **`CREATE TABLE` help/usage skeleton** for the
  4a.2 `DEFAULT`/`CHECK`/composite-`UNIQUE`, 4a.3 table-`CHECK`, 4b FK
  forms (4d/4e/4f/4g forms already carry their own help/usage).
- **(b)** `describe` display of **table-level constraints** (composite
  `UNIQUE` + table `CHECK`, now incl. **named** CHECKs from 4g) — the
  unique-*index* `[unique]` marker shipped in 4d, so only the table-
  level *constraint* display remains.
- **(c)** 4b **self-ref FK pre-submit indicator** (the diagnostic
  false-flags a `references <self>` parent as unknown).
- **(d)** **shared-entry-word completion merge** — `create`/`drop` (and
  now constraint forms) should offer every valid continuation across the
  SQL + DSL nodes; 4d/4g widened this.
- **(e)** **user discussion flag (2026-05-25):** visually distinguish
  simple- vs advanced-mode completions in the hint UI (likely colour) —
  a UX design conversation, before/with (d).
- Plus the §4i staples: typing-surface + matrix coverage, engine-neutral
  error pass, undo-parity (one step per statement).

## §5. Tracked follow-ups (not lost)

- **H1 wording — `--create-fk` leak (4g).** `ALTER … ADD FOREIGN KEY` on
  a *missing* child column reuses `do_add_relationship`'s error, which
  mentions the DSL flag `--create-fk` — off-key in an SQL context. Fix
  the wording for the SQL path (or branch the message). H1.
- **Coverage gap — pre-4g 3-column `CHECK_TABLE` migration (4g/fix).**
  The rebuild-time `ALTER … ADD COLUMN name` migration and the named-
  CHECK-on-old-project refusal (`check_table_has_name_column`) are
  logically sound + guarded, but the **genuinely-old 3-column table**
  in-place path is **untested** — a 3-col `CHECK_TABLE` can't be
  fabricated through the public API (no raw-SQL exec). The common case
  (old project whose `.db` is deleted → fresh rebuild → 4-col table) IS
  tested. A test helper (raw-SQL exec, or a fixture `.db`) would close
  it; flagged to the user, not yet decided.
- **H1 friendly wording** for the 4e CHECK-guard refusals and the 4f
  type-conversion diagnostics (engine-neutral but terse) — carried.
- The §8 items from handoff-39 (app-lifecycle-command runtime-failure
  journalling; M4 execution-time mode side-channel; `blob` value
  literal; CI/TT5; DSL→SQL teaching echo) remain open.

## §6. Patterns the implementer must not forget

1. **Walker `Choice` does not backtrack between same-leading-keyword
   branches.** This bit 4g (two `add` branches "expected column"). Keep
   **one branch per leading verb**, fan out on a **distinct** second
   token via an inner `Choice`. 4h's `rename` must join `rename column`
   under one `rename` branch the same way.
2. **Reuse low-level executors; the runtime decomposes `SqlAlterTable`.**
   4f wrapped `change_column_type`; 4g wrapped `do_add_relationship` +
   added `do_alter_add_table_check`/`do_alter_add_unique`/
   `do_drop_constraint_by_name`. 4h is the exception — a genuinely **new**
   `do_rename_table` (§6).
3. **Two DDL generators stay in sync** (`do_create_table` /
   `schema_to_ddl`). 4g added the `CONSTRAINT <name> CHECK` emission to
   `schema_to_ddl`; `do_create_table`'s table-CHECK insert writes
   `name = NULL` (CREATE-TABLE CHECKs stay unnamed — an ALTER-only
   naming, a noted consistency item).
4. **`do_rebuild_from_text` reconstructs ALL metadata from yaml.** It now
   wipes + repopulates `META` / `REL` / **`CHECK_TABLE`** and recreates
   indexes. **A new metadata table or column MUST be added to both the
   wipe and the repopulation** or a fresh rebuild silently drops it (the
   exact bug `50a889e` fixed). 4h touches metadata *values* (renames), so
   verify a renamed table round-trips through a fresh rebuild.
5. **Exhaustive matches:** a new `AlterTableAction` variant forces arms
   in `runtime.rs` (inner `match action`) and `app.rs`
   (`build_translate_context`'s inner match). The compiler finds them.
6. **Catalog lockstep + vocab audit** — refresh `sql_alter_table`
   help/usage for the rename form; keep wording engine-neutral
   (`engine_vocabulary_audit` enforces it).
7. **Probe, don't reason** — the combined `/runda` caught the rebuild bug
   precisely because a test was written for the *fresh*-rebuild path the
   original e2e skipped. For 4h, test the CSV-rename + both-relationship-
   ends + table_checks-follow paths explicitly, and a fresh rebuild.

## §7. Process pins (unchanged, still binding)

- **Confirm every commit.** Propose the message; wait for the go-ahead.
  No AI attribution.
- **Push is the user's step.** Never push/force-push. Unpushed commits
  are normal (see §1).
- **Escalate ambiguity / new cost.** 4g's three scope forks (ADD set,
  CHECK migration, DROP scope) and the discovered `project.yaml`-format
  implication were all surfaced + user-decided before building. Do the
  same for 4h's open calls (e.g. rename-to-existing-name behaviour, any
  describe/echo wording).
- **DA hat each slice; `/runda` at the planning exit AND on the finished
  slice.** The user opted to **combine** the finished-slice `/runda`
  across 4f+4g this session — it found a real bug (the rebuild metadata
  loss). Budget for at least one finished-slice `/runda` covering 4h
  (+4i); it pays off.
- **Keep docs lockstep** — ADR status/§13 + README + `requirements.md`
  in the same edit.

## §8. How to take over

1. **Read, in order:** this file →
   `docs/adr/0035-advanced-mode-sql-ddl.md` (§6 rename, §13 4h/4i) →
   `CLAUDE.md` → `docs/requirements.md` (`Q1`, `C1`). The 4f/4g plans
   (`docs/plans/20260525-adr-0035-sql-ddl-4f.md`, `…-4g.md`) are good
   models for the **4h plan you must write**.
2. **Baseline:**
   ```
   cargo test    # expect 1885 passing / 0 failing / 0 skipped / 1 ignored
   cargo clippy --all-targets -- -D warnings   # clean
   ```
3. **Plan 4h** (Phase 1→3 + planning `/runda`), surfacing any open calls
   to the user; then implement test-first per §3 (grammar restructure of
   the `rename` branch → `do_rename_table` executor → runtime/app arms →
   Tier-1/Tier-3 tests incl. a fresh rebuild → catalog + docs lockstep →
   finished-slice `/runda` → commit proposal).
4. **Then 4i** (§4) — the verification sweep closes Phase 4.
