A Clark View source val serving the 2026 FIBA Women's Basketball World Cup (2026-09-04 to 2026-09-14). It implements source protocol v1 and nothing else: one source, one competition, one upstream.
Split out of plusjade/source-sports, which still carries its own fiba
competition. This val did not remove FIBA from that one — de-duplicating the
two is a separate decision with its own migration.
| Path | Role |
|---|---|
rpc.ts | Mount point. Bearer check, then serveSource. Also /health and /coverage. |
wfibaSource.ts | The source definition: settings schema, parseSettings, read, one write. |
| Shared SDK | source-sdk, imported through source-sdk@12-main/mod.ts. No vendored SDK. |
lib/store.ts | The only module that touches SQLite. |
lib/espn.ts | ESPN scoreboard payload → rows. Pure. |
lib/feed.ts | Selection: each favorite's own next game. |
lib/items.ts | Rows → the widget's item shape, including the startsAt/expiresAt window and the caption derived from it. Pure. |
lib/dates.ts | Client-offset date math. Pure. |
lib/channels.ts | Broadcaster labels. Pure. |
lib/schema.sql | Complete schema and roster seed. |
Two tables, both provisioned by tools/deploy-schema.ts at deployment — never
by a request.
wfiba_games holds one row per game, with away_code and home_code as
real columns. This is the deliberate departure from source-sports, which
stores a whole day's ESPN scoreboard as one JSON blob in source_cache and can
therefore only filter by team in TypeScript after loading and parsing every
day. Here "which games involve Spain" is a WHERE clause against an index.
wfiba_teams is the 16-nation selectable roster: slug, display name, ESPN code,
order. It is a table rather than a SELECT DISTINCT over the games so the
settings form's choices do not depend on ingest state. Slugs match the sports
val's catalog (spain, united-states, …), so a selection means the same thing
in both.
ESPN blocks Val Town's egress — 403 Access Denied regardless of headers,
re-verified 2026-09-12 with a default fetch (earlier header variants tested Sep 7).
So read never fetches; an external process does.
POST /v1/write with operation: "games.ingest" and
{dateKey, payload}, where payload is ESPN's raw scoreboard JSON. The val
does the shape translation, so the ingest process stays a dumb pipe. Each date's
payload is authoritative: it replaces that date's rows rather than merging,
so a rescheduled or cancelled game disappears instead of lingering on a widget.
Idempotent — re-running is the intended way to refresh statuses.
tools/ingest.ts is that process. Run it off-platform:
deno run --allow-net --allow-env tools/ingest.ts
Storage has 36 games across nine dates, ending Sep 13. The Sep 12 semifinals are
now ingested: France–Germany at 14:30 UTC and Spain–USA at 18:00 UTC; the authenticated
feed returned both, with TBS. Sep 9–10 participants/statuses were also refreshed.
Inputs and ESPN-to-FIBA ID mapping: tools/espn-snapshot-20260912.json.
The earlier manual FIBA capture remains in tools/fiba-snapshot-20260909.json.
Preserve fiba-2026-game-{official game number} IDs when changing providers;
the unchanged ESPN runner does not perform this mapping. Refreshed home/away
fields follow ESPN's supplied roles, not the earlier first-listed-side convention.
The unchanged tools/ingest.ts was run in Val Town and failed before writes:
ESPN 403 for 2026-09-04 (evaluation 01a09666-9e55-7069-92bc-7307b4d4d729).
A separate Sep 12 request also returned 403. Local requests returned HTTP 200,
including both semifinals: ESPN coverage is no longer limited to Sep 9.
The successful refresh fetched ESPN off-platform and replayed reviewed payloads
through the existing games.ingest endpoint; no browser interaction was needed.
Temporary probe/replay scripts were removed.
Open: Sep 13 ESPN still returned two TBD-vs-TBD games at capture time, so the existing medal-game bracket placeholders were retained. Recheck after semifinals and replace the complete Sep 13 date when participants are published; verify the team-filtered read as well as stored coverage. Close this status when resolved.
Preflight the required dates before writing. Reject malformed/off-bucket data and
review missing games or regressions from resolved teams to TBD before an authoritative
date replacement. Do not blindly rerun tools/ingest.ts over hand-verified dates:
it has no completeness guards or FIBA ID mapping. The script was left unchanged for
the as-is evaluation.
Prefer accessible structured ESPN data when it covers the requested games. Use the official FIBA tournament page and its date controls when ESPN is incomplete, cross-checking client-local browser times against explicit GMT times. A manual browser fallback has been proven; an automated FIBA parser has not.
A scheduler on a tested external runtime could fetch ESPN and call /v1/write
without computer use; the local fetch succeeded, but no unattended host was tested
or scheduled. Direct Val Town cron execution currently encounters the same 403.
If a browser fallback becomes necessary, a cron can orchestrate a remote browser
(Val Town's Kernel guide);
that needs a configured service and validated extraction, not this interactive session.
Before scheduling, add stable-ID mapping, completeness checks, and visible failures.
Recheck network access from the intended scheduler before selecting it.
GET /v1/descriptor publishes a closed object in the supported subset the
parent's generic form renders: an array of 16 string choices carrying
const/title/x-group. Only teams is accepted; unknown keys are rejected.
Expiry visibility belongs to device Presentation in the parent. This source returns
its selected candidates even after expiry. Captions follow the published window;
stored status does not override it.
capabilities.validateSettings is true and POST /v1/validate-settings runs
the same parseSettings a read does, without reading or leaking parsed
internal context.
The source check reads production data and removes its disposable fixture. All SDK imports use https://esm.town/v/plusjade/source-sdk@12-main/mod.ts; keep them on one snapshot and rerun this check on a branch when upgrading.
tools/source-contract-check.ts— 50 assertions over the whole HTTP surface, plus pinned pure-function behaviour (Unix seconds, the two-hour window and the captions derived from it, per-team selection, the ingest date filter) and a disposable row on1900-01-01. Shared protocol conformance and boundary checks now live inplusjade/source-sdk/tools/sdk-check.ts.