Skip to content

fix(service-analytics): narrow the executeAggregate config mirrors' aggregations[].method to AggregationFunction - #13361

Merged
os-elon merged 1 commit into
mainfrom
claude/issue-12940-analytics-method-mirrors
Aug 30, 2026
Merged

fix(service-analytics): narrow the executeAggregate config mirrors' aggregations[].method to AggregationFunction#13361
os-elon merged 1 commit into
mainfrom
claude/issue-12940-analytics-method-mirrors

Conversation

@os-elon

Copy link
Copy Markdown
Collaborator

Fixes#12940

Type-only. #12776 narrowed StrategyContext.executeAggregate's aggregations[].method from string to the six-value AggregationFunction; this brings @objectstack/service-analytics' own consumer-local mirrors of that same slot into line, so the compile-time vocabulary reaches the people who write a custom bridge instead of stopping at the package boundary.

Sites: three, not two

The card enumerated two. Deriving the population from the tree (grep -rn 'method:' packages/services/service-analytics/src) found three declarations of the slot, all narrowed here:

sitewhat it is
plugin.tsAnalyticsServicePluginOptions.executeAggregatethe declaration an app author's own bridge is typed against
analytics-service.tsAnalyticsServiceConfig.executeAggregatethe config twin whose comment claims lockstep with the contract
plugin.tsparseEngineAggregateFunction(method, alias)the auto-bridge's runtime parse, the third place a reader was told the vocabulary is open

Producers were already narrow before this PR (ObjectQLStrategy's aggregation locals carry AggregationFunction end to end since #12776), so this closes the split rather than opening a new one.

Prose repaired

Verification (all at 978a4a9)

  • pnpm --filter '@objectstack/service-analytics^...' build then pnpm --filter @objectstack/service-analytics typechecktsc --noEmit, exit 0.
  • pnpm --filter @objectstack/service-analytics exec vitest run --maxWorkers=283 files, 1805 tests passed.
  • The typecheck really covers the test mirrors: tsc --noEmit --listFiles puts 81 test files from src/__tests__ in the program, including all nine that carry their own { field: string; method: string; alias: string } doubles. Those nine needed no edit — a handler accepting method: string accepts a superset and stays assignable (parameter contravariance), exactly as the finding: StrategyContext.executeAggregate declares aggregations[].method as string while the engine contract declares the six-value AggregationFunction #12776 changeset predicted for implementors. No any, no @ts-expect-error, no widening, and nothing excluded from the tsc program.
  • Reverse verification A (caller position) — a temporary probe filling method: 'median' against both config mirrors: tsc exit 2 with TS2322: Type '"median"' is not assignable to type '"min" | "max" | "count" | "sum" | "avg" | "count_distinct"' at both sites, while a sibling method: 'sum' call stayed clean. Probe removed; git diff HEAD empty; tsc exit 0.
  • Reverse verification B (ablation of the narrowing) — widening the plugin mirror back to string turned tsc red at plugin.ts(346,52), TS2345: Argument of type 'string' is not assignable to parameter of type ..., i.e. at the parseEngineAggregateFunction call. That is what makes the third site load-bearing rather than decorative. Mutation confirmed on disk by anchored grep -c (1 widened / 0 narrowed) and by git hash-object; restored with git checkout HEAD -- and proven byte-identical to the HEAD blob, git diff HEAD empty, tsc exit 0 again.
  • Gates derived at edit time from the actual changed set with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (26 matched families plus the convention-triggered check:dispatcher-error-vocabulary) — all green, including check:type-check-coverage (OK - 66/78 workspace packages type-checked), check:test-source-alias, check:type-source-resolution, check:cross-package-test-inputs, check-adr-0087-registration (this PR adds no declared-breaking changeset), check-empty-changeset, check-changeset-no-major.
  • Not measured locally, named rather than counted as passes: check-test-completeness.mjs and scripts/pm/check-half-states.mjs both exit 3 = PREREQUISITE NOT MET in this container (no test log to hand the first; no valid GitHub credential for the second). check:type-check-debt exits 1 only in its --re-measure leg, which refuses to measure without a built closure for 48 workspace dependencies; its --self-test and coverage legs passed. That refusal cannot hide a ratchet red from this diff: no DEBT entry depends on service-analytics, and of the three TEST_DEBT entries that do (runtime, cli, verify), only runtime references the narrowed surface at all — one test double typed options: any. CI builds the closure before the step, as lint.yml does.
  • Lint, narrowed and declared: eslint --no-inline-config --format json over the 2 changed files, 0 errors / 0 warnings. The narrowing is a measurement, not a skip: this repo runs one eslint.config.mjs which, by its own documented and positively-controlled statement, "never enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules) for ANY file", so a declaration change here cannot move a verdict on a file it does not edit.

Changeset: patch for @objectstack/service-analytics.


Generated by Claude Code

…ggregations[].method to AggregationFunction
#12776 narrowed StrategyContext.executeAggregate's aggregations[].method from
`string` to the six-value AggregationFunction enum, but this package's own
consumer-local mirrors of that slot kept declaring `string`, so a custom-bridge
author still typed their handler against the open vocabulary.
Narrows all three sites the tree carries: the AnalyticsServicePluginOptions
mirror (plugin.ts), the AnalyticsServiceConfig twin (analytics-service.ts) and
parseEngineAggregateFunction's parameter, and repairs the two comments that
explained the auto-bridge's runtime parse by pointing at the old `method:
string` declaration. The parse itself is untouched: with both ends of the
`method` -> `function` rename now declaring the same enum, it is defence in
depth behind a compile-time check rather than the only check.
Type-only; no runtime change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WkdHQwHr2KQmaX7P1BHzi
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/service-analytics, touching 3 documentable anchor(s).

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/plugins/packages.mdx(via AnalyticsServicePlugin (symbol))
What this run could not see
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 8 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json e5ed9434365e6a7520b5070da441f0552030b37dpackageMentionDocs.

Which tree this was computed on

This run read content/docs from 864525b3b12f52c693b27c0b3b8e01aec2e3d96e — the merge of head 978a4a9abb79e731d905ba468aea0a2a37d4f01d into base e5ed9434365e6a7520b5070da441f0552030b37d, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 864525b3b12f52c693b27c0b3b8e01aec2e3d96e && git checkout 864525b3b12f52c693b27c0b3b8e01aec2e3d96e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e5ed9434365e6a7520b5070da441f0552030b37d 978a4a9abb79e731d905ba468aea0a2a37d4f01d && git checkout -B drift-repro e5ed9434365e6a7520b5070da441f0552030b37d && git merge --no-ff 978a4a9abb79e731d905ba468aea0a2a37d4f01d
node scripts/docs-audit/affected-docs.mjs --json e5ed9434365e6a7520b5070da441f0552030b37d

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs e5ed9434365e6a7520b5070da441f0552030b37d → pass the list as
args.docs, on the commit named under Which tree this was computed on.

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

Labels

documentationImprovements or additions to documentationsize/mtooling

Projects

None yet

2 participants

@os-elon@claude