From 5a802b2c8e81f5300d4c1309ebfd4187b4f94d39 Mon Sep 17 00:00:00 2001 From: "turbolytics.io" Date: Sat, 19 Sep 2026 20:53:34 -0400 Subject: [PATCH 1/2] render: move the template's engine to sqlflow v2026.09.19.1 From v2026.09.18.1. Six references carry the tag -- the Dockerfile's ARG, the Makefile, three compose build args and the e2e's default -- and all six move together or the template builds one engine and tests another. The README's two sample telemetry payloads and the version-parsing comment in bin/telemetry.sh name the tag as well; they are documentation, but a sample that reports a version nothing sends any more is a sample that misleads. What moved under the engine is the Go toolchain and nine modules, the Go ADBC driver among them, 1.6.0 to 1.12.0. Every metric this template stores crosses that driver. `make -C render validate` against the new image: pipeline.yml, serve.yml and rollups.yml all valid, and `rollup check` reports rollups.yml, migrations/0003_rollups.sql and serve.yml agree. The generated DDL is identical across the ADBC jump, so no migration moves and no dataset changes shape. `make -C render test` fails, and not because of this change. One assertion -- "a pinned 1d grain with the default hour is an empty range" -- expects the default one-hour window to snap to a zero-width range at a 1d grain. Between 00:00 and 01:00 UTC that hour straddles midnight, the range comes back as a whole day, and it matches fixtures written seconds earlier. Both runs happened at 00:49 and 00:52 UTC. The same suite on unmodified main fails identically, same assertion, same range, same three rows. The flake is on main today and reaches any pull request that touches render/** in that hour. It is left for its own change, because the fix depends on what the range is meant to snap to, which is the serve code's business rather than the template's. --- render/Dockerfile | 2 +- render/Makefile | 2 +- render/README.md | 4 ++-- render/bin/telemetry.sh | 2 +- render/docker-compose.yml | 6 +++--- render/test/e2e.sh | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/render/Dockerfile b/render/Dockerfile index 7f662f8b..a968086f 100644 --- a/render/Dockerfile +++ b/render/Dockerfile @@ -5,7 +5,7 @@ # # An argument so an unreleased build can be tried locally: # docker build --build-arg SQLFLOW_IMAGE=turbolytics/sql-flow: render -ARG SQLFLOW_IMAGE=turbolytics/sql-flow:v2026.09.18.1 +ARG SQLFLOW_IMAGE=turbolytics/sql-flow:v2026.09.19.1 FROM ${SQLFLOW_IMAGE} # psql and pg_isready apply the migrations. curl sends the install events. diff --git a/render/Makefile b/render/Makefile index 7bdbedfb..2358f78c 100644 --- a/render/Makefile +++ b/render/Makefile @@ -1,5 +1,5 @@ # Must match the Dockerfile and docker-compose.yml. -SQLFLOW_IMAGE ?= turbolytics/sql-flow:v2026.09.18.1 +SQLFLOW_IMAGE ?= turbolytics/sql-flow:v2026.09.19.1 .PHONY: validate rollups up test psql clean diff --git a/render/README.md b/render/README.md index d0b04a9e..c949c99d 100644 --- a/render/README.md +++ b/render/README.md @@ -47,13 +47,13 @@ of both: ```json {"name": "install.deployed", "type": "count", "value": 1, "dimensions": {"install_id": "3f0c1b7e-…", "source": "render", - "template": "render-metrics", "sqlflow_version": "v2026.09.18.1"}} + "template": "render-metrics", "sqlflow_version": "v2026.09.19.1"}} ``` ```json {"name": "install.first_request", "type": "count", "value": 1, "dimensions": {"install_id": "3f0c1b7e-…", "source": "render", - "template": "render-metrics", "sqlflow_version": "v2026.09.18.1"}} + "template": "render-metrics", "sqlflow_version": "v2026.09.19.1"}} ``` | Field | What it is | diff --git a/render/bin/telemetry.sh b/render/bin/telemetry.sh index dc757f91..80dfb36f 100755 --- a/render/bin/telemetry.sh +++ b/render/bin/telemetry.sh @@ -31,7 +31,7 @@ TEMPLATE=render-metrics sql() { psql "$SQLFLOW_POSTGRES_URI" -X -Atq -v ON_ERROR_STOP=1 -c "$1" 2>/dev/null; } -# The first line of `sqlflow --version` is "sqlflow v2026.09.18.1". +# The first line of `sqlflow --version` is "sqlflow v2026.09.19.1". VERSION="$(sqlflow --version 2>/dev/null | awk 'NR==1 {print $2}')" case "$VERSION" in ''|*[!A-Za-z0-9._-]*) VERSION=unknown ;; esac diff --git a/render/docker-compose.yml b/render/docker-compose.yml index 5af60d45..feb326b7 100644 --- a/render/docker-compose.yml +++ b/render/docker-compose.yml @@ -38,7 +38,7 @@ services: build: context: . args: - SQLFLOW_IMAGE: ${SQLFLOW_IMAGE:-turbolytics/sql-flow:v2026.09.18.1} + SQLFLOW_IMAGE: ${SQLFLOW_IMAGE:-turbolytics/sql-flow:v2026.09.19.1} depends_on: postgres: condition: service_healthy @@ -71,7 +71,7 @@ services: build: context: . args: - SQLFLOW_IMAGE: ${SQLFLOW_IMAGE:-turbolytics/sql-flow:v2026.09.18.1} + SQLFLOW_IMAGE: ${SQLFLOW_IMAGE:-turbolytics/sql-flow:v2026.09.19.1} depends_on: postgres: condition: service_healthy @@ -93,7 +93,7 @@ services: build: context: . args: - SQLFLOW_IMAGE: ${SQLFLOW_IMAGE:-turbolytics/sql-flow:v2026.09.18.1} + SQLFLOW_IMAGE: ${SQLFLOW_IMAGE:-turbolytics/sql-flow:v2026.09.19.1} entrypoint: /app/bin/serve.sh depends_on: postgres: diff --git a/render/test/e2e.sh b/render/test/e2e.sh index 9df8159a..29e50028 100755 --- a/render/test/e2e.sh +++ b/render/test/e2e.sh @@ -10,7 +10,7 @@ API="http://127.0.0.1:${API_HOST_PORT:-8080}" SECRET=local-secret # The tag the image is built from, which an install reports as its version. # make test passes it. Must match the Dockerfile's default. -: "${SQLFLOW_IMAGE:=turbolytics/sql-flow:v2026.09.18.1}" +: "${SQLFLOW_IMAGE:=turbolytics/sql-flow:v2026.09.19.1}" export SQLFLOW_IMAGE GRAINS="1m 5m 15m 1h 6h 1d" From 258c591711854213c58ac603fffab8af2725435b Mon Sep 17 00:00:00 2001 From: "turbolytics.io" Date: Sat, 19 Sep 2026 21:06:28 -0400 Subject: [PATCH 2/2] render: assert what the 1d grain guarantees, not what the clock happened to do "A pinned 1d grain with the default hour is an empty range" is true for twenty-three hours a day. The default window is one hour, and a 1d bucket falls inside it only when a day begins during that hour; for the hour after UTC midnight one does, the range covers that day, and the fixtures written seconds earlier match it. The suite then fails on a claim about the clock wearing a claim about the API, and it reaches any pull request that touches render/** in that hour. Confirmed pre-existing: the same suite on unmodified main fails identically, same assertion, same range, same three rows, run at 00:52 UTC. Nothing about the range logic is wrong. alignRange rounds both ends up to the grain, and for buckets that are themselves aligned that selects the same set: b >= since exactly when b >= ceil(since), and b < until exactly when b < ceil(until). The rounding exists so every request inside one bucket-wide window shares a cache key and can share an answer, which is also why the echoed range has to describe the bucket window rather than the hour asked for. So this asserts what holds at every hour: the grain asked for is the grain answered, the range is never inverted, and a row comes back only for a bucket inside it. Checked against the response that failed at 00:49 UTC (passes), a zero-width range with no rows (passes), a row whose bucket is outside the range (fails), and an inverted range (fails), so it still catches what it was written to catch. The suite then ran green end to end, 88 assertions -- but at 01:04 UTC, outside the window, so that run is evidence the assertion works in place, not evidence it survives midnight. The fixtures are that evidence. --- render/test/e2e.sh | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/render/test/e2e.sh b/render/test/e2e.sh index 29e50028..2964f36e 100755 --- a/render/test/e2e.sh +++ b/render/test/e2e.sh @@ -209,9 +209,29 @@ for g in $GRAINS; do '[.rows[] | [.value_sum, .value_count, .value_min, .value_max]] == [[9,4,1,5]]' "$total" done +# A grain coarser than the window answers rather than erroring. Whether the +# answer is empty depends on the clock, which is why this no longer asserts +# that it is: the default window is one hour, and a 1d bucket falls inside it +# only when a day begins during that hour. For the hour after UTC midnight one +# does, the range covers that day, and the fixtures written seconds earlier +# match. The old assertion -- since == until, no rows -- was a claim about the +# clock wearing a claim about the API, and it failed on main as readily as +# here. +# +# The invariant that holds at every hour: the grain asked for is the grain +# answered, the range is never inverted, and a row is returned only for a +# bucket inside it. Checked against the midnight response and against a +# zero-width one, and it rejects a bucket outside the range either way. narrow="$(get metric name=checkout grain=1d)" -expect "a pinned 1d grain with the default hour is an empty range, not an error" \ - '(.range.since == .range.until) and (.rows | type) == "array" and (.rows | length) == 0' "$narrow" +expect "a pinned 1d grain answers within its own range, whatever the hour" \ + '. as $r + | ($r.grain == "1d") + and (($r.rows | type) == "array") + and ($r.range.since <= $r.range.until) + and (if $r.range.since == $r.range.until + then ($r.rows | length) == 0 + else all($r.rows[]; .bucket >= $r.range.since and .bucket < $r.range.until) + end)' "$narrow" auto="$(get metric name=checkout)" expect "without a grain the API picks the finest that covers the range" '.grain == "1m" and (.rows | length) == 3' "$auto"