---
title: 'generate-league-formats cannot run: extract_scoring omits fumble_return_touchdowns'
type: task
status: completed
description: >-
  scripts/generate-league-formats.mjs aborts on every run with a NOT NULL violation on
  league_scoring_formats.fumble_return_touchdowns: extract_scoring never names the column, and it
  has neither a schema DEFAULT nor a registry default_value. Pre-existing and reproduced before the
  SFB16 work. It is the only writer that seeds the named format catalog, so no new named format can
  be published while it is broken.
base_uri: user:task/league/fix-generate-league-formats-null-fumble-return-touchdowns.md
created_at: '2026-08-05T19:51:01.895Z'
entity_id: ee6f11be-1295-41b4-afe5-8bc5b2d26789
finished_at: '2026-08-05T20:45:56.340Z'
owner_identity_uri: █████████████████████████
priority: High
public_read: true
tags:
  - user:tag/league-xo-football.md
updated_at: '2026-08-05T20:45:56.340Z'
visibility_analyzed_at: '2026-11-05T23:49:23.905Z'
---

# generate-league-formats cannot run: extract_scoring omits fumble_return_touchdowns

`scripts/generate-league-formats.mjs` aborts on every invocation with:

```
null value in column "fumble_return_touchdowns" of relation "league_scoring_formats" violates not-null constraint
```

Found 2026-08-05 while trying to seed the SFB16 named formats. **Reproduced at `3c7df0e22` and earlier, so it is not caused by the SFB16 work** — that check was run explicitly before filing.

## The mechanism

`extract_scoring` (`scripts/generate-league-formats.mjs:120-143`) builds a scoring config from a league row by naming each column explicitly. It names 23 columns and **does not name `fumble_return_touchdowns`**.

That column became a scoring column when fumble return touchdowns were made configurable. It is `smallint NOT NULL` on `league_scoring_formats` with **no schema DEFAULT**, and its registry entry carries **no `default_value`**. So:

- `extract_scoring` yields `undefined` for it
- `resolve_scoring_config` maps an absent key to `default_value_for_column(...)`, which is `null` when the registry has no default
- the insert violates NOT NULL

The named-format loops later in the same script are fine — every entry in `scoring_formats` sets the column explicitly. It is the combination/`getLeague({ lid: 0 })` path that fails, and it runs first, so the script never reaches the named formats.

## Why it matters beyond the error

The script is the only writer that seeds the named format catalog into the database. While it is broken, **no new named format can be published**, and `scripts/verify-named-format-catalog.mjs` cannot be brought to a clean state for anything added since. SFB16 (`sfb16_mfl`, `sfb16_sleeper`) is defined in `libs-shared/league-format-definitions.mjs` as of league `4e14bc44d` and is blocked here.

Note the failure mode is loud, which is the one good thing about it — it throws rather than writing a partial catalog.

## The fix is a choice, and the registry spec constrains it

`test/libs-shared.scoring-registries.spec.mjs` asserts `default_value` equals the schema DEFAULT, and that a column with no schema default has no `default_value`. So adding a registry default alone will fail that spec. The options:

1. **Name the column in `extract_scoring`.** Smallest change, but only correct if every `c` reaching it actually carries the column — `getLeague({ lid: 0 })` and the generated combinations need checking, and if they do not carry it the null simply moves.
2. **Give the column a schema DEFAULT of 6 and a matching registry `default_value`.** Consistent with how every kicking and DST column was handled, and with the from-plays fallback for this role, which is already `'6'`. Costs a production DDL.

Option 2 is the one that matches the established pattern, and it also fixes any other caller holding a partial config rather than just this script.

## Worth checking in the same pass

Whether any OTHER scoring column is in the same state — NOT NULL, no schema default, no registry default, and unnamed by `extract_scoring`. The condition is mechanical and a one-off script can enumerate it; fixing one instance while leaving siblings is what produced this one.

## Plan

- [ ] **Enumerate the whole class before fixing one member**
  - Verify: for every registry column, whether it is NOT NULL with no schema default AND absent from `extract_scoring`. Report the list.

- [ ] **Decide between naming the column and defaulting it**
  - Check what `getLeague({ lid: 0 })` and the generated combinations actually carry before assuming option 1 works.

- [ ] **Run the seeder to completion and verify the catalog**
  - Verify: `scripts/verify-named-format-catalog.mjs` exits 0 with `sfb16_mfl` and `sfb16_sleeper` present. That is the completion signal, and it is currently unreachable.
