---
title: Conform every points-added name to the tense, variant and unit standard
type: task
status: completed
description: >-
  Four operator rulings define the points-added naming standard — explicit tense, explicit variant,
  the retirement of "earned", and the retirement of bare "adj"/"adjusted" — and this task owns
  applying it. Names state the pool each number prices against. Covers columns, data-view field ids
  (a saved-view contract), labels, map keys, the API surface and SQL-bearing prose.
base_uri: user:task/league/conform-points-added-vocabulary.md
created_at: '2026-08-05T22:21:51.978Z'
entity_id: 1a17d953-8ff5-45e3-8ee8-81b6ec39809d
finish_by: '2026-08-06T00:00:00.000Z'
finished_at: '2026-08-18T06:20:19.313Z'
observations:
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
owner_identity_uri: █████████████████████████
priority: High
public_read: true
relations:
  - follows [[████████████████████████████████-█████-████████████]]
  - relates [[user:task/league/rename-adjusted-valuation-columns.md]]
  - relates [[████████████████████████████-██████-█████-███████████]]
  - relates [[███████████████████████████-████-██-██████-███████████]]
  - precedes [[████████████████████-█████-███████████-████████-██████-████████]]
tags:
  - user:tag/league-xo-football.md
  - user:tag/software-task.md
updated_at: '2026-08-18T06:37:44.009Z'
visibility_analyzed_at: '2026-11-06T00:17:43.609Z'
---

## Overview

Apply the points-added naming standard to every surface it reaches, so a name carries its tense, its variant and its unit wherever it travels.

**What changes when this is done.** A reader of any column, data-view field id, label or map key can tell without opening the producer whether a number is realized or projected, positive or net, points or cap dollars. Today none of those is recoverable from the name, and the same screen shows both populations.

**Ordering.** The DDL half rides the [[███████████████████████████-████-██-██████-███████████]] cutover; this task's non-DDL sweep deploys immediately ahead of that apply. [[████████████████████-█████-███████████-████████-██████-████████]] coins a new variant token and must land after this task settles the grammar. [[user:task/league/rename-adjusted-valuation-columns.md]] owns the two cap-savings columns and consumes the naming rulings recorded here.

## Background

The standard is [[████████████████████████████████-█████-████████████]] § "Name the tense, the variant, and the unit":

```
dollars:  <tense>_<variant>_salary          realized_net_salary
points:   <tense>_points_added_<variant>    projected_points_added_net
```

- **Tense** (`realized` / `projected`) is always explicit; a bare `points_added` is never acceptable on a column, field name, label, or map key (ruling 2026-08-05).
- **Variant** (`positive` / `net`) is always explicit; never imply positive by absence (ruling 2026-08-04).
- **`earned` is retired** in both its meanings — it read as tense while meaning variant.
- **Bare `adj` / `adjusted` is retired** (ruling 2026-08-05). An adjusted quantity must name what it was adjusted against. Two live columns carry the token and adjust against entirely different things, which is the failure the ruling exists to prevent.

### Measured corpus

Counted against the tree 2026-08-05, not inferred:

| surface | count |
| --- | --- |
| DDL side (`db/schema.postgres.sql`, `db/migrations`) | 80 occurrences |
| non-DDL side (`app/`, `libs-shared/`, `libs-server/`, `scripts/`, `test/`) | 837 occurrences |
| columns still carrying an old spelling | 38 across 8 tables |
| `earned`-family columns | 23 |
| `market_salary` | 333 occurrences across 51 files |
| data-view field ids carrying `earned` | 14 |

The `earned` family spans a domain the projection tables do not touch — `league_format_player_careerlogs` (52,884 rows), `league_format_player_gamelogs` (3.67M), `league_format_player_seasonlogs` (235,811), `league_team_player_seasonlogs` (2,949), `roster_asset_holding` (4,954).

`earned` carries two unrelated senses that must not be conformed to one token: the points variant (`points_added_earned`) and cap dollars (`earned_salary`). They are distinct columns sitting side by side on careerlogs and seasonlogs.

### Assumptions

- **`ALTER TABLE RENAME COLUMN` is catalog-only and does not rewrite storage.** Checked: Postgres updates `pg_attribute.attname`; the 1.46 GB gamelogs table renames in milliseconds under a brief `ACCESS EXCLUSIVE` lock. This is what makes folding every rename into one window cheap.
- **No PL/pgSQL function body references these tokens.** Checked: all 13 `plpgsql` functions in `pg_proc` queried, zero matches. The hazard class is real; the live count is zero.
- **No Immutable `Record` carries these keys.** Checked: `app/core/players/player.js:140` returns `new Map(params)`, and a `Map` does not drop undeclared keys. Every `Record(` declaration under `app/core/` and `app/views/` was searched.
- **`salary_adj_pts_added` is non-negative on every table.** Checked: `libs-shared/calculate-prices.mjs:131` floors it at zero for every aggregate key, net included. Consequence: `salary_adj_points_added_net` names a net variant while carrying a floored value — its name contradicts its contents.
- **`market_salary` and `earned_salary` are the same arithmetic at different tenses.** Checked: `libs-shared/calculate-prices.mjs:85-140` writes `player.market_salary[aggregate_key]` regardless of tense; `scripts/calculate-points-added.mjs:208` reads `earned_salary` off `market_salary.earned`.
- **`market_salary` has no external contract exposure.** Checked: `api/swagger/config.mjs` has zero mentions; the only API route reading it validates bids server-side.
- **The saved-view column-id rename mechanism EXISTS.** Checked against `libs-shared/data-views-saved-view-migration.mjs`: `COLUMN_ID_RENAMES` is a declarative `old_id: new_id` map carrying 41 entries, `apply_column_id_rename` is exported for short URLs, and `migrate_column_entry` consults the same map for saved views. The earlier "no map, only a hardcoded regex special case" claim is stale — it was inherited from the redesign task and never checked. This task ADDS entries; it does not author the mechanism.
- **`migrate_column_entry` does a SINGLE lookup and does NOT chain.** Checked at the map lookup inside `migrate_column_entry`: one `hasOwnProperty` hit, one substitution, no re-entry. Every value written into `COLUMN_ID_RENAMES` must be the id the registry carries TODAY. A value pointing at an intermediate spelling resolves to a dead id and renders a BLANK CELL rather than raising. Chaining is legitimate for PARAM keys only, and only when the second rule sits in a map merged LATER in `PARAM_KEY_RENAMES`.
- **A column-id rename target IS verified, as of league `de141c3a9`.** The claim that nothing checks it was true when written and is now closed: `test/data-views-saved-view-migration.spec.mjs` § "rename-map target liveness" resolves every `COLUMN_ID_RENAMES` value through `apply_column_id_rename` against `libs-shared/data-view-fields-index.mjs`, and pins the entry count. Both assertions were proven able to fail. Two consequences for this task: a dead target now fails the suite rather than reaching production as a blank cell, and **the pinned count must be raised deliberately in the same change** that adds entries — the count is at 41 and adding entries without moving it fails the suite.
- **No existing rename-map entry targets any id this task renames.** Checked: zero hits for `pts_added`, `points_added_earned`, `earned_salary` or `market_salary` anywhere in the migration file. The entries this task adds are pure additions, with nothing to repoint.
- **This task touches no `nfl_plays` param registry key.** Checked: zero hits for any of this task's tokens in `libs-shared/nfl-plays-column-params.mjs`. So the param-map machinery — the per-batch `*_PARAM_RENAMES` map, its pinned rule count, the exported-map total — is out of scope here. Only `COLUMN_ID_RENAMES` is in play.
- **`process-projections` runs hourly at :30.** Checked: `server/crontab-main/league-imports.cron:54`.

## Design

### Target names

Names state the **pool** each number prices against, not the mechanism that produces it. Mechanism names read as jargon and do not survive travel; the pool is the only thing that actually differs between the members of each set.

| today | becomes |
| --- | --- |
| `pts_added` (projection-value tables) | `projected_points_added_<variant>` |
| `points_added_earned` and every derived form — `_per_game`, `_rank`, `_position_rank`, `_per_game_rank`, `_per_game_position_rank`, `_first_season` through `_first_five_seasons`, `_second_season`, `_third_season` (gamelogs, seasonlogs, careerlogs) | `realized_points_added_positive…` |
| `points_added_net` and its derived forms on the same three tables | `realized_points_added_net…` |
| `best_season_points_added_earned_per_game` | `best_season_realized_points_added_positive_per_game` |
| `pts_added_earned_optimal` / `_rostered` / `_started` (`league_team_player_seasonlogs`) | `realized_points_added_positive_optimal` / `_rostered` / `_started` |
| `realized_pts_added_earned_through_termination` (`roster_asset_holding`) | `realized_points_added_positive_through_termination` |
| `earned_salary` (seasonlogs, careerlogs) | `realized_positive_salary` |
| `best_season_earned_salary` (careerlogs) | `best_season_realized_positive_salary` |
| `points_added_net_cap_dollars` (seasonlogs) | `realized_net_salary` |
| `market_salary` | `projected_positive_salary_at_full_cap` |
| `market_salary_adj` | `projected_positive_salary_at_available_cap` |
| `salary_adj_pts_added` | `projected_points_added_positive_including_cap_savings` |

The last three carry the 2026-08-05 `adjusted` ruling. What each actually computes, read from the producer rather than the name:

- `market_salary` prices against the **whole discretionary cap** (`libs-shared/calculate-prices.mjs:102`).
- `market_salary_adj` prices against **money teams can still bid** — each team's `availableCap − min_bid × availableSpace`, positive teams only, over the points of unrostered players (`scripts/process-projections.mjs:391-397, 463-472`). A rostered player is priced as if he re-entered the pool.
- `salary_adj_pts_added` adds the **points his cap savings could buy elsewhere** to his own points (`libs-shared/calculate-prices.mjs:119-133`). Savings add, so the number rises for cheap players.

`after_salary` and `net_of_salary` were rejected: both imply subtraction, and `net` is a reserved variant token — reusing it recreates the two-axes-in-one-token failure that retired `earned`.

### The cap-savings columns have four competing spellings on record

Recorded here so none resurfaces as precedent. [[user:task/league/rename-adjusted-valuation-columns.md]] owns execution:

- `contract_adjusted_pts_added` — that task's own target, self-flagged stale, and inaccurate: `player.value` is a single-season salary, not a multi-year contract.
- delete — this task's former position on `salary_adj_points_added_net`, now handed over.
- "reconcile to one spelling under the rule" — `projection-rest-of-season-redesign.md:943`, leftover pre-split content.
- `salary_adj_points_added_positive` — `db/adhoc/2026-08-04-add-net-salary-adj-to-league-period-projection-values.sql:25-30`, a live artifact from a Completed task.

### Knowledge Base Impact

- [[████████████████████████████████-█████-████████████]] § "Name the tense, the variant, and the unit" gains the `adjusted` retirement and the name-the-pool rule. Without it the next author reintroduces `adj`, exactly as the adhoc SQL reintroduced a fourth spelling.
- No new domain entry doc; the guideline is the canonical home.
- No scratch slug — the corpus measurement fits in the task body and the sweep is a code change.
- No orchestrator — this is one bounded task, not a multi-week program.

## Plan

### Measurement and target settlement

- [ ] Re-measure the corpus at execution time against the counts in Background. Verify: report per-surface counts and diff them against the Background table; a divergence means the tree moved and the plan's inventory needs re-derivation before any sweep.
- [ ] Separate bare-name contamination from genuine call sites. Treat a token as contaminated when it appears outside the points-added domain. Verify: for each excluded occurrence, cite the file and the domain it belongs to — an unexplained exclusion is a missed call site.
- [ ] Settle the target spelling for every name against the Design table, reading the producer for any name whose variant is not explicit. Verify: for each settled name, cite the producer line establishing its variant. Do not guess.
- [ ] Confirm each renamed data-view field id against currently-live registry keys for a collision of the `box_defenders` / `box_ngs` shape documented in the migration file's header. Verify: zero collisions, or a dated one-shot authored for each.

### Sweep and deploy

- [ ] Add a `COLUMN_ID_RENAMES` entry to `libs-shared/data-views-saved-view-migration.mjs` for every changed field id. The map already exists — add to it, do not author a new one. Verify: `node db/gates/check-data-view-sql-validity.mjs` passes — goldens, suite and lint all pass over invalid emitted SQL, so this is the only gate that sees a broken field id; AND `test/data-views-saved-view-migration.spec.mjs` § "rename-map target liveness" is green, which resolves every added value against `libs-shared/data-view-fields-index.mjs` and fails on a dead target — raise its pinned entry count from 41 in the same change, or the suite fails on the additions themselves.
- [ ] REGENERATE the data-view goldens that carry a renamed id — do not sweep them textually. Eight files under `test/data-view-queries/` name this task's tokens today; re-measure at execution time. Verify: pass the DB env to `scripts/update-data-view-snapshots.mjs` and name each fixture explicitly rather than running it unfiltered, then check the updated file count against `git status -- test/data-view-queries/` and revert anything unintended. A textually-patched golden still FAILS: the `m_<hash>` metric alias derives from the column expression and moves with the rename, and `test/utils/compare-queries.mjs` positionalizes only 32-char table-alias hashes, leaving the 12-char `m_` hash compared literally. Seven goldens failed exactly this way in the `recv` batch. Separately grep each fixture's `setup` block for `INSERT INTO` naming an old column, since regeneration does not touch those.
- [ ] Sweep the non-DDL surfaces and commit. Verify: `node db/gates/check-renamed-column-consumers.mjs` reports no consumer reading a name the schema will not carry.
- [ ] Deploy with `yarn deploy:all` — the change spans backend and SPA, and a partial deploy is the documented recurring failure here. Verify: `ssh <host> "cd <path> && git rev-parse --short HEAD"` on `league`, the second production host and `████████████` per `league/CLAUDE.md:370-374`; all three must match the deployed commit.

### Handoff

- [ ] Hand the column-rename set to the projection cutover for its apply window, including the `earned` tables. Verify: the cutover's plan text names every column in the Design table above; a column absent there has no apply window.

### Testing and validation

- [ ] Run `node db/gates/check-saved-view-param-coverage.mjs` against production. Verify: no orphaned ids, AND a negative control — introduce a known-bad id and confirm the check fails. The check has a documented false-green history on comment tokenizing and short keys; never accept a green without the control.
- [ ] Run `node db/gates/check-data-view-url-param-coverage.mjs`. Verify: no orphaned params. URL query strings carry no version field and never enter the migration chain, which is why a rename forgets this surface specifically.
- [ ] Watch the next scheduled `process-projections` run (hourly at :30). Verify: row counts written for the affected columns are non-zero and match the prior run's order of magnitude. Exit code 0 is not evidence of a write — a caught exception and a silent no-op both exit 0.
- [ ] Re-check `pg_proc` for function bodies referencing the renamed tokens. Verify: zero rows. Currently zero, but `ALTER TABLE RENAME COLUMN` does not rewrite stored function text, so this must be re-run against whatever the apply window actually lands.

### Knowledge base updates

- [ ] Add the `adjusted` retirement and the name-the-pool rule to [[████████████████████████████████-█████-████████████]]. Verify: the guideline states what an adjusted name must disclose, and a reader can derive `at_full_cap` versus `at_available_cap` from it without this task.

## Traps

- Unqualified object-literal predicates (`.where({ pts_added })`) read as local variables and do not grep like column references.
- Object-shorthand insert keys are column references. `scripts/process-projections.mjs:337` and `scripts/process-projections-for-league-format.mjs:94` build rows with shorthand, so the JS key IS the column name — renaming one before the DDL lands breaks the insert.
- String-literal column references do not grep like the object-literal form and are the same hazard class: `scripts/generate-tag-board.mjs:119`, `libs-server/roster-asset-lineage/compute-snapshots-bulk.mjs:198`, `libs-server/record-league-format-projection-value-history.mjs:60`.
- `onConflict([...])` target lists are column references that do not look like ones. The failure mode is a committed delete plus a thrown insert, leaving the table EMPTY rather than stale. No current target list keys on `pts_added` itself, but the pattern is live at `process-projections.mjs:258,280,570,584,601,617`.
- The API surface carries these names — `api/swagger/config.mjs` and `api/routes/players.mjs`. Renaming there changes a contract, not internal plumbing.
- A rename the SPA reads needs its frontend deploy sequenced immediately behind the apply, not at cluster end. The Immutable `Record` silent-drop hazard does NOT apply here — `player.js` uses a plain `Map` — so the risk is a stale bundle, not an undefined key.
- `ALTER TABLE RENAME COLUMN` does not rewrite PL/pgSQL function bodies, which are stored as text. Zero live instances today; re-check `pg_proc` against what the apply actually lands.
- `docs/` prose decays independently of the code corpus and needs its own sweep pass. No gate reads it.
