Uh oh!
There was an error while loading. Please reload this page.
fix(spec): narrow StrategyContext.executeAggregate aggregations[].method to AggregationFunction - #12937
Conversation
…hod to AggregationFunction Maintainer ruling 2026-08-28 (option A, census-first). The engine contract (IDataEngine.aggregate -> AggregationNodeSchema.function) and the analytics strategy contract described the same slot with two types: a closed six-value enum on one side, bare string on the other. One slot, one declaration: method now carries the spec's own AggregationFunction (count | sum | avg | min | max | count_distinct). The #11833 runtime parse-and-refuse in the bridge stays as defence in depth. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
…ategy's aggregation locals The spec narrowing surfaces exactly two TS2322s at the strategy's ctx.executeAggregate call sites (measured against a BASE-spec baseline: error sets differ by only these two). The local aggregations annotations and resolveMeasureAggregation's return type now carry the enum; the alias path proves it by the existing equality guard (no cast), the direct path asserts it with prose keeping the documented no-allowlist posture for host-drift cubes. Runtime behaviour unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
…entry) The adr-0087 gate's own verdict: a changeset carrying a FROM->TO prescription must register its migration. Semantic entry (D3): a TS interface member has no authored document or sys_metadata row to rewrite, so the ledger entry plus the compile error are the upgrade channel. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
…gregation-method-narrowing
…gregation-method-narrowing
📓 Docs Drift CheckThis PR changes 2 package(s): 5 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 126 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 6a548d0353951ae4f30df6063eb43ed80131c09c && git checkout 6a548d0353951ae4f30df6063eb43ed80131c09c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6228c1f0ef3aad1946763b543e9fb207b8bf7de2 7ed016475dae78eec1bbe92dc28212d9186c5975 && git checkout -B drift-repro 6228c1f0ef3aad1946763b543e9fb207b8bf7de2 && git merge --no-ff 7ed016475dae78eec1bbe92dc28212d9186c5975
node scripts/docs-audit/affected-docs.mjs --json 6228c1f0ef3aad1946763b543e9fb207b8bf7de2
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#12776
Maintainer ruling 2026-08-28 (comment 5448715921, option A, census-first):
StrategyContext.executeAggregate'saggregations[].methodnarrows from barestringto the spec's ownAggregationFunction(count | sum | avg | min | max | count_distinct,data/query.zod.ts) — the same closed vocabularyIDataEngine.aggregatedeclares for the identical slot (AggregationNodeSchema.function). One slot, one declaration; the #11833 runtime parse-and-refuse in the bridge stays as defence in depth (untouched, per the ruling).Census (the ruling's hard precondition — measured BEFORE the narrowing landed, verdict: zero illegal)
Population: repo-wide grep for
executeAggregateover all tracked TS (excluding dist), then every hit classified.Producers (call sites filling
method):ObjectQLStrategy.executemain path — values fromresolveMeasureAggregation: (a) direct measures emitdirect.typeafter the A custom-SQL measure reachesengine.aggregateun-refused on the ObjectQL path and answers a silentnull— the repair #12053's probe scoped #12209EXPRESSION_METRIC_TYPESrefusal (number/string/booleanrefused), andAggregationMetricTypeminus those three is exactly the six enum values; (b) the alias path requires membership in the hard-coded six-element list ANDcandidate.type === typeequality; (c) fallback emits the literalcount. LEGAL.ObjectQLStrategy.resolveFkAttr(line ~1088) andplugin.tsdisplayField fetch (line ~508): literalcount. LEGAL.method:literals across service-analytics, rest, runtime, qa): onlycount/sum/avg/min/max/count_distinctobserved. LEGAL.CubeSchema(host drift) could carry an out-of-enum type through the direct path —resolveMeasureAggregation's own comment declares this deliberately unblocked (the engine's undeclared-500 tier owns it, refusing here would re-blame the caller with a 400). Runtime posture preserved exactly.Implementors (functions assigned into
StrategyContext.executeAggregate): the plugin auto-bridge (which already parsesmethodwith the enum at runtime, from #11833),analytics-service.ts's config wiring, and roughly 45 test fakes across service-analytics, rest (3 files) and runtime (1 file). All acceptmethodatstring,any, or ignore the options — a handler accepting a superset stays assignable to the narrowed member (parameter contravariance), so implementors are source-compatible by construction. Verified empirically: zero fixture-side TS errors appeared (below).Measured downstream impact (baseline-controlled)
tsc --noEmiton@objectstack/service-analyticswas run twice against rebuilt spec dist — once with the BASE contract (mutation confirmed on disk and in dist, restore proven by emptygit diff HEAD), once narrowed. The error sets differ by EXACTLY two lines: TS2322 at the strategy's twoctx.executeAggregatecall sites, whose localaggregationsannotations still saidstring. 10 pre-existing test-hygiene errors are byte-identical in both runs (that package has notypecheckscript, so no CI gate reads them; noted for the PM as a finding candidate).Those two errors are the narrowing working, and this PR carries the mechanical consumer fix in
objectql-strategy.ts(a DECLARED deviation from the dispatch file surface, judged the anticipated mechanical-breakage allowance): the two localaggregationsannotations andresolveMeasureAggregation's return type now carry the enum. The alias path proves it cast-free via the existingcandidate.type === typeguard (the six-element list becomesas constand the guard-proven-equal loop variable is returned); the direct path takes one type assertion with prose keeping the documented host-drift posture. Runtime behaviour byte-identical — after the fix the tsc error set is again identical to baseline (10 pre-existing, 0 new, 0 gone).ADR-0087 (per the gate's own verdict, as ruled)
The gate refused
not-requiredbecause the changeset carries a FROM-TO prescription, and demanded registration. Registered: semantic entrystrategy-context-aggregation-method-narrowed(entries/semantic/18.…, registry regenerated bygen:migration-registry, self-check green). Changeset is breaking-grade prose on aminorbump per the launch-window convention (check-changeset-no-majorgreen), FROM-TO stated, on@objectstack/spec(minor) and@objectstack/service-analytics(patch).Verification
Union at
ed2bd44dd(post-merge of main at 750fff5): spec build + typecheck green; spec tests 440 files / 11646 passed; service-analytics build green, tests 83 files / 1805 passed, tsc reading identical to baseline; consumer readings — rest fixture tests 3 files / 54 passed pluscheck:test-typecheckOK ("test layer compiles"), runtime fixture test 1 file / 11 passed (that fixture types options asany; no gate typechecks runtime tests — stated as not-measured, safe by contravariance). Gate families derived bydispatch-gates.mjsat the branch tree: 26 matched families run green, plus changeset-conditional families and the migrations-triggeredcheck:spec-changes/check:upgrade-guide/check:query-options-erasure— all green.check:api-surface: measured UNCHANGED (gen produced zero diff — the dispatch expectation that it would move is falsified; the surface tracker does not fingerprint this inline options member). Two gates PREREQUISITE NOT MET locally, no reading either way, CI owns them:check-dev-prereqs(37 unrelated app packages have no local dist in this partial-build worktree) andcheck-half-states(no GitHub API credential in the container). After main moved a second time (docs/PM-tooling only, no path intersection), merged again and re-ran the ratchet set at7ed016475: api-surface, authorable-surface, migration-registry, strictness-ledger, spec-changes, upgrade-guide, adr-0087, no-major, empty-changeset, nul-bytes — 10/10 green; test suites not re-run for that merge (incoming paths touch neither package — declared narrowing).Clause-② applies (path limb and content limb): this PR stays DRAFT for the contract-review chain; the review chain owns enqueue.
Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
Generated by Claude Code
Generated by Claude Code