feat(#630): migrate query execution/export off generic client mechanics, delete transport seam (phase 7) - #652
Conversation
Phase 7 checkpoint 1 (plan sections 5, 23): compose authenticatedRequest with the package's ensureClickHouseSuccess classifier so a caller that must own its own byte-stream consumption (raw export, later checkpoints) can get back the exact successful native Response, untouched, with a non-2xx status raised as the package's ClickHouseError. authenticatedRequest remains sole owner of token/epoch/refresh/lifecycle; this adds exactly one classification after settlement, no retry, no second fetch. Adds unit coverage for Response identity, unread body, non-2xx -> ClickHouseError, abort/network TypeError identity propagation, and unchanged one-refresh-then-classify bounds. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwFPT465eDJqYcRa8HGNLz
…t expectedEpoch The public interface declared captureCancellationLease() as parameterless even though the implementation already accepted an optional expectedEpoch with the epoch fence. Widen the declared signature to captureCancellationLease(expectedEpoch?: number) so owner-scoped cancellation callers (p7-03) can pass an explicit owner epoch without a cast; existing zero-arg callers remain valid since the internal default is unchanged. Add dedicated unit tests per plan §9.1/§23: no-arg capture at the current epoch, an explicit matching expected epoch, a mismatching replacement epoch returning null, and a same-epoch refreshed-credential capture at cancel time. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwFPT465eDJqYcRa8HGNLz
…runQuery/exportQuery/killQuery (Phase 7 checkpoints 2A+2B)
QueryExecutionService (src/application/query-execution-service.ts) now
owns SQL Browser's Table/KPI/TSV/explicit-format wire mapping and the
ordinary positive row-cap policy directly, injected only three narrow
authenticated primitives (runProgress/runText/cancel) instead of
{runQuery, killQuery, ctx}. Format/settings mapping matches the retired
net/ch-client.ts runQuery exactly, including applying a positive rowLimit
cap uniformly across all four format branches (plan §2.5) and keeping the
script over-fetch cap in params, spread after stmt.params so it always
wins a collision, never duplicated into settings (plan §2.3/§8).
ExportService (src/application/export-service.ts) now uses
exportResponse/runEffectText (mirroring authenticatedResponse/
authenticatedText) instead of exportQuery/runQuery, deleting its own
resp.ok/resp.text() classification path — package HTTP success
classification happens once via authenticatedResponse, and the
successful Response stays unread until streamToFile's own
body.getReader(). Both explicit cancel paths (grid Cancel button and
Export's own Cancel) now go through a single owner-scoped
cancelOwnedQuery(ownerEpoch, queryId) callback in app.ts, which fences a
replacement (non-owner) authenticated-execution-scope epoch via
conn.captureCancellationLease before reaching the frozen kill — local
abort always happens before the best-effort remote KILL QUERY. Owner
epoch is captured once at operation registration/start (workbench
ActiveRun.ownerEpoch; ExportService's exportOwnerEpoch/
exportScriptOwnerEpoch), never re-read at cancel time.
app.ts wires QES/ExportService's authenticated primitives directly over
authenticatedProgress/authenticatedText/authenticatedResponse
(net/authenticated-clickhouse-request.ts) against the live chCtx, and
carries the documented killWithLease bridge cast so ch.killQueryWithLease
keeps compiling with its still-required 3rd sqlString argument until a
later sub-task drops it.
Deviations from the declared file scope, both required to keep the
shared `npm run check:types` gate green and explained here rather than
silently expanded: tests/spike/clickhouse-client/parity.test.ts and
live-sessions.test.ts needed a small compile-compat adapter
(runTextViaShim) bridging their pre-Phase-7 (ctx, sql, RunQueryOptions)
shims to QueryExecutionDeps's new shape — NOT the real Checkpoint 2C
spike retarget (plan §19), which is a later sub-task's job.
runQuery/exportQuery/ordinary killQuery still exist in net/ch-client.ts
(their own tests keep them covered) — only production QES/ExportService/
app.ts consumers stop using them, per plan §21/Checkpoint 2D (deletion)
being a later sub-task.
Full local gate green: check:types, check:arch, check:schemas,
check:examples, npm test (225 files / 7386 tests, 100/100/97.11/100
coverage, no per-file floor violations), npm run build.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwFPT465eDJqYcRa8HGNLz
…y/killQuery (Phase 7 Checkpoint 2C spike portion) current-adapter.ts now drives authenticatedResponse/authenticatedProgress/ authenticatedText (authenticated-clickhouse-request.ts) plus the package's stateless createClickHouseHttpClient(...).killQuery(...), mirroring the same Table/KPI/TSV/explicit-format mapping QueryExecutionService now owns, instead of ch-client.ts's retiring runQuery/exportQuery/mutable-context killQuery and its ChCtx type. official-adapter.ts's makeOfficialRunQueryShim (which satisfied the retiring RunQueryOptions/RunQueryResult shape) is replaced by makeOfficialQueryExecutionAdapter, a spike adapter satisfying QueryExecutionDeps['runProgress' | 'runText'] directly. parity.test.ts and live-sessions.test.ts drop their pre-Phase-7 runTextViaShim compile-compat bridges and RunQueryOptions/RunQueryResult/ ChCtx imports, wiring the new adapters' runProgress/runText straight into QueryExecutionService; candidate-entry.ts's tree-shaking retention list follows the rename. No spike .ts file (excluding run-matrix.mjs/run-matrix.test.ts, owned by a separate sub-task) imports/type-references runQuery, exportQuery, RunQueryOptions, or RunQueryResult anymore. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwFPT465eDJqYcRa8HGNLz
…sport adapter tests/e2e/clickhouse-http-transport.html no longer imports the retiring src/net/clickhouse-http-transport.ts compatibility adapter. Generic request scenarios (1-8, invalid-UTF-8) now drive the package's own createClickHouseHttpClient(...).request() directly through the existing makeClient helper (previously only used by Scenario 9); auth/lifecycle scenarios keep driving authenticatedRequest()/authenticatedProgress() unchanged. Every original behavioral assertion is preserved; Scenario 9 remains query-progress coverage, not export coverage. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwFPT465eDJqYcRa8HGNLz
…c run/export/kill + transport seam
Phase 7 subtractive cutover (plan §10/§11/§14/§15/§16/§21/§2.1/§2.6,
Checkpoints 2C-kill + 2D):
- src/net/ch-client.ts: killQueryWithLease now builds a one-shot
createClickHouseHttpClient({fetch, origin}) and calls its stateless
killQuery({queryId, authorization}) — drops the sqlString parameter
entirely (the package owns KILL QUERY quoting now). Deletes the generic,
format-agnostic runQuery/RunQueryOptions/RunQueryResult, exportQuery/
ExportQueryOptions, and the ordinary mutable-context killQuery outright
(no forwarding wrapper) — their policy already moved to
QueryExecutionService/ExportService in the prior checkpoint. Drops the
now-dead createHttpTransport import and the ClickHouseTransport/
TransportDeps/TransportRequest re-export.
- Deletes src/net/clickhouse-http-transport.ts, src/net/clickhouse-
transport.types.ts, and tests/unit/clickhouse-http-transport.test.ts —
the package is now the sole generic ClickHouse HTTP transport
implementation.
- tests/unit/clickhouse-transport-contract.ts retypes against the
package's public ClickHouseHttpClientDeps/ClickHouseHttpRequest with a
test-local RequestSender façade, instead of the deleted local transport
contract; every existing behavioral case (Response identity, one fetch,
exact SQL/Authorization, raw invalid UTF-8, live origin/fetch, abort,
untouched body, settings/params serialization) is unchanged.
- tests/unit/clickhouse-http-package-policy.test.js's Phase 3 former-owner
loop is now deletion-aware: explicit absence assertions for the two
retired files plus a real read+clean-scan of the surviving
src/core/stream.ts, replacing the unconditional readFileSync loop that
would ENOENT. PHASE3_LEGACY_OWNER_FILES stays pinned to its historical
three-path registry unchanged, and every sabotage probe for the retired
filenames is retained.
- tests/unit/ch-client.test.ts drops the runQuery/killQuery/exportQuery
describe blocks (coverage moved to the owning services) and retests
killQueryWithLease per §23: exact frozen queryId/auth/fetch/origin,
package-owned quoting (embedded-quote proof), no token lookup/refresh/
retry, and a non-2xx ClickHouseError swallowed the same as a network
failure.
- tests/spike/clickhouse-client/parity.test.ts: minimal compile-compat fix
for its three direct killQueryWithLease(...) calls, which passed an
inline sqlStringFn as a 3rd argument against the real (uncast) function
— dropped, since check:types covers this spike tree too and the real
signature is now 2-arg.
npm run test:client-spike is expected red after this change (run-matrix.mjs's
CH_CLIENT_CLASSIFICATION table goes stale for the deleted symbols) — that is
p7-07's job, not this checkpoint's; everything in this checkpoint's own gate
is green.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwFPT465eDJqYcRa8HGNLz
…st-cutover tree Phase 7 deleted ch-client.ts's isCurrentEpoch/staleEpochAbort/transportFor/ authedFetch/killQuery/exportQuery/runQuery and the whole local transport seam (src/net/clickhouse-http-transport.ts, clickhouse-transport.types.ts) outright, but the spike deletion-estimate tooling still classified those retired symbols, still read the deleted transport file, and still summed its bucket into the headline LOC figure. Removed the seven stale classification entries (CH_CLIENT_CLASSIFICATION now matches ch-client.ts's real top-level symbols exactly, in both directions), removed HTTP_TRANSPORT_CLASSIFICATION and its disk read/manifest entry, and adjusted computeDeletionEstimate()'s formula to ch-client.ts's own bucket alone. Also reconciled OFFICIAL_ADAPTER_TEST_ONLY_SYMBOLS with official-adapter.ts's real symbols (makeOfficialRunQueryShim -> makeOfficialQueryExecutionAdapter), which the prior Checkpoint 2C spike-portion commit had left unclassified. run-matrix.test.ts's assertions follow the new file/manifest shape, plus a real-tree regression proving computeDeletionEstimate() works with src/net/clickhouse-http-transport.ts genuinely absent. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwFPT465eDJqYcRa8HGNLz
…app.ts killQueryWithLease was rewritten in 43bb02e to take only (lease, queryId) — the package now owns KILL QUERY's SQL and quoting — but the two composition- root call sites (cancelOwnedQuery, resumeAuthenticatedExecution's cancelRemote) still carried the pre-cutover "CRITICAL bridge" cast/comment from dbe6de1 and an unused, factually-wrong 3rd sqlString argument. JS silently ignored the extra arg and the cast made tsc pass, but it defeated real type-checking of both call sites and misled readers about the current signature. Call ch.killQueryWithLease(lease, queryId) directly; drop the now-unused AuthenticatedCancellationLease import. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwFPT465eDJqYcRa8HGNLz
…ry and transport-file resurrection Plan §22/the invariant map (§26) require check:arch to fail if src/net/clickhouse-http-transport.ts or clickhouse-transport.types.ts reappear, and to ban top-level resurrection of the retired runQuery/RunQueryOptions/RunQueryResult/exportQuery/ExportQueryOptions/ ordinary killQuery — with an explicit carve-out for the package's own client.killQuery(...) member call inside frozen-lease cancellation. Neither was implemented on this branch. Extends the existing real-TypeScript-parser mechanism (build/lib/check-legacy-owners.mjs) rather than a hand-rolled scanner, per CLAUDE.md/#630's established convention: - PHASE7_DELETED_TRANSPORT_FILES + a path-existence check in check-boundaries.mjs, mirroring PHASE5_DELETED_ROOT_FILES. - PHASE7_RETIRED_TOP_LEVEL_NAMES + findRetiredTopLevelApiViolations(), a NEW declaration-scoped AST check (inspects only sourceFile.statements — never a blanket identifier walk like findNamedIdentifierViolations) so a PropertyAccessExpression such as client.killQuery(...) structurally can never trip it; no name-based exception needed. - Mirrored sabotage/drift-bind tests in tests/unit/clickhouse-http-package-policy.test.js. Verified for real against the working tree (not just unit probes): recreated both transport files, appended top-level runQuery/exportQuery/killQuery declarations to ch-client.ts, and added a direct application-layer package transport import to export-service.ts — each made `npm run check:arch` fail for the stated reason; each was restored to exact original bytes and check:arch went green again. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwFPT465eDJqYcRa8HGNLz
Plan §18/Checkpoint 3 and A15's Definition of Done require a dedicated
export-shaped Chromium+WebKit e2e fixture proving native post-header
cancellation semantics survive through the actual export path (headers
settle, first chunk arrives past the 32 KiB hold-back, the next read is
held, abort mid-read, assert progress/write/writer-cleanup/.partial/
owner-epoch remote-cancel/no-offline/no-refresh/no .text() dependency).
This did not exist: clickhouse-http-transport.{html,spec.js} only ever
covers query/progress (Scenarios 1-9), and ac52df2 explicitly did not add
export coverage when it retargeted that harness off the deleted local
transport adapter.
Adds:
- tests/spike/clickhouse-client/fault-server.mjs: a new
'export-post-header-abort-hold' fixture (raw TSV, first chunk > the
ExportService HOLDBACK constant, then a held second chunk), mirroring
'post-header-abort-hold' but export-shaped.
- tests/e2e/export-post-header-cancel.html: drives the real
createExportService/authenticatedResponse/createAuthenticatedExecutionScope/
the package's own client.killQuery(...)/window.fetch/AbortController;
only the File System Access API is faked (in-memory FileHandleLike), per
the plan's own sanctioned carve-out for automating both engines. A
successful Response's .text() is instance-shadowed to throw, proving the
raw byte-stream path never depends on it.
- tests/e2e/export-post-header-cancel.spec.js: starts the shared fault
server in CORS mode and asserts the full invariant list above, including
a real KILL QUERY network request bearing the correct owner epoch/query id.
Verified green on both required engines:
npx playwright test tests/e2e/export-post-header-cancel.spec.js --project=chromium --project=webkit
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwFPT465eDJqYcRa8HGNLz
The first version scheduled cancelExport() from a fixed 150ms wall-clock delay after the first progress event, then asserted an exact progress-event count and a byte threshold derived from the wrong side of the hold-back buffer. Under real parallel Chromium+WebKit load this occasionally let the whole 3000ms server-side hold elapse before the delayed timer fired, racing the export to normal EOF completion instead of cancelling it (observed flake: movedToPartial null since retainPartial's move() never fired), and WebKit was separately observed splitting the initial burst into more than one native read/progress pair, breaking the exact-count assertions. Fixes: - Schedule the cancel synchronously from inside the FIRST update() callback (same deterministic, load-independent technique clickhouse-http-transport.spec.js's own Scenario 6/7/9 already use) instead of a wall-clock wait. - Relax the pre-cancel assertions to ">=1 progress/write pair" and "committed bytes > 0" (the amount actually written is bytes-received minus the 32 KiB still held back, not itself > 32 KiB) rather than assuming exactly one native read delivers the whole first chunk. Verified with 20 consecutive chromium+webkit runs, all green (previously flaky within single-digit repeats). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwFPT465eDJqYcRa8HGNLz
…-execution/export migration Adds the Phase 7 narrative (docs/ARCHITECTURE.md new section, CHANGELOG.md [Unreleased] entry, .wiki/Decisions-and-Roadmap.md phase paragraph) and fixes every now-stale present-tense claim describing the deleted generic runQuery/exportQuery/ordinary killQuery and the deleted local transport seam (src/net/clickhouse-http-transport.ts / clickhouse-transport.types.ts) as if they were still the current production path, across docs/ARCHITECTURE.md, .wiki/Architecture.md, and .wiki/Source-Map.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwFPT465eDJqYcRa8HGNLz
ChatGPT review pass 2Reviewed head: P2 — the new export cancellation e2e does not actually cancel an already-pending
|
Defer the export post-header-cancel fixture's cancelExport() call with queueMicrotask() instead of calling it synchronously from inside the first onProgress callback. streamToFile's for(;;) loop only issues its NEXT reader.read() after onProgress() returns, so a synchronous cancel fired before that read existed, leaving the fixture's "pending second reader.read()" claim (and its inline comments) unproven. One deferred microtask lands after the loop's synchronous continuation has issued the next read, making the mid-read-abort genuinely true while staying deterministic under parallel Chromium+WebKit load like the prior synchronous version. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwFPT465eDJqYcRa8HGNLz
ChatGPT review pass 3Previously reviewed head: Reviewed head: The new head is exactly one commit ahead of the pass-2 head, and that commit changes only P2 —
|
ChatGPT review — final certification (manual continuation, same conversation)This session's own Pass 3 initially re-raised the pass-2 finding about the
Reviewed head: |
Summary
Phase 7 of #630 ("Epic: extract the Fetch-native ClickHouse client"). Migrates SQL
Browser's query execution and export off the generic
runQuery()/exportQuery()/ordinary (mutable-context)
killQuery()functions and onto the authenticated requestlayer established in Phase 6, then deletes the now-superseded generic functions and
the legacy transport seam (
src/net/clickhouse-http-transport.ts,src/net/clickhouse-transport.types.ts) outright — no forwarding alias, no secondimplementation left anywhere.
QueryExecutionService/ExportServicekeep all SQL Browser policy (format mapping,row-limit caps — now uniformly across Table/KPI/TSV/explicit-raw with regression
coverage that didn't exist before — retry classification, script stop-on-first-failure,
owner-scoped cancellation) while consuming the authenticated seam directly.
ConnectionSession.captureCancellationLeasewidened to accept anexpectedEpochparameter (internal semantics unchanged — verified byte-identical against
origin/mainby two independent reviewers).
killQueryWithLeasewas rewritten onto the package'sown stateless
client.killQuery(...)— confirmed it still never touches mutableChCtx/credential/refresh/lifecycle state, the same invariant established in Phase 6.Claims A14 (QueryExecutionService owns logical policy, not generic HTTP/stream
mechanics), A15 (ExportService streams native bytes via package classification/
late-exception framing), A16 (superseded generic mechanics deleted, not retained as
a second implementation). A1-A13 already shipped; A17/A18 remain deferred to Phase 8.
Implementation
Approved plan (High + Large risk classification) implemented via a decompose-and-implement
loop: 7 sub-tasks, sequential (the plan's own
dependsOngraph left no genuinelyindependent sub-tasks for this migrate-then-delete unit). One sub-task died mid-response
from a transient API error and was cleanly re-run from the same definition. Plus 4 more
commits from the mandatory High-risk + security pre-PR review pass (below) and one
documentation-reconciliation commit — 12 commits total.
Pre-PR review (mandatory for this High-risk, 7-sub-task decomposed unit)
Two independent read-only reviews (a High-risk pass, and a security-focused pass
standing in for this repo's
security-reviewskill, which isn't installed in thisenvironment) both converged on the same findings — no security vulnerability found in
either. All were fixed and independently re-verified before this PR opened:
own Checkpoint 3/A15 requirement. Added
tests/e2e/export-post-header-cancel.{html,spec.js}driving the real
ExportService/authenticatedResponse/packagekillQueryagainst areal cross-origin fault server, proving header settlement, a 32 KiB-holdback commit, a
deterministic mid-read cancel, writer cleanup +
.partialsemantics, a correctowner-epoch remote KILL, and no offline/refresh misclassification. Green on both
Chromium and WebKit, verified 20+ consecutive runs for flake.
check:archresurrection guards — the plan's own §22/A16requirement. Extended
build/lib/check-legacy-owners.mjs(the existingreal-TypeScript-parser mechanism from Phases 3/5/6 — no new hand-rolled scanner) with
a declaration-scoped AST check banning top-level resurrection of
runQuery/exportQuery/ordinarykillQuery/the retired types, plus path-existence guards forthe two deleted transport files. Verified live: reintroducing any of these makes
check:archfail for the stated reason; the package's ownclient.killQuery(...)call inside the frozen-lease path is correctly never flagged (it's a property access,
not a top-level declaration).
src/ui/app.ts— a "CRITICAL bridge" castasserting
killQueryWithLeasestill needed a 3rd argument, left over from anintermediate checkpoint and never cleaned up when the real cutover commit rewrote the
signature to 2 params. Removed; both call sites now call the real 2-arg signature
directly.
docs/ARCHITECTURE.mdgained a newPhase 7 section and every stale present-tense reference to the deleted
runQuery/exportQuery/transport seam elsewhere in the file was corrected;CHANGELOG.md[Unreleased]Phase 7 entry added;.wiki/{Architecture,Source-Map, Decisions-and-Roadmap}.mdupdated to match.Gate
Part of #630.
Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01LwFPT465eDJqYcRa8HGNLz