Skip to content

test(objectql): two engine fixtures spell the declared aggregation key function, un-erased (#8112) - #8206

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-8112-aggregation-function-key-fixtures
Aug 12, 2026
Merged

test(objectql): two engine fixtures spell the declared aggregation key function, un-erased (#8112)#8206
os-zhuang merged 1 commit into
mainfrom
claude/issue-8112-aggregation-function-key-fixtures

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #8112

Two engine fixtures passed aggregations: [{ func: 'count', field: 'id', alias: 'n' }]. The declared key on AggregationNodeSchema is function; func exists on no type. Both call sites erased the options bag with as any.

Re-spelled to function: and dropped the as any in the same edit, in packages/objectql/src/engine-count-read-filter.test.ts and packages/objectql/src/engine-filter-tokens.test.ts. Both bags type cleanly with no erasure at all — the removal surfaced no other error in either file.

Before / after, measured per file

The card asserted neither test was wrong in its verdict. Verified, not inherited:

before after
engine-count-read-filter.test.ts 5/5 pass 5/5 pass
engine-filter-tokens.test.ts 26/26 pass 26/26 pass

Both green, both sides — 31/31 across the two files. No verdict changed, and none should have: the engine runs no validation on this member, so the malformed node simply reached the driver double and nothing read it.

What the double actually recorded

A throwaway probe on the same harness drove both spellings through ql.aggregate and logged what driver.aggregate received:

BEFORE (func:)      aggregations=[{"func":"count","field":"id","alias":"n"}]
AFTER  (function:)  aggregations=[{"function":"count","field":"id","alias":"n"}]

The engine passes the array through verbatim — it does not strip the unknown key. So the fixture was inert not because the member was dropped in flight, but because no assertion looked at it.

Each aggregate case now asserts the recorded aggregations, so the fixture reads what it constructs. engine-count-read-filter.test.ts already carried the comment "groupBy/aggregations survive on the same ast" with only groupBy asserted; that is now true as written.

One correction to the card's premise

The card says the aggregation schemas are not .strict(), so func is "dropped, never rejected". Measured, the second half does not hold:

EngineAggregateOptionsSchema.safeParse({ aggregations: [{ func: 'count', … }] })
  success=false   invalid_value at aggregations.0.function

func is dropped as an unknown key, but that leaves the required function missing, so the bag is rejected. Dropped key, rejected bag. The fixture survived only because the engine seam runs no parse — not because the schema tolerates it.

Reverse verification — the direction inverted, and the reason matters

Predicted before running: restore func: with the erasure gone and tsc goes red. It did not — typecheck stayed green.

packages/objectql/tsconfig.json carries "exclude": [… , "**/*.test.ts"], so tsc --noEmit never compiles either file. The type can only speak where it is read: the same bag placed in a compiled non-test file gives the card's exact error,

TS2353: Object literal may only specify known properties, and 'func' does not
exist in type '{ function: "count" | … ; alias: string; field?: string | undefined; … }'

so the type is correct and the corrected bags are genuinely well-typed. But the card's rationale — dropping the erasure is what makes the next mistake visible — does not hold for these two files today, because no tsc program reads them. This is known, ledgered debt, not a new finding: @objectstack/objectql sits in TEST_DEBT at 355 errors for exactly this hidden test layer, and check:type-check-debt reports 20 packages hiding 817 test files repo-wide. No issue filed; it is already tracked.

That makes the added assertions the load-bearing half. Second reverse verification, predicted red and observed red — func: restored in both files, erasure still gone:

FAIL src/engine-count-read-filter.test.ts > aggregate(): the middleware filter reaches driver.aggregate
FAIL src/engine-filter-tokens.test.ts    > aggregate(): placeholders are expanded before grouping
AssertionError: expected [ Array(1) ] to deeply equal [ { function: 'count', …(2) } ]
Tests  2 failed | 29 passed (31)

Restored from the commit, 31/31 green again. The assertions are the only thing in the repo that would now catch a re-introduced func: here.

Ratchet

check:query-options-erasure test surface fell 242 to 240 — exactly the two removed erasures. Ratcheted down with --update and committed here; the diff is one line, nonTest untouched, no key added.

Baseline-race check before writing it: no open PR rewrites this baseline, and none moves its count. All 12 open PRs were diffed against their merge base for scripts/query-options-erasure-baseline.json and eslint.config.mjs (zero hits), then the four with as any churn under packages/ were read line by line — plugin contexts, response bodies, a schema cast, driver.initObjects — none at an engine query-options position.

Gates

Derived with node scripts/pm/dispatch-gates.mjs over the three changed paths, not hand-enumerated.

gate exit
pnpm --filter @objectstack/objectql test (192 files, 3400 tests) 0
pnpm --filter @objectstack/objectql typecheck 0
eslint on both changed files 0
pnpm check:query-options-erasure 0
pnpm check:type-check-debt --re-measure 0
pnpm check:engine-double-contract 0
pnpm check:durability-log-level 0
scripts/check-engine-split-ratio.mjs 0
pnpm check:nul-bytes + control-byte self-scan 0

No baseline or ledger entry was raised. The one baseline that moved went down. check:type-check-debt --re-measure reports 33 entries, none above its recorded number; --lower was not run, and the surplus it notes is pre-existing across 9 other entries.

Changeset

skip-changeset. The root package owning scripts/ is private: true and @objectstack/objectql publishes only dist — neither changed file reaches a consumer, so this releases nothing. That follows #8191 (test-only objectql, skip-changeset) rather than #7846, whose changeset described a correction to matcher behaviour that decided test verdicts; this one changes no verdict, measured above.


Generated by Claude Code

…y `function`, un-erased (#8112)

`engine-count-read-filter.test.ts` and `engine-filter-tokens.test.ts` each
passed `aggregations: [{ func: 'count', field: 'id', alias: 'n' }]`. The
declared key on `AggregationNodeSchema` is `function`; `func` exists on no
type. Both call sites erased the options bag with `as any`, which is the only
reason the wrong spelling compiled.

Re-spell to `function:` and drop the `as any` in the same edit — the erasure is
what hid the mistake, so removing it is what makes the next one visible. Both
bags type cleanly with no erasure at all; the removal surfaced no other error.

Neither test's verdict was wrong before, and this was verified rather than
inherited: 31/31 green before, 31/31 after. The engine does not validate the
member, so the malformed node reached the driver double verbatim
(`[{"func":"count",...}]`) and no assertion read it.

Each aggregate case now asserts the recorded `aggregations`, so the fixture
reads what it constructs — `engine-count-read-filter.test.ts` already carried
the comment "groupBy/aggregations survive on the same ast" with only `groupBy`
asserted.

One correction to the card's premise: the schema does not silently accept the
bag. `func` is dropped as an unknown key (the object is not `.strict()`), but
that leaves the required `function` missing, so `EngineAggregateOptionsSchema`
.safeParse REJECTS it — `invalid_value` at `aggregations.0.function`. Dropped
key, rejected bag; the fixture was inert only because the engine seam runs no
parse.

`check:query-options-erasure` test surface 242 -> 240, baseline ratcheted down
in this commit. No open PR rewrites that baseline or moves its count.

Fixes #8112

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014C8pAprWdmtecFsEprZax4
@vercel

vercel Bot commented Aug 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 12, 2026 8:18pm

Request Review

@os-zhuang os-zhuang added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 12, 2026 — with Claude
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

No hand-written docs reference the 0 changed package(s). ✅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

2 participants