fix(bridge,client,core): STDDEV/VARIANCE over a transformed expression — per-major emission (#222) - #291
Merged
Conversation
…hBodySerializer seam, per-major serializers (#222) Work in progress on story BIDC-3; tests, docs and the final message follow in a later commit. Story BIDC-3 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…efusalBoundarySpec (#222) Story BIDC-3 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…y so a refusal is answered at run (#222) Story BIDC-3 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… verbatim; null-safety has one owner (#222) Story BIDC-3 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Story BIDC-3 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…red, elastic4s fallback handler kept, prose + headers (R3-1..R3-9) Independent review of 398dd5e: APPROVE-WITH-FIXES (0 HIGH, 3 MEDIUM, 6 LOW). All applied. R3-2 (rebase): rebased onto origin/feature/BIDC-2 10f3f8f (two further BIDC-2 commits, both touching BOTH AggregationNamingSpec copies). The merged tree now compiles and runs: bridge and es6 bridge 176/176 on 2.12.20 and 2.13.16. A textually clean merge-tree is not a build. R3-8 (route contract): only SYNCHRONOUS throws were converted, so an extension deferring the translation into its own Future still escaped while the PR body, spec and findings log all claimed "answered at run on EVERY route". Implemented rather than scoped: `dispatch(statement).recover { case e: ElasticError => ... }` beside the existing catch, sharing one `refused` helper, pinned by a deferring-extension case in GatewayRefusalBoundarySpec (6/6). A boundary that only catches is half a boundary when the callers include third-party code — the SPI is exactly that. R3-1: the one-case PartialFunction REPLACED elastic4s's `defaultCustomAggregationHandler`, turning a NotImplementedError naming the class into a bare MatchError for anything the library cannot build; now `scriptedExtendedStats orElse defaultCustomAggregationHandler`, with a dummy-Aggregation test in both client modules (es8 + es9 9/9). That test earned itself immediately: the first form declared the composed val ABOVE the val it reads, so the object initialiser saw null and every emission died with ExceptionInInitializerError. Ordering fixed. R3-3: the upstream backports are OPEN and were unnamed — elastic4s#4105 (series/7.x, the one that LIFTS the es7 refusal) and elastic4s#4106 (series/8.x, the one that retires the es8/es9 handler), both cherry-picks of #4100. Retargeted in the watch note and the PR body, no closing keyword. R3-4 / R3-5 / R3-6: prose corrected — the discriminator's scaladoc named a reader that does not read it; the fifth door's justification was wrong (ElasticMultiSearchRequest has ZERO repo-wide references; the verdict and in-code comment were right); TestSerializers.RawDefault claimed a NotImplementedError it never reaches (it returns an asserted sentinel). R3-7: the windowed-scroll fallback arm's new `sql` is kept (it matches the success arm) and is now a release-note item, since it changes that arm's core SQL log line. R3-9: licence headers on the three new bridge/core test files. AD-S3-1' is LEAD-RATIFIED (2026-09-06); AD-S3-1's named mechanism is recorded in the spec as REFUTED BY MEASUREMENT — AggregationBuilderFn matches its typed ExtendedStatsAggregation arm before consulting any custom handler, so a handler keyed on that type is unreachable dead code. Story BIDC-3 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… a transform (#222, AD-S3-4) Scope extension, lead-decided 2026-09-06: elastic4s 7.17.26 is released and carries the series/7.x backport (elastic4s#4105) of the extended_stats script fix, so ES 7 must compute, not refuse. FORK MIGRATION, not a version bump: 7.17.26 exists only under `nl.gn0s1s` (the `com.sksamuel.elastic4s` 7.x line stops at 7.17.4). Both es7 arms of `elastic4sDependencies` and `elastic4sTestkitDependencies` move, plus `Versions.elastic74s`. Package names are unchanged, so the entire es7 tree recompiled with ZERO source changes (`+ es7/compile`, `+ softclient4es7-sql-bridge`, `+ es7rest`, `+ softclient4es7-core-testkit`, both Scala legs). MIGRATION FINDING — a Jackson split that the resolution alone did not reveal. 7.17.26 is a recent release of an old line, so it declares jackson-databind/core/annotations 2.22.2, while this build curates a coherent Jackson per ES major. Resolved, that left databind 2.22.2 beside the curated jackson-module-scala 2.19.0, which validates its databind at class-init: every emitted query died with ExceptionInInitializerError ("Scala module 2.19.0 requires Jackson Databind >= 2.19.0 and < 2.20.0 - Found 2.22.2") on the first XContentBuilder.string. Fixed the way this build already handles it for `org.elasticsearch:elasticsearch`: `excludeAll(jacksonExclusions)` on both es7 elastic4s artifacts, so the curated set governs. Verified by re-resolving the classpath (one coherent 2.19.0 set) AND by running the suites, which exercise that exact path. AD-S3-4 — a THIRD behaviour beside render (ES 8/9) and refuse (ES 6): UNWRAP. 7.17.26 has no `customAggregation` seam (both `SearchBodyBuilderFn.apply` and `AggregationBuilderFn.apply` are one-argument) and does not need one, because its default builder is now correct. So the ES 7 serializer substitutes the bridge's `ScriptedExtendedStatsAggregation` marker for the `ExtendedStatsAggregation` it wraps, at every depth, and lets the stock builder render the script. The unwrap lives in the es7 client module, NOT on the shared trait: ES 8/9 must not unwrap (their pinned elastic4s still drops the script and they need the marker to reach their handler) and ES 6 must not either (it refuses). One major needs it today (Rule of Three). `RestHighLevelClientExtendedStatsRejectionSpec` is REPLACED by `RestHighLevelClientExtendedStatsEmissionSpec` (10/10): the JSON carries the script on both doors and both binds, for all six family members; the unwrap is asserted to preserve the marker's script verbatim and to leave a marker-free request byte-identical to the stock builder. The testkit's #222 cases now branch on `computesTransformedStats` (ES >= 7), so the ES 7 leg asserts the same value oracle as ES 8/9; ES 6 alone keeps the named-400 refusal. Story BIDC-3 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…222) The per-major table in functions_aggregate.md, the DQL paragraph and the known-limitations section all move with the elastic4s 7.17.26 migration: ES 7, 8 and 9 compute the statistic over the transform; ES 6 alone refuses, permanently, because its client library line is unmaintained. Story BIDC-3 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… elastic4s FLOOR, supersession (D-2..D-7) Delta review of 4a6d3be: APPROVE-WITH-FIXES (0 HIGH, 0 MEDIUM on this branch, 6 LOW). All applied. D-2 `unwrap`'s marker arm returned `inner` verbatim while the generic arm recursed, so a marker nested below a marker would have survived to the one-argument builder's NotImplementedError. Unreachable today (an extended_stats never carries sub-aggs in this bridge) — but "unreachable" is a property of the emitter, not of this function, so the arm now recurses and the invariant leaves the reader's head. One extra call on a path that runs once per query. D-3 the capability floor, the item worth the most here. `computesTransformedStats` keys on the ES SERVER major, but the capability belongs to the CLIENT LIBRARY: it holds only while `Versions.elastic74s` >= 7.17.26. The unwrap is unconditional, so pinning an older 7.x elastic4s would silently restore #222's original defect — a statistic over the RAW field — with the refusal that used to guard it gone, and the predicate would still answer true. The floor is now stated beside the pin (do not lower without restoring the refusal) and named in the predicate's scaladoc, which ships: core-testkit is published and downstream reads it. D-4 functions_aggregate.md claimed the upstream drop "is fixed for the 7.x, 8.x and 9.x lines the driver ships" — true for 7.x only; 8.18.2 / 9.0.0 still drop it and the driver works around it. The causal clause is corrected; the behaviour table was already right. D-5 a watch-note row still promised to replace the es7 refusing serializer "when a 7.17.x release carrying #4105 exists" — that fate executed in this PR. The earlier edit had silently matched nothing (a replace that no-ops still "succeeds"); this one asserts, before and after. D-6 supersession pointers added at the four spec sites that still read as if es7 refuses (AD-S3-1 twice, AD-S3-3, T6). D-7 PR-body release notes renumbered 1..6. Contract sentence qualified in the PR body, the spec and the findings log: it holds for the extended-stats refusal on every route this story owns, and is NOT claimed as a universal invariant of `run` — merged BIDC-4 code returns `Source.failed(...)` for a temporal-literal rejection that both consumers wrap into an ElasticSuccess (the review's D-1, a MEDIUM in that code, not on this branch, awaiting the lead). Verified: scalafmtCheckAll green; sql 662/662; bridge template 176/176; es6 bridge 176/176; core 874/874; `++ 2.12.20` Test/compile for bridge, es6bridge, core, es7rest and the testkit template; es7 emission spec 10/10 after the recursion change. Story BIDC-3 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Brings BIDC-4 (temporal literals, #276), BIDC-5 and BIDC-2 as merged on main, so D-1 — a pre-execution rejection that reaches the caller as a stream failing at materialisation instead of an ElasticFailure — can be fixed in the same PR as the extended-stats refusal it must be symmetric with (lead ruling 2026-09-06). Story BIDC-3 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…a stream that dies later (D-1, #276) Lead ruling 2026-09-06: fold D-1 into this PR so the two pre-execution rejection kinds behave identically and the contract sentence can be stated universally instead of scoped. `ScrollApi`'s temporal-literal branch returned `Source.failed(error)`. Both consumers of `scroll` wrap the Source they receive into a SUCCESS — `SearchExecutor`'s scroll arm and `CoreDqlExtension.cappedScroll` — so `GatewayApi.run` answered `ElasticSuccess` carrying a stream that only died when somebody materialised it, on the un-LIMITed plain-projection route every BI tool takes. It now THROWS, exactly as a client module's extended-stats refusal does during translation, and comes back from `run` as the `ElasticFailure(400)` it always was. Audited for symmetry, as instructed: every `SearchApi` call site of `resolveTemporalLiterals` (:244, :276, :598, :623, :1143, :1154) already returned `ElasticResult.failure`; `ScrollApi` was the one place that could not, because it must return a `Source`. No other `Source.failed(<status-bearing ElasticError>)` remains on a pre-execution path — the other three in `ScrollApi` (:271, :316, :321) and the window-enrichment one (:783) carry `IllegalArgumentException` / `UnsupportedOperationException` / `RuntimeException`, and `file/package.scala`'s are IO failures. `TemporalLiteralSearchSpec`'s scroll pin is RETARGETED, not deleted: it recorded the contract "an unparseable literal is a 400 that reaches the caller" through the lens of the day (a failed Source, observable only by materialising) — and that lens was the defect. It now asserts the throw plus `lastQuery shouldBe None`, so the pre-execution property is pinned too. Three cases added to `GatewayRefusalBoundarySpec` (un-LIMITed row route, one-shot + windowed, and both rejection kinds answered identically). Falsified: with `Source.failed` restored, exactly those three go red and the six extended-stats cases stay green. Verified on the merged tree: scalafmtCheckAll green, sql 687/687, bridge template 182/182, es6 bridge 182/182, core 896/896, `++ 2.12.20` Test/compile for bridge, es6bridge, core, es7rest and the testkit template. Story BIDC-3 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fupelaqu
marked this pull request as ready for review
September 6, 2026 20:32
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #222
Story BIDC-3 (BI defect-closure epic). PR base =
main(withorigin/mainmerged in, so D-1 could be fixed alongside the refusal it must be symmetric with — lead ruling 2026-09-06). (BIDC-2's PR #290 merged intofeature/BIDC-2rather thanmain, so this child was never carried onward and #222 stayed correctly open;feature/BIDC-3sits directly on10f3f8fc, which IS onmain.) Superseded note: it was originally cut as a stacked branch — PR base =feature/BIDC-2(STACKED, lead decision 2026-09-06) — BIDC-2's PR is held, not merged, and this story builds on its correctedbuckets_path/ naming (ElasticAggregation.scalaneighbourhood). Cut fromorigin/feature/BIDC-24368d9dc, rebased onto BIDC-2's head10f3f8fc(its review follow-upsa5f376f2·b09aaf27·b5a86cf9·a363f579·10f3f8fc— every rebase clean; the last two touch BOTHAggregationNamingSpeccopies, so the merged tree was re-compiled and re-run, not just merged).git log origin/main..feature/BIDC-3therefore lists BIDC-2's two commits as well — expected; GitHub retargets tomainwhen BIDC-2 merges. Version line0.23.0-SNAPSHOTverified, untouched. NopublishLocal, no GitHub issue operation. Upstream reference: Philippus/elastic4s#4100 (no closing keyword — a WATCH item, see below).Per-major behaviour (lead ruling R-1b)
"extended_stats":{"field":"createdAt"}), orextended_stats: {}rejected by ESextended_statscarries itsscriptSearchBodyBuilderFnwith a handler for the bridge's marker (AD-S3-1′)nl.gn0s1s, backport #4105) emits the script natively; the module UNWRAPS the marker and lets the stock 1-arg builder render it (AD-S3-4)400, naming Elasticsearch 6 — permanentes6/restandes6/jest; the 6.x elastic4s line is unmaintained, so no fix can reach itScope extension (lead, 2026-09-06): ES 7 was specced — and first shipped here — as a loud reject
"lifted when the 7.17.26 backport ships". It shipped, so the lift is part of this PR.
RestHighLevelClientExtendedStatsRejectionSpecis replaced byRestHighLevelClientExtendedStatsEmissionSpec, and the testkit's #222 cases branch oncomputesTransformedStats(ES ≥ 7) so the ES 7 leg asserts the same value oracle as ES 8 / ES 9.The ES 7 fork migration — and what it surfaced
com.sksamuel.elastic4s7.17.4 →nl.gn0s1s7.17.26 (7.17.26 exists only under the fork; thesksamuel 7.x line stops at 7.17.4). Package names are unchanged, so the entire es7 tree recompiled
with zero source changes on both Scala legs.
🔴 It needed a Jackson exclusion, and resolution alone did not reveal that. 7.17.26 is a recent
release of an old line: it declares jackson-databind/core/annotations 2.22.2, while this build
curates a coherent Jackson per ES major. Resolved as-is, that left databind 2.22.2 beside the curated
jackson-module-scala2.19.0, which validates its databind at class-init — so every emitted querydied with
ExceptionInInitializerError("Scala module 2.19.0 requires Jackson Databind >= 2.19.0 and< 2.20.0 - Found jackson-databind version 2.22.2") on the first
XContentBuilder.string. Fixed withexcludeAll(jacksonExclusions)on both es7 elastic4s artifacts — exactly whatelasticDependenciesalready does for
org.elasticsearch:elasticsearch. Verified twice: the classpath re-resolves to onecoherent 2.19.0 set, and the suites that exercise that path run green.
es8 / es9 are unchanged and stay pinned at 8.18.2 / 9.0.0 (lead decision): the latest 8.x (8.19.1)
and 9.x (9.3.0) releases predate #4106 / #4100, so their builder still drops the script — the marker
and handler remain.
Raw-field
STDDEV(salary)is byte-identical on every major (pinned). Every other scripted metric (MAX(YEAR(x)), …) is untouched (pinned: the es8/es9 serializer is byte-identical to the default for any request without a marker, over 7 shapes incl. HAVING/bucket_script/nested).Why the handler cannot key on
ExtendedStatsAggregation— AD-S3-1′ (LEAD-RATIFIED 2026-09-06)The spec's AD-S3-1 named a handler keyed on
ExtendedStatsAggregation: that mechanism is refuted by measurement, not merely superseded — it is unreachable dead code, independently re-derived from the elastic4s 8.18.2 / 9.0.0 sources by the fresh-context reviewer and ratified by the lead. The seam's SHAPE (client-module-injected serializer; template contributes only a discriminator; es6/es7 reject in the client modules) is unchanged; only the handler's key moves, to a marker type.elastic4s's
AggregationBuilderFn(8.18.2, byte-identical in 9.0.0) matches the typed armcase agg: ExtendedStatsAggregation => ExtendedStatsAggregationBuilder(agg, handler)BEFOREcase other => customAggregationHandler(other)— thecustomAggregationseam is consulted only for a type the library does not know. So the bridge binds a transform-bearingextended_stats(both binds: plain:199and windowed:225, es6:200/:226) to a template-owned markerScriptedExtendedStatsAggregation(inner: ExtendedStatsAggregation)— an elastic4sAggregationwhose abstract members are byte-identical on 6.7.8 / 7.17.4 / 8.18.2 / 9.0.0 (javap) — and the es8/es9 handler keys on the marker. The template stays major-agnostic; the seam is aSearchBodySerializertrait injected as an implicit-with-default onrequestToElasticSearchRequestANDsqlQueryToAggregations(both doors), carried onElasticSearchRequest(defaulted last field).SearchBodySerializer.Default(= today's 1-arg builder) REFUSES a marker loudly and by name (it can never emit the script; without the guard elastic4s throwsNotImplementedError("…Please add a PR!")on it — loud on every line, but misleading).Surfacing:
singleSearchToJsonQueryreturns aString, and translation runs synchronously insidesearchAsync/scrollbefore any Future exists — a refusal used to escapeGatewayApi.runas a raw exception (the same route the bridge'srequire(...)failures take). ONE boundary inGatewayApi.run(statement)—catchfor a synchronous throw (every in-tree route) and.recoverondispatchfor a refusal an out-of-tree extension defers into its own Future, so the contract holds for the SPI's callers too (pinned by a deferring-extension test) — the point every route converges on, extension (CoreDqlExtension's quota-capped scroll callsclient.scrolldirectly and never enters an executor) or executor — turns a thrown status-bearingElasticErrorintoElasticFailure(operation = "dql"). The windowed row query's scroll translated its window-aggregation request INSIDEFuture(...)and built its source lazily, so the refusal only failed the stream at materialisation: its translation is now hoisted onto the calling thread (additiveSearchApi.windowAggregationQuery; theprotectedone-argumentexecuteWindowAggregationskeeps its signature). Contract: a refusal known at translation time is answered atrun— on every route this PR owns, i.e. for the extended-stats refusal, executor and extension alike (GatewayRefusalBoundarySpec, 6 routes incl. an extension deferring into its own Future). The claim is universal, and now true for BOTH pre-execution rejection kinds: D-1 is fixed in this PR (see below), so a temporal-literal rejection (#276) takes the same path as the extended-stats refusal. Pinned byGatewayRefusalBoundarySpec(9 cases) over both kinds × the executor, extension-scroll, one-shot, windowed and deferring-extension routes.T2 — reproduction captured on the base commit BEFORE the fix (both bridge copies, both doors, both binds)
After, ES 8/9 (door 1; door 2 identical modulo the wrapper):
After, ES 6/7:
ElasticFailure(400, "STDDEV/VARIANCE over a transformed expression is not supported on Elasticsearch 7: the underlying elastic4s builder drops the aggregation script (elastic4s#4100), so the statistic would silently be computed over the raw field. Aggregate over a raw field, or use Elasticsearch 8+.")— before any JSON exists.Null-safety (lead directive 2026-09-06)
The scripts reaching ES 8/9 through
extended_statsfor the first time are the metric scripts the other aggregates already emit (def param1 = (doc[..].size() == 0 ? null : <chain>); param1,(param1 == null) ? null : Double.valueOf(Math.abs(param1))).AggregationNamingSpec's structural guard (both bridge copies) now walks 9 transform-bearingextended_statsshapes (plain + windowed) off the marker tree; the es8/es9 spec re-checks the rendered JSON.Fifth door found, exempt with a comment
ElasticMultiSearchRequest.query(MultiSearchBuilderFn, default handler) is referenced nowhere in the repository — core builds_msearchbodies from each request's ownsingleSearchToJsonQuery(ElasticQueries.multiQuery), andrequestToMultiSearchRequestbuilds elastic4s'sMultiSearchRequest, not this case class. Exempt with a comment; a marker inside it would still fail loudly on every line (elastic4s'sNotImplementedError).queryToJsonexempt (query-only) as specified.Downstream fixture sweep (AC 8, read-only)
extended_stats/STDDEV/VARIANCE/VAR_POP/VAR_SAMP/std_deviation/ElasticSearchRequest/SearchBodyBuilderFn/singleSearchToJsonQuery/elastic.sql.bridgeacrosssoftclient4es-jdbc,softclient4es-arrow,softclient4es-extensions: 0 hits — nothing downstream pins the moved emission or consumes the bridge directly. Binary-incompatible arity onElasticSearchRequest(new defaulted last field) — downstream rebuilds on0.23.0anyway (same class as BIDC-2 / 21.1).Upstream watch (AC 9, AD-S3-3)
Local record
docs/issues/local-BIDC-3-elastic4s-4100-upstream-watch.md(never staged). All three upstream PRs are MERGED: #4100 (series/9.x), #4105 (series/7.x), #4106 (series/8.x). Trigger 2 is DONE and consumed by this PR — #4105 shipped in elastic4s 7.17.26, which is why ES 7 now computes. Trigger 1 remains open: the latest 8.x (8.19.1) and 9.x (9.3.0) releases predate #4106 / #4100, so the es8 / es9 marker + handler stay until a release carrying them exists. es6 never.Release-note items (0.23.0)
STDDEV,STDDEV_SAMP,STDDEV_POP,VARIANCE,VAR_SAMP,VAR_POPover a transformed expression (STDDEV(YEAR(x)),VARIANCE(ABS(x)), plain orOVER (PARTITION BY …)) now REFUSE with a 400 naming the release. They used to return the statistic of the raw field (or fail at Elasticsearch with an emptyextended_stats). Aggregate over a raw field there, or use ES 7+. (The Single-table MATERIALIZED VIEW rejected with internal error 'Number of stages must be positive' #185 precedent: honest rejection replacing silent acceptance.) The refusal is permanent — the 6.x client library line is unmaintained.com.sksamuel.elastic4s7.17.4 tonl.gn0s1s7.17.26 (a groupId change, 22 patch versions on) — anyone pinning elastic4s themselves alongside this driver must follow, and the es7 closure now excludes elastic4s's own Jackson in favour of the curated set.ElasticSearchRequestgains a defaulted last field (serializer), andrequestToElasticSearchRequest/sqlQueryToAggregationseach gain a defaulted implicit parameter — all three source-compatible, all three binary-incompatible (arity). Downstream rebuilds on0.23.0.failOnWindowError = false) now builds its baseElasticQuerywithsql = Some(baseQuery.sql)like the success arm — one shared, eagerly-built value; the core SQL log line for that arm gains the statement text.GatewayApi.runas anElasticFailure(400) instead of escaping as an exception — REPL, JDBC and Arrow see the message.ElasticFailure(400)fromrun, instead of anElasticSuccesscarrying a stream that fails at materialisation (D-1, folded in by lead ruling). DirectSearchApi.scrollcallers now receive the thrownElasticErrorrather than a failedSource— the same shape the extended-stats refusal always had.dateformat before rendering the query (#276) #289: the rejection was already loud, but only once somebody ran the stream, andrunreported success until then.Docs
documentation/sql/functions_aggregate.md(STDDEV / VARIANCE family — "Transformed operands are per-major" + table),documentation/sql/dql_statements.md(one paragraph),documentation/sql/known_limitations.md(new section). MDX twins to sync by the web maintainer:softclient4es-web/src/content/docs/sql/functions-aggregate.mdx(or wherever the STDDEV family lives),sql/dql.mdx,sql/known-limitations.mdx— not edited here.Test evidence
Evidence (SUPERSEDED — the tree as it stood before the es7 extension and the D-1 fold-in; the authoritative numbers are in "D-1 fold-in — final evidence (head
fc56fb7e)" below):+ softclient4es-sql-bridge/test(template, ES 8 elastic4s)ExtendedStatsEmissionSpec16 + the marker-tree null-safety guard inAggregationNamingSpec)+ es6bridge/test(hand-maintained)softclient4es7-sql-bridge/test,softclient4es8-sql-bridge/test(generated)softclient4es9-sql-bridge/test(generated,sbt17)+ core/testGatewayRefusalBoundarySpec6: SearchExecutor aggregation shape, SearchExecutor LIMIT row, CoreDqlExtension quota-capped scroll, windowed un-LIMITed scroll — all answered atrun—, non-refusing control, plus an extension deferring its refusal into its own Future)es8java/es9javaJavaClientExtendedStatsEmissionSpec(sbt17, Docker-free)NotImplementedErrorpreserved for an aggregation nobody can buildes7rest/es6rest/es6jest*ExtendedStatsRejectionSpec(sbt17, Docker-free)MAX(YEAR(x))keeps its script+ softclient4es-sql-bridge/compile+ es6bridge/compile+ core/compile+++ 2.12.20 <bridge, es6bridge, core, es8java, es7rest, es6rest, es6jest>/Test/compile+++ 2.12.20 softclient4es-core-testkit/compile— 11/11 greenscalafmtCheckAllgreen; CompileheaderCheckgreen on all 9 modules;es7rest/Test/headerCheckred on 12 PRE-EXISTING headerless specs only (none new; every new client-module spec carries the header)Docker (
sbt17, one major per invocation,WindowFunctionSpec= 38 existing + 2 new #222 cases)— snapshot at
398dd5e2, BEFORE the ES 7 scope extension; the ES 7 row is superseded by thepost-extension re-run recorded further down:
es8javaOVER (PARTITION BY department): the same sd per department on every row. The raw-field statistic (STDDEV(salary)19334.15, andhire_datein millis) sits beside them, unchangedes9javaes7restREFUSAL(SUPERSEDED — ES 7 now COMPUTES, see below) —client.run→ElasticFailure(400, "…not supported on Elasticsearch 7 … (elastic4s#4100)…")for the plain AND the windowed statement; the direct API throws the sameElasticErrores6restassume(sample keys need ES 7.7+)es6jestRe-runs at that intermediate base (superseded, kept for the audit trail): bridge template 174/174 ×2 legs, es6 bridge 174/174 ×2 legs, core 873/873 ×2 legs (
+ …/test); generated es7 + es8 174/174 (both invocations); generated es9 (sbt17) 172/174 on the FIRST invocation — the twoIN over an aggregatepins BIDC-2'sb09aaf27rewrote, compared against the stale previous test copy (the documented race) — then 174/174 on the second; es8JavaClientExtendedStatsEmissionSpec8/8; ES 8.18.3WindowFunctionSpec40/40.Falsification: es8 handler's
scriptline removed ⇒JavaClientExtendedStatsEmissionSpec6/8 RED (the two survivors are the parity + injection checks); restored, green.Review:
bmad-code-review's three layers run inline by the implementer (no reviewer sub-agent may be spawned per the run brief) — 4 findings applied during the run (boundary moved fromSearchExecutortoGatewayApi.run; lazy windowed translation hoisted; a test-scope implicit silently falling back to the Default serializer; an over-strict null-safety proxy replaced by verbatim marker-script equality); no open HIGH/MEDIUM. No independent fresh-context reviewer ran on this diff.Post-review re-runs (on
10f3f8fc, after the R3 follow-up commit):scalafmtCheckAllgreen; bridge template 176/176 ×2 legs; es6 bridge 176/176 ×2 legs; core 874/874 ×2 legs; generated es7 + es8 176/176 (both invocations each); generated es9 (sbt17) 176/176 (both); es8 + es9JavaClientExtendedStatsEmissionSpec9/9 each (the R3-1 diagnostic test included);GatewayRefusalBoundarySpec6/6 (the deferring-extension route included); ES 8.18.3WindowFunctionSpec40/40 on real Elasticsearch.Post-extension re-run (ES 7 now COMPUTES, tree
4a6d3bed)ES 7.17.29
es7restRestHighLevelClientWindowFunctionSpec40/40 — VALUES, the same fixtureoracle ES 8 / ES 9 assert (Engineering sd 2.1602 / vs 4.6667 / vp 4.0000 / sdp 2.0000, plain AND
windowed) — the row the snapshot table above records as REFUSAL. Whole es7 tree cross-compiled on
both Scala legs after the
nl.gn0s1smigration;RestHighLevelClientExtendedStatsEmissionSpec10/10replaces the deleted rejection spec.
D-1 fold-in — final evidence (merged tree, head
fc56fb7e)origin/mainmerged in atd1269d29(clean; five files overlap BIDC-4 —ScrollApi,SearchApi,es8/es9
JavaClientApi,dql_statements.md), D-1 fixed atfc56fb7e.scalafmtCheckAllsql/test+ softclient4es-sql-bridge/test(template)+ es6bridge/test(hand-maintained)+ core/testGatewayRefusalBoundarySpecnow 9: 4 extended-stats routes + deferring extension + non-refusing control + 3 new temporal-literal cases)++ 2.12.20 <bridge, es6bridge, core, es8java, es7rest>/Test/compileFalsification (D-1):
Source.failed(error)restored atScrollApi⇒ exactly the three newtemporal-literal cases go red, nothing else; restored, green.
TemporalLiteralSearchSpec's scroll pinwas RETARGETED (not deleted): it used to assert the failure arrives on the stream, it now asserts
runanswers the 400 before any stream exists (client.lastQuery shouldBe None).Symmetry audit (as instructed): every
SearchApicall site ofresolveTemporalLiteralsalreadyreturned
ElasticResult.failure;ScrollApiwas the only one that could not, because it must returna
Source. No otherSource.failed(<status-bearing ElasticError>)remains on a pre-execution path —ScrollApi's other three carryIllegalArgumentException/UnsupportedOperationException/RuntimeException, andfile/package.scala's are IO.Docker, D-1 tree, both specs per invocation (
TemporalLiteralSpec11 +WindowFunctionSpec40 = 51):es8javaes7restes9javaes6restassume: sample keys need ES 7.7+)es6jestAll five majors run, one per
sbt17invocation.origin/main(99d1f438) is an ancestor of thebranch head, so the PR merges into
mainas a fast-forward.🤖 Generated with Claude Code