1
0

feat: the state becomes California, and the port fills with ships

**Stage 1 of one California.** The owner's complaint had two halves and this is
the first: the state board was a CROPPED SLAB. `california.ts` stopped at 38.05 N,
so the board disagreed with its own minimap about the shape of California in a
single frame, and Bug Fire's 93,733 acres burned off-frame while the panel said
all clear. Bounds now run 32.50-42.05 N / -124.50 to -114.0 W — Cape Mendocino,
the ruled Oregon parallel, the 120th-meridian corner into the Nevada diagonal.

**And it got cheaper.** 391,169 triangles to 375,351, while gaining the North
Coast, the Sacramento Valley, the Klamath knot, the Cascade arc, Shasta at 4,320 m
and Lassen at 3,190 m. Extending the bounds alone would have doubled the lattice
to 168,813 points and blown the mobile cap; coarsening cellLat 0.022 -> 0.0312 and
cellLng 0.027 -> 0.0383 holds it at ~83,800. The cell as a FRACTION of the board
moves 0.0030 -> 0.0033 — unchanged in frame — because the camera retreats to frame
whatever it is given. That argument was already written in the pack's own comment.

The second half — three boards becoming one world you zoom through — is NOT here.
Merging at Bay density would be 34.04M triangles, 13x the highest budget, and
merging at SoCal density would downgrade San Francisco from 40 m lots to 164 m.
Both delete the board every marketing still is shot from. `sf.ts` and `socal.ts`
are untouched by design.

**Aerial perspective, which the state board could not have had before.** The old
fog started at 1.15 board spans = 944 km, on a board whose longest diagonal is
820 km — so no pixel could ever be fogged. Fog now responds to camera altitude,
clamped to the authored pair as a ceiling.

`Atmosphere.aerial(env, view)` is a second pure method returning `{ near, far }`
and **deliberately no colour**. That is structural, not stylistic: it is why a
future camera-dependent term cannot reach `environmentKey()`'s colour fingerprint
and start rebuilding the PMREM cubemap on every camera step. Coarsening the
fingerprint instead would have hidden one instance and armed the mechanism. A
mutation-tested seam guard fails if anyone merges the two paths back together.

**The port.** Terminal Island rendered as a bare tan polygon with generic white
blocks while the chapter text called it the busiest port complex in the
hemisphere. Now six container yards drawn as canvas atlases, 56 gantry cranes at
varied boom angles, the 13 km San Pedro breakwater, the dredged channel. Five
buckets merging ACROSS ports the way airports.ts merges across fields, so a
second complex costs no extra draws: +11 draws and +4,377 triangles for all of it.

At vertical exaggeration 3.4 a 130 m gantry is 1.132 units tall against a 400 m
ship's 1.024 long — the crane is the taller object, and it is what makes a port
read as a port from altitude.

**Ships, and the wake carries the information.** Moored hulls have no foam,
verified at three terminals; a tug under way in the Main Channel trails a clean
Kelvin V. One hull geometry, one InstancedMesh, orientation from the BERTH rather
than the wire. The AIS gate strips sog 102.3, heading 511 and cog 360 — all mean
"not available" — with an explicit test that cog 358.7 SURVIVES, because a naive
range check on cog eats real headings near north.

"Empty or full" is not in AIS position reports and is not invented per ship. The
honest answer is at port level and is a better story: 348,691 of 460,467 boxes
left Los Angeles empty in July 2026, corroborated by FBX01 $7,491 inbound against
FBX02 $347 outbound.

**Radar and birds ship dark, and say why.** California is 0.47% wet and migration
is nocturnal and seasonal, so both layers have nothing to say on most days. The
panel reads "No radar feed is configured, so this board draws no weather. That is
a fact about this box, not about the sky."

Also recorded, and it matters beyond this commit: **the GPU on amd-server never
leaves 500 MHz of a possible 2725**, traced across 80 seconds of sustained load.
`bay-area/desktop` is fragment-bound at that clock and sits on the vsync deadline,
so a trivial change in fragment work flips it between 16.8 and 33.3 with geometry
identical to the digit. Every frame-time number measured on this box is a floor.
Two investigations reached two different wrong conclusions from single-run
comparisons before this was traced. Geometry is the gate; frame time is advisory.
No cap was raised.

Tests 1,340 -> 1,540, server 280 -> 295.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-22 23:35:09 -07:00
parent b25f217e3e
commit acf4d1a510
52 changed files with 14436 additions and 142 deletions
+12
View File
@@ -13,8 +13,11 @@
*/
import Fastify, { type FastifyError, type FastifyInstance } from "fastify";
import { createBirdsService } from "./birds/index.ts";
import { registerCachePolicy } from "./cache.ts";
import { loadConfig, type Config } from "./config.ts";
import { createRadarService } from "./radar/index.ts";
import { registerBirds } from "./routes/birds.ts";
import { registerDevices } from "./routes/devices.ts";
import { registerFires } from "./routes/fires.ts";
import { registerFlights } from "./routes/flights.ts";
@@ -23,6 +26,7 @@ import { registerMarkers } from "./routes/markers.ts";
import { registerMedia } from "./routes/media.ts";
import { registerOffices } from "./routes/offices.ts";
import { registerPresence } from "./routes/presence.ts";
import { registerRadar } from "./routes/radar.ts";
import { registerRealtime } from "./routes/realtime.ts";
import { registerSatellites } from "./routes/satellites.ts";
import { registerSession } from "./routes/session.ts";
@@ -52,6 +56,14 @@ export function buildApp(config: Config = loadConfig()): FastifyInstance {
registerFlights(app, services);
registerSatellites(app, services);
registerFires(app, services);
// The two sky feeds are constructed here rather than on `Services`. They own
// their own cache and their own failure behaviour exactly as every other
// service does, and neither can throw at a route. `sources.radar` and
// `sources.birds` are on the health body — `routes/health.ts` reads them off
// the config rather than off a service, which is what every line in that route
// does and is why it still touches no upstream.
registerRadar(app, createRadarService(config, app.log));
registerBirds(app, createBirdsService(config, app.log));
registerWeather(app, services);
registerMarkers(app, services);
registerMedia(app, services);
+188
View File
@@ -0,0 +1,188 @@
/**
* The migration feed's one upstream: a **projection**, served by the machine
* that owns the database.
*
* The same arrangement as `fires/cloud1.ts` and `radar/cloud1.ts`, and it is
* worth saying why it applies here too even though `birds.sqlite` holds nothing
* private. The value is not only in what this particular store contains: it is
* that **cloud-2 has no database at all**, so no route written on this box next
* year can select a column from any of them. Making that true of every feed is
* what keeps it true of the one that matters.
*
* ### Both halves, or neither
*
* The newest ten-minute granule and last night's state summary are asked for in
* parallel, and `null` comes back if either fails. That matters more here than
* anywhere else in this build, because the two halves answer *different*
* questions and the second one is the whole empty state: without last night's
* figures the daylight sky has nothing to say but "nothing is aloft", which is
* the blank panel this layer was designed to avoid.
*
* ### The state row is dropped on the way in, and again on the way out
*
* `counties` upstream is 59 rows: 58 counties plus `US-CA`, `kind='state'`, with
* NULL coordinates and — in tonight's granule — 793,141 birds aloft against the
* largest county's 82,549. It is refused here by `countyRows`, refused again by
* `promoteBirds` in the browser, and a third time by the layer's own reader.
* Three lines for one row is not paranoia: the failure it prevents is a
* three-quarter-million-bird blob at 0,0, and the reason it would happen is that
* the row looks exactly like the other fifty-eight.
*/
import { getJson } from "../http.ts";
import { countyRows } from "../../../src/server/birds.ts";
import type { WireBirdCounty } from "../../../src/server/wire.ts";
/** How long either GET may take. */
const TIMEOUT_MS = 8_000;
/** California has 58 counties; the cap is a bound on a bad day, not a fit. */
const MAX_COUNTIES = 256;
export interface BirdsSnapshot {
/** Epoch ms at which this box completed the fetch. */
fetchedAt: number;
/** ISO-8601 of the ten-minute granule, or "" if the upstream sent none. */
observedAt: string;
counties: WireBirdCounty[];
/** Last night, from the state row. Never a sum over counties. */
statewide: {
crossed: number;
peakAloft: number;
peakAt: string;
meanAltitude: number;
heading: string;
} | null;
attribution: string[];
}
export interface BirdsLog {
warn(msg: string): void;
}
interface CountiesResponse {
observedAt?: unknown;
counties?: unknown;
attribution?: unknown;
}
interface NightResponse {
night?: unknown;
attribution?: unknown;
}
export async function fetchProjection(
base: string,
key: string,
log: BirdsLog,
): Promise<BirdsSnapshot | null> {
if (base === "") return null;
const headers = key === "" ? undefined : { "x-tera-key": key };
const options = { timeoutMs: TIMEOUT_MS, ...(headers === undefined ? {} : { headers }) };
const [countiesBody, nightBody] = await Promise.all([
getJson<CountiesResponse>(`${base}/counties`, options),
getJson<NightResponse>(`${base}/night`, options),
]);
if (countiesBody === null || nightBody === null) {
log.warn(
"birds:cloud1: the projection did not answer with both halves " +
`(counties ${countiesBody === null ? "failed" : "ok"}, ` +
`night ${nightBody === null ? "failed" : "ok"}); keeping the last whole body`,
);
return null;
}
const rows = readArray(countiesBody.counties, MAX_COUNTIES, readCounty);
return {
fetchedAt: Date.now(),
observedAt: nonEmptyString(countiesBody.observedAt) ?? "",
// The state row goes here and not downstream. Everything past this point is
// a county with a coordinate and an area.
counties: countyRows(rows),
statewide: readNight(nightBody.night),
attribution: mergeAttribution(countiesBody.attribution, nightBody.attribution),
};
}
// ---- Reading somebody else's JSON -----------------------------------------
function readCounty(raw: unknown): WireBirdCounty | null {
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return null;
const row = raw as Record<string, unknown>;
const id = nonEmptyString(row.id);
const lat = finite(row.lat);
const lon = finite(row.lon);
const areaKm2 = finite(row.areaKm2);
// No id, no coordinate or no area means nothing downstream can place it or
// scatter it. All three are structural, not cosmetic — and the state row is
// missing two of the three, which is the second reason it never survives.
if (id === null || lat === null || lon === null || areaKm2 === null) return null;
return {
id,
name: nonEmptyString(row.name) ?? "",
lat,
lon,
areaKm2,
aloft: Math.max(0, finite(row.aloft) ?? 0),
altitude: Math.max(0, finite(row.altitude) ?? 0),
direction: finite(row.direction) ?? 0,
speed: Math.max(0, finite(row.speed) ?? 0),
};
}
/**
* Last night's state row.
*
* `null` rather than a zeroed object when it is missing, because zero birds
* crossing California is a finding and an absent summary is not one.
*/
function readNight(raw: unknown): BirdsSnapshot["statewide"] {
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return null;
const row = raw as Record<string, unknown>;
const crossed = finite(row.crossed);
const peakAloft = finite(row.peakAloft);
if (crossed === null || peakAloft === null) return null;
return {
crossed,
peakAloft,
peakAt: nonEmptyString(row.peakAt) ?? "",
meanAltitude: Math.max(0, finite(row.meanAltitude) ?? 0),
heading: nonEmptyString(row.heading) ?? "",
};
}
function readArray<T>(raw: unknown, cap: number, read: (row: unknown) => T | null): T[] {
if (!Array.isArray(raw)) return [];
const out: T[] = [];
for (const row of raw) {
if (out.length >= cap) break;
const parsed = read(row);
if (parsed !== null) out.push(parsed);
}
return out;
}
function mergeAttribution(a: unknown, b: unknown): string[] {
const out: string[] = [];
for (const source of [a, b]) {
if (!Array.isArray(source)) continue;
for (const line of source) {
if (typeof line !== "string" || line === "" || out.includes(line)) continue;
out.push(line);
}
}
return out;
}
function nonEmptyString(value: unknown): string | null {
if (typeof value !== "string") return null;
const trimmed = value.trim();
return trimmed === "" ? null : trimmed;
}
function finite(value: unknown): number | null {
return typeof value === "number" && Number.isFinite(value) ? value : null;
}
+122
View File
@@ -0,0 +1,122 @@
/**
* Which migration this box serves — California's, or none.
*
* The same shape as `fires/index.ts` and `radar/index.ts`. One body, one cache
* key, no region parameter: the whole state's granule is 58 rows and every board
* is a rectangle inside it, so a server that filtered by board would be doing a
* worse version of a clip the client has to do anyway (`promoteBirds` in
* `src/server/birds.ts`).
*
* ### The empty body carries a reason, and that is the difference from fires
*
* A fire body with nothing in it needs no explanation beyond its own age:
* nothing is burning. An empty *migration* body needs one, because the layer is
* absent about fourteen hours in every twenty-four by construction and a viewer
* arriving at noon would otherwise read a working feed as a broken one. So
* `quiet` is not optional on the wire and is filled in here even for a box with
* no source at all.
*
* ### `none` is a quiet sky, not an invented flock
*
* The same asymmetry `fires/index.ts` argues. An invented aeroplane is a
* plausible aeroplane; three hundred thousand invented birds over a named county
* is a claim about a real place. So the synthetic case is the honest quiet one —
* which is also what keeps `scripts/check-zero-config-boot.mjs` green with
* `TERA_BIRDS_SOURCE` unset.
*/
import { fetchProjection, type BirdsSnapshot } from "./cloud1.ts";
import { createUpstream } from "../upstream.ts";
import type { Config } from "../config.ts";
import type { BirdsBody } from "../../../src/server/wire.ts";
export interface BirdsService {
current(): Promise<BirdsBody>;
}
export interface BirdsLog {
warn(msg: string): void;
}
const CALIFORNIA_KEY = "california";
/**
* Five minutes.
*
* Granules are ten minutes apart and the collector reads them in batches about
* twenty minutes behind, so anything faster is two machines' work for identical
* bytes. Floored for the reason every other feed here is floored:
* `TERA_BIRDS_TTL=0` reads like "fresh" and means "one fetch pair per request".
*/
const MIN_TTL_SECONDS = 300;
/**
* What a box with no source answers with.
*
* Note the `quiet` reason: `no-data`, not `daylight`. Nobody has looked, which
* is a different thing from having looked and found the sun up, and the panel
* says so in words rather than showing the same blank for both.
*/
function emptyBody(ttlSeconds: number): BirdsBody {
return {
source: "none",
fetchedAt: new Date(0).toISOString(),
observedAt: null,
counties: [],
statewide: null,
quiet: {
reason: "no-data",
message:
"No migration feed is configured on this box, so nothing is drawn over " +
"the state. That is a fact about this box, not about the sky.",
},
ttlSeconds,
};
}
export function createBirdsService(config: Config, log: BirdsLog): BirdsService {
const { source, url, key, ttlSeconds } = config.birds;
const ttl = Math.max(MIN_TTL_SECONDS, ttlSeconds);
const upstream = createUpstream<BirdsSnapshot>({
label: "birds:cloud1",
ttlSeconds: ttl,
log,
});
return {
async current(): Promise<BirdsBody> {
if (source === "none") return emptyBody(ttl);
const snapshot = await upstream.get(CALIFORNIA_KEY, () => fetchProjection(url, key, log));
if (snapshot === null) return emptyBody(ttl);
// Whether the sky is quiet is decided in the browser, where the sun's
// elevation is already known to a hundredth of a degree
// (`atmosphere.ts`). The server would have to compute a second opinion
// from a clock the scrubber does not own, and the two would disagree on
// exactly the frames that matter. So `quiet` is `null` here whenever
// anything at all arrived, and `promoteBirds` fills it in.
const quiet: BirdsBody["quiet"] =
snapshot.counties.length > 0
? null
: {
reason: "no-data",
message:
"The migration feed answered with no counties in it. " +
"Either the newest granule has not landed yet or nothing was measured.",
};
return {
source,
fetchedAt: new Date(snapshot.fetchedAt).toISOString(),
observedAt: snapshot.observedAt === "" ? null : snapshot.observedAt,
counties: snapshot.counties,
statewide: snapshot.statewide,
quiet,
ttlSeconds: ttl,
...(snapshot.attribution.length > 0 ? { attribution: snapshot.attribution } : {}),
};
},
};
}
+95
View File
@@ -24,10 +24,12 @@ import { isSafeIceUrl } from "../../src/media/iceValidation.ts";
import { adsbAttribution, checkAdsbEndpoint, FIRST_PARTY_RECEIVER } from "./flights/licence.ts";
import type {
AuthMode,
BirdsSourceId,
DevicesSourceId,
FiresSourceId,
FlightsSourceId,
MarkersSourceId,
RadarSourceId,
SatellitesSourceId,
WeatherSourceId,
} from "../../src/server/wire.ts";
@@ -124,6 +126,25 @@ export interface FiresConfig {
detectionWindowHours: number;
}
/**
* One sky feed — radar or birds — pointed at a cloud-1 projection, or at
* nothing.
*
* Identical in shape to `FiresConfig` minus the window, because both feeds have
* the same relationship to their upstream: no database on this box, one base URL
* on somebody else's tailnet, an optional key, and a source that is `none` until
* an operator says otherwise. `source` is kept generic over the two id types so
* that neither feed can be handed the other's.
*/
export interface SkyConfig {
source: RadarSourceId & BirdsSourceId;
/** Base URL, no trailing slash. Empty on every source but `cloud1`. */
url: string;
/** Sent as `x-tera-key` when set. See `FiresConfig.key`. */
key: string;
ttlSeconds: number;
}
/**
* A first-party hardware bridge — an operator's own studio, over their own
* network.
@@ -286,6 +307,16 @@ export interface Config {
flights: FlightsConfig;
satellites: SatellitesConfig;
fires: FiresConfig;
/**
* The two sky feeds, both off by default and both harmless when they are.
*
* They are separate sections rather than one `sky` because they are separate
* upstreams with separate cadences — a NEXRAD composite advances every ten
* minutes and a BirdCast granule is written once and never changes — and a
* shared TTL would be wrong for one of them on every request.
*/
radar: SkyConfig;
birds: SkyConfig;
markers: MarkersConfig;
devices: DevicesConfig;
/** The first-party device bridge. Read by `devices/firstParty.ts` only. */
@@ -312,6 +343,8 @@ export function loadConfig(env: Env = process.env): Config {
const flights = loadFlights(env, degraded);
const satellites = loadSatellites(env, degraded);
const fires = loadFires(env, degraded);
const radar = loadSky(env, "RADAR", "radar", 300, degraded);
const birds = loadSky(env, "BIRDS", "migration", 600, degraded);
const studio = loadStudio(env);
const devices = loadDevices(env, studio, degraded);
const auth = loadAuth(env, degraded);
@@ -344,6 +377,8 @@ export function loadConfig(env: Env = process.env): Config {
flights,
satellites,
fires,
radar,
birds,
markers,
devices,
studio,
@@ -691,6 +726,66 @@ function loadFires(env: Env, degraded: string[]): FiresConfig {
};
}
/**
* One sky feed, off by default, for exactly the reason fires are.
*
* A zero-config clone must make no outbound requests at all
* (`scripts/check-zero-config-boot.mjs`), and both of these would be requests to
* a *private* machine on somebody else's tailnet. There is no public default to
* fall back to and there should not be: a stranger's board draws no rain and no
* birds, which is the truth about what that box knows.
*
* `none` serves a real, empty body — never an invented shower and never an
* invented flock. The asymmetry with the simulated flight plan is the one
* `SatellitesBody` states: an invented aeroplane is a plausible aeroplane, and
* an invented thunderstorm is a claim that it is raining on somebody who can
* look out of the window.
*
* Note what is deliberately NOT here: an unset `TERA_RADAR_SOURCE` appends
* nothing to `degraded`. Off by default is a choice this repo made, not a
* default that needs configuring, and `check-zero-config-boot.mjs` refuses to
* pass with *any* demotion on an empty environment. What tells a viewer that a
* quiet board is quiet because nobody asked is `sources.radar` / `sources.birds`
* on the health body and the epoch-zero `fetchedAt` on the body itself.
*/
function loadSky(
env: Env,
prefix: "RADAR" | "BIRDS",
noun: string,
defaultTtl: number,
degraded: string[],
): SkyConfig {
const asked = str(env, `TERA_${prefix}_SOURCE`, "none");
let source = oneOf(asked, SKY_SOURCES);
if (source === null) {
degraded.push(
`TERA_${prefix}_SOURCE="${asked}" is not one of ${SKY_SOURCES.join(", ")}; ` +
`serving no ${noun}.`,
);
source = "none";
}
const url = str(env, `TERA_${prefix}_URL`, "").replace(/\/+$/, "");
if (source === "cloud1" && url === "") {
degraded.push(
`TERA_${prefix}_SOURCE=cloud1 needs TERA_${prefix}_URL, the base of the projection ` +
`endpoint. Serving no ${noun}: an empty sky is the honest answer, and the board says ` +
"how old its last answer is.",
);
source = "none";
}
return {
source,
url,
key: str(env, `TERA_${prefix}_KEY`, ""),
ttlSeconds: num(env, `TERA_${prefix}_TTL`, defaultTtl, degraded),
};
}
/** Both sky feeds take the same two ids, which is why one list serves both. */
const SKY_SOURCES: (RadarSourceId & BirdsSourceId)[] = ["none", "cloud1"];
const SATELLITE_SOURCES: SatellitesSourceId[] = ["none", "celestrak"];
/**
+199
View File
@@ -0,0 +1,199 @@
/**
* The radar feed's one upstream: a **projection**, served by the machine that
* owns the database.
*
* ### Read this before changing anything here
*
* `echo_cells` carries a column called `distance_km`, and it is measured **from
* a private house**. It is the same leak class `server/src/fires/cloud1.ts` was
* written to make impossible, and the resolution is the same one: this module
* has **no database**. It makes two bounded GETs against an endpoint whose
* SELECT lists are written out by hand on the other machine, and it cannot be
* careless with a column it was never sent.
*
* That ordering is the security property. A route somebody writes next year
* cannot select a column that is not on this box, and no amount of care in this
* file would have been as strong. It is also why there is no sqlite driver in
* this repo's dependency list and why `scripts/check-dependency-licenses.mjs` is
* entitled to keep it that way.
*
* **Nothing here may ever name `distance_km` — not in a query string, not in a
* field list, not in a comment that becomes a query string.**
* `server/src/test/sky.test.ts` asserts the adopted key set *equals* an
* allowlist and that no request path contains the word, which is the second
* line: an `assert.ok(!keys.has("distance_km"))` would pass for every column
* nobody thought to name, and that is precisely the class of column that gets
* added later.
*
* ### Both halves, or neither
*
* Cells and station status are asked for in parallel and `null` is returned if
* **either** fails. A lattice with a silently empty station list is a board that
* has quietly stopped drawing its own coverage holes, and it would draw them as
* clear sky — the strongest possible version of the failure this whole layer
* exists to prevent. One snapshot, one answer; `upstream.ts` above this keeps
* serving the last good whole body until a whole one arrives.
*/
import { getJson } from "../http.ts";
import type { RadarCell, RadarStation } from "../../../src/server/radar.ts";
/** How long either GET may take. The upstream reads sqlite behind its own cache. */
const TIMEOUT_MS = 8_000;
/**
* The most rows this build will adopt from either endpoint.
*
* The statewide lattice is under 1,700 cells and the whole station table is
* sixteen rows, so both of these are two orders of magnitude clear of anything
* real. They are the second of two bounds rather than the only one — a caller
* that trusts an upstream's cap inherits the day the upstream's cap changes.
*/
const MAX_CELLS = 8_000;
const MAX_STATIONS = 128;
export interface RadarSnapshot {
/** Epoch ms at which this box completed the fetch. */
fetchedAt: number;
/** ISO-8601 of the volume scan the cells came from. */
observedAt: string;
/**
* Share of California at or above the rain threshold, 0..1.
*
* Measured upstream over the whole state's pixels, not over our lattice. It is
* what promotion is decided from, and it is scale-free — see
* `RADAR_MIN_WET_FRACTION` in `src/server/radar.ts` for why that matters.
*/
wetFraction: number;
cells: RadarCell[];
stations: RadarStation[];
attribution: string[];
}
export interface RadarLog {
warn(msg: string): void;
}
/** What the projection endpoint answers with. Restated, never imported. */
interface CellsResponse {
observedAt?: unknown;
wetFraction?: unknown;
cells?: unknown;
attribution?: unknown;
}
interface StationsResponse {
stations?: unknown;
attribution?: unknown;
}
export async function fetchProjection(
base: string,
key: string,
log: RadarLog,
): Promise<RadarSnapshot | null> {
if (base === "") return null;
const headers = key === "" ? undefined : { "x-tera-key": key };
const options = { timeoutMs: TIMEOUT_MS, ...(headers === undefined ? {} : { headers }) };
const [cellsBody, stationsBody] = await Promise.all([
getJson<CellsResponse>(`${base}/cells`, options),
getJson<StationsResponse>(`${base}/stations`, options),
]);
if (cellsBody === null || stationsBody === null) {
log.warn(
"radar:cloud1: the projection did not answer with both halves " +
`(cells ${cellsBody === null ? "failed" : "ok"}, ` +
`stations ${stationsBody === null ? "failed" : "ok"}); keeping the last whole body`,
);
return null;
}
return {
fetchedAt: Date.now(),
observedAt: nonEmptyString(cellsBody.observedAt) ?? "",
// Clamped, not trusted. A `wetFraction` served as a percentage rather than a
// fraction would promote every frame for ever, and the number is the only
// thing standing between a quiet day and 1,596 texels of nothing.
wetFraction: clamp01(finite(cellsBody.wetFraction) ?? 0),
cells: readArray(cellsBody.cells, MAX_CELLS, readCell),
stations: readArray(stationsBody.stations, MAX_STATIONS, readStation),
attribution: mergeAttribution(cellsBody.attribution, stationsBody.attribution),
};
}
// ---- Reading somebody else's JSON -----------------------------------------
//
// Field by field, checked rather than cast, and the field list is exhaustive on
// purpose: what is not named here does not enter this process. The upstream is a
// machine on the same tailnet run by the same person, which is exactly the
// relationship that produces "it will always be the right shape" — and then a
// collector gains a column.
function readCell(raw: unknown): RadarCell | null {
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return null;
const row = raw as Record<string, unknown>;
const lat = finite(row.lat);
const lon = finite(row.lon);
const dbz = finite(row.dbz);
if (lat === null || lon === null || dbz === null) return null;
return { lat, lon, dbz };
}
function readStation(raw: unknown): RadarStation | null {
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return null;
const row = raw as Record<string, unknown>;
const lat = finite(row.lat);
const lon = finite(row.lon);
const id = nonEmptyString(row.id);
if (id === null || lat === null || lon === null) return null;
return {
id,
lat,
lon,
// Carried verbatim and judged once, in the gate, where the argument for the
// judgement can be read. Six of sixteen stations read "Maintenance Action
// Mandatory" right now and every one of them is transmitting.
type: nonEmptyString(row.type),
operability: nonEmptyString(row.operability),
};
}
function readArray<T>(raw: unknown, cap: number, read: (row: unknown) => T | null): T[] {
if (!Array.isArray(raw)) return [];
const out: T[] = [];
for (const row of raw) {
if (out.length >= cap) break;
const parsed = read(row);
if (parsed !== null) out.push(parsed);
}
return out;
}
/** Both credit lines, de-duplicated, in the order they arrived. */
function mergeAttribution(a: unknown, b: unknown): string[] {
const out: string[] = [];
for (const source of [a, b]) {
if (!Array.isArray(source)) continue;
for (const line of source) {
if (typeof line !== "string" || line === "" || out.includes(line)) continue;
out.push(line);
}
}
return out;
}
function nonEmptyString(value: unknown): string | null {
if (typeof value !== "string") return null;
const trimmed = value.trim();
return trimmed === "" ? null : trimmed;
}
function finite(value: unknown): number | null {
return typeof value === "number" && Number.isFinite(value) ? value : null;
}
function clamp01(value: number): number {
return Math.min(1, Math.max(0, value));
}
+113
View File
@@ -0,0 +1,113 @@
/**
* Which reflectivity this box serves — which is California's, or none.
*
* The same shape as `fires/index.ts`, and it takes the same decisions for the
* same reasons: one body, one cache key, no region parameter, a TTL with a
* floor, and `none` serving a real empty body rather than an invented shower.
*
* ### The lattice is built here, on the server, and that is deliberate
*
* `RadarBody.field` is a lattice rather than a list of cells, so somebody has to
* turn one into the other. Doing it here means the anomalous-propagation rule,
* the coverage-hole rule and the promotion threshold are applied **once, in one
* place, by the module that can be tested without a browser** — and it means a
* quiet day costs the wire an empty body rather than 1,596 texels' worth of JSON
* that the client would then throw away. `src/server/radar.ts` is imported by
* both ends precisely so there is one statement about the data and not two.
*
* The board's own coastline comes from the city pack, which is data and types
* and imports no renderer. That is the same arrangement `media/bindings.ts`
* already uses for the office packs, and it is what keeps the land/sea test from
* being a second, drifting copy of the shoreline.
*
* ### `none` serves an empty sky and never an invented one
*
* The flights service falls back to a simulated plan, because an empty sky over
* a city reads as a bug. Nothing here does. An invented aeroplane is a plausible
* aeroplane; an invented thunderstorm is a claim that it is raining on somebody
* who can look out of the window. So the synthetic case *is* the clear sky —
* `field: null`, `source: "none"`, `fetchedAt` at the Unix epoch — which is
* exactly what `weather/synthetic.ts` does when it answers with
* `precipitation: 0`, and it is what keeps `scripts/check-zero-config-boot.mjs`
* green on a keyless clone.
*
* `fetchedAt` is the epoch and not "now", because "now" would be a claim that
* this was fetched a moment ago. It was never fetched, and the board says so.
*/
import { fetchProjection, type RadarSnapshot } from "./cloud1.ts";
import { createUpstream } from "../upstream.ts";
import { buildRadarField } from "../../../src/server/radar.ts";
import CALIFORNIA_CITY from "../../../src/cities/california.ts";
import type { Config } from "../config.ts";
import type { RadarBody } from "../../../src/server/wire.ts";
export interface RadarService {
current(): Promise<RadarBody>;
}
export interface RadarLog {
warn(msg: string): void;
}
/** One state, one key. See `fires/index.ts` for the argument. */
const CALIFORNIA_KEY = "california";
/**
* Two minutes.
*
* The composite advances every ten minutes and reaches the collector about five
* minutes late, so asking faster than this spends two machines' work to receive
* identical bytes. The floor exists because `TERA_RADAR_TTL=0` reads like "as
* fresh as possible" and means "one fetch pair per inbound request" — the trap
* `TERA_FLIGHTS_TTL=0` and `TERA_FIRES_TTL=0` both were.
*/
const MIN_TTL_SECONDS = 120;
function emptyBody(ttlSeconds: number): RadarBody {
return {
source: "none",
fetchedAt: new Date(0).toISOString(),
field: null,
ttlSeconds,
};
}
export function createRadarService(config: Config, log: RadarLog): RadarService {
const { source, url, key, ttlSeconds } = config.radar;
const ttl = Math.max(MIN_TTL_SECONDS, ttlSeconds);
const upstream = createUpstream<RadarSnapshot>({
label: "radar:cloud1",
ttlSeconds: ttl,
log,
});
return {
async current(): Promise<RadarBody> {
if (source === "none") return emptyBody(ttl);
const snapshot = await upstream.get(CALIFORNIA_KEY, () => fetchProjection(url, key, log));
if (snapshot === null) return emptyBody(ttl);
const built = buildRadarField({
cells: snapshot.cells,
stations: snapshot.stations,
bounds: CALIFORNIA_CITY.bounds,
coast: CALIFORNIA_CITY.landmasses,
observedAt: snapshot.observedAt,
wetFraction: snapshot.wetFraction,
});
return {
source,
fetchedAt: new Date(snapshot.fetchedAt).toISOString(),
// `null` on a quiet day, which is nine days in ten and is not a fault.
// The age beside it is what tells a viewer apart from a dead feed.
field: built.field,
ttlSeconds: ttl,
...(snapshot.attribution.length > 0 ? { attribution: snapshot.attribution } : {}),
};
},
};
}
+27
View File
@@ -0,0 +1,27 @@
/**
* `GET /api/v1/birds` — tonight's nocturnal migration, and, far more often, the
* reason there is none.
*
* No query, for the same reason `/radar` takes none: one granule covers the
* whole state and every board is a rectangle inside it.
*
* The body is publicly cacheable and carries nothing personal at all — BirdCast
* is a Cornell Lab forecast product aggregated to counties, and the county
* coordinates are Census internal points. The interesting property is the other
* one: **an empty body is the normal body**, so this route answers 200 with an
* empty `counties` array and a filled-in `quiet` for about fourteen hours of
* every twenty-four. A client that treats an empty array as a fault will be
* wrong most of the day, which is why `quiet` is not optional on the wire.
*/
import type { FastifyInstance } from "fastify";
import { publicCache } from "../cache.ts";
import type { BirdsService } from "../birds/index.ts";
export function registerBirds(app: FastifyInstance, birds: BirdsService): void {
app.get("/api/v1/birds", async (req, reply) => {
const body = await birds.current();
publicCache(req, reply, body.ttlSeconds);
return body;
});
}
+8
View File
@@ -43,6 +43,14 @@ export function registerHealth(app: FastifyInstance, services: Services): void {
markers: config.markers.source,
devices: config.devices.source,
fires: config.fires.source,
// The two sky feeds. Optional on `HealthBody`, so a client reading an
// older box's answer sees `undefined` and correctly concludes it serves
// neither; published here so "why is there no rain on this board" is
// answered by the box rather than by its env file. Read straight off the
// config for the same reason every line above it is: this route touches
// no service and cannot be made to fail by somebody else's outage.
radar: config.radar.source,
birds: config.birds.source,
},
auth: {
mode: config.auth.mode,
+34
View File
@@ -0,0 +1,34 @@
/**
* `GET /api/v1/radar` — the statewide reflectivity lattice, for everybody.
*
* No query, for the reason `/fires` and `/satellites` take none: the answer does
* not vary by who asked or where they are looking. There is one composite over
* California, the boards are rectangles inside it, and with no parameter there
* is no key space and none of the amplification concerns that shape
* `regions.ts`.
*
* ### Publicly cacheable, and the reason is structural
*
* The upstream store carries `echo_cells.distance_km`, measured from a private
* house. It cannot reach a shared cache, and not because this route is careful:
* because **this box has no database**. cloud-1 serves a projection with a
* hand-written column list and there is nothing here to be careless with. What a
* CDN can hold is a grid of NWS reflectivity — a public-domain US government
* product, the same one every weather site in the country redraws.
*
* `publicCache` still applies its own credential check, so a request that
* arrived with a session attached falls back to the fail-closed
* `private, no-store` default.
*/
import type { FastifyInstance } from "fastify";
import { publicCache } from "../cache.ts";
import type { RadarService } from "../radar/index.ts";
export function registerRadar(app: FastifyInstance, radar: RadarService): void {
app.get("/api/v1/radar", async (req, reply) => {
const body = await radar.current();
publicCache(req, reply, body.ttlSeconds);
return body;
});
}
+406
View File
@@ -0,0 +1,406 @@
/**
* The two sky feeds, and the one property they exist to guarantee.
*
* Most of this file is about **which keys reach the wire**, which is unusual for
* a feed test and is the whole point of this one. `radar.sqlite`'s `echo_cells`
* table carries a column called `distance_km`, and it is measured from a private
* house — the same leak class `server/src/test/fires.test.ts` was written
* against, in a store nobody would think to check because it is full of weather.
*
* The structural defence is that this box has no database: cloud-1 serves a
* projection with a hand-written column list, so there is nothing here to be
* careless with. The tests below are the second line, and they take the same
* shape the fire test does — the adopted key set **equals** an allowlist, so a
* field added upstream fails here rather than arriving in a browser. An
* `assert.ok(!keys.has("distance_km"))` would have passed for every column
* nobody thought to name, which is precisely the class of column that gets added
* later.
*
* The rest is the ordinary feed contract, plus one thing neither flights nor
* fires has: **an empty body is the normal body**. California is under rain a
* mean 0.596% of the time and BirdCast measures only after dark, so a quiet
* answer here is a finding and not a fault, and `quiet` is not optional on the
* birds body for exactly that reason.
*/
import assert from "node:assert/strict";
import { after, beforeEach, describe, it } from "node:test";
import { buildApp } from "../app.ts";
import { loadConfig } from "../config.ts";
import type { BirdsBody, RadarBody } from "../../../src/server/wire.ts";
/**
* Every key `RadarBody.field` may carry. The lattice, and the two panel
* counters, and nothing else.
*
* Adding to this list is a **privacy decision**, not a refactor. The question to
* answer first is "can this be inverted, joined or differenced into the distance
* from a rain cell to somebody's front door" — because `echo_cells.distance_km`
* is literally that, already computed, sitting one careless SELECT away on the
* machine at the other end of this fetch.
*/
const FIELD_KEYS = [
"cellLat", "cellLng", "cols", "dbz", "minLat", "minLng",
"observedAt", "rows", "stations", "stationsDown", "wetFraction",
] as const;
/** The same, for a radar station. */
const STATION_KEYS = ["id", "lat", "lon", "operability", "type"] as const;
/** And for a county. BirdCast is public, but the rule is the rule. */
const COUNTY_KEYS = [
"aloft", "altitude", "areaKm2", "direction", "id", "lat", "lon", "name", "speed",
] as const;
/** Home-relative names, in every spelling the two sides use. None may appear. */
const FORBIDDEN = ["distance_km", "distanceKm", "bearing_deg", "bearingDeg", "threat", "home", "px"];
const realFetch = globalThis.fetch;
let calls: string[] = [];
let cellsBody: unknown = null;
let stationsBody: unknown = null;
let countiesBody: unknown = null;
let nightBody: unknown = null;
/**
* A projection response shaped as cloud-1 would serve one — including the
* columns this build must never adopt, and one it simply does not read.
*/
function upstreamCells(): unknown {
return {
observedAt: "2026-08-23T03:55Z",
wetFraction: 0.01941,
readAt: "2026-08-23T04:00:21.000Z",
attribution: ["NEXRAD base reflectivity via NOAA/NWS and Iowa State University IEM"],
cells: [
// Riverside, in the monsoon. Real.
{ lat: 33.875, lon: -117.375, dbz: 61.5, px: 302, distance_km: 18.4, provenance: "us-nws/iem-n0q" },
{ lat: 33.625, lon: -117.375, dbz: 44.0, px: 91, distance_km: 26.1 },
{ lat: 34.125, lon: -117.625, dbz: 33.0, px: 40, distance_km: 31.7 },
],
};
}
function upstreamStations(): unknown {
return {
attribution: ["Radar status from NOAA/NWS"],
stations: [
{ id: "KSOX", lat: 33.81773, lon: -117.63599, type: "WSR-88D", operability: "RDA - Maintenance Action Required", alarms: "Communication", power_w: 620 },
{ id: "KVTX", lat: 34.41166, lon: -119.1786, type: "WSR-88D", operability: "RDA - Maintenance Action Mandatory" },
{ id: "KHNX", lat: 36.31416, lon: -119.63213, type: "WSR-88D", operability: "RDA - On-line" },
],
};
}
function upstreamCounties(): unknown {
return {
observedAt: "2026-08-23T03:20:00Z",
attribution: ["Nocturnal migration from BirdCast (Cornell Lab of Ornithology / Colorado State)"],
counties: [
{ id: "US-CA-019", name: "Fresno County", lat: 36.761006, lon: -119.655019, areaKm2: 15569, aloft: 82549, altitude: 333, direction: 140.0, speed: 6.9, mtr: 372.1, vid: 5.3 },
{ id: "US-CA-107", name: "Tulare County", lat: 36.228834, lon: -118.781055, areaKm2: 12531, aloft: 52108, altitude: 446, direction: 130.1, speed: 7.5 },
// The row that is a state pretending to be a county: no coordinate, no
// area, and 9.6 times the largest county's birds.
{ id: "US-CA", name: "California", lat: null, lon: null, areaKm2: null, aloft: 793141, altitude: 605, direction: 140.6, speed: 4.8 },
],
};
}
function upstreamNight(): unknown {
return {
attribution: ["Nocturnal migration from BirdCast (Cornell Lab of Ornithology / Colorado State)"],
night: {
crossed: 393290.37,
peakAloft: 1501193.14,
peakAt: "2026-08-22T06:20:00Z",
meanAltitude: 726.0,
heading: "south-east",
granules: 65,
},
};
}
globalThis.fetch = (async (input: unknown) => {
const url = String(input);
calls.push(url);
const body = url.includes("/cells")
? cellsBody
: url.includes("/stations")
? stationsBody
: url.includes("/counties")
? countiesBody
: nightBody;
if (body === null) return new Response("nope", { status: 503 });
return new Response(JSON.stringify(body), {
status: 200,
headers: { "content-type": "application/json" },
});
}) as unknown as typeof globalThis.fetch;
after(() => {
globalThis.fetch = realFetch;
});
beforeEach(() => {
calls = [];
cellsBody = upstreamCells();
stationsBody = upstreamStations();
countiesBody = upstreamCounties();
nightBody = upstreamNight();
});
function appWith(env: Record<string, string>) {
const config = loadConfig(env);
config.logLevel = "silent";
return buildApp(config);
}
const CLOUD1 = {
TERA_RADAR_SOURCE: "cloud1",
TERA_RADAR_URL: "http://127.0.0.1:9/api/radar",
TERA_BIRDS_SOURCE: "cloud1",
TERA_BIRDS_URL: "http://127.0.0.1:9/api/birds",
};
async function radar(app: ReturnType<typeof buildApp>) {
const res = await app.inject({ method: "GET", url: "/api/v1/radar" });
assert.equal(res.statusCode, 200);
return { body: res.json() as RadarBody, headers: res.headers };
}
async function birds(app: ReturnType<typeof buildApp>) {
const res = await app.inject({ method: "GET", url: "/api/v1/birds" });
assert.equal(res.statusCode, 200);
return { body: res.json() as BirdsBody, headers: res.headers };
}
/** Every key anywhere in a body, however deep. */
function keysOf(value: unknown, into = new Set<string>()): Set<string> {
if (Array.isArray(value)) {
for (const item of value) keysOf(item, into);
} else if (value !== null && typeof value === "object") {
for (const [key, inner] of Object.entries(value)) {
into.add(key);
keysOf(inner, into);
}
}
return into;
}
// ---- Off by default -------------------------------------------------------
describe("a box with no sky source", () => {
it("serves a real empty sky rather than an invented shower", async () => {
const app = appWith({});
after(() => app.close());
const { body } = await radar(app);
assert.equal(body.source, "none");
assert.equal(body.field, null);
// The epoch, not "now". "Now" would claim this was fetched a moment ago; it
// was never fetched, and a board with nothing on it has to be able to say
// which of those two it is looking at.
assert.equal(Date.parse(body.fetchedAt), 0);
assert.equal(calls.length, 0, "and nothing was asked of anybody");
});
it("serves an empty migration body that says why it is empty", async () => {
const app = appWith({});
after(() => app.close());
const { body } = await birds(app);
assert.equal(body.source, "none");
assert.deepEqual(body.counties, []);
assert.equal(body.statewide, null);
// The difference from fires: an empty fire body needs no explanation, and an
// empty migration body does, because the layer is absent fourteen hours in
// every twenty-four by construction.
assert.ok(body.quiet, "quiet is not optional");
assert.equal(body.quiet.reason, "no-data");
assert.match(body.quiet.message, /fact about this box/);
assert.equal(calls.length, 0);
});
it("adds no demotion line, because off is a choice and not a misconfiguration", async () => {
const config = loadConfig({});
assert.deepEqual(config.degraded, []);
assert.equal(config.radar.source, "none");
assert.equal(config.birds.source, "none");
});
it("demotes cloud1 with no URL, and says so in one sentence", async () => {
const config = loadConfig({ TERA_RADAR_SOURCE: "cloud1", TERA_BIRDS_SOURCE: "cloud1" });
assert.equal(config.radar.source, "none");
assert.equal(config.birds.source, "none");
assert.equal(config.degraded.length, 2);
assert.match(config.degraded.join(" "), /TERA_RADAR_URL/);
assert.match(config.degraded.join(" "), /TERA_BIRDS_URL/);
});
});
// ---- The leak -------------------------------------------------------------
describe("what may cross the wire", () => {
it("adopts exactly the named cell and station keys and nothing else", async () => {
const app = appWith(CLOUD1);
after(() => app.close());
const { body } = await radar(app);
assert.ok(body.field, "the fixture is a real Riverside storm and must promote");
const keys = keysOf(body);
for (const forbidden of FORBIDDEN) {
assert.ok(!keys.has(forbidden), `${forbidden} reached the wire`);
}
// The lattice is built on the server, so a cell never crosses as a cell: the
// field is one row-major array of numbers with a shape beside it. That is a
// stronger property than filtering a cell's columns — there is no object for
// a new column to attach to — and this is what asserts it stayed true.
assert.deepEqual(
Object.keys(body.field).sort(),
[...FIELD_KEYS].sort(),
"the field carries exactly the keys the wire contract declares",
);
assert.deepEqual(
[...keys].filter((key) => (STATION_KEYS as readonly string[]).includes(key)),
[],
"a station survives only as a count — `stations` and `stationsDown`",
);
assert.ok(!keys.has("alarms"), "…and its alarm summary never leaves cloud-1");
assert.ok(!keys.has("power_w"));
});
it("never names distance_km in a request path", async () => {
const app = appWith(CLOUD1);
after(() => app.close());
await radar(app);
await birds(app);
assert.ok(calls.length >= 4);
for (const url of calls) {
for (const forbidden of FORBIDDEN) {
assert.ok(!url.includes(forbidden), `${url} names ${forbidden}`);
}
}
});
it("adopts exactly the named county keys", async () => {
const app = appWith(CLOUD1);
after(() => app.close());
const { body } = await birds(app);
assert.equal(body.counties.length, 2, "the state row is dropped on the way in");
for (const county of body.counties) {
assert.deepEqual([...Object.keys(county)].sort(), [...COUNTY_KEYS].sort());
}
const keys = keysOf(body.counties);
assert.ok(!keys.has("mtr"), "a column this build does not read must not be adopted");
assert.ok(!keys.has("vid"));
assert.ok(!body.counties.some((c) => c.id === "US-CA"));
});
it("takes last night from the state row and never from a sum", async () => {
const app = appWith(CLOUD1);
after(() => app.close());
const { body } = await birds(app);
assert.ok(body.statewide);
assert.equal(Math.round(body.statewide.crossed), 393290);
// Summing the counties for that night gives 2,360,086. Nothing here does.
assert.notEqual(Math.round(body.statewide.crossed), 2360086);
assert.ok(!("granules" in body.statewide), "and only the five fields the wire declares");
});
});
// ---- Both halves, or neither ---------------------------------------------
describe("a projection that half answers", () => {
it("returns null when either half of the radar fetch fails", async () => {
const app = appWith(CLOUD1);
after(() => app.close());
stationsBody = null;
const first = await radar(app);
// A lattice with a silently empty station list would draw every coverage
// hole as clear sky, which is the strongest possible version of the failure
// this whole layer exists to prevent.
assert.equal(first.body.field, null);
assert.equal(Date.parse(first.body.fetchedAt), 0);
});
it("returns null when either half of the birds fetch fails", async () => {
const app = appWith(CLOUD1);
after(() => app.close());
nightBody = null;
const { body } = await birds(app);
// Without last night's figures the daylight sky has nothing to say but
// "nothing is aloft", which is the blank panel this layer exists to avoid.
assert.deepEqual(body.counties, []);
assert.equal(body.statewide, null);
assert.ok(body.quiet);
});
it("keeps serving the last whole body when the upstream goes away", async () => {
const app = appWith({ ...CLOUD1, TERA_RADAR_TTL: "0" });
after(() => app.close());
const good = await radar(app);
assert.ok(good.body.field);
const stamp = good.body.fetchedAt;
cellsBody = null;
// Past the floored TTL, so the next request really does try again.
await new Promise((resolve) => setTimeout(resolve, 5));
const stale = await radar(app);
assert.ok(stale.body.field, "ten-minute-old radar beats no radar, and beats a 503 by more");
assert.equal(stale.body.fetchedAt, stamp);
});
it("floors the TTL, so TERA_RADAR_TTL=0 is not a fetch pair per request", async () => {
const config = loadConfig({ ...CLOUD1, TERA_RADAR_TTL: "0", TERA_BIRDS_TTL: "0" });
const app = buildApp({ ...config, logLevel: "silent" });
after(() => app.close());
const first = await radar(app);
assert.ok(first.body.ttlSeconds >= 120);
const second = await birds(app);
assert.ok(second.body.ttlSeconds >= 300);
});
});
// ---- The lattice ----------------------------------------------------------
describe("the lattice the server builds", () => {
it("is the product's own grid clipped to the state board", async () => {
const app = appWith(CLOUD1);
after(() => app.close());
const { body } = await radar(app);
assert.ok(body.field);
assert.equal(body.field.rows, 38);
assert.equal(body.field.cols, 42);
assert.equal(body.field.dbz.length, 38 * 42);
assert.equal(body.field.cellLat, 0.25);
assert.equal(body.field.stations, 3);
// Two of the three carry a maintenance work order and both are transmitting.
assert.equal(body.field.stationsDown, 0);
assert.ok(JSON.stringify(body).length < 40_000, "the whole body is small enough to cache");
});
it("draws nothing when statewide coverage is under the floor", async () => {
const app = appWith(CLOUD1);
after(() => app.close());
cellsBody = { ...(upstreamCells() as Record<string, unknown>), wetFraction: 0.0001 };
const { body } = await radar(app);
assert.equal(body.field, null, "promotion is decided from coverage, not from cell count");
assert.notEqual(Date.parse(body.fetchedAt), 0, "…and the feed is alive, which the age says");
});
it("is publicly cacheable, because a projection cannot leak what it never sent", async () => {
const app = appWith(CLOUD1);
after(() => app.close());
const { headers } = await radar(app);
assert.match(String(headers["cache-control"]), /public/);
const bird = await birds(app);
assert.match(String(bird.headers["cache-control"]), /public/);
});
});