- 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).
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-leaseto 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
3247e3cif 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). Reusesdo_add_index/do_drop_index.CREATE UNIQUE INDEXadmitted in advanced mode (ADR-0025 Amendment 1) via an additiveIndexSchema.uniqueflag (round-tripsproject.yaml, survives rebuild,[unique]markers in the structure view + items panel); simpleadd unique indexstays deferred.IF [NOT] EXISTSreuses the 4c skip path.create/dropeach gained a second advanced node (all-candidates dispatch).do_add_indexgained an internal-__rdbms_*guard (both surfaces). - 4e —
ALTER TABLEadd/drop/rename column (bbc2e34).alteris a new advanced-only entry word;SqlAlterTable { AlterTableAction }is runtime-decomposed to the existingdo_add_column/do_drop_column/do_rename_column(one undo step each — no new worker layer).do_add_columnextended to consume rawdefault_sql/check_sqlso 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 thecheck_references_columntokenizer in the shared executors (both surfaces) — fixing a latent rename-drift bug. SQL DROP COLUMN refuses an index-covered column (no--cascadeSQL spelling). The column executors +do_add_indexcarry 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 actionChoicebranch (AT_ALTER_COLUMN, leadsalter), builder discriminated by thetypekeyword (contains_word("type"), checked first — unique to this action; ADD COLUMN's type is an ident). Reusesuper::sql_create_table::SQL_TYPEfor the type slot. - Runtime-decompose to
database.change_column_type(table, column, ty, ChangeColumnMode::ForceConversion, src)→CommandOutcome::ChangeColumn(the existing simple-change columnpath — surfaces the client-side lossy note). No new mode, no new note, no new persistence. ForceConversionIS the §7 advanced policy (verified in code): incompatible cells refuse; lossy cells are performed (the dry-run skips the refuse only forForceConversion, db.rs:4575) and counted → the existing engine-neutralclient_side.transformed_lossynote fires.- Static refusals (verified
type_change::static_refusal): same-type; non-int → serial (onlyint → serialallowed); any↔ blob; directdate ↔ datetime; not-in-matrix. ⚠️int → serialis ALLOWED (auto-fill nulls + UNIQUE, ADR-0018 §8) — ADR-0035 §7's "static-refused →serial" summary is looser than the code. Testint → serialas supported,text → serial/↔ blobas refused. Do not "fix" int→serial to refuse. - Fold the internal-
__rdbms_*guard intodo_change_column_type(user-confirmed 2026-05-25, both surfaces) — closes the simplechange columnexposure. (do_add_constraint/do_add_relationshipstay a 4g follow-up.) - Out:
SET DATA TYPEsynonym, PostgresUSING, 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 TABLEadd/drop constraint, add FK. The add-FK path reuses the 4b shared relationship helpers; named CHECK adds anamecolumn to__rdbms_playground_table_checks. Fold the internal-table guard intodo_add_constraint/do_add_relationshiphere (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_checksrows). - 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_typeis closed in 4f;do_add_constraint/do_add_relationshipride in 4g. (The pattern: areject_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;
blobvalue literal; CI/TT5; DSL→SQL teaching echo) remain open.
§7. Patterns the implementer must not forget
- Reuse low-level executors.
Sql*DDL wraps the existing helpers (4f wrapschange_column_typewithForceConversion); they do not fork. The runtime decomposesSqlAlterTableper action. - 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. - Grammar leading-
Optionaltrap — eachChoicebranch leads on a concrete keyword (4f'sAT_ALTER_COLUMNleadsalter). - Exhaustive matches: a new
AlterTableActionvariant forces arms inruntime.rs(the innermatch action) andapp.rs(build_translate_context's inner match). TheCommand-level matches (verb/target_table/typing-surface) are unchanged (the variant is internal toSqlAlterTable). The compiler finds them. - Catalog lockstep + vocab audit — 4f refreshes the existing
sql_alter_tablehelp/usage (no new keys); keep wording engine-neutral. - Probe, don't reason — verify the
type-keyword discriminator and theSQL_TYPEextraction (mirrorbuild_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_typeinternal guard) was surfaced + user-confirmed 2026-05-25; the §7 lossy policy + the no-force/no-USINGexclusions are ADR-settled. - DA hat each slice;
/rundaat the planning exit AND on the finished slice. Both 4d and 4e had a planning/rundaand 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.mdin the same edit.
§9. How to take over
- Resolve §1.1 (the git divergence) with the user before any new commit work.
- 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). - Baseline:
cargo test # expect 1854 passing / 0 failing / 0 skipped / 1 ignored cargo clippy --all-targets -- -D warnings # clean - Implement 4f per the plan §9 (test-first): internal guard first,
then command/grammar/builder (probe the
typediscriminator), then runtime + catalog, then the full sweep + docs +/runda+ commit proposal.