DVGEarth-state beacon

API

Canonical beacon, schema dvg-beacon/v1

The beacon is the lowercase hex SHA-256 of the UTF-8 canonical document below. Lines are separated by LF. There is no CR, no trailing space, and the document ends with one LF. Numbers use the ECMA-262 JSON.stringify spelling. held_6dp uses Number.prototype.toFixed(6). Tokens outside [A-Za-z0-9_.:/+-] are JSON strings.

unix_ns is the publish time in milliseconds with six zeros appended. The gauge stores millisecond ticks, so this is not a nanosecond measurement. prev is not inside the hash. It is the beacon of the previous publish row, or 64 zero digits for the earliest row. Leaving it outside the hash means a backfill of an older tick does not rewrite later permalinks.

Event-log rows are not inputs. A latch can close later. Latch fields come from held_json on the publish row. Domain lines are always in the order ground, solar, space, seismic, atmosphere. 0.3 rows have no threshold, flagged, coincidence, strength, or overlap; those lines are null.

schema=dvg-beacon/v1
source=prod|staging
track=0.4|0.3
split_version=<token>
tick_id=<epoch ms>
utc=<YYYY-MM-DDTHH:mm:ss.sssZ>
unix_ns=<tick_id with six zeros>
held_raw=<json number or null>
held_6dp=<six decimals or null>
baseline=<json number>
threshold=<json number or null>
flagged=0|1|null
coincidence=<json number or null>
strength=<json number or null>
overlap_ms=<integer or null>
domain.ground=<intensity>|<0|1>|<direction>
domain.solar=<intensity>|<0|1>|<direction>
domain.space=<intensity>|<0|1>|<direction>
domain.seismic=<intensity>|<0|1>|<direction>
domain.atmosphere=<intensity>|<0|1>|<direction>
latch.event_id=<token or null>
latch.origin=<token or null>
latch.fired_at=<integer or null>
latch.peak_at=<integer or null>
latch.domains=<comma-separated tokens or null>
latch.value_raw=<json number or null>

Endpoints

JSON, CORS *, no API key. GET responses are cacheable. /v1/now and /v1/check use max-age=20. A beacon for a tick more than ten minutes old uses a day of cache. Art for a beacon URL is immutable, because a changed row would be a different beacon. POST responses are no-store.

The default source is production track 0.4 (dvg-split-0.4.7). ?source=staging selects the staging database. ?track=0.3 or ?version=0.3 selects dvg_split_publish. Production has no 0.3 tables; that request returns available: false and does not query a missing table.

Rate limit: 120 requests a minute per IP, a fixed window kept in the isolate. It is best-effort. A cached GET may be served without reaching the worker. Over the limit, the API returns 429.

A beacon path that is 64 hex characters is looked up by hash. Pass ?t= with the tick id the permalink already carries. Without it, the worker hashes recent ticks, up to 20,000 of them.

Receipt, schema dvg-beacon-receipt/v1

Ed25519 over this document. The signature is standard base64. key_id is ed25519- plus the first 8 hex characters of SHA-256 of the raw 32-byte public key. The prose statement is not signed. Verification rebuilds this document from the fields and requires it to match canonical.

schema=dvg-beacon-receipt/v1
sha256=<64 hex>
signed_at=<ISO>
signed_at_unix_ns=<ms with six zeros>
beacon=<64 hex>
tick_id=<ms>
tick_utc=<ISO>
held_6dp=<six decimals or null>
held_raw=<json number or null>
source=prod|staging
track=0.4|0.3
split_version=<token>
key_id=<ed25519-########>

A valid receipt has a signature under the current service key, a beacon equal to the stored tick, signed_at at or after that tick, and no newer tick at or before signed_at. An optional sha256 must match the receipt. The receipt log is informational: a missing log entry does not by itself make a signature false.

The private key is the Worker secret BEACON_SIGNING_KEY: standard base64 of 64 bytes, the Ed25519 seed followed by the raw public key. Generate it with node scripts/generate-signing-key.mjs. Do not commit it.

Draw, algorithm sha256-counter-v1

n is 1 to 1,000,000. k is 1 to 100 and no larger than n. Picks are distinct integers in 0..n-1, in acceptance order. picks_one_based adds one for a human list.

The resolving tick is the first publish row with t >= seed_tick. Until that row exists, the response is a commitment. The commitment document is:

schema=dvg-beacon-draw/v1
seed_tick=<ms>
n=<n>
k=<k>
committed_beacon=<latest beacon>

Each counter hashes the following document and reads the first 8 bytes as a big-endian uint64. Values at or above floor(2^64 / n) * n are skipped, which removes modulo bias. Duplicates are skipped. The counter starts at 0.

schema=dvg-beacon-draw-pick/v1
beacon=<resolving beacon>
n=<n>
k=<k>
counter=<i>

Sigil and sound

The SVG is a function of the beacon bytes and the five intensities. Rim ticks follow beacon bits. The inner seven-point rune uses bytes 8 through 22. Spoke length is min(intensity, 3) / 3. Hot domains get a thicker spoke and a lamp. The sound sigil plays eight sine notes. Byte i picks a semitone in two octaves above 220 Hz, and domain i mod 5 bends the pitch by up to 12 percent. Note length is 140 ms plus 30 ms times byte mod 5.

Check

domain is held or one of the five domain ids. op is gt, gte, lt, lte, or eq. Equality compares the six-decimal display. The route only reads the latest tick.

Data

Reads use dvg_split04_publish and dvg_split04_events on track 0.4, and dvg_split_publish and dvg_split_events on track 0.3. Held value is held_json.value. Domain order on the publish row is ground, solar, space, seismic, atmosphere, each with intensity, hot, and direction. The worker prepares SELECT statements only for those databases. Receipts go to a separate KV namespace.