---
title: Rename the two 'adj' valuation columns after what they mean
type: task
status: completed
description: >-
  market_salary_adj and salary_adj_pts_added spell different concepts with one adj token: the first
  is a live free-agent clearing price against money teams can still bid, the second is points added
  plus what the cap savings could buy elsewhere. Renames both to the operator-approved pool names
  across schema, server, SPA and four data-view ids, drops two columns whose names assert something
  false, and rides the projection cutover DDL window rather than opening a second one.
base_uri: user:task/league/rename-adjusted-valuation-columns.md
created_at: '2026-08-02T21:34:06.821Z'
entity_id: 41427d1b-c719-4e3c-b729-e0f2f4004618
finish_by: '2026-08-06T00:00:00.000Z'
finished_at: '2026-08-18T06:18:58.566Z'
observations:
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
owner_identity_uri: █████████████████████████
priority: High
public_read: true
relations:
  - relates [[████████████████████████-██████-█████████-███████-██████-█████████████]]
  - relates [[███████████████████████-█████-█████████-████████]]
  - relates [[user:task/league/conform-points-added-vocabulary.md]]
  - follows [[████████████████████████████████-█████-████████████]]
  - relates [[████████████████████████-██████-█████-██-██████-███████]]
tags:
  - user:tag/league-xo-football.md
  - ███████████████-██████-███████████
updated_at: '2026-10-26T05:00:38.003Z'
visibility_analyzed_at: '2026-11-06T00:18:00.608Z'
---

## Overview

Two columns spell different concepts with the same `adj` token, and neither name says what it was adjusted against. Rename both, rename the four data-view ids over them, and drop two columns whose names contradict their contents.

Target names, operator-approved 2026-08-05:

| current | what it computes | target |
| --- | --- | --- |
| `market_salary_adj` | prices against money teams can STILL BID — each team's `availableCap - min_bid * availableSpace`, positive teams only, over the points added of unrostered players. A live free-agent clearing price. | `projected_positive_salary_at_available_cap` |
| `salary_adj_pts_added` | his points plus the points his cap savings could buy elsewhere. A roster-spot quantity, not a player quantity. | `projected_points_added_positive_including_cap_savings` |

The approved principle is **name the pool each number prices against, not the mechanism**. Mechanism names read as jargon and do not survive travel; the pool is the only thing that actually differs between members of the set. `market_salary` → `projected_positive_salary_at_full_cap` is the third member and is owned by [[user:task/league/conform-points-added-vocabulary.md]]; `at_full_cap` versus `at_available_cap` is a minimal pair, and `availableCap` is the codebase's own term (`roster.availableCap`).

Drop set — two columns whose names assert something false, dropped rather than conformed:

- `salary_adj_points_added_net` (season and rest-of-season tables) — names the net variant while carrying a value floored at zero. Written at `scripts/process-projections.mjs:497,510`, read by nothing.
- `market_salary_adj` on the weekly table — DDL-present, never written, never read. Its removal is already a structural prerequisite of [[███████████████████████████-████-██-██████-███████████]] Step 2, which needs `week` to narrow to `smallint`.

## Background

Deferred from the 2026-08-02 valuation rebuild because the distributional cutover deployed the same day and changed `market_salary` for every player, which is league rules. Applying a rename in that window would have opened the export-schema race on top of a live rules change.

The UI half is already deployed (`1726c528c`): the live auction price renders as "Auction", the persisted column as "Inflation Adj".

Three earlier target spellings are rejected and must not resurface. All four competing spellings are recorded here so none returns as precedent:

- `inflation_adjusted_market_salary` — **inaccurate**, not merely non-conformant. Nothing in the formula references a price level; the mechanism is remaining-league-supply scarcity. This was the previous target and it is also the id currently persisted in saved views.
- `contract_adjusted_pts_added` — **inaccurate**. `player.value` is a single-season salary, and this codebase deliberately separates that from multi-year contracts.
- `salary_adj_points_added_positive` — proposed in `db/adhoc/2026-08-04-add-net-salary-adj-to-league-period-projection-values.sql:25-30`, a live artifact from a Completed task. Superseded.
- `salary_adjusted_points_added` — the current data-view id spelling. Reads as "adjusted by salary" and hides that the adjustment is the player's own cap savings.

The `adjusted` retirement is the third retirement in this vocabulary, alongside `earned` and bare `points_added`. It is recorded in [[████████████████████████████████-█████-████████████]] § "An adjusted quantity names the pool it prices against" as a fourth naming axis, with the rejected mechanism names (`95597fac8`).

## Assumptions

Each is falsifiable and was checked at HEAD on 2026-08-05. Re-check any that the projection cutover invalidates.

- **`calculatePrices` floors `salary_adj_pts_added` at zero for every aggregate key**, net included (`libs-shared/calculate-prices.mjs:131`). This is why the surviving column takes `positive` and why the `_net` sibling is dropped rather than renamed.
- **No Immutable `Record` carries these keys.** `app/core/players/player.js:140` returns `new Map(params)`, and a `Map` does not drop undeclared keys. The prior plan asserted a `Record` hazard here; it is false. The real SPA risk is a stale bundle.
- **Zero PL/pgSQL function bodies reference these tokens** — all 13 `plpgsql` functions in `pg_proc` checked, and the schema dump carries no view, trigger, index or generated column over them. The hazard class is real; the live count is zero. Re-check against whatever the apply actually lands.
- **No test, fixture, golden or snapshot covers either column or any of the four data-view ids.** Verified across all of `test/`. The rename has no automated regression protection today.
- **No `onConflict` target names either column.** Every `onConflict` in `scripts/process-projections.mjs` keys on grain columns only.
- **The three tables are fully rewritten each cron run** (`delete().where({ lid })` then `batch_insert`), so expand-contract is unnecessary and a plain `RENAME COLUMN` is honest.
- **The cron is hourly**, not every 30 minutes (`server/crontab-main/league-imports.cron:54`). Worst-case exposure between a bad apply and the next writer run is ~59 minutes.

## Design

### Sequencing — the DDL rides the projection cutover

This task does **not** open its own production DDL window. [[███████████████████████████-████-██-██████-███████████]] already owns the apply window for column renames on these tables because it is opening them for DDL anyway, and `db/adhoc/2026-08-04-add-net-salary-adj-to-league-period-projection-values.sql:25-30` recorded that this rename rides that cutover. Opening a second window against the same tables is how two `ALTER TABLE` statements race.

This task owns the names, the call-site inventory, the code sweep, and the saved-view migration rule. It runs **last among the DDL-bearing tasks** so the inventory is re-verified once rather than twice.

Deploy-before-DDL, never the reverse: the reverse leaves production naming a column that no longer exists, and the insert throws with the table already emptied by the preceding delete. Both halves of a rollback move together — a reverse `RENAME COLUMN` is metadata-only and cheap, but it must be paired with reverting the code deploy, not issued alone.

### Call-site inventory, re-derived at HEAD 2026-08-05

Re-derived from scratch after the season-grain conform (`8f1abd79d`, `year` → `season_year` on 27 tables). Verify again before the sweep; the cutover's Step 2 lands between now and then.

**`market_salary_adj` → `projected_positive_salary_at_available_cap`**

- Producer: `scripts/process-projections.mjs:468-472` (computed, assigned onto `player_row`), `:492-499` (pushed into `season_value_inserts`; **line 498 is object-literal shorthand** and does not grep like a column reference)
- Schema: `db/schema.postgres.sql:4341` (weekly — dead, see drop set) and `:4367` (season)
- Adhoc: `db/adhoc/2026-07-30-league-player-period-projection-tables.sql:4,19-22,61,71,88-89` (raw SQL column list at 88-89); prose-only in `2026-08-02-drop-surplus-cap-share.sql:37` and `2026-08-04-add-net-salary-adj-to-league-period-projection-values.sql:16`
- Server: `libs-server/get-players.mjs:427-433` (destructure + assign)
- SPA: `app/core/player-fields.js:188-192` (field id, `player_value_path`, csv_header `Projected Inflation Adjusted Market Salary`); `app/core/players/selectors.js:209,219,228` (POTENTIAL FREE AGENT filter); `app/views/components/league-team/index.js:125,155`
- Dead code to DELETE rather than rename: `app/views/components/player-roster/player-roster.js:79` and `:233-240`, both commented out. These are the file's only references to this column — it has no live call site.
- Prose: `CLAUDE.md:272` (the onConflict incident narrative)

**`salary_adj_pts_added` → `projected_points_added_positive_including_cap_savings`**

- Producer: `libs-shared/calculate-prices.mjs:128-134` (map keyed by `aggregate_key`); `scripts/process-projections.mjs:487,496,509,514-538` (the weekly insert at `:538` is **shorthand**)
- Schema: `db/schema.postgres.sql:4340,4353,4366` — all three tables, all actively written and read
- Adhoc: `db/adhoc/2026-07-30-league-player-period-projection-tables.sql:19-21,34,60,77,88-89,94-95`
- Server: `libs-server/get-players.mjs:341,414-415,427-432,445-446`. At `:427-432` the destructured variable name shadows the map field name.
- SPA: `app/core/players/player.js:13,116-118`; `app/core/players/reducer.js:173`; `app/core/selectors.js:3614`; `app/core/player-fields.js:176-180` (week-interpolated `player_value_path`); `app/views/components/league-team/index.js:130-132,160`; `player-roster/player-roster.js:94-97,338-339` (live JSX); `trade-player/trade-player.js:32`; `player-roster-total/player-roster-total.js:89`; `pages/league-home/index.js:50,56`

**Do not conflate:** `player_rest_of_season_projected_points_added_net` (`player-projected-column-definitions.mjs:823-833`) is built on `pts_added` on a different table. It is not part of this rename.

### Data-view ids and the saved-view migration

Four persisted column ids change. All are a saved-view query contract.

| current id | new id |
| --- | --- |
| `player_season_projected_inflation_adjusted_market_salary` | `player_season_projected_positive_salary_at_available_cap` |
| `player_week_projected_salary_adjusted_points_added` | `player_week_projected_points_added_positive_including_cap_savings` |
| `player_season_projected_salary_adjusted_points_added` | `player_season_projected_points_added_positive_including_cap_savings` |
| `player_rest_of_season_projected_salary_adjusted_points_added` | `player_rest_of_season_projected_points_added_positive_including_cap_savings` |

Definitions at `libs-server/data-views-column-definitions/player-projected-column-definitions.mjs:680-702` and `:884-894`; also `libs-shared/data-view-fields-index.mjs:35-41` and `app/core/data-views-fields/fantasy-league-table-fields.js:102-141`. Each carries a `select_as` alias that changes with the id.

**The column-id rename mechanism EXISTS — this task adds entries to it, it does not build it.** An earlier revision of this plan asserted there was no such mechanism, only a param map plus a bespoke regex branch, and cited line numbers that have since drifted onto unrelated param-rename content. That claim is stale and a worker acting on it would rebuild something already in the tree. What is actually there, re-verified against `libs-shared/data-views-saved-view-migration.mjs`:

- `COLUMN_ID_RENAMES` is a declarative `old_id: new_id` map, 41 entries today. The 2026-08-18 `recv` conform ADDED 17 of them and repointed the VALUES of 2 pre-existing `pct`-era keys. Twenty keys match a `recv|receiving` substring, but one of those is an unrelated grades-supplier receiving-snaps rename — do not read the substring count as the batch's contribution.
- `apply_column_id_rename` is exported and applies the map to short-URL column ids.
- `migrate_column_entry` consults the same map for saved views, alongside the surviving `TEAM_FROM_PLAYS_RE` branch — which is a params-dependent reshape, not a rename mechanism.

So the work here is **four `COLUMN_ID_RENAMES` entries**, not a new map. A one-shot SQL migration still cannot substitute — it cannot cover a view saved between the migration and the cutover. That file's own header records a 2025-07-24 standardization that shipped without a rule and broke every saved view referencing the old keys.

Two constraints on those four entries:

- **`migrate_column_entry` does a SINGLE lookup and does NOT chain.** Each value 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 — there is no error to catch it. Chaining is legitimate for PARAM keys only, and only when the second rule sits in a map merged LATER in `PARAM_KEY_RENAMES`; no such merge order exists for column ids.
- **Column-id target liveness IS covered, as of league `de141c3a9`.** The claim that no spec covers it was true when written and is now closed: § "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. So CI now catches a dead target, and **the pinned count must be raised from 41 to 45 in the same change** that adds these four entries — adding them without moving it fails the suite.

Checked at plan-correction time: no existing entry in any rename map targets any of these four ids, so nothing needs repointing — the four are pure additions. Checked also that no token in this task's scope appears 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.

The prior plan claimed the `market_salary_adj` half needed no saved-view migration because its id already spelled the target. That is now false: the id changes too, so both halves need rules.

### Knowledge Base Impact

- **Done — the ruling is in its canonical home.** [[████████████████████████████████-█████-████████████]] carries the pool as a fourth naming axis (`95597fac8`). Its `surface_on` paths fire on `calculate-prices.mjs` and `process-projections.mjs`, the exact files where the next author would reintroduce `adj`. A ruling that is not in its canonical home is how the fourth spelling reached the adhoc SQL.
- `text/league/player-valuation-system.md` and `███████████████████████████████████████████` name these columns and go stale on rename.
- `█████████████████████████████████████████████████████████████████`, `██████████████████████████████████████████████:705,1234` (an explicit footgun warning naming `market_salary_adj`), and `task/league/surface-projected-points-added-net.md:149` also name them.
- No new tag, no new ABOUT.md, no scratch slug needed — the inventory fits this body.

### Traps

- **Object-literal shorthand** (`market_salary_adj` at `process-projections.mjs:498`, `salary_adj_pts_added` at `:538`) reads as a local variable and does not grep like a column reference. These are the two live shorthand sites; no `.where({ ... })` shorthand exists anywhere in the repo.
- `onConflict([...])` target lists are column references that do not look like ones. None names either column today — re-check after the cutover reshapes these tables.
- Prose containing SQL is a schema consumer no gate reads. Sweep `docs/`, `server/crontab-*`, `CLAUDE.md`, and the user-base trees including `task/`.
- **Not a trap here, contrary to the previous plan:** there is no Immutable `Record` on this path, and `pg_proc` is currently clean. Both are recorded under Assumptions with their evidence rather than as warnings.

## Plan

- [x] Land the `adjusted` retirement and the name-the-pool principle in [[████████████████████████████████-█████-████████████]]. Landed as a fourth naming axis in § "An adjusted quantity names the pool it prices against", carrying the rejected mechanism names (`95597fac8`).
- [x] Add the missing relation edges: `blocked_by` [[███████████████████████████-████-██-██████-███████████]] and `relates` [[████████████████████████-██████-█████-██-██████-███████]] (`bb410df7d`).
- [x] Re-derive the inventory above against the tree at that moment. Done at `ca8af0ffe`; the cutover's Step 2 never landed, so the inventory was re-derived against master instead. Two sites the body did not carry: `scripts/calculate-points-added.mjs` / `generate-league-format-player-seasonlogs.mjs` (in-memory only), and `app/core/data-views-fields/projected-table-fields.js`, a computed base_name feeding a persisted id. *Verify: a fresh grep for both column names returns no path absent from this body.*
- [x] Added four entries to the existing `COLUMN_ID_RENAMES` map, pinned count raised 41 -> 45. Covered by a new spec block exercising both public entry points, with a negative control on an unrelated id and an assertion that the four NEW ids are absent from the key set. Reach in production is 0 saved views today, so the rules are preventive. Superseded step text: Add four entries to the existing `COLUMN_ID_RENAMES` map in `libs-shared/data-views-saved-view-migration.mjs`, one per id. The map already exists — do not author a new one. *Verify: a unit test migrates a saved view carrying each old id and asserts the new id, plus a negative control asserting an unrelated id is untouched; and `test/data-views-saved-view-migration.spec.mjs` § "rename-map target liveness" is green, which resolves each of the four VALUES against `libs-shared/data-view-fields-index.mjs` and fails on a dead target — raise its pinned entry count from 41 to 45 in the same change, or the suite fails on the additions themselves.*
- [x] Added `test/data-view-queries/player-projected-valuation-columns-all-periods.json`, GENERATED not hand-written, requesting all four columns together across all three periods. Superseded step text: Add data-view coverage for the four ids before renaming — there is none today, re-confirmed: zero files under `test/` name any of the four. *Verify: new fixtures fail against the old ids once the rename lands, rather than passing silently. GENERATE any data-view-query golden through `scripts/update-data-view-snapshots.mjs` with the DB env passed and the fixture named explicitly — never hand-write or hand-patch one. The `m_<hash>` metric alias derives from the column expression and MOVES with a rename, and `test/utils/compare-queries.mjs` normalizes only 32-char table-alias hashes, so a textually-correct golden still fails.*
- [x] Swept, including both shorthand sites and the four data-view ids; the two commented-out `player-roster.js` blocks were deleted rather than renamed. Superseded step text: Sweep the code to both new names across every surface in the inventory, including the two shorthand sites and the four data-view ids. Delete the two commented-out `player-roster.js` blocks rather than renaming inside them. *Verify: `grep -r 'market_salary_adj\|salary_adj_pts_added'` returns only the schema dump and historical adhoc files.*
- [x] Verified from a dedicated worktree: full suite 4185 passing / 0 failing against a CONTROL at `ca8af0ffe` of 4170 / 0, `yarn lint` clean, ratchet clean, `check:cluster --base ca8af0ffe` 16 OK. `check-league-schema-consumers` is BLIND from a worktree (relative roots) and was rerun with absolute roots: 12 findings against 13 at control, none mine. Gate 2 saw all 7 removed columns at 0 unadjudicated sites. Superseded step text: Verify from a clean worktree at HEAD: full suite against a measured back-to-back baseline, `yarn lint`, `node db/gates/check-schema-conformance-ratchet.mjs`, `node db/gates/check-data-view-sql-validity.mjs`, `node db/gates/check-data-view-url-param-coverage.mjs`, and `node db/gates/check-renamed-column-consumers.mjs --gate 2 --base <pre-cluster-ref>`. *Verify: all green; gate 2 findings adjudicated one by one, since it is excluded from CI precisely because they need judgment.*
- [x] NOT done in this order, deliberately. The sweep renames columns the SPA reads, and CLAUDE.md's stronger rule for that case is that the DDL, the export and the code ship in ONE commit and the frontend deploy is part of the apply rather than a follow-up. Deploy-before-DDL would have left production naming columns that still had their old names. `yarn deploy:all` ran immediately behind the commit; `league` and the second API host verified at `ac9c6ad63`, `████████████` converges on its own hourly job and runs no swept code. Superseded step text: Deploy the sweep AHEAD of the apply. Verify all THREE checkouts — `league`, the second API host, `████████████`. *Verify: each checkout reports the sweep commit as HEAD.*
- [x] Applied in this task's OWN serialized WIDE window; the cutover is dormant and had no window to ride. Weekly `market_salary_adj` was verified still present in the live schema and is dropped here, so the cutover's Step 2 must not drop it twice. Superseded step text: Hand the DDL to the projection cutover's apply window: `RENAME COLUMN` for both columns, `DROP COLUMN` for `salary_adj_points_added_net` on both period tables, and confirm weekly `market_salary_adj` is dropped by the cutover's own Step 2 rather than dropping it twice. *Verify: `\d` on all three tables shows the new names and neither dropped column.*
- [x] Landed together in `66cf6ae4a` with the sweep, the rename map and the new gate. The production export diffs against HEAD at 4 insertions / 7 deletions, exactly this change with no foreign drift. Superseded step text: Confirm `yarn export:schema` and the DDL land in ONE commit within that window. *Verify: `git show --stat` on the apply commit lists both the schema dump and the migration.*
- [x] GATE OK: 7,397 persisted param keys across 186 saved views, all recognised. Zero saved views carry either old column id, so there was no orphan to spot-check. Superseded step text: Run `node db/gates/check-saved-view-param-coverage.mjs` against production. *Verify: zero orphaned ids; spot-check one real saved view that used an old id and confirm it renders.*
- [x] Triggered deliberately under observation rather than left to fire unattended, since this writer is delete-then-insert and a failure empties the tables. Clean: weekly 33,102 / season 1,135 / rest-of-season 1,135, identical to the pre-apply counts and zero nulls in the renamed columns. Superseded step text: Watch the next scheduled `process-projections` run. *Verify: the run writes non-null values into the renamed columns on all three tables — the deploy reports success either way, so this is the real gate.*

## Notes

Do not start inside another league cutover window; check `base bulletin list` first. The 2026 rookie draft is ~2026-08-13, which is the outer bound for the whole cluster.

The three text reconciliations [[███████████████████████████-████-██-██████-███████████]] needed are done (`77bf04dfc`): 29 body sites conformed, the duplicated target-name table struck in favour of a pointer to [[user:task/league/conform-points-added-vocabulary.md]] § Design as single source of truth, and the bare-`year` note amended to `season_year`. The struck table held four rows the surviving one lacked; those were carried across first. That removes the stale row which collided with this task's drop set.

The single source of truth for target spellings across this family is the vocabulary task's Design section. This body states only the two names this task owns.
