Un-claim CLV on the public profile; un-fabricate player-page FORM

Two Truth-Law fixes found by auditing the product logged-out.

FIX 1 — /u/[handle] claimed a "CLV-verified record" with "closing-line
value included" while ZERO closing-line value renders there. Verified
live: GET /api/profiles/vyndr returns beat_close_pct null (gated behind
CLV_CAPTURE_RELIABLE, unset while C4 is open). Eight instances found —
two of them (the OG + portrait "CLV-VERIFIED RECORD · 30D" eyebrows)
only by the post-removal residual sweep; two more printed the claim in
exactly the no-record branch.

Copy now describes what the page shows. The gated CLV-VERIFIED badge and
the BEAT CLOSE figure are removed from the public profile, OG card and
portrait card. DISPLAY ONLY: beat_close_pct, clvCaptureReliable() and
the whole CLV data path are untouched, and the earned directional badge
stays Analyst+Desk. The claim returns when CLV genuinely renders here.

Also fixes the doubled "· VYNDR · VYNDR" title (layout's '%s · VYNDR'
template already supplies the suffix); verified on composed output by
serving the build and reading the real HTML, not on source.

FIX 2 — the player page's FORM was `70 + 4 × (count of tonight's graded
props)`. Nothing on the HTTP path ever sets stats.form, so that fallback
WAS the live number: Josh Bell's "74" is 70 + 4×1 prop, confirmed
against his live payload. MATCHUP was gradeFromForm(that number), with a
hardcoded 'B' on the no-archetype branch — both fabricated letters with
no opponent input on the path. Systemic: buildIntel is the unconditional
path for every player and sport.

FORM and MATCHUP now render "—" (kind 'plain', so no bar width or colour
is computed off a null). gradeFromForm is deleted and the prop count is
no longer passed into buildIntel. computeFormScore's hardcoded 75 now
returns undefined. Induced across MLB/NBA/WNBA: all render cleanly, and
real values (USAGE 3.6 AB/G, REST B2B) still render.

Neither form value feeds the grade — engine1 reads raw l5_avg/l20_avg
against the line and never a form key; buildIntelFields decorates the
already-graded object. Grade inputs are byte-identical.

Held (needs a per-sport headline-stat design call): a real player-level
form metric + label disambiguation.

Tests 3491 passed / 289 suites, web build exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCNgGSt5qvcLxaeQqa7Zpj
This commit is contained in:
Kev
2026-07-20 17:05:04 -04:00
parent ca9ca34cbb
commit f5156dd16d
12 changed files with 286 additions and 83 deletions
+100 -1
View File
@@ -1,5 +1,5 @@
# VYNDR — STATE OF THE WORLD
### As of `7ea0af2` (main, DEPLOYED + fingerprinted live), 2026-07-20. This file opens every future session. **Start with the CURRENT STATUS + OPEN ITEMS block below.**
### As of `HEAD` on main (DEPLOYED + fingerprinted live), 2026-07-20. This file opens every future session. **Start with the CURRENT STATUS + OPEN ITEMS block below.**
---
@@ -131,6 +131,105 @@ exist locally; harmless, the data restores completely.
`specs/snapshot-retention.md` (Phase 2 report + schema) ·
`specs/audit-data/grade-collapse.md` · `specs/audit-data/gate-simulation.md`.
---
# 🟢 SESSION 65 — TWO TRUTH-LAW FIXES (2026-07-20, from Chrome's logged-out audit)
Both found by auditing the product as a STRANGER sees it. Neither touches the
triplet build; push scoring untouched.
## Doc drift corrected in this session
The header of this file was stale at `7ea0af2` and this file carried **no record
of the directional-CLV session** (`dcdad60``ca9ca34`: `src/services/
directionalClv.js`, the Analyst+Desk server-gated `ClvBadge`, and removal of the
legacy C4 CLV chip from five surfaces). `BUILD-STATE.md` is stale to April 2026 —
**`specs/STATE.md` is the authoritative state file**; treat BUILD-STATE as archive.
## FIX 1 — "CLV-VERIFIED" UN-CLAIMED ON THE PUBLIC PROFILE (copy + 2 render blocks)
`/u/[handle]` was titled **"CLV-verified record"** with the subhead **"closing-line
value included"** while **zero** closing-line value renders there. Verified live,
not assumed: `GET /api/profiles/vyndr``beat_close_pct: null` (gated behind
`clvCaptureReliable()` / `CLV_CAPTURE_RELIABLE`, which is unset while C4 is open).
A verification claim with nothing verified shown is an unearned claim, and it
leaked the standing CLV hold onto a surface that had never been audited.
**EIGHT instances found — two of them ONLY by the post-removal residual sweep:**
| # | Where | Was |
|---|---|---|
| 1 | `u/[handle]/page.tsx` | `<title>` / `og:title` / `twitter:title` — "CLV-verified record" |
| 2 | `u/[handle]/page.tsx` | meta + og + twitter description — "closing-line value included" |
| 3 | `u/[handle]/opengraph-image.tsx` | no-record fallback tagline |
| 4 | `u/[handle]/portrait/route.tsx` | no-record fallback tagline |
| 5 | `PublicProfile.tsx` | house subhead — "closing-line value included" |
| 6 | `PublicProfile.tsx` | `✓ CLV-VERIFIED` badge (gated, dark, one env flag from re-claiming) |
| **7** | `opengraph-image.tsx` | **eyebrow `CLV-VERIFIED RECORD · 30D`** — missed in the first report |
| **8** | `portrait/route.tsx` | **same eyebrow** — missed in the first report |
Instances 3 + 4 printed the verification claim **exactly when there is no record
at all** (the `rec == null` branch), i.e. the claim was loudest where the data was
most absent.
**Applied:** honest copy on 15 + 78 (settled record · wins and misses · nothing
curated); the badge (6a) and the `BEAT CLOSE` figure (6b) REMOVED from the public
profile, OG card and portrait card. **DISPLAY ONLY — `beat_close_pct`,
`clvCaptureReliable()`, `closing_captures` and the whole CLV data path are
untouched**, and the earned directional badge stays Analyst+Desk (never public).
The claim returns here when CLV genuinely renders here.
**Title double-append fixed and VERIFIED ON COMPOSED OUTPUT, not source:** the page
title carried its own `· VYNDR` while `layout.tsx`'s metadata template already
appends `%s · VYNDR` → live was `…@vyndr · VYNDR · VYNDR`. Built the app, served
it, and read the real HTML: `<title>Settled record — @vyndr · VYNDR</title>`
exactly one suffix. Note `openGraph`/`twitter` titles **bypass** the template, so
they carry the brand suffix explicitly.
Both share images re-rendered locally (200 / image/png) and eyeballed: no overflow,
no clip, no residual CLV scaffold.
## FIX 2 — FORM WAS FABRICATED ON THE PLAYER PAGE (un-fabricate; steps 2+3 HELD)
Chrome saw Josh Bell as **FORM 74** (player page) and **Form 63** (generated read)
in one session. Traced both. **Neither feeds the grade** — proven, not assumed:
`engine1.gradeProp` runs at `analyzeViaEngine1.js:446`, `buildIntelFields` (which
computes form) decorates the already-graded object at `:468`; `engine1.js` never
reads a `form` key (only raw `l5_avg`/`l20_avg` **against the line**), and nothing
in `src/` reads `result.form`. Grade inputs are byte-identical before/after.
- **63 is real** — `70 + (l5_avg / l20_avg 1) × 60`, per-prop, per-stat.
- **74 was manufactured** — `70 + 4 × (count of tonight's graded props)`, capped
92. **Nothing on the HTTP path ever sets `stats.form`**, so that fallback WAS
the live value. Confirmed empirically: Bell's payload carries `activeProps`
length **1** → 70 + 4 = **74**, exact.
- It **poisoned a second tile**: `MATCHUP` was `gradeFromForm(form)` — Bell's
"B" was `gradeFromForm(74)`, from the prop count, with **no opponent input on
that path at all**; the no-archetype branch hardcoded `'B'`. Both fabricated.
- **SYSTEMIC, not Bell-specific** — `buildIntel` is the unconditional path for
every player and every sport.
**Applied (absent renders absent):** FORM and MATCHUP now emit `kind:'plain'`
with `'—'` when there is no real value — `'plain'` deliberately, so the page
draws no progress bar and computes no width/colour off a null. `gradeFromForm`
deleted; the prop count is no longer passed into `buildIntel` at all.
`computeFormScore`'s hardcoded **75** (returned whenever the baseline was missing
or zero) now returns `undefined` → the card's self-hiding intel section drops the
row. Induced live across **MLB / NBA / WNBA**: all render `—` cleanly; real values
(`USAGE 3.6 AB/G`, `REST B2B`) still render — the fix is surgical to fabrications.
**HELD (needs a per-sport headline-stat design call, deliberately NOT built):** a
real player-level form metric + label disambiguation (page-level vs per-stat). It
lands as `stats.form` / `stats.matchup` and lights these tiles straight back up.
### Audit of the same file (order item 10) — reported, NOT changed
`toNum()` (`playerIntelService.js:18`) returns **0** for absent, feeding
`classifierInput`: `k_rate` (`pa > 0 ? … : 0`), `ip_per_start`, `k9`, `saves`.
These are **classifier inputs, not display** — a 0 there can misclassify an
archetype (0% K-rate reads as elite contact). Same family as the `Number(null)
=== 0` rule. `mlbLast10Rows` renders `${st.hits ?? 0}-${st.atBats ?? 0}`, which
prints a real-looking `0-0` line if a game-log row lacks its stat object.
**Ambiguous — changing classifier inputs shifts archetypes, so it needs its own
order.** Display rows elsewhere (`mlbSeasonRows`, ESPN season rows) correctly use
`?? '—'`.
---
## 🟢 MLB opp_rank_stat LIVE — computed, CONSUMED, retained (2026-07-20 16:51 UTC)