fix(#627): support ClickHouse 24.8 meta-less result streams in Table view - #674
Conversation
applyStreamLine's json.row arm now establishes name-only StreamColumns (type: '') from the first row's object keys when no meta line has ever arrived, so ordinary ClickHouse 24.8-and-earlier query results reach the Table view instead of being silently discarded at EOF. No value-based type inference, no auxiliary metadata query, no App-shape change. Adds stream.ts regression coverage for the true-EOF-with-no-meta case, stable first-row column ordering, no synthetic type inference, meta-less row caps, and unchanged meta-first typed behavior, plus representative type: '' regressions across the exhaustive result-column consumer audit (grid-render, results/openCellDetail, chart-data/autoChart, kpi, logs/detectLogsView, variable-options, panel-cfg/autoPanel, and the spec-completion adapter's existing spec-editor test) confirming every typed consumer fails closed/generic rather than crashing or fabricating a type. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwFPT465eDJqYcRa8HGNLz
…R-0005 README replaces the stale #627 "render silently empty" gap paragraph with a new ClickHouse server compatibility section: 24.8 is limited support (query execution + Table results; typed-result features may degrade). docs/ARCHITECTURE.md and .wiki/Architecture.md describe the meta-first vs meta-less normalization now owned by core/stream.ts. .wiki/Decisions-and- Roadmap.md records #627 as resolved without reopening ADR-0005. docs/ADR-0005-clickhouse-web-client.md adds a post-decision "#627 production compatibility follow-up" note and corrects its now-false "does not mean 24.8 is newly supported" sentence, while ADR-0005 itself remains Rejected. docs/evidence/585/README.md drops the dead check:client-spike:evidence instruction (retired in #630 Phase 8) and cross-references the new docs/evidence/627/ live evidence; the historical #585 matrix itself is untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwFPT465eDJqYcRa8HGNLz
Both pinned historical 24.8 digests (OSS 24.8.14.39, Altinity Stable
24.8.14.10547.altinitystable) verified against the real production
decoder/accumulator (packages/clickhouse-http's streamLines() +
src/core/stream.ts's applyStreamLine()): neither raw stream ever emits a
meta record, and the normalized {columns,rows,error,capped} exactly
match independently declared expected literals, including full lexical/
Decimal-precision preservation. Raw captured ndjson + normalized JSON for
both rows committed under docs/evidence/627/; the temporary verifier
script itself was not committed, per plan.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwFPT465eDJqYcRa8HGNLz
…t-Logs-cfg coverage, fix ADR-0005 tense
- src/core/stream.ts: a zero-key `{"row":{}}` line no longer "establishes"
empty columns before real columns exist. That left columns.length===0
ambiguous between "not yet established" and "established with zero
columns," so a later real row would re-establish columns out from under
an already-pushed zero-width row, breaking the invariant that every
stored row's width equals result.columns.length. A zero-key row carries
no values, so declining to store it discards no query data. Not
reachable from a real ClickHouse SELECT (a projection always has >=1
column). Added a regression test driving the exact {}-then-real-row
sequence; sabotage-checked (reverting the guard fails the new test) and
restored.
- tests/unit/panel-cfg.test.ts: pin that an explicit Logs cfg resolves
time/msg by column NAME even against meta-less columns (type: ''),
unlike convention detection which fails closed on ''. This is
pre-existing, intended name-based-path behavior permitted by #627's
degraded-functionality contract, not a production change — the gap was
that it was untested.
- docs/ADR-0005-clickhouse-web-client.md: three passages still asserted
applyStreamLine's meta-less gap in the present tense; shifted the
code-state claims to the past and pointed at the existing "#627
production compatibility follow-up" note. Historical evidence/results
are unchanged; ADR-0005 remains Rejected.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwFPT465eDJqYcRa8HGNLz
ChatGPT review pass 1Reviewed head: Findings
Other adversarial checks
I could not run focused tests locally because this review runtime could not resolve VERDICT: REVISE |
docs/evidence/627/README.md pinned the "Tested commit" to the live-capture SHA (036760a) and claimed src/core/stream.ts's meta-less fallback "is unchanged since" -- true when written, but the branch's final commit (edcb8ba) later added a zero-key-row guard to the exact json.row fallback arm this evidence exercises, making that blanket claim stale relative to the head acceptance criterion 7's evidence is meant to attest to. Correct the provenance text to distinguish the live-capture SHA from the final head, and add tests/unit/evidence-627-replay.test.ts: a permanent regression test that replays both committed raw NDJSON captures through the real streamLines() -> applyStreamLine() production path at whatever commit npm test runs against, proving the guard is inert against this corpus (no committed row is ever zero-key) without needing to re-run the live Docker capture. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwFPT465eDJqYcRa8HGNLz
ChatGPT review pass 2Previously reviewed head: Reviewed head: Reassessment of pass-1 findingThe substantive part is fixed. Commit Findings
Complete-PR regression reassessment
I could not execute the focused tests locally because this runtime still cannot resolve VERDICT: REVISE |
- docs/evidence/627/README.md: stop pinning "final head" to edcb8ba (the commit that added this prose was itself one commit later, e94b7c5, proving the SHA-pinned framing goes stale immediately). Reword to a stable alias ("the zero-key-guard commit") and describe the replay test as validating whichever checkout/HEAD executes it, not a permanently final SHA. - Fix "byte-identical"/"byte-for-byte" overstatement in README.md and tests/unit/evidence-627-replay.test.ts's header comment: the replay test does deep/structural equality on parsed *.normalized.json objects, not a byte-for-byte file comparison. Reworded to "exactly/structurally equal". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwFPT465eDJqYcRa8HGNLz
… note Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwFPT465eDJqYcRa8HGNLz
ChatGPT review pass 3Previously reviewed head: Reviewed head: Reassessment of earlier findingsAll earlier findings are resolved.
The follow-up Complete-PR regression reassessmentI re-reviewed the complete 23-file PR. The production footprint is still only The support/documentation side is still internally consistent: ClickHouse 24.8 is documented as limited support, #71 records that decision while remaining open, ADR-0005 remains Rejected, and the #585 evidence remains historical rather than being rewritten. Current-head CI is green: test, e2e, bundle, Docker smoke, size, and aggregate gate completed successfully. The test job ran 221 files / 7,550 tests, including the evidence replay (2/2), and No actionable findings remain. One optional bookkeeping cleanup is that the PR description still says the zero-key change made this the “fourth commit” and still reports 7,548 tests; the canonical PR now has 7 commits and current CI reports 7,550 tests. That metadata drift does not affect implementation or merge readiness. VERDICT: SHIP |
What & why
Closes #627.
ClickHouse 24.8 never emits the
{"meta":[...]}record forJSONStringsEachRowWithProgress/JSONEachRowWithProgress(upstream added it in ClickHouse PR #74181, merged 2025-01-06,postdating 24.8).
applyStreamLine()learned column names only frommeta, soresult.columnsstayed empty and every successful row became[]— the UI rendered asilently empty result while the wire response carried the data.
This implements the issue's limited, data-safe Level-1 support contract: when a
rowarrives while
result.columnsis empty, establish name-only columns fromObject.keys(row)with the explicit unknown-type sentineltype: '', then store everyrow through that established order. No value-based ClickHouse type inference, no
auxiliary metadata query, no version branch, no
Column.typeshape change.Production footprint is one file (
src/core/stream.ts). The audit below found thatno other production change was needed: every result-type consumer already fails closed
on
''.Layer ownership
The fallback is SQL Browser result policy, so it lives in
src/core/stream.ts, keepingthe settled #630 Phase 3 split intact —
packages/clickhouse-http'sstreamLines()stillonly decodes what the server actually sent and never synthesizes metadata. Nothing under
packages/clickhouse-http/**,src/net/**,src/application/**, orsrc/ui/**changed.Contract coverage (all 9 acceptance criteria, none deferred)
stream.tsfallback + true-EOF test + live evidencetype: '', no inference'')renderGridfidelity test + live two-row proofmetaarm untouched; test asserts metadata order stays authoritative when it deliberately differs from row-key orderresults,chart-data,kpi,logs,variable-options,panel-cfg,spec-editordocs/evidence/627/(see below)## ClickHouse server compatibilityREADME section; #71 body records the decision (and stays open)Result-type consumer audit
isNumericType('')→ false;parseClickHouseType('')→null;chartStripType('')→''(so
chartRolenever returns numeric/time andautoChart()yieldsnull); logs typeregexes match nothing;
isOptionColumnType('')→ false (unknown metadata is not treatedas
String); the cell-detail type badge is truthiness-gated;autoPanelfalls through toTable.
param-type.ts/isSupportedOptionScalarand the schema/catalogc.typesites areauthored-declaration and catalog consumers, not result columns, and are excluded.
One nuance worth naming:
resolveLogsShaperesolves an explicitcfg.time/cfg.msgbycolumn name, never by type — unlike convention-based auto-detection, which fails closed on
''. So an authored Logs panel now resolves against a meta-less result (formatting anuntyped string as its time column). That is that consumer's pre-existing unknown-type
behavior — it neither throws, discards row data, nor fabricates a type — so #627's
degraded-functionality contract permits it. It is now pinned by a test rather than left
undocumented.
Invariant verification
Every invariant was sabotage-checked — the mutation was applied, the expected failure
confirmed, and the original bytes written back (never
git checkout --):type: ''→'String'Number(...)NaN)''isNumericType('')true.numassertions failcolumns.length[[], ['srv-7','ok']])The last one came out of internal review (below), which is why it landed as a separate commit
rather than in the original implementation.
Live ClickHouse 24.8 verification
Both pinned digests from the #585 compatibility matrix, run against the real production
decoder —
streamLines()→newResult()/applyStreamLine()— over the actual capturedbytes, asserted against independently declared literals:
version()metain streamclickhouse/clickhouse-server@sha256:1ffa82ed…24.8.14.39type: ''altinity/clickhouse-server@sha256:d0c45645…24.8.14.10547.altinitystabletype: ''Raw NDJSON and normalized output are committed under
docs/evidence/627/with the fullprocedure. The verification is deliberately one-off with committed evidence, not a new
permanent Docker-matrix harness: these are two immutable historical digests, and the #585
spike harness was itself retired in #630 Phase 8.
The precision corpus is the point of the query —
9007199254740993.12345678901234567890(beyond IEEE-754 exactness),
-9007199254740993.00000000000000000001, and thelexically-significant
001.2300/0002all survive byte-for-byte to rendered Table cells.Known, deliberately unfixed edge (named, not overlooked)
A
row-then-late-metastream would leave already-stored rows bound to row-key orderwhile headers flip to
metaorder, so a differing order could misalign row 0. This isnot reachable in either
…WithProgressformat (meta, when present, is always thefirst line), the
json.metaarm is untouched by this PR, and guarding it would require theextra
StreamResultstate the approved plan explicitly forbids. Before this PR the samesequence produced an empty row 0 instead; either way no values are discarded. Recorded
here rather than silently left as an unexamined assumption.
Internal review
One targeted read-only pass (Medium risk) produced 3 real findings, all fixed in
edcb8ba:the unsound zero-key establishment sentinel (treated as a missing invariant, not a spot
patch); the
resolveLogsShapegap in the audit's exhaustiveness claim (test-only — theconclusion held); and three ADR-0005 passages still asserting the defect in the present
tense (shifted to past tense with a pointer to the follow-up note — historical observations
and
docs/evidence/585/**are untouched, and the ADR remains Rejected).Checklist
npm testpasses (per-file coverage gate 100/95/90/100 held;src/core/stream.tsat 100/100/100/100)npm run buildsucceeds (single-filedist/sql.html)src/core/, package protocol mechanics untouchedCHANGELOG.md([Unreleased] → Fixed) updateddocs/evidence/585/README.md(also drops its instruction to run the retiredcheck:client-spike:evidencescript),docs/ARCHITECTURE.md,.wiki/Architecture.md,.wiki/Decisions-and-Roadmap.md. Roadmap Roadmap to 1.0.0 #68 needs no change — this fulfils a decision inside existing Document the supported-browser matrix (browsers, ClickHouse versions, IdP requirements) #71 work rather than reshaping a track.Test/gate results
Full explicit gate green (
.npmrcsetsignore-scripts=true, so the chain is runexplicitly rather than relying on
pretest):check:types,check:arch(1899 files, 10rules, no violations),
check:schemas,check:examples, 7550 unit tests, build (countsare for the final head
97d65a0; three review-pass commits landed after this body was firstwritten, adding 2 tests).
E2E: Chromium fully green; WebKit showed one
page.goto30 s timeout, but a differenttest on each of two full runs (
dashboard-tree.spec.js:1124, thenvariable-tab.spec.js:127),and the affected spec passes 12/12 in isolation. Both are features this diff does not
touch, and it adds no App-shape, fixture, or import-graph change — the known WebKit
under-parallelism flake, not a regression.
🤖 Generated with Claude Code
https://claude.ai/code/session_01LwFPT465eDJqYcRa8HGNLz
Review record
Plan: ChatGPT-authored, approved by Fable/high on pass 3 of 5.
Code review: 3 formal passes in one ChatGPT conversation, certified
VERDICT: SHIPat97d65a0. Pass 1 → 1 accepted finding (stale evidence provenance, fixede94b7c5, which alsoadded the permanent replay test). Pass 2 → 2 accepted findings, both caused by pass 1's own fix
(a SHA pinned as "final head"; a "byte-identical" overstatement that is really structural
equality) — fixed
e9ca678.97d65a0then removed review chronology from the permanent testfile and evidence note on the coordinator's own initiative, since those two findings shared one
root cause: this evidence doc asserting things about code state it cannot keep true. Pass 3 → no
accepted findings; one non-blocking bookkeeping observation about this body's own commit/test
counts, corrected above.