Skip to content

feat(#630): create @altinity/clickhouse-http, move low-level request/URL (phase 2) - #641

Merged
BorisTyshkevich merged 4 commits into
mainfrom
feat/630-p2-clickhouse-http-package
Aug 7, 2026
Merged

feat(#630): create @altinity/clickhouse-http, move low-level request/URL (phase 2)#641
BorisTyshkevich merged 4 commits into
mainfrom
feat/630-p2-clickhouse-http-package

Conversation

@BorisTyshkevich

Copy link
Copy Markdown
Collaborator

What & why

Part of #630 — replacing the rejected @clickhouse/client-web adoption path (#585) with
a first-party, Fetch-native @altinity/clickhouse-http package extracted from generic
protocol code SQL Browser already maintains.

This is Phase 2 of 8. It creates the repository's first npm workspace,
packages/clickhouse-http, and moves chUrl()/URL serialization plus the low-level
Fetch request into it. src/net/clickhouse-http-transport.ts's send() becomes a thin
delegating compatibility adapter; stream reading (streamLines()) and all SQL Browser
auth/epoch/lifecycle/retry policy stay local until later phases.

Claims: A2 (@altinity/clickhouse-http exists in-repo with no SQL Browser source
imports), A4 (SQL Browser consumes the package through its public export), A5 (URL
serialization has exactly one implementation, owned by the package). A6–A18 remain
deferred to later phases.

Approved plan: see the ship-log comment on #630. Plan review took an unusual path this
round — Agent Chrome disconnected mid-loop during the automated ChatGPT-author/Fable
loop; the recovery (reading the completed ChatGPT response directly from the DOM and
invoking Fable directly, bypassing the chatgpt-review skill/Playwright) is documented
in full in the ship-log's Phase 2 handoff. Fable/high approved on the 3rd real review
pass after 3 findings were incorporated (a Docker build break, a zero-runtime-deps
enforcement gap, and an async/sync settlement drift — all three verified fixed by an
independent Fable re-review).

Contract coverage

Invariant Enforcement Proof Sabotage
Package→root-src ban check-boundaries.mjs Rule A policy mirror test client.ts importing ../../../src/net/ch-client.js → caught
Root→package-internals ban check-boundaries.mjs Rule C policy mirror test root file deep-importing package internals → caught
Zero bare specifiers in package source dedicated scan (closes the generic RULES loop's bare-specifier blind spot) policy mirror test url.ts importing @preact/signals-core → caught by check:arch (verified independently)
Bare @altinity/clickhouse-http restricted to src/net/** dedicated scan policy mirror test bare import from src/core/** → caught
Docker workspace reproducibility Dockerfile COPY packages ./packages before npm ci real docker build removing the line → esbuild "Could not resolve @altinity/clickhouse-http" (verified independently, legacy builder — buildx blocked in this sandbox)
Nine Phase-1 transport invariants, direct contract suite registered against package's own request() tests/unit/clickhouse-http-package.test.ts (Phase 1's existing sabotage cases, now proven twice: package + adapter)
Single serializer (A5) one function chUrl( definition, in the package grep across src/**+packages/**
Promise settlement request()/send() both stay async, preserving today's rejected-promise contract for malformed URLs focused test in both files

Tests

  • npm run check:types && npm run check:arch && npm run check:schemas && npm run check:examples && npm test && npm run build — green (100%/97.14%/100%/100% coverage, including the 3 new package files, verified via lcov).
  • npm ci --no-audit --no-fund — green; node_modules/@altinity/clickhouse-http resolves as a real local workspace symlink, not a registry package.
  • npm run test:e2e -- tests/e2e/clickhouse-http-transport.spec.js --project=chromium --project=webkit — 18/18 passed, now through the package-backed compatibility adapter.
  • Full root e2e regression, --project=chromium --project=webkit — 432 passed, 4 skipped (pre-existing, unrelated).
  • Real docker build (legacy non-buildx driver — buildx blocked in this sandbox) — succeeds; independently re-verified it fails exactly as expected when the COPY packages ./packages fix is removed.
  • 8 raw-ESM e2e fixtures reaching the transport gained an import-map entry for the new bare specifier; the full fixture graph was audited twice independently (once via an esbuild-transform-accurate script correcting for import type erasure, once by a separate reviewer) confirming no additional fixture needs one.

Build

npm run build succeeds — single self-contained dist/sql.html, no new runtime
dependency (the workspace package is first-party project code, not a third-party
dependency — CLAUDE.md hard rule 4 clarified accordingly).

Invariant/sabotage verification

Verified three times: once by the implementer, once by the coordinator directly
(diff footprint, npm ci linkage, full gate, lcov coverage, the bare-specifier and
Docker sabotages, e2e), and once by a separate targeted read-only review covering the
remaining invariant-map items (import-ban sabotages, move-not-duplicate test claims,
ch-client.ts composition, streamLines() locality, single-serializer invariant,
direct-package contract registration) — no defects found in any pass.

Checklist

  • npm test passes (the per-file coverage gate is non-negotiable)
  • Tests added/updated in the same change as the code
  • npm run build succeeds (single-file dist/sql.html)
  • Layers kept honest: package boundary enforced by 4 new architecture rules, each with a sabotage-tested mirror
  • No new runtime dependency (first-party workspace package; CLAUDE.md hard rule 4 clarified)
  • CHANGELOG.md ([Unreleased]) updated
  • Reconciled affected tracked work — ship-log comment on Epic: extract the Fetch-native ClickHouse client into a reusable package #630 updated; CLAUDE.md, docs/ARCHITECTURE.md, ADR-0005 addendum, and .wiki pages updated per the plan's Checkpoint 3; the issue's own ## Phases checklist is ticked only after merge is verified on origin/main, per its explicit stated convention

Part of #630.

BorisTyshkevich and others added 4 commits August 7, 2026 15:24
Introduce the repository's first npm workspace, packages/clickhouse-http,
as a statement-free private package with no production imports yet:
root package.json declares the workspace + a 0.0.0 dependency on
@altinity/clickhouse-http, package-lock.json is regenerated via npm
install (never hand-edited), tsconfig.json includes the package source,
the Dockerfile copies packages/ into the build stage before npm ci, and
the CI path filters add packages/** to the unit/build/bundle/e2e/docker
classifications. Verified npm ci, the full six-command gate, and a real
docker build are green at this checkpoint before any runtime cutover.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…altinity/clickhouse-http (Checkpoint 2)

Implement packages/clickhouse-http/src/url.ts (chUrl, moved verbatim) and
client.ts (createClickHouseHttpClient/request(), the direct injected-fetch
invocation) behind the package's public "." export, and cut over
src/net/clickhouse-http-transport.ts to a temporary compatibility adapter
whose send() delegates to the package's request() — streamLines() stays
local, deferred to Phase 3. src/net/clickhouse-transport.types.ts now
aliases TransportDeps/TransportRequest from the package's own types, and
ch-client.ts binds its chUrl re-export to the package while keeping its
eager pre-credential preflight call and composition graph unchanged.

Architecture: build/check-boundaries.mjs gains Rules A-D (package must not
import root src/**; package source has zero bare specifiers — an empty
allowlist, since root hoists real runtime deps like @preact/signals-core
that TS/esbuild could otherwise resolve; root src/** must not deep-import
the package's own src/**; the bare package name is importable only under
src/net/**, and its deep-import subpath form is banned everywhere), mirrored
in tests/unit/clickhouse-http-package-policy.test.js with non-writing virtual-
file sabotage probes for each rule plus a drift-bind check against the
production RULES source.

Tests: the exact chUrl literal suite and the Phase-1 contract suite move to
tests/unit/clickhouse-http-package.test.ts, registered directly against the
package's own request() (not just the compatibility adapter) — proving native
Response identity, exactly-one-Fetch, exact SQL, opaque Authorization, live
origin/fetch, and raw-byte safety hold at the package boundary. New focused
tests pin the reviewed promise-settlement shape: a synchronous chUrl URIError
must surface as a rejected promise, never a synchronous throw, at both the
package request() and the compatibility adapter's send().

Bundle attribution treats packages/** as first-party project code (not
"other"), proven against the real esbuild metafile. All 8 known raw-ESM e2e
fixtures gain an import-map entry for the bare @altinity/clickhouse-http
specifier; a full esbuild-transform-accurate dependency-graph audit of every
other e2e fixture (which also excludes type-only `import type` chains erased
at serve time, unlike a naive text scan) found no additional fixture needing
one.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ickhouse-http package

Document the Phase 2 extraction across the repository's canonical
architecture references: CLAUDE.md hard rules 2 and 4 now distinguish
SQL Browser network integration/application policy (src/net/**) from
reusable ClickHouse HTTP/Fetch mechanics (packages/clickhouse-http),
name the package in the repo map, and clarify it as first-party project
source rather than an eighth bundled runtime dependency.
docs/ARCHITECTURE.md's transport-seam section and layer diagram now
describe the package/adapter split and its architecture-enforced
boundaries. docs/ADR-0005-clickhouse-web-client.md gains a new
addendum noting #630 extracts SQL Browser's own proven Fetch-native
mechanics into a first-party package — independent of, and not
reopening, the Rejected @clickhouse/client-web decision — without
rewriting any historical official-client evidence.
.wiki/Source-Map.md and .wiki/Decisions-and-Roadmap.md are updated to
match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Copy link
Copy Markdown
Collaborator Author

ChatGPT review pass 1

Reviewed head: 62f018365f10d194e4565c625e02b1afa215510b

Required change — Major: the architecture scanner does not fully enforce the claimed dynamic-import boundary

build/check-boundaries.mjs uses one regex-based extractSpecifiers() for Rules A–D. Its dynamic-import pattern accepts only single- or double-quoted arguments (import('...') / import("...")). A valid no-substitution template-literal import such as:

import(`@preact/signals-core`)

produces no extracted specifier, so Rule B does not report the bare package import. The same bypass applies to the other rules when the template-literal target is a forbidden relative root/deep path or @altinity/clickhouse-http outside src/net/**. I reproduced this directly against the exact regex in this head: static imports, export * from, named re-exports, and quoted dynamic imports are detected; template-literal dynamic imports (and dynamic imports with a comment before the literal) are not.

The policy mirror copies the same four regexes, so it shares the blind spot rather than independently catching it. Its sabotage probes currently use static imports; they do not pin the dynamic/re-export forms named by the acceptance question.

Action: make dynamic-import handling fail closed. At minimum recognize no-substitution template literals; stronger is to reject any import(...) in guarded package source unless its argument is a statically provable relative literal. Add explicit sabotage cases for import('@preact/signals-core'), import(`@preact/signals-core`), export * from '@preact/signals-core', and named/namespace re-exports. Do the equivalent location/deep-import probes for Rule D.

This is an acceptance blocker even though the current package tree is clean: the PR claims the zero-bare-specifier/package-boundary rules are mechanically enforced and sabotage-tested. A valid JavaScript spelling that bypasses both the production gate and its mirror makes that enforcement claim false.

Other adversarial checks

No additional blocking defect found. The new chUrl() body is behaviorally identical to the base implementation, including zero/empty values, reserved-character encoding, settings-before-params ordering, and insertion order; the exact-literal suite is moved into the package test rather than duplicated. request() and compatibility send() both remain async, both have focused malformed-URL rejected-promise tests, and the production transport.send(...) path is awaited inside authedFetch; eager chUrl() validation still happens before getToken().

Checkpoint 1 (5c21cb770bfa23d3b6c33839b3f454923e67fa7c) is structurally isolated: the workspace package is statement-free, production does not import it until Checkpoint 2, and the Dockerfile already copies packages/ before npm ci. I found no code-level reason that checkpoint should fail the stated gate, but GitHub has no Actions run recorded for that intermediate SHA, so I cannot independently certify the historical “full gate + real docker build” claim from CI evidence.

The raw-ESM footprint is consistent with the stated eight fixtures: seven fixtures load /src/ui/app.js, and the transport-specific fixture imports clickhouse-http-transport.js directly; all eight are patched. Current canonical head CI is green, including the PR Chromium e2e job (218/218), docker-smoke, unit/coverage, build, bundle, and size jobs. The extra local WebKit claims in the PR description were not independently rerun in this sandbox.

streamLines() remains SQL-Browser-local and the auth/epoch/lifecycle/retry policy remains in ch-client.ts; the package owns only URL serialization, request types/deps, and the low-level injected Fetch call. I found no duplicate chUrl() implementation or moved responsibility still executing in its former owner.

VERDICT: REVISE

Copy link
Copy Markdown
Collaborator Author

ChatGPT review pass 2

Previously reviewed head: 62f018365f10d194e4565c625e02b1afa215510b
Current canonical head: 62f018365f10d194e4565c625e02b1afa215510b

There is no code delta between review passes: GitHub reports the two SHAs as identical (0 commits, 0 changed files). This pass therefore reassesses the pass-1 finding against repository history/scope and re-checks the complete unchanged PR for regressions.

Pass-1 finding reassessment

Dynamic-import scanner gap — real, but not a merge blocker for this PR. I re-confirmed the gap against the exact current regex: static imports, export * from, named re-exports, and quoted import('...') are detected; no-substitution template-literal dynamic imports and comment-prefixed dynamic-import arguments are not. However, commit f16e4a1a10a603b188453f8612a4b192ad58a6de introduced build/check-boundaries.mjs with this same regex and the explicit design assumption that the codebase has no exotic import syntax. Comparing PR base 20f2b5979544e05e8f9c02c8e40b8ec048562fce to the reviewed head shows SPECIFIER_PATTERNS is unchanged byte-for-byte; #641 adds Rules A/C and the Rule B/D consumers, not the parser limitation itself. The three package source files use only relative static imports/re-exports and contain no live bypass. Under the repository's existing checker contract, this is a cross-cutting follow-up rather than a regression introduced by #641.

The strongest objection is that Rule B's prospective "zero bare specifiers" promise is new even if its parser helper is old. If the DoD required language-complete parsing of every valid JavaScript import spelling, the pass-1 blocker would stand. I do not read A2/A4/A5 or this repository's explicitly regex-scoped checker contract that way: the required current package boundary is clean, and this PR does not introduce or rely on an unsupported dynamic-import spelling.

Checkpoint-1 CI evidence — not a defect. Checkpoint 1 (5c21cb770bfa23d3b6c33839b3f454923e67fa7c) is structurally inert: its package entry point is export {}, production does not consume the package until Checkpoint 2, and its Dockerfile already copies packages/ before npm ci. The absence of an isolated GitHub Actions run for an intermediate commit in a multi-commit PR is not evidence of a failing checkpoint.

WebKit reproduction scope — not a defect. The repository's PR workflow explicitly runs Chromium-only Playwright on pull requests; WebKit is part of nightly/manual/release coverage, not the PR gate. The required head CI run is green.

Complete PR regression re-check

No new regression was introduced between passes because the head did not move. Re-inspection of the full 34-file PR still finds no additional blocker:

  • packages/clickhouse-http has no runtime dependencies and exposes only .; its source currently contains only relative imports/re-exports.
  • chUrl() remains the single serializer implementation and is mechanically identical to the base behavior for zero/empty values, reserved-character encoding, settings-before-params ordering, and insertion order. The exact-literal suite is moved to the package test rather than duplicated.
  • Package request() and compatibility send() remain async; focused tests prove malformed URL serialization rejects a returned promise without synchronous throw. Production authedFetch() awaits transport.send(...), while eager chUrl() validation still executes before getToken().
  • ChCtx -> transportFor -> createHttpTransport -> send remains structurally intact; no credential/refresh hook or new ChCtx package field appears.
  • streamLines() remains SQL-Browser-local; auth/epoch/lifecycle/retry policy remains in ch-client.ts.
  • The eight raw-ESM transport-reaching fixtures are the same eight patched fixtures reviewed in pass 1.
  • Canonical head CI is green across unit/coverage+build, bundle, size, docker-smoke, e2e, and the aggregate CI gate.

No merge-blocking finding remains for the claimed Phase-2 acceptance subset.

VERDICT: SHIP

@BorisTyshkevich
BorisTyshkevich merged commit 5f62b39 into main Aug 7, 2026
8 checks passed
@BorisTyshkevich
BorisTyshkevich deleted the feat/630-p2-clickhouse-http-package branch August 7, 2026 14:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant