---
title: Add an erasure oracle for nfl_plays qb_pid
type: task
status: draft
description: >-
  qb_pid has no oracle for destroyed attribution: it is written by a separate path from the owned
  role families and carries zero play_changelog rows, so the shipped role-attribution-erased check
  cannot grade it. Establish how its values arrive, then close the gap.
base_uri: user:task/league/add-qb-pid-erasure-oracle.md
created_at: '2026-08-23T18:40:28.860Z'
entity_id: d57ffd2f-b17e-4d31-a511-ffa3d4e0dd67
owner_identity_uri: █████████████████████████
priority: Medium
public_read: true
relations:
  - relates [[█████████████████████████-████-████-███████████-████-███████████████]]
  - relates [[user:task/league/redesign-role-attribution-ownership.md]]
  - relates [[user:text/league/data-quality-and-validation.md]]
  - relates [[user:guideline/software/design-data-checks.md]]
tags:
  - user:tag/league-xo-football.md
updated_at: '2026-08-23T18:40:51.898Z'
visibility_analyzed_at: '2026-11-05T23:48:16.576Z'
---

# Add an erasure oracle for nfl_plays qb_pid

## Context

The role-attribution work that shipped 2026-08-22 built `role-attribution-erased` (`db/checks/registry.mjs`, predicate in `libs-server/erased-role-attribution.mjs`) to grade passer attribution that the system once held and then destroyed. During its calibration `qb_pid` was found to have **no erasure oracle at all**, and that gap was recorded rather than closed. This task closes it.

`qb_pid` is not covered by the existing check for two reasons, and the second is the hard one:

1. It is written by a different path from the owned single-player families — `scripts/populate-qb-pid.mjs` — rather than by `map_owned_player_field` in `player-identification-enrichment.mjs`.
2. It carries **zero `play_changelog` rows**. The shipped erasure predicate works by joining plays against changelog entries whose `previous_value` holds what was deleted. With no changelog rows there is nothing to join to, so the existing technique cannot be pointed at `qb_pid` by parameter change alone.

`qb_pid` is live in the data-view layer — `pid_columns: ['qb_pid']` appears on two column definitions — so a silent loss would reach users.

## What makes this non-trivial

The changelog is not an oracle for a value that arrived at row INSERT rather than by UPDATE. That was established during the 2026-08-22 triage of five bogus passer stamps, whose writer could not be identified for exactly this reason. Before designing a detector, establish how `qb_pid` values actually arrive and whether any surviving record of a prior value exists. If none does, the honest end state may be a forward-looking oracle plus a `record_changelog` call added to the writer, not a retrospective one.

Do not assume the shipped predicate generalizes. Read its doc comment first — it explains at length why it is scoped the way it is.

## Open questions to settle before building

- How do `qb_pid` values arrive: insert-time, `populate-qb-pid.mjs`, or both? Measure, do not infer.
- Does `populate-qb-pid.mjs` record to `play_changelog`? If not, adding that call may be the whole fix and the detector becomes trivial afterward.
- Is there a second-source cross-check (the play row's own passer columns, `nfl_play_stats`) that can grade `qb_pid` without any changelog history?
- What is the healthy value, and can it reach zero? A permanently-red check with no path to green is not an oracle — that lesson is written into the shipped check's calibration.

## Constraints

- Detector and healer must share one predicate module rather than re-spelling it, matching how `erased-role-attribution.mjs` is used today.
- A `_gsis` is never deleted on our own initiative; it is updated to match upstream, retractions included. See the ownership model doc.
- Any new check follows `docs/guides/design-data-checks.md`: exit codes carry detector health only, thresholds live on the denominator and never on the finding, and `min_denominator` is required where the row count is fixed by construction.
- Show every gate red before green. A green that has not been shown to go red is not evidence.

## Key locations

- Shipped predicate and its reasoning: `libs-server/erased-role-attribution.mjs`
- Check registry: `db/checks/registry.mjs` (`role-attribution-erased`)
- The writer: `scripts/populate-qb-pid.mjs`
- Ownership model and the three-state ruling: `user:text/league/data-quality-and-validation.md`
- Where the gap was recorded: `████████████████████████████████████████████████████████████████████`, Replacement Oracle group
