docs: session handoff 62 — C4 m:n convenience command + issue #19
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
# 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 relationship` convenience 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_M2N` `CommandNode`** in
|
||||
`dsl/grammar/ddl.rs` (entry `create`, opener `Node::Literal("m")` —
|
||||
not a keyword, so it never shadows an identifier), registered Simple
|
||||
in `grammar/mod.rs` `REGISTRY`. `build_create_m2n` →
|
||||
`Command::CreateM2nRelationship { t1, t2, name }`.
|
||||
- Worker: `Request::CreateM2nRelationship`,
|
||||
`Database::create_m2n_relationship`, executor
|
||||
`do_create_m2n_relationship` (reads each PK, guards self-ref /
|
||||
PK-less, builds columns + compound PK + 2 `SqlForeignKey`s, calls
|
||||
`do_create_table`).
|
||||
- Runtime: `execute_command_typed` arm. Echo:
|
||||
`echo::render_create_m2n` (advanced-mode DSL→SQL teaching echo, ADR-
|
||||
0038 — the generated `CREATE TABLE … FOREIGN KEY …`, round-trips as
|
||||
valid SQL), wired in `build_schema_echo`.
|
||||
- Surfaces: completion `("m","m:n")` composite; `help.ddl.create_m2n` +
|
||||
`parse.usage.create_m2n` catalog (+ `keys.rs` declarations);
|
||||
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**:
|
||||
|
||||
1. **Dispatch** (`walker/mod.rs` `decide`) committed `simple.first()`
|
||||
unconditionally → now tries simple candidates (so `create table` no
|
||||
longer shadows `create m:n`). Reduces to the old single-candidate
|
||||
commit when there is one.
|
||||
2. **Completion continuation-merge** (`walker/mod.rs`) was gated
|
||||
`if mode == Advanced` → now runs in simple mode too, **gated on
|
||||
`simple_count > 1`** so single-form entry words are untouched.
|
||||
3. **Usage disambiguator** (`grammar/mod.rs` `usage_key_for_input`)
|
||||
knew the `1:n` opener but not `m: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):**
|
||||
1. **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.
|
||||
2. **SD1 `seed`** then **H2 `hint`** — 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.
|
||||
3. **V2/S3 multi-result tabs** or **V4 journal** — larger output-model
|
||||
redesign, design-first, own ADR. V4 also unlocks diagram live-reflow.
|
||||
4. **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
|
||||
|
||||
1. Read handoffs 60 → 61 → 62, then `CLAUDE.md`, `docs/requirements.md`
|
||||
(X1/C4 now `[x]`), `docs/adr/README.md`.
|
||||
2. **Before adding logging:** the level discipline in the
|
||||
`src/logging.rs` module doc.
|
||||
3. **For grammar/command work:** an entry word can now carry **multiple
|
||||
DSL forms** in simple mode (C4 generalized the dispatch + completion +
|
||||
usage paths). `create` is the first such entry word (table + m:n).
|
||||
4. **For relationship/FK work:** ADR-0013/0043/0044/0045 are all landed;
|
||||
`SqlForeignKey` carries `inline`; `do_create_table` now guards
|
||||
internal names.
|
||||
5. Codebase on `main` at `8bd43cc`, clean. Commits user-confirmed,
|
||||
append-only, no AI attribution. Process pins that paid off: **two
|
||||
`/runda` passes 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).
|
||||
Reference in New Issue
Block a user