---
title: Take over five unowned loose ends from the betting-market key migration
type: task
status: planned
description: >-
  Five loose ends from the completed betting-market key migration — a silent ratchet fallback, a
  vacuously-passing equivalence fixture, blind check:cluster gates, ~100ms dead work on season-grain
  views, and an unanchored year-split offset window — promoted from a dispatch brief whose session
  is gone. Investigate each and recommend before changing code.
base_uri: user:task/league/data-views/take-over-unowned-betting-market-loose-ends.md
created_at: '2026-10-14T21:36:13.900Z'
entity_id: 2aa4615a-a364-459b-8818-356d2441529a
owner_identity_uri: █████████████████████████
priority: High
public_read: true
relations:
  - relates [[█████████████████████-███████████-███████-██████-███████-███-████████████-███████]]
tags:
  - user:tag/league-xo-football.md
updated_at: '2026-10-14T21:36:13.900Z'
visibility_analyzed_at: '2026-11-05T23:49:03.437Z'
---

## Plan

- [ ] Work the five loose ends below in the stated order; for each, return a reading, a recommendation, and the reasoning.
- [ ] Surface every contested item to the operator for a ruling before changing code; "nothing, and here is why" is a welcome answer on any item.
- [ ] Implement the items the operator approves.

## Context

Promoted 2026-10-14 from `scratch/league/data-view-gate-loose-ends/brief.md` (dispatch brief; the
owning session is gone). Parent: [[█████████████████████-███████████-███████-██████-███████-███-████████████-███████]] (Completed).

Repository: `repository/active/league`. Production is readable through the `mcp__db__query_league` MCP tool.

**Investigate before you build.** The operator's instruction is to research and understand what, if anything, should be done — for each item separately. "Nothing, and here is why" is a legitimate and welcome answer on any of these; so is "smaller than described". Do not treat the list as a work queue with five fixes at the end of it. Come back with a reading of each, a recommendation, and your reasoning, and let the operator rule before you change code on the contested ones. Where an item is unambiguous and cheap, say so and get on with it.

## 1. A ratchet helper falls back silently, so a typo passes the gate

`physical_year_projection` in `libs-server/data-views/physical-season-columns.mjs` maps a registered TABLE NAME to that table's physical year column. Handed anything unregistered — an alias, a CTE name, a misspelling — it falls back to `'year'` and emits a projection for a column that does not exist. The caller satisfies the schema-conformance ratchet and then throws a 42703 at runtime, or worse, silently reads a real `year` column that means something else.

This is not theoretical: a sibling session hit it exactly while complying with advice to route a projection through the helper. Two things to weigh — how many call sites pass something that is not a registered base table, and whether the right answer is to throw on an unregistered name (loud, but it may break legitimate CTE-alias callers) or to make the registry total. Check what the existing callers actually pass before choosing.

## 2. A result-equivalence fixture can pass vacuously

`test/libs-server.data-view-queries-result-equivalence.mjs` projects each fixture's `compare_columns` out of both the executed rows and the oracle. When a named column exists in NEITHER, the comparison is null against null and the fixture passes while asserting nothing.

Mine failed loudly only because the expected side carried real values — which is luck, not a property of the harness. The whole point of this corpus is to catch semantics that valid SQL gets wrong, so a fixture that can pass while comparing nothing is the exact failure shape the repo's verification rule exists for. Likely fix is a guard in the harness: refuse when a `compare_column` is absent from the executed row set. Sweep the existing fixtures for any that are already passing vacuously before adding the guard — and if you add it, red-prove it against a deliberately misspelled column name.

## 3. `yarn check:cluster` cannot give a clean verdict on this host

Three gates skip because the `private` submodule is not checked out, and four more run with no negative control — meaning they would report success even if they could not see. Worth understanding whether this is a real hole or an accepted local-environment limitation with the coverage living on the runner. `docs/guides/gates.md` is the guide, and it carries the repo's standard on negative controls.

## 4. Roughly 100 ms of dead work on every season-grain view

`league 78f4b21a8` moved season-grain team stats onto a per-week attribution wrap. On that path the wrap CTE keys on pid and reads `player_week_teams`, but the source-attach dispatcher still applies the `player_year -> team_year` bridge from its rule's `required_identity_bridges`, so `player_year_teams` — the old season-long majority-vote CTE — is still computed and outer-joined while nothing reads it. Measured at about 100 ms on a three-season production query.

I left it because suppressing it means changing shared join-routing code that ten other columns depend on, and `required_identity_bridges` is a function of `params` only — it cannot see the wrap decision. Judge whether that is worth reshaping or whether 100 ms is the right price for keeping the routing rule simple. `libs-server/data-views/source-attach/dispatcher.mjs` is where the required bridges are applied.

## 5. Year split combined with a multi-year offset window is unanchored

`main_select_string_year_offset_range` in `libs-server/data-views-column-definitions/team-stats-from-plays-column-definitions.mjs` correlates its subquery on the subject key with NO year predicate. Under a year split each row should see its own year's window; instead the subquery pools the whole window into every row. This predates my change and my change did not worsen it — the pre-existing team-grain correlation had the same gap — but no fixture covers the combination, so nothing would report it either way.

Establish first whether the combination is reachable in the interface at all. If it is not, the honest fix may be a refusal rather than a correlation. If it is, a result-equivalence fixture spanning two years with an offset window is the gate, and it should be red-proved before any code moves.

## Working constraints

- **The suite runs on this host only:** `yarn test:isolated <spec...>`. It does NOT work from a git worktree. Read `docs/guides/test.md` first — it documents four ways the suite hangs rather than failing, and a hang looks like a slow run.
- Read `docs/guides/gates.md` before touching a gate, ratchet or negative control, `docs/guides/data.md` before touching a data-view golden, and `docs/create-data-view-test.md` before writing a fixture.
- **Red-prove everything.** A green you have not shown can go red is not evidence. Every gate here is on this list precisely because it fails in the direction that looks like success.
- `yarn verify` gates the PUSH, not just CI, so a lint error blocks every session's push fleet-wide. Run it before committing anything that adds a JSDoc block.
- The working tree is shared with a dozen live sessions. `git commit -- <path>` commits that path's WHOLE working-tree state, so check `git status` for the file first and message the owner if it is not yours. Note that form carries no UNTRACKED file — `git add` a new file explicitly, which I got wrong today and needed a follow-up commit for.
- Never use a backtick inside `git commit -m`; use `git commit -F -` with a quoted heredoc.
- No GitHub issues or pull requests. Do not create entities to hold analysis — report in your response; the thread timeline is the durable record.

## State of the world

The task these came from, `█████████████████████████████████████████████████████████████████████████████████`, is code-complete and DEPLOYED — both hosts and the SPA are at `7f59978b9` as of 2026-09-02 01:45 UTC. So these are not blocking anything, which is exactly why they need someone to decide their fate deliberately rather than by neglect.

Owned elsewhere, do not pick these up: the `player_gamelogs` single-week team flips (session `diagnose-gamelog-team-flips`, thread `86cfc397`), the betting `selection_metric_line` row axis and its interface (`user-base-55`), and live in-play market contamination in the betting dedup (`user-base-79`).

---
Dispatched by: █████████████████████████████████████████

## Key locations

- Source brief: `scratch/league/data-view-gate-loose-ends/brief.md` (being retired; content is above).
- Parent task: `████████████████████████████████████████████████████████████████████████████`
- Dispatched by session `repl-33d1b74a`.
