- docs/handoff/20260622-handoff-77.md — session handoff for the #39 schema-cache-gate fix; records the four open issues (incl. the new #40). - CLAUDE.md — add the changelog-discipline rule: update [Unreleased] in the same change as any user-facing behaviour change (scripted/pasted paths count), under the two copy rules; release-time sweep of commits/handoffs as the backstop. - CHANGELOG.md — [Unreleased] → Fixed bullet for the #39 paste/scripted- input fix.
7.5 KiB
Session handoff — 2026-06-22 (77)
Continues from handoff-76. Per the user's direction (handoff-76 §7: clear the open Gitea issues before resuming feature work), this session took the bug of the four open issues — #39 (Form-B insert misparse against a stale schema cache after fast DDL) — and fixed it end-to-end. Three open issues remain (#36/#37/#38, all enhancements); see §3.
§1. State
Branch main. Commits this session (on main, not pushed — push is the
user's step):
07575dafix(app)— schema-refresh submission gate (#39) + Tier-1 and Tier-4 PTY regression tests + ADR-0022 Amendment 8 + README index.- this
docs(handoff-77)commit — folds in theCHANGELOG.md[Unreleased]Fixed bullet for #39 and a new changelog-discipline rule in projectCLAUDE.md(see §4).
Test baseline: 2521 passed / 0 failed / 1 ignored (was 2519; +1 lib test,
+1 e2e_pty test → e2e_pty now 7 on Linux). clippy --all-targets -D warnings +
fmt --check clean. The new PTY test was confirmed to time out without the
fix (genuine guard), green with it.
Issue #39 is closed (resolution comment + diagnosis recorded on the issue).
§2. What shipped — issue #39
Root cause. App::update is pure-sync and validates submissions against
App::schema_cache. The runtime refreshes that cache asynchronously after
the worker applies a command — a SchemaCacheRefreshed event posted on the
same FIFO channel as key events (runtime.rs:1591, applied app.rs
SchemaCacheRefreshed arm). Under faster-than-human input (paste / script /
unpaced PTY) the next Enter is processed before the refresh lands, so a
simple-mode Form-B insert (insert into T values (…), columns derived from
the cache) submitted right after add column sees the pre-DDL columns, its
arity can't match, and the friendly layer tags it "trying to write SQL?". The
worker itself is never wrong (it runs commands serially); the bug was purely in
client-side pre-validation racing the refresh.
Fix — submission gate (src/app.rs). Two new private App fields:
awaiting_schema_refresh: bool + held_submissions: VecDeque<(String, EffectiveMode)>. dispatch_dsl arms the flag on every ExecuteDsl
dispatch; while armed, a new DSL submission is held (queued in submission
order) rather than validated. The SchemaCacheRefreshed handler clears the flag
and drains the queue against the now-fresh cache, stopping as soon as a
drained command re-arms the gate (the rest then wait for its refresh — order
preserved). App-lifecycle commands route through dispatch_app_command before
dispatch_dsl, so quit/help/load/undo/rebuild are never held.
Why arm on every dispatch, not just DDL — it keeps at most one DSL command
in flight, so refreshes are strictly one-per-dispatch and in order, making the
boolean provably correct. Arming only on DDL would let a preceding non-DDL
command's refresh clear the gate early and drain a held insert against a
pre-DDL cache (a real corner case). Cost was one existing test
(walking_skeleton::colon_escape_in_simple_mode_is_one_shot) updated to model
the post-dispatch refresh — faithful, since it had been assuming it.
Scope = interactive only. The replay / history-log / startup
rebuild-from-text batch path already re-snapshots the schema synchronously,
inline, before every line (run_replay → build_schema_cache,
runtime.rs:2514), so it always had this ordering guarantee; the fix brings the
interactive path in line with it. No interactive-user impact (the gate clears in
ms); held input is never lost because the runtime sends a SchemaCacheRefreshed
after every dispatch, success or failure (the unconditional post-match block
in spawn_dsl_dispatch).
Tests (both verified red→green):
app::tests::form_b_insert_after_ddl_is_held_until_refresh_then_dispatched— Tier-1, deterministic: stale cache → submit DDL (arms) → submit insert (held, no error) → deliver freshSchemaCacheRefreshed→ insert dispatches.e2e_pty::back_to_back_insert_after_ddl_still_succeeds— Tier-4 PTY, the unpaced inverse of flow 3; asserts the insert's own('Alice') ✓echo.
Docs. ADR-0022 Amendment 8 records the gate (the §9 schema-cache refresh-vs-validation timing contract); README index updated in the same edit (ADR-0000 rule).
§3. Open / follow-ups
Per the user's direction the open issues come before feature work. After closing #39, four issues are open. The hint/help trio (#36/#37/#38) are all enhancements on the pedagogy mission (read handoff-76 §7's table for fuller scope; read ADR-0053 first — it orients #37/#38). #40 is a CI/packaging follow-up filed this session.
| # | One-line | read |
|---|---|---|
| #36 | help <sql-form> shows no distinct content — the 7 advanced-mode SQL nodes share help_id: None (src/dsl/grammar/mod.rs:915-918) |
Contained. Good next pick; touches H3/help, maybe an ADR amendment. |
| #37 | Clause-concept hints (cursor inside on delete …, with pk, 1:n/m:n, create-table constraint slots) — deferred ADR-0053 extension |
Medium scope, richest teaching value. Likely an ADR-0053 amendment. |
| #38 | Pre-submit-diagnostic F1 route + ~33 diagnostic.* tier-3 blocks; needs a class/message_key threaded through every diagnostic site |
Broad mechanism, most marginal value. Get the user's do/defer/close call before investing. |
| #40 | Wire CHANGELOG.md into the winget release notes (komac --release-notes-url); ADR-0056 area |
Filed this session (the changelog→winget thread, see §4). Small, deferred-by-decision originally. |
Suggested order (confirm with the user): #36 (contained) → #37 (highest on-mission value) → #38 (decision first: do / defer / close). #40 is independent (release pipeline) and can slot in whenever. #38 in particular should be escalated for a do-or-close call rather than silently built.
§4. Changelog discipline — new rule + open winget thread
The session surfaced that no rule existed for keeping CHANGELOG.md current
(it was created in handoff-76's plan but never given a maintenance process), so
the #39 fix wasn't logged until the user asked. Decided with the user:
- New
CLAUDE.mdrule (this commit): update[Unreleased]in the same change that alters user-facing behaviour (incl. scripted/pasted/power-user paths, not just the interactive happy path), under the two copy rules; no entry for refactor/test/CI-only changes; at release time rename[Unreleased]and sweep commits/handoffs since the last tag as a backstop. - #39 entry added under
[Unreleased] → Fixed. - winget release notes → issue #40. komac supports
--release-notes-url/--release-notes; thewingetjob (publish.yaml≈ L276) passes neither. Tracked, not done (it's a release-pipeline / ADR-0056 change, kept out of this bug-fix session by the user's call).
§5. Process pins
- Commit user-confirmed, no AI attribution, append-only, on
main; push is the user's step (this commit is unpushed). - Test-first honored: both regression tests were confirmed RED before the fix
(the PTY one via a
git stashofsrc/app.rs), GREEN after. A written Devil's-Advocate pass on the implementation surfaced one comment imprecision (fixed) and no behavioral findings. cargo sweepnot run this session; the build grew modestly (one extra PTY test binary). Consider a sweep at the next milestone.