9.6 KiB
Session handoff — 2026-06-10 (62)
Sixty-second handover. Continues from handoff-61 (X1 logging full sweep
- T3 residuals). This session was a list-trimming + one-feature run:
it closed C4 (the
create m:n relationshipconvenience command, ADR-0045) and, in passing, resolved Gitea issue #19 (drop-PK guard). Handoff-61 itself was written mid-session, so the X1 / T3 work it describes is also part of this session's commit range.
§1. State at handoff
Branch: main. HEAD 8bd43cc. Push is the user's step.
Tests: 2237 passing / 0 failing / 1 ignored (the 1 ignored is the long-standing doc-test). Clippy clean (nursery, all targets). +30 over the handoff-60 baseline of 2207.
This session's commits (8, on top of session-60's 5):
8bd43cc feat: create m:n relationship convenience command (C4, ADR-0045)
e598008 docs: ADR-0045 m:n convenience command (C4); accepted
e44d298 test+docs: lock drop-PK-refused on advanced surface; document no-PK advanced mode (#19)
b803468 docs: session handoff 61 — X1 logging full sweep + T3 residuals closed
5a33f2a fix(fk): compound-FK violation message names every column pair
6985a43 fix(fk): inline FK referencing a compound PK points at the table-level form
0a7612e feat: comprehensive logging across parser, app, persistence, runtime (X1)
a8ad0c6 feat(db): comprehensive logging across worker + executors (X1)
Requirements closed this session: X1 [x] (logging), T3
residuals (both ADR-0043 messaging items), C4 [x] (m:n). Gitea
#19 closed.
§2. X1 — comprehensive logging (closed) — see handoff-61 §2
Full detail in handoff-61. In brief: ~75 → 137 tracing sites under
a documented level discipline (read the src/logging.rs module doc
before adding logs). Logs go to a file (--log-file >
RDBMS_PLAYGROUND_LOG_FILE > ~/.rdbms-playground/playground.log);
level via the separate RDBMS_PLAYGROUND_LOG env (default info).
debug = per-command detail (off by default), trace = hot paths
(per-keystroke parse).
§3. T3 residuals (both closed) — see handoff-61 §3
6985a43 inline-FK arity wording (points at the table-level form;
added inline: bool to SqlForeignKey). 5a33f2a compound-FK
violation names every column pair (comma-joined in the single-column
facts slots; enrich_fk_violation). ADR-0043 now has no residuals.
§4. Issue #19 — drop-PK guard (closed, e44d298)
A parallel check the user requested. Finding: dropping a PK column is
already refused in both modes via the shared do_drop_column guard
("cannot drop primary-key column …") — simple drop column and
advanced ALTER … DROP COLUMN both route through it. Added end-to-end
coverage (tests/it/sql_alter_table.rs: single + compound PK, refusal
for the right reason). Corrected a long-standing misconception: the
issue's premise ("we don't support creating a table with no PK") is true
only in simple mode — advanced SQL create table t (a int) makes a
real PK-less table (SQLite's implicit rowid keys it; only
WITHOUT ROWID lacks one, which this app never creates). The simple-mode
with pk requirement is pedagogical (ADR-0029), not an engine
constraint. Documented in docs/simple-mode-limitations.md.
§5. C4 — create m:n relationship (the feature, ADR-0045)
create m:n relationship from <T1> to <T2> [as <name>] generates a
junction table: one FK column per parent PK column
({table}_{pkcol}, typed via fk_target_type — ADR-0011), a compound
PK over all of them, and two CASCADE 1:n relationships — all in
one do_create_table call = one undo step (no batch needed;
do_create_table already takes foreign_keys + writes per-FK
relationship metadata). Auto-named {T1}_{T2} (optional as), available
in both modes, compound-parent PKs supported (ADR-0043).
Forks (all user-confirmed): compound-over-FKs PK (vs surrogate /
none); CASCADE actions; auto-name + optional as; both modes; FK
columns {table}_{pkcol}. Refused: self-referential m:n (from T to T — full stop, OOS); PK-less parent; internal __rdbms_* junction
name; name collision.
Where the code lives:
- Grammar: a separate
CREATE_M2NCommandNodeindsl/grammar/ddl.rs(entrycreate, openerNode::Literal("m")— not a keyword, so it never shadows an identifier), registered Simple ingrammar/mod.rsREGISTRY.build_create_m2n→Command::CreateM2nRelationship { t1, t2, name }. - Worker:
Request::CreateM2nRelationship,Database::create_m2n_relationship, executordo_create_m2n_relationship(reads each PK, guards self-ref / PK-less, builds columns + compound PK + 2SqlForeignKeys, callsdo_create_table). - Runtime:
execute_command_typedarm. Echo:echo::render_create_m2n(advanced-mode DSL→SQL teaching echo, ADR- 0038 — the generatedCREATE TABLE … FOREIGN KEY …, round-trips as valid SQL), wired inbuild_schema_echo. - Surfaces: completion
("m","m:n")composite;help.ddl.create_m2n+parse.usage.create_m2ncatalog (+keys.rsdeclarations); highlighting is grammar-driven (automatic).
Tests: 14 integration (tests/it/m2n.rs), 7 typing-surface matrix
(tests/typing_surface/create_m2n.rs — completion/hint/highlight/parse),
plus echo / highlight / usage-disambiguator / internal-name units.
§6. Framework fixes the C4 build + two /runda passes surfaced
C4's "separate node" design rested on an ADR premise that proved only half true: "the walker already dispatches multiple nodes per entry word" held in advanced mode but not simple. Three latent simple-mode assumptions ("≤1 DSL form per entry word") were generalized, all behaviour-preserving for existing single-form commands:
- Dispatch (
walker/mod.rsdecide) committedsimple.first()unconditionally → now tries simple candidates (socreate tableno longer shadowscreate m:n). Reduces to the old single-candidate commit when there is one. - Completion continuation-merge (
walker/mod.rs) was gatedif mode == Advanced→ now runs in simple mode too, gated onsimple_count > 1so single-form entry words are untouched. - Usage disambiguator (
grammar/mod.rsusage_key_for_input) knew the1:nopener but notm:n→ added an explicit branch.
Plus a root-cause bug fix (user-chosen scope): do_create_table
now rejects internal __rdbms_* names. This closed both the C4 as __rdbms_* hole and a pre-existing hole — simple-mode DSL create table __rdbms_* was accepted at parse (the TABLE_NAME_NEW slot had no
guard; only the advanced-SQL path rejected internal names). The shared
executor is the single choke point; the SQL path still rejects earlier
at parse.
Process note: the two /runda passes were worth it. The first
(pre-build) corrected the inverted "no PK-less tables" assumption and
confirmed the do_create_table reuse against code. The second
(pre-commit) closed five test-coverage gaps — two of which
(highlighting, persistence round-trip) had been wrongly claimed
verified (the typing-surface Assessment has no highlight field;
"transitively covered" was a hand-wave) — and found the two bugs above.
Lesson re-confirmed: verify a claimed-tested surface actually has an
assertion; "transitively covered" is a DA red flag.
§7. Remaining open landscape
Closed since handoff-60: X1, both T3 residuals, C4, #19. ADR-0043 and ADR-0045 fully landed.
Still open (by readiness, unchanged otherwise):
- TT5 CI — test infra solid (2237 green); no pipeline. Gitea Actions / Woodpecker (a fresh decision tied to the migration + ADR-0001's reopened distribution question). Friction: the requirement is Linux/macOS/Windows on stable — self-hosted Gitea can do Linux easily, but mac/Windows runners need machines that may not exist; likely needs a Linux-first scope decision.
- SD1
seedthen H2hint— the two unblockers for A1 app-commands; both net-new, own ADR (SD2 is the seed-generator design ADR). SD1 should now seed m:n junctions too (valid FK refs from parent rows) — C4 makes that concrete. - V2/S3 multi-result tabs or V4 journal — larger output-model redesign, design-first, own ADR. V4 also unlocks diagram live-reflow.
- C3a modify relationship — small follow-up (drop+add covers it today; ADR pending).
ADR-0045 OOS for later: self-referential m:n (deliberate non-goal); per-relationship action overrides; extra junction payload columns; m:n-as-diagram echo. Pre-existing, now-fixed: the internal-name hole (§6) — no separate issue needed, it's closed.
§8. How to take over
- Read handoffs 60 → 61 → 62, then
CLAUDE.md,docs/requirements.md(X1/C4 now[x]),docs/adr/README.md. - Before adding logging: the level discipline in the
src/logging.rsmodule doc. - For grammar/command work: an entry word can now carry multiple
DSL forms in simple mode (C4 generalized the dispatch + completion +
usage paths).
createis the first such entry word (table + m:n). - For relationship/FK work: ADR-0013/0043/0044/0045 are all landed;
SqlForeignKeycarriesinline;do_create_tablenow guards internal names. - Codebase on
mainat8bd43cc, clean. Commits user-confirmed, append-only, no AI attribution. Process pins that paid off: two/rundapasses per feature (design + pre-commit) — both found real bugs and gaps every time; verify a claimed-tested surface has an actual assertion; escalate genuine forks (every C4 design choice- the internal-name fix scope was the user's).