Bring main's latest (ADRs 0049-0053 + their features) onto the CI branch so
the gate runs against current main before CI lands on main. Clean merge —
ci and main touched disjoint files.
macOS is no longer deferred — built natively on a Tart (Apple-Silicon)
runner (real hardware → licensed SDK, no grey area). Amendment documents
release-macos.yaml (dispatch-only, needs main), the libiconv de-nix +
ad-hoc re-sign, the runner-label `:host` backend nuance, generation-based
cache pruning, and D2-on-macOS (system libs only). All six D1 targets now
produce artifacts. Updates the deferred list + index entry.
The first exemplar (`add 1:n relationship`) showed per-node keying is
too coarse for multi-form commands, so revise the mechanism to per-form.
- CommandNode `hint_id: Option<&str>` -> `hint_ids: &[&str]` (mirrors
usage_ids); hint_key_for_input_in_mode reuses a factored-out
pick_form_key (shared digit/m:n/suffix form disambiguation with
usage_key_for_input_in_mode)
- wire INSERT + ADD (all four forms) with hint_ids
- author the three approved exemplars: hint.cmd.insert,
hint.cmd.add_relationship, hint.err.foreign_key.child_side
(what/example/concept) + keys.rs registration
- revise ADR-0053 D3 to per-form; record clause-concept hints as a
deferred extension (issue #37); update README + plan
- +5 tests; 2488 pass / 1 ignored, clippy clean
The mechanism for the contextual hint, with tier-2 fallback; the
tier-3 corpus lands in later phases.
- new CommandNode `hint_id` field (all None for now)
- AppCommand::Hint + HINT grammar node + REGISTRY + dispatch
- F1 read-only overlay in handle_key (buffer/cursor/memo untouched)
- note_hint* renderers; hint_id_for_input_in_mode (shared selection
helper refactored out of usage_keys_for_input_in_mode)
- last_error_hint_key + friendly::error_hint_class classifier
- catalogue: help.app.hint / parse.usage.hint / hint.getting_started
- +12 tests; 2483 pass / 1 ignored, clippy clean
Phased build plan: mechanism skeleton with tier-2 fallback first
(hint_id field, AppCommand::Hint, F1 read-only overlay, last_error_hint_key,
note_hint* renderer), then catalogue + the three approved exemplars,
then comprehensive content in batches, then polish. Reuses the existing
command_for_entry_word / usage_keys_for_input_in_mode lookups for
command identification. Test spine includes the comprehensiveness
coverage test that gates "comprehensive for v1".
The macOS release leg: workflow_dispatch (tag input) on the Tart runner —
test → build both *-apple-darwin targets → rewrite nix libiconv to /usr/lib
+ ad-hoc re-sign → upload binary + .sha256 to the tagged release (idempotent
create-or-get) → prune the nix store by generation. Composed entirely of
parts the smoke-test proved green, so the smoke-test is removed.
Dispatch-only fits the intermittent runner and keeps the 4-target Linux/
Windows release independent. Becomes triggerable once CI is on the default
branch (workflow_dispatch is default-branch-only in Gitea).
Settles the `hint` slot ADR-0003 left pending; closes the last open
piece of A1. Two surfaces (F1 → live-input hint; `hint` command →
last-error expansion), no topic arg, and a new tier-3 teaching corpus
keyed on a new CommandNode `hint_id` so advanced-SQL forms get distinct
mode-correct content. Comprehensive content for v1, authored
exemplars-first. Refines ADR-0003; references ADR-0019/0021/0022/0049/
0051. Files #36 for the parallel help-side gap.
- Add `cargo test` before the darwin builds (gate is Linux-only; the macOS
leg is test-then-build) — a full dry-run of release-macos bar the upload.
- Add an `if: always()` prune step. The runner wipes the workspace each run,
so cargo target/ never accumulates (no sweep). The persistent cache is the
nix store: record the current toolchain in a persistent profile, keep the
2 newest generations (nix-env --delete-generations +2), reclaim the rest
(nix-collect-garbage). Pairs with min-free/max-free in the runner nix.conf.
libiconv is the only /nix/store dep the darwin stdenv bakes in (everything
else is system frameworks + libSystem/libobjc). The smoke-test now rewrites
that load path to /usr/lib/libiconv.2.dylib (ABI-compatible, present on
every Mac), re-signs ad-hoc (install_name_tool breaks the sig; arm64
requires a valid one), then verifies no /nix/store paths remain, the
signature is valid, and the native binary launches. Flake comment updated
to reflect the propagated-libiconv reality.
The smoke-test caught the aarch64 binary linking a /nix/store libiconv.dylib
— non-portable (won't exist on a user's Mac). The Apple SDK already provides
a system libiconv stub, so removing pkgs.libiconv makes the linker resolve
-liconv to /usr/lib instead. The smoke-test now fails if any /nix/store dylib
is linked.
Add the two *-apple-darwin targets to rust-toolchain.toml and apple-sdk +
libiconv to the flake devShell (darwin only) so the nix toolchain links
AppKit; make cargo-zigbuild/zig Linux-only (macOS builds natively). Repoint
the throwaway macOS workflow to actually build both darwin targets through
the flake on the Tart runner — the first real check of the macOS leg, which
can't be verified locally. Delete once release-macos lands.
In act_runner a label is `<name>:<backend>`; `:host` is the execution-
backend schema (run on host, no container), not part of the label. The
runner registered as `macos:host` therefore has the label `macos`, which
is what runs-on must reference.
Gitea only exposes workflow_dispatch for workflows on the default branch
(main); our CI is on `ci`, so the manual-run button/API isn't available.
Add a push trigger (filtered to the probe file) so we can drive the macOS
runner test from the ci branch. workflow_dispatch kept for post-merge.
Manual-dispatch probe on runs-on macos:host to confirm the runner picks up
jobs and report arch / macOS version / Xcode SDK / toolchains (nix, rustup,
cargo) / git+node, before wiring the macOS release leg. Delete once done.
ADR-0052 moved success journaling out of the worker to the dispatch
layer, leaving the `source` that handlers threaded purely for the
worker's old history.log write dead. Remove it:
- drop `_source` from finalize_persistence and do_rebuild_from_text
- inline + delete the three read-only *_request wrappers
- drop the now-unused `source` param from the ~30 forwarding worker
handlers (leaf + composite), compiler-guided
- remove the `source` field from the DescribeTable/QueryData/RunSelect
requests and their DatabaseHandle methods (call sites updated)
The only worker `source` left is the snapshot/undo label
(snapshot_then / stage_pre_mutation / begin_batch). Purely mechanical,
no behaviour change. 2471 pass / 0 fail / 1 ignored, clippy clean.
Record the submission mode per history entry so advanced commands are
reusable in simple mode, and fix the bug where a ':'-one-shot command
lost its ':' across sessions (ADR-0052, closing #30).
Format: the history.log status token gains an optional ':adv' suffix
(ok / ok:adv / err / err:adv); 'source' stays last and canonical, so
replay is unaffected. The in-memory ring (still Vec<String>) stores
advanced entries ': '-prefixed; recall strips the ':' in advanced mode
and keeps it in simple; hydration reconstructs the prefix from the tag.
Journaling moved from the worker to the dispatch layer (spawn_dsl_-
dispatch / run_replay / app-command sites), where the mode is in scope
with no worker plumbing; finalize_persistence writes only yaml/csv
(commit-db-last still atomic for state). The journal write is now
best-effort (command already committed), consistent with the failure
path. App commands journal simple, so they recall bare. Journaling is
now uniform (every successful command, per ADR-0034) — closing a gap
where show tables/relationships/explain didn't journal.
Amends ADR-0034 (status tag + journaling location), ADR-0015 §6
(history.log out of the worker tx), ADR-0040 (journal-write best-effort).
15 worker-level journaling tests retired, re-covered at the new layer
(history.rs format, app.rs recall matrix, iteration6 cross-session
regression, replay). 2471 pass / 0 fail / 0 skip, clippy clean.
Record the multi-platform build strategy as its own decision: cargo-zigbuild
for the four non-macOS targets, the static/standalone posture per platform,
the Windows synchronization stub, the test->build matrix workflow, and the
macOS deferral with its licensing rationale (the public CI image can't carry
the SDK). Shrinks the ci-001 amendment to a pointer; updates the index.
Runtime-verified by the user: Linux x86_64 + Windows aarch64 run correctly.
Per ADR-0051 (closing issue #27): the bottom status line is now a
keystrokes-only, state-selected strip. A pure status_bar_bindings()
picks the binding set by priority (first match wins):
sidebar focus → Ctrl-O next pane · ↑↓/PgUp/PgDn scroll · Esc input
completion live → Tab/Shift-Tab cycle · Esc cancel · Enter run
history nav → ↑↓ browse · Esc clear · Enter run
editing → Esc clear · Ctrl-A/E home/end · Ctrl-W del word · Enter run
default (empty) → Ctrl-O sidebar · Tab complete · ↑ history · Enter run
The editing state surfaces the #29 readline keys (ADR-0049's deferred
advertisement). Typed-command words (mode advanced/simple, the ':'
one-shot) and Ctrl-C quit leave the strip; simple mode's empty-input
hint gains a '`mode advanced` for SQL' pointer (advanced mode shows
none — a switcher knows the way back, and help covers it).
Mechanism: status_bar_bindings + a thin renderer (unit-testable);
App::is_browsing_history() exposes the private history_cursor; the mode
pointer lives in resolve_hint_lines. Catalog: 12 new shortcut labels +
panel.hint_mode_advanced (en-US.yaml + keys.rs, validator 1:1), 5 dead
strip strings removed.
Forks user-chosen: editing state shows #29 keys; quit omitted; no
width-drop machinery (longest strip ~65 cols fits; a width-budget test
keeps it lean). Modal-aware strip is OOS (pre-existing). Tests: 9
Tier-1 unit (per-state key sets, width budget, mode pointer), 1 Tier-3
rewritten, 15 full-panel snapshots re-accepted (reviewed). 2467 pass /
0 fail / 0 skip, clippy clean.
release.yaml becomes test (once, host) -> build (matrix) over the four
cargo-zigbuild targets; each matrix job uploads its binary + .sha256 to
the shared release (idempotent create-or-get). Records the expansion in
ADR-ci-001 (2026-06-13 amendment); macOS stays deferred.
Replace the single-target musl cc with cargo-zigbuild + zig in the flake
devShell — one universal cross cc/linker (incl. rusqlite's bundled SQLite
C) for all four non-macOS D1 targets, added to rust-toolchain.toml:
x86_64/aarch64-unknown-linux-musl (static, D2)
x86_64-pc-windows-gnu, aarch64-pc-windows-gnullvm (standalone .exe)
Windows links -lsynchronization (std WaitOnAddress), which rust-overlay's
toolchain and zig's mingw don't ship; the symbols are forwarded by
kernel32, so an empty stub libsynchronization.a (ci/winstub/, wired via
.cargo/config.toml for the windows targets only) satisfies the linker.
Verified: all four build; linux static; windows valid PE32+.
Per ADR-0050 (closing issue #28): the confirmation echo after an
incidental structural edit — create table, add/drop/rename/change
column, add/drop index — now renders the structure only (header +
column box + indexes + constraints) and no longer appends the
References:/Referenced by: relationship block.
Rationale: a confirmation reports the change just made, not the
table's relationships, which the user didn't touch. Relationship info
is still one `show table <T>` away, and the relationship-subject
surfaces (show table, add/drop relationship) keep their ADR-0044
diagrams unchanged.
Scope is all incidental DDL (user-confirmed). Mechanism: drop the
relationship-block call from render_structure (all its callers are
incidental DDL); the handle_dsl_success diagram-vs-structure routing
is unchanged. The orphaned relationship_prose_lines + cols_disp
helpers are deleted (the prose format survives in ADR-0016 §5 + git
history for a future OOS-7 always-prose setting).
ADR-0050 supersedes ADR-0044 §1's incidental-DDL prose clause and the
relationship-block half of ADR-0016 §5 (both annotated). Tests: prose-
presence unit test + snapshot removed; new unit test locks structure-
only with inbound+outbound relationships present; the misnamed add-
column integration test inverted + renamed. 2458 pass / 0 fail / 0
skip, clippy clean.
Add paths-ignore (docs/**, **/*.md) to the gate's push + pull_request
triggers so markdown/docs-only changes don't run a full clippy+test that
can't change the outcome. Mixed code+docs pushes still gate (not all
files are ignored); flake/toolchain changes are deliberately not ignored.
Also refresh a stale ADR-0049 -> ADR-ci-002 comment reference.
Records the CI/release pipeline as ADR-ci-001 and relocates the nix-flake
ADR from main's ADR-0049 to ADR-ci-002 (content unchanged, history note
added). Both live in docs/ci/adr/ with a README index — a dated,
ci-segmented namespace disjoint from main's integer ADR sequence, the
same split the website subproject uses to avoid cross-branch number
collisions. Drops the ADR-0049 entry from docs/adr/README.
ci-001 covers the runner model, the baked nix CI image, the clippy+test
gate, the static-musl release on tag, trigger hygiene, auth, and scope.
Implement the deferred I1b readline shortcuts in the command input
field (ADR-0049, closing issue #29):
Esc clear a partly-typed command (only when no completion memo)
Ctrl-A cursor to line start (Home alias)
Ctrl-E cursor to line end (End alias)
Ctrl-W delete the previous word (readline-style, UTF-8 safe)
Ctrl-K kill to end of line
Ctrl-U kill to start of line
Esc precedence is preserved: a live Tab-completion memo still wins
(Esc undoes the completion first, ADR-0022); Esc clears only when no
memo is alive. While a sidebar panel is focused (Ctrl-O), Esc exits
navigation mode upstream and never clears the input draft. Cursor-only
keys leave history navigation intact like Home/End; buffer-mutating
keys end it like Backspace.
New helpers clear_input / delete_prev_word / kill_to_end /
kill_to_start in src/app.rs. 22 new Tier-1 tests (2458 pass / 0 fail
/ 0 skip, clippy clean). ADR-0049 amends ADR-0046's OOS list;
requirements.md I1b marked done.
- Run cargo test before the build so a tag never publishes untested code.
- Pin shell: bash on the scripted steps; the runner defaults to dash,
which rejected `set -o pipefail` and failed run 22's package step.
- Swap `file` (absent in the slim image) for `ls -l`.
Tag pushes ignore paths: filters, so a release tag spuriously rebuilt
the unchanged CI image and re-ran the gate on a commit the branch push
already gated. Add branches: ['**'] to both push triggers — tag pushes
no longer fire them (release.yaml owns tags). Pushing commits + a tag
together still gates the commits via the branch push.
On a v* tag, builds the x86_64-unknown-linux-musl binary in the CI image
and publishes it (+ .sha256) to a Gitea release via the API and the
auto GITEA_TOKEN. x86_64 Linux only for now; rest of the D1 matrix and
D3 packaging layer on later. Correctness comes from the branch gate.
rust-toolchain.toml gains the x86_64-unknown-linux-musl target; the
flake devShell gains a musl cc (pkgsCross.musl64) + CC/linker env so a
`cargo build --target …-musl` compiles rusqlite's bundled SQLite C and
links fully static (D2: single static binary, no runtime deps). Cargo
release profile strips symbols (13MB -> 10MB). Verified locally: the
musl binary is static-pie, statically linked, stripped, runs standalone.
At `seed <table> ▮` the hint showed only the `set`/`--seed` chips and
never mentioned the optional row count — a bare positional number with no
candidate, on an already-complete command, so neither the candidate
ladder nor the resolver surfaced it. (A prior IntroProse attempt was
reverted: pending_hint_mode is cleared by the trailing optionals.)
Carry a skipped Optional's IntroProse hint: walk_optional stashes the
inner's key into a new WalkContext.surviving_intro_hint (key + position)
before the empty match clears pending_hint_mode; the snapshot keeps it
only when the skip position is the cursor (so it never leaks past a
later-consumed `set …` clause, nor once the count is given); the
resolver returns it ahead of the empty-expected short-circuit. The seed
count is wrapped Hinted{IntroProse("hint.seed_count")}; the prose names
the count (default 20), the `.column` column-fill form, and `set` /
`--seed`. Tab still cycles the keywords.
Only IntroProse is carried; ProseOnly/ForceProse and the CREATE-TABLE
element (a required Repeated) are untouched. No AmbientHint/renderer
change. Fires in both modes.
ADR-0022 Amendment 7; +3 tests.
Job-container image the gate runs in. node:22-bookworm-slim satisfies
the act_runner contract (sleep/bash/node) far more cheaply than the
catthehacker images; single-user nix installed on top (pre-create /nix
+ empty build-users-group so it installs as root in a container) with
the flake's devShell pre-warmed — CI enters a ready 1.95.0 toolchain in
~1.4s. Verified by local build. ~5.5GB (rust toolchain closure); dep/
target caching is a noted follow-up.
Two additive D7 catalogue rules, surfaced while writing the website seed
docs. No change to the type fallback, executor, or grammar.
#33 — year-like int columns. `published`/`birth_year` were just `int`, so
they fell to the unbounded int path and produced nonsense (`9419`). Add an
int-gated year rule (after the quantity rule, so `year_count` stays a
count): `year`/`*_year`/`published`/`founded` -> a bounded 1950-2025 year
(new `YearRecent`), or the dob-style birth window 1945-2007 for
`birth`/`born`/`dob` (new `YearBirth`). Plain int; not added to the D9
named-generator vocabulary.
#34 — conventional choice sets. A few enum-ish names have a near-canonical
small set that reads far better than lorem text. Add a type-gated PickFrom
lookup (reusing the existing generator): priority/prio, severity,
rating/stars. `status` is deliberately excluded (values too
domain-specific) and keeps the D12 advisory; a user IN-CHECK still wins.
`priority` leaves ENUM_TOKENS.
ADR-0048 Amendment 1; +8 tests (incl. a column-fill integration test that
also closes a pre-existing gap on that path).
Diagnostic to determine how the ci-public runner executes jobs and
where the nix toolchain is reachable (host vs default container vs a
custom container:), so the real gate is built on facts. Delete once
the gate lands.
Root flake with two outputs: devShells.default (pinned 1.95.0
toolchain via rust-toolchain.toml + rust-overlay, plus cargo-sweep)
and packages.default (rustPlatform.buildRustPackage from the committed
Cargo.lock; doCheck=false). flake.lock pins nixpkgs nixos-26.05 /
rust-overlay / flake-utils. .envrc (use flake) for direnv parity.
Single source of toolchain for dev and the upcoming CI, so they can't
drift. Verified through the flake: nix build yields a working binary,
clippy clean, 2424 tests pass / 0 fail / 1 intentional ignored doctest.
First step toward requirements.md TT5 + D1/D2/D3.
The focused sidebar panel border (ADR-0046 DC3) was bright `fg` plus
`Modifier::BOLD`. Bold box-drawing glyphs render as broken/gapped
line-art in the asciinema cast player and are fragile in some terminals.
`panel_border_style` now marks focus with a non-bold accent colour
(`theme.mode_simple`, blue); the unfocused border stays muted. Bold is
untouched on text spans (titles, key hints) — the constraint is
specifically that box-drawing borders carry no bold attribute.
Pure style change: the Tier-2 snapshots are text-only so none needed
re-accepting; the Tier-1 assertion was updated and a render-level test
now checks the rendered border cells carry the accent and no bold.
ADR-0046 Amendment 1.
`decimal` is stored as exact TEXT, but SQLite has no native decimal type,
so arithmetic/aggregation implicitly coerces it to an IEEE-754 double.
The computed result carries no playground type, so `sum(price * qty)`
rendered the double's full noise — `298.59999999999997` for `298.60` — a
confusing, off-topic float lesson for a teaching tool.
Add `format_real_display`: round REAL values to 15 significant figures
(a double's reliable precision) then take the shortest round-tripping
form, collapsing `298.59999999999997` to `298.6`. Wired into `format_cell`
(result-set / `show data` cells) only — the sole surface where the noise
appears, since it arises from arithmetic.
Every other f64->string path keeps full precision for semantic, not
cosmetic, reasons: CSV persistence stays byte-exact for round-trip;
`render_value` is a canonical identity key for the uniqueness dry-runs
(dry_run_unique, check_uniqueness_collisions), where rounding would
report collisions the exact-valued engine wouldn't; FK-key matching and
EXPLAIN-SQL literals likewise stay exact.
ADR-0005 Amendment 1; +7 tests.
A bare table alias typed where a column is expected — `… GROUP BY o`,
with `o` aliasing `FROM Orders o` — was a blind spot: completion offered
nothing for `o`, and the hint panel called the in-scope alias an unknown
column (`no such column o on table Orders, ...`).
Completion now offers each FROM source's qualifier (alias-if-present-else
table-name) at a bare sql_expr_ident slot, folded into the column
candidate list; on an exact-qualifier partial the alias source steps
aside so the diagnostic can surface. The bare-reference diagnostic arm
emits a targeted `alias_used_as_column` / `table_used_as_column` hint
("`o` is a table alias — write `o.<column>` ...") after the
projection-alias check, so ORDER-BY alias refs still win and a genuine
unknown column still reports `unknown_column`.
Two guards keep the qualified-form advice correct: SQL only (role
`sql_expr_ident`, so the DSL `expr_column` path keeps `unknown_column`
since the DSL has no `table.column` syntax) and effective-qualifier
match (alias-if-present-else-table, so an aliased source referenced by
its shadowed real name falls through rather than being advised as
`name.<column>`). The diagnostic is a drop-in replacement for
`unknown_column` at the same span/Error severity, so verdict/overlay/hint
paths are unchanged.
ADR-0032 Amendment 3; +10 tests.
Captures this session's three committed fixes (flag-completion dash,
table-alias validity, `:` one-shot live feedback), the two
investigated-as-designed findings (comma-FROM, aggregate-without-GROUP
BY), and points the next session at the filed enhancement issues
(#26 seed-count hint, #27 bottom status line, #28 add-column
relationship prose) plus the standing roadmap.
The `:` one-shot escape (ADR-0003) is stripped at submission, but the
*live* feedback kept the leading `:` in the buffer it handed the
walker — so Tab completion, the validity verdict, and the highlight
overlays all bailed at the `:` and treated the SQL as an unknown
command. Effect: in `:`-mode, Tab completed nothing and a valid query
could flash an error, while the identical line in full `mode advanced`
worked. (The ambient hint already stripped it, which is why the hint
showed the right column name while Tab did nothing.)
Add `App::feedback_view()` — the `:`-stripped SQL view, the cursor
mapped into it, and the stripped byte offset — and route all four live
paths through it:
- completion (Tab): complete against the view, then shift the returned
`replaced_range` back by the offset so the edit lands in the buffer;
- validity verdict: verdict the SQL, not the sigil;
- highlight/overlays: new `render_input_runs_feedback` highlights and
diagnoses the view (shifted by the offset) while the `:` renders as
plain text;
- ambient hint: consolidated onto `feedback_view`, replacing the
duplicate local `strip_one_shot_prefix`.
In a SELECT, the projection can reference a table alias whose defining
FROM binding sits textually *after* the cursor — e.g.
`select sum(ol.count*p.price) … from … OrderLines ol …`. The candidate
engine already recovers that scope via the §10.6 full-input lookahead
(ADR-0032), but the typing-time validity indicator
(`invalid_ident_at_cursor`) walked only the text before the cursor,
found `ol` in no scope, and flagged it as an unknown column — a red
"ERR" overlay on an otherwise-valid query. (Other aliases escaped only
by coincidentally prefix-matching real columns.)
Give the validity check the same full-input lookahead: at a SQL
expression slot, recover the from-scope from the whole input and bail
when the partial prefix-matches a binding's alias or table name.
The partial-token walk stopped at `-`, so after typing `-` (or `--`)
the partial was empty and the replaced range was a zero-width point
*after* the dash. Two bugs followed at a flag position (e.g.
`add 1:n relationship … -`): the `on` keyword was offered (it
prefix-matched the empty partial), and accepting a candidate inserted
after the dash — `-on`, `---create-fk`, `----all-rows`.
Detect a dash-prefixed token at a word boundary as a flag-in-progress
and fold the whole dash-run into the partial, gated on a flag actually
being expected there (so `where x = -5` stays a signed number, not a
flag). The flag matcher now strips leading dashes and matches the body
uniformly (empty / `-` / `--` → all flags; `--cr` → create-fk).
Keywords like `on` no longer appear after a dash, and accept replaces
the dash(es) so `-` → `--create-fk` and `--all` → `--all-rows`. Two
partial-flag snapshots updated (they had captured the old behaviour).
Build the two SD2 surfaces Phase 1 deferred:
- `set` override clause (D2): comma-separated per-column pins —
`= 'v'` (fixed), `in ('a','b')` (pick-list), `as <generator>`
(named), `between x and y` (range; numeric and quoted dates).
Type-aware via the typed `current_column_value` slot; an override
drops its column from the generic-fill advisory (D13). Folded from
the flat matched path (build_seed_overrides) and applied to the
per-column plan (apply_seed_overrides).
- `<table>.<column>` column-fill (D1 form 2): an UPDATE over existing
rows. Refuses PK/autogen targets, empty-table no-op, FK-samples the
parent, collision-free for UNIQUE/identifier targets, one undo step;
`set` may only adjust the filled column.
Supporting work: KNOWN_GENERATORS vocabulary + generator_for_name
(src/seed/vocabulary.rs, D9); a range Generator + range_bounds_reason;
IdentSource::Generators and HighlightClass::Function; completion of the
generator vocabulary after `as` and the set/.col column slots; the
typing-time validity indicator for an unknown generator; help,
parse-error pedagogy rows, and the D13 advisory's Phase-2/3 wording.
A bounded override (fixed value / too-short pick-list) on a
single-column-UNIQUE target is a friendly error rather than a silent
uniqueness cap (post-implementation /runda finding, user-chosen).
Dates in the range form are quoted (no date-literal token exists);
ADR-0048 D2 amended accordingly. Both modes (D5); reproducible (D4).
- ADR-0048 status -> Accepted; Phase 1 implemented (commits
202e25a..fbd219b), with the pre-build and post-implementation /runda
passes and the 2358-test green state recorded; index entry updated.
- requirements.md: SD1 [x] (whole-row seed + FK/junction, both modes,
--seed reproducibility with no exceptions), SD2 [/] (core generators /
determinism done; the set override clause + column-fill are Phase 2),
A1 14/15 (only hint/H2 remains unregistered).
- Handoff 65: the full seed Phase-1 build, the two /runda passes, where
the code lives, and Phase-2 / next steps.
P1.4 — user-visible surface:
- Grammar: `seed <table> [count] [--seed <n>]` (the first DSL flag with a
value); build_seed disambiguates the seed value from the positional count.
- Verified the auto-wired surface: table-name completion, --seed offered as
a candidate, validity consistent with `show data`, an ADR-0042 near-miss
row for bare `seed`, and render tests for the seed outcome.
/runda hardening — eight DA findings, all resolved:
- FK sampling now uses ORDER BY so --seed reproducibility no longer relies
on SQLite's unspecified DISTINCT order (D4).
- shortid columns now generate from seed's seeded RNG (new
shortid::generate_with_rng) — D4 now holds with no exceptions.
- Added the missing coverage the DA flagged: undo-one-step (D15), replay
re-runs a seed line (D16), advanced-mode (D5), atomic rollback on a
constraint failure, seed 0 no-op, complex-CHECK advisory (D17), and
FK + shortid reproducibility.
2358 pass / 0 fail / 0 skip, clippy all-targets clean.
do_seed inserted row-by-row through do_insert, re-writing the whole
table CSV each time — O(N^2). Extract do_insert's row core into a new
insert_one_row (bind + serial/shortid autofill + FK-enriched execute,
no tx/persist), shared by:
- do_insert: one row in its own transaction (behaviour unchanged).
- do_seed: all rows in ONE transaction, with a single
finalize_persistence before the single commit — O(N), preserving
ADR-0015 §6 commit-db-last. A mid-batch failure now rolls the whole
seed back atomically; the capped preview is read back by rowid.
A near-max 10000-row seed drops from ~tens of seconds to well under
one. do_insert behaviour unchanged (whole suite green: 2346 pass /
0 fail / 0 skip, clippy clean); seed's existing tests exercise the
batch path.
A dedicated SeedResult replaces the borrowed insert outcome (X5):
- CommandOutcome::Seed + DslSeedSucceeded event + handle_dsl_seed_success
render: the echo, "N row(s) seeded into T", a capped preview table
(D18, first 20 rows; full count always reported), and a Hint-styled
advisory naming enum-ish / un-derivable-CHECK columns filled with
generic text (D12/D13, Phase-1 wording).
- SeedResult carries requested vs produced, so a junction cap is now
reported to the user, not only logged.
- Count cap (D6): a seed over 10000 rows is refused with a friendly error.
- Catalog keys ok.rows_seeded / seed.capped / seed.advisory_generic.
4 new tests (advisory flag, IN-check not flagged, preview cap, excess
count). 2346 pass / 0 fail / 0 skip, clippy clean.