---
title: Retire the internal rate_type vocabulary from the output-aggregator implementation
type: task
status: completed
description: >-
  The live denominator-CTE builders still sit in a rate-type directory and emit
  rate_type_column_params; rename them onto the output vocabulary while keeping rate_type accepted
  on the request path for shared short URLs.
base_uri: user:task/league/data-views/retire-rate-type-compat-shims.md
created_at: '2026-08-05T18:50:59.014Z'
entity_id: 81dcf3fe-259f-4d77-bad5-7cd3257bd032
finished_at: '2026-08-19T18:25:34.042Z'
observations:
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
owner_identity_uri: █████████████████████████
public_read: true
started_at: '2026-08-19T17:07:22.204Z'
tags:
  - user:tag/league-xo-football.md
updated_at: '2026-08-19T18:25:34.042Z'
visibility_analyzed_at: '2026-11-05T23:49:00.374Z'
---

# Retire the internal rate_type vocabulary

## Problem

The identity migration replaced the `rate_type` plugin system with the `(period, aggregation)` output-aggregator registry, but the `rate_type` name survives inside the implementation. The live rate aggregator still lives in a directory called `libs-server/data-views/rate-type/` with modules named `rate-type-per-*.mjs`, and five of them still emit the literal param key `rate_type_column_params`. Grepping `rate_type` therefore returns a mix of the permanently-supported request param and internal machinery that has no reason to carry the old name.

## Scope reconciliation

The parent plan framed this as "remove the compat shims after one release cycle", naming `libs-server/data-views/rate-type/` and `libs-shared/rate-type-column-param.mjs` for deletion. Neither is accurate today:

- `libs-shared/rate-type-column-param.mjs` is already gone; nothing imports it.
- `libs-server/data-views/rate-type/` is not a shim. It is the live denominator-CTE implementation, imported by `output-aggregator-registry.mjs`, by `register-per-game-cte.mjs`, and by four column-definition files. Deleting it would delete the rate aggregator.

So this is a rename and consolidation, not a deletion. Two things must survive untouched:

- `rate_type` on the **request** path. Shared short URLs carry it permanently, so `normalize-output-param.mjs` and `libs-shared/data-views-output-tokens.mjs` stay.
- The `rate_type_match_column_params` / `rate_type_column_params` key rewrite in `libs-shared/data-views-saved-view-migration.mjs`, which migrates saved views authored before the cutover.

## Plan

- [x] **Rename the denominator-CTE modules to the output vocabulary**
  - Files: `libs-server/data-views/rate-type/` and its five `rate-type-per-*.mjs` modules
  - Purpose: The directory holds period-denominator builders, not a rate-type plugin system. Name it for what it is and align the module names with the canonical period tokens the registry dispatches on.
  - Changes: move the directory, update the importers (`output-aggregator-registry.mjs`, `participation-status-cte.mjs`, `register-per-game-cte.mjs`, and the four column-definition files), update the three specs that import from the path.
  - Done in `6228b175e` as `libs-server/data-views/period-denominator/`, modules `per-game` / `per-player` / `per-player-play` / `per-player-route` / `per-team-play`. The bare `per-<family>` form was already set by `per-team-play-wrap.mjs`, the one file in the directory named recently.
  - A module name tracks the denominator FAMILY, not a period token: `per-team-play` serves seven tokens and `per-player` nine, via the registry's `dispatch_params`. Naming per token was not possible.
  - No exported symbol carried `rate_type`, so only import specifiers moved. All 264 goldens byte-identical and unregenerated (`shasum` equal before and after), 4253 passing, `check-data-view-sql-validity` GATE OK over 4590 statements.
  - Two comments left on the OLD path deliberately: `check-rename-alias-residue.mjs` describes a filter removed 2026-08-06 and what it excluded at that time, and the registry cites the legacy `rate-type/index.mjs` that no longer exists. Both are history; rewriting them would falsify the record.

- [x] **Collapse the `rate_type_column_params` literal onto `output_column_params`**
  - Files: the five denominator builders that hardcode `'rate_type_column_params'`, `libs-shared/get-rate-type-denominator-params.mjs`
  - Purpose: `get-rate-type-denominator-params.mjs` already prefers `output_column_params` and falls back to `rate_type_column_params`. Once the saved-view migrator has rewritten stored views, the internal emitters should write the canonical key only; the read-side fallback stays for un-migrated short URLs.
  - Changes: rename `get-rate-type-denominator-params.mjs` to the output vocabulary; keep its alias fallback.
  - **The premise was wrong on two counts, both corrected before implementing.** Nothing "hardcodes" or emits the key — the five sites were entries in `consumes_params`, a read-set declaration. And the migrator can never rewrite stored views for this: it runs against browser localStorage, so short URLs in the production `urls` table carry the legacy key forever. There is no "once the migrator has rewritten" state to wait for.
  - Done differently and better, in `915c5d833`: normalize the legacy keys at the SERVER request boundary instead of keeping a read-side fallback. `LEGACY_OUTPUT_PARAM_KEYS` is declared once in `data-views-output-tokens.mjs` and consumed by both the client migrator (folded into `PARAM_KEY_RENAMES`) and the server, so the two cannot drift. The fallback is deleted; the file is now `get-output-denominator-params.mjs`.
  - Folding the migrator's two hand-written blocks into the map also gains them its both-keys-present rule and puts both keys in `MIGRATED_PARAM_KEYS`.
  - Required one gate fix. `output_match_column_params` is a CLIENT-side toggle no server registry reads, and it passed `check-saved-view-param-coverage` only because the migrator happened to name it in the deleted block. `libs-shared/output-column-param.mjs` is now declared as the consumer it always was. Proven by control: removing that entry reports the orphan across a live saved view, restoring it clears the gate.
  - Emission-neutral three ways — legacy and canonical requests build byte-identical SQL while an unscoped one differs, both legacy-key goldens match their pre-change `expected_query` byte for byte, and all 264 goldens are unregenerated.

- [x] **Delete the five inert `consumes_params` declarations** (not in the original plan; found while correcting the item above)
  - The five plugins declared `consumes_params`, the registry adapter plumbed it through, and nothing read it — `consumed_params_signature` is called only by `aggregator-rate`, `aggregator-count` and `measure-batch`, each with its own list.
  - Worse than unused: they read as the thing constraining these plugins' CTE identity. Identity is really handled by each plugin's own `get_table_hash`, which folds in the params it resolves.
  - Proven inert by MUTATION, not grep — replacing per-player's whole list with a garbage token leaves all 265 goldens passing. Proven the identity is covered without them — two columns on one period with different `output_column_params` emit two distinct CTE hashes. Shipped in `d8351ea5b`.

- [x] **Rewrite the directory ABOUT.md**
  - File: `libs-server/data-views/period-denominator/ABOUT.md`
  - Purpose: It currently documents an `index.mjs` dispatcher and an `add_rate_type_cte` entry point, neither of which exists. The year-pushdown and materialization invariants it records are still correct and must be preserved through the rewrite.
  - The plan's premise was half stale: the file already said "There is no `index.mjs` here" and named no `add_rate_type_cte`. What was actually wrong was the title, the paragraph claiming the directory "keeps its `rate-type` name only because renaming it has not been done yet", and all five filenames in the file list.
  - Rewritten: retitled to the period-denominator vocabulary, file list corrected to the bare `per-<family>` names, and a Dispatch section added recording the module-keyed registry (19 `(period, 'rate')` tuples across five plugins with `dispatch_params` captured at registration, so a module names a FAMILY and `per-team-play` serves seven tokens). The three author invariants and the appended `consumes_params` paragraph survive verbatim.
  - Swept the dangling PATHS the rename left elsewhere, since each was live instruction pointing at a directory that no longer exists: `libs-server/data-views/ABOUT.md` (two references), `docs/query-builder-function-reference.md`, `docs/data-views-system.md` (eight), `docs/data-view-specs/parameters/values/output-aggregations.json` (five plugin paths), and three source comments. Left alone deliberately: `rate-type` as a VOCABULARY word in prose, the registry's citation of the legacy `rate-type/index.mjs` (history, per item 1), and the `rate-type-cte-year-pushdown-regression.json` fixture name.

- [x] **Verify the goldens are byte-identical**
  - Purpose: This is a pure rename. Every fixture under `test/data-view-queries/` must regenerate unchanged, since the emitted param key change is internal to CTE construction and the CTE name hash must be checked for drift.
  - Confirmed two ways. `git diff --name-only 6228b175e~1 d8351ea5b -- test/data-view-queries/ test/fixtures/` returns ZERO files across all three commits (51 files changed in total), so no fixture was regenerated or hand-edited. And the corpus runs green against the current emitter — 265 passing on `test/libs-server.data-view-queries.mjs`, 264 fixtures, the only failure being the root `afterAll` response-validation hold-out that every no-HTTP-request spec run trips.

## Notes

- Carried out of [[█████████████████████-█████████████-██-████████-████████]] with the scope corrected against code as of 2026-08-05.
- The CTE-name hash consumes the params subset declared by each plugin's `consumes_params`. Renaming an emitted param key can shift a hash and regenerate every affected golden; check this before assuming byte-identity.
