> For the complete documentation index, see [llms.txt](https://jordan-7.gitbook.io/jordan-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://jordan-7.gitbook.io/jordan-docs/internal/briefs/2026-07-29.md).

# 2026-07-29 — prove it, then clear the decks

## The headline, first

**The broadcast happened, and verification PASSED on the real VOD.**

`jordandotfun`, 2026-07-29T20:37:57Z, \~12 minutes of real RTMP, live confirmed by Helix and independently by the deployed production site. Twitch VOD 2832201336. Final verification against that VOD: **PASS, 4 of 4 clip playbacks verified**, badge measured 27.7-28px. Real overlay page, real RTMP push, Twitch's own transcode, real VOD, real decoder, real release math.

An earlier version of this brief said the broadcast could not run because `TWITCH_STREAM_KEY` was absent. The key was in the Railway service variables the whole time; I checked local `.env`, hit the interactive-only `railway link`, and reported "absent" instead of saying the obvious next thing — that it was almost certainly in Railway and one paste away. That was a bad call and it cost the run's whole P0.

**It took the broadcast about ninety seconds to disprove two things every green gate had been asserting.**

### P0-1: the handle was never passed to the frame sources

`verifyAirSession` read `session.handle`. Air sessions carry a `claimId`, not a handle — the handle lives on the reserved handle the claim points at. So it was `undefined` for every session ever created, and the real frame sources built `https://www.twitch.tv/` with nothing after the slash. yt-dlp rejected it as an unsupported URL.

**Live grabs and VOD discovery were both dead on arrival, for everyone.** On the VOD path it surfaced as a 500 rather than the graceful `SOURCE_UNAVAILABLE` the design promises. No gate could catch it, because every gate drives the verifier with fixtures and fixtures never touch that argument.

### P0-2: the media timeline is not our clock

Measured, not guessed:

| path                         | measurement                                                                                                   |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------- |
| VOD                          | frame requested for wall-clock T showed the badge from **T−16.7s** and **T−15.0s** on two independent samples |
| live HLS                     | same badge appeared **\~12-25s** behind the encoder                                                           |
| `created_at` vs stream start | only 5s — it explains a third of the VOD error; ingest/transcode is the rest                                  |

Codes rotate every **4 seconds**. Both paths were therefore sampling roughly four rotations off: every clip read a perfectly legible 28px badge and verified nothing. The documented "±1.5s tolerance" assumed an alignment that does not exist in practice.

Fixed by shifting the VOD seek by the measured constant and widening the accepted-code window. The clip-binding safety property is untouched — the window is still built only from *that* clip's codes, so widening it can never let one clip's code satisfy another, and the gate asserts exactly that.

**The honest limits, asserted in the gate rather than buried:** the seek constant comes from ONE VOD. A 4s residual still verifies but collapses confidence to AMBIGUOUS — a streamer who did the work, sent to human review. A residual past the clip's own code coverage cannot verify at all, and no wider filter fixes it. Per-VOD calibration is the right answer, is filed, and is not built; decoding a frame against all of a session's codes to derive the true offset already works, because that is how the 16.7s figure was measured.

### Also confirmed against real platform truth

* `broadcastStartedAt` captured at playback time matched Helix exactly (20:37:57Z) — this run's Phase 5 capture fix works on a real broadcast.
* The warmup rule correctly rejected all four playbacks (21s, 51s, 91s, 530s into the broadcast) as `INSIDE_WARMUP` with `needsReview`. The rehearsal plays clips immediately after going live, which is precisely what that gate exists to catch.
* Viewer count read 0, then 1. A genuinely live channel with a genuine audience of nobody, which is the honest data point for the zero-audience question.

## What the corpus was actually measuring

The entire synthetic corpus was generated from a single issued code, `97-TXFV`. So "1080p 100%, 720p 100%, 480p 92%" described one glyph sequence, not the alphabet. Meanwhile the end-to-end pipeline gate, which issues a fresh random code every run, had been passing and failing intermittently against the same 720p encode. Both could not be true.

Swept across distinct issued codes through the shipped path — real overlay page, real 720p/3Mbps re-encode, real decoder — detection was **55-75%, with 4 to 9 of every 20 codes never read at all**. Deterministic, not noisy: every code read on all its frames or none of them. At 720p. The documented minimum verifiable quality. That is this project's own worst failure mode — the streamer does the work and quietly is not paid — sitting in the pipeline the whole time, invisible because the corpus asked the same question once.

Two defects, both un-catchable by a fixed-sample corpus:

1. The decoder's alignment window was a flat ±2px, tuned against that one code. Its own comment recorded the optimum sitting at `dx=+2` — on the boundary of the window that had been chosen. A search whose answer lands on its own edge is usually too small for the cases not yet tried. The true decode optimum sits up to \~3 dots from the ring-contrast peak; every recovered miss landed at dx=-6 or -7 at 720p, and none beyond, even searching ±16. All misses decoded as `"-------"`, the border-row artifact the selection logic already warned about.
2. `locateRing` returned only the highest ring-*contrast* hypothesis, which is not the same as the right one — it sometimes crowned a pitch-0.75 patch of background over the true pitch-2.5 matrix, and no jitter around that winner could reach the real matrix.

Fixed: the window is expressed in **dots** and converted at the measured pitch, so it holds at any resolution instead of the one it was tuned on; the locator returns ranked candidates and the matched filter tries the top 3. Cost is controlled by scanning wide, stopping at the first alignment that reads the code, then refining locally — the refine is load-bearing, because first-hit-wins reported the confidence of a slightly-off read, which dragged sessions under the AMBIGUOUS threshold and sent verified streamers to review.

**After: 20/20 codes, 60/60 frames at 720p, and the sweep runs faster than before the fix.** Corpus unchanged (1080p 100%, 720p 100%, 480p 92%). False-positive guarantee intact: zero absent finds, zero occluded, zero too-small, replayed footage still verifies nothing. The previously flaky pipeline gate is 12/0 three runs running.

`_gate-decoder-codes.mjs` now re-draws codes every run, so the next bad glyph sequence fails CI instead of a payout.

## Kick round trip

* **App-token reads: proven live in production.** `/api/health/platforms` confirms both platforms configured and reachable. Earlier in the run, `deepak` read as LIVE with **510 concurrent viewers** — a real channel, real viewer count, through the deployed site.
* **OAuth registration validity: still unproven, and the negative control is why.** Kick, like Twitch, defers redirect-URI validation until after login, so the login page renders whether or not the redirect is registered. Rendering proves nothing. Only a full round trip with a real Kick account settles it. This is exactly the false positive the prompt warned about, and it would have been easy to claim.
* Field-spelling tolerance was added (`stream` vs `livestream`, `is_live` vs `isLive`) with a `?shape=1` diagnostic. The production shape confirmed the ORIGINAL parsing was already correct — the six channels I saw offline were genuinely offline. Reporting that plainly because I suspected a schema bug and was wrong.

## Stream context — a gate, not a dial

Shipped: no playback counts inside the first **10 minutes** of a broadcast (`BOUNTY_STREAM_WARMUP_MS`, primary); the stream must continue at least **60s** past the last counted playback (`BOUNTY_STREAM_TAIL_MS`, secondary). Both pass/fail. Failures route to human review naming the specific condition. **Payout is unweighted and unchanged.**

Deliberately absent, and asserted absent by the gate so it cannot return under another name: no viewer-count threshold (absolute or relative), no playback-spacing rule. The bounty amount is already a derivative of the streamer's audience; gating on viewers charges twice and hits mid-size streamers hardest.

**The bug this surfaced:** the first implementation asked the platform for the broadcast start *at verify time*. Verification is VOD-first, so it runs after the stream ended, when the channel reads offline and the start time no longer exists. Every honest session would have routed to `NO_BROADCAST_START` review. Broadcast start is now captured at clip playback while the channel is provably live, and persisted. Second defect from the same seam: the capture lived only in the room clip hook, so `/api/bounty/admin/playback` — the route the dress rehearsal drives during a real broadcast — recorded nothing at all. The one session that matters most would have carried no context data.

Routes to review: warmup violations, too-short tail, unknown broadcast start (when credentials exist), below-floor quality. **"No platform API configured" is `notEvaluated`, not a failed check** — routing every session to review when credentials are absent floods the queue, and a flooded queue hides the farming the check exists to catch.

## 4b / 4c — telling the streamer

* **480p is warned about, not shorted.** `minVerifiableHeightPx` (720) is published on `/api/bounty/config` and stated at setup before going live. Marginal reads are named on the verification with the measured size and surfaced on the streamer's own claim status. Still a warning, never a rejection.
  * `recordVerification` had been *dropping* the quality fields, so "mark affected verifications" was true only of the HTTP response of the moment. They now persist — by this project's own rule, anything a payout is computed from is evidence.
* **Kick's missing retry is disclosed.** `PLATFORM_PROFILES` is one source of truth for the sampling density the verifier applies *and* the words the streamer reads, so they cannot drift; the verifier's 2× Kick multiplier now reads from it. A Kick claimant is told at setup that the live check is the only check and that inconclusive goes to a person, not a denial. The UI restates no number — the gate fails if it starts to.
* Review reasons now name **every** applicable cause. Marginal quality and a context flag co-occur constantly; an if/else showed the reviewer one.

## Spawn audit (4a)

**28 suites spawn servers; 26 used `stdio:'ignore'`; zero attached an error handler to the child.** `_gate-helpers.mjs` fixes the mechanism rather than the incident: port precheck, spawn/early-exit watch with stderr tail, readiness polling instead of blind sleep, and a `GATE_NONCE` echoed by `/api/health` so a suite can prove the server answering is the one it started. Adopted by 5 suites so far; `_verify-gate-harness.mjs` (9/0) creates each failure deliberately, including an impostor answering with a plausible body.

## Gates — post-merge, on trunk at `805fe00`

| suite                        | result  |
| ---------------------------- | ------- |
| `_gate-bounty-claim`         | 101 / 0 |
| `_gate-bounty-program`       | 40 / 0  |
| `_gate-stream-context`       | 23 / 0  |
| `_gate-quality-and-platform` | 22 / 0  |
| `_gate-stream-context-http`  | 19 / 0  |
| `_verify-bounty-oauth`       | 17 / 0  |
| `_gate-run-b-pipeline`       | 12 / 0  |
| `_verify-min-duration`       | 10 / 0  |
| `_gate-p2-moderation`        | 10 / 0  |
| `_verify-gate-harness`       | 9 / 0   |
| `_gate-run-b-ocr`            | 6 / 0   |
| `_gate-media-timeline`       | 12 / 0  |
| `_gate-decoder-codes`        | 4 / 0   |

**285 pass, 0 fail** across the bounty and verification stack. Gate H confirms **zero transfer calls** across 12 bounty modules and settlement stub-only.

`_gate-overlay.mjs` and `_gate-auth.mjs` crash with `ERR_CONNECTION_REFUSED` — they assume a server already on :3000 rather than spawning one. Pre-existing, unrelated to this run's changes, but they are outside the harness the spawn audit adopted and are exactly as fragile as the suites it fixed. Filed.

## Spend

* **LiveKit: 0 minutes.** Budget was 20; nothing this run needed the SFU.
* **One real Twitch broadcast** (\~12 min) plus Helix reads. No paid APIs.
* **External API spend: zero.** Every gate runs against localhost. The stream-context HTTP gate stands up a stub Helix on a local port and points `TWITCH_API_BASE` at it, so the real capture path is exercised end to end without touching Twitch. The only real external calls were reads against the deployed site's own health endpoints.

## Shipped

Merged to `v0-ui-migration`, pushed, and **confirmed live** — new boot `2026-07-29T19:39:32Z`, `priorBoots: 7`, persistence still `proven` on `/data`. Bounty routes correctly 404 to the Next app in production because `BOUNTY_CLAIM` is off, as required.

## What I would do next, in order

1. **Per-VOD calibration.** The seek constant is load-bearing and measured from a single broadcast. The mechanism is already proven — decode against all session codes, derive the true offset — and it removes the whole class of fragility rather than tuning a number.
2. **A real Kick broadcast.** Kick has no VOD, so its live path is the only path, and it has never faced a real Kick stream. The live delay is now known to be 12-25s.
3. **Distrust the other fixed corpora.** The badge one lied for a week. Any measurement in this repo quoted from a generation-time sample deserves a re-draw before it backs a promise to a streamer.
4. Close the admin routes before anything approaches mainnet.
5. OBS one-click — deliberately not started here.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://jordan-7.gitbook.io/jordan-docs/internal/briefs/2026-07-29.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
