• v0.24.0 ec09cdffe4

    v0.24.0 — a read-only query API over the published tables
    All checks were successful
    CI / gate (push) Successful in 48s
    CI / image (push) Successful in 17s
    Stable

    joeyr released this 2026-08-06 20:51:27 -05:00 | 97 commits to main since this release

    Phase 2 of docs/query-api-plan.md: a read-only HTTP service over the tables 0.23.0 started writing.

    Nothing consumes it yet. The crawler still listens on nothing and still commits to Forgejo; lol-companion still fetches the raw URLs.

    The service

    A second entrypoint on the same image — node dist/src/api/index.js. Three routes, each one query:

    Route Auth Returns
    GET /v1/health none liveness, plus the age of the oldest artifact
    GET /v1/manifest key patch, publishedAt, sha256, size per artifact
    GET /v1/artifacts/{name} key the whole file, byte-identical, brotli, ETag

    Built on node:http rather than a framework. This is a router with one path parameter over a service that never accepts a body, and the dependency count stays at three.

    The stored brotli is served untouched when the client accepts it, so the common path spends no CPU compressing. Measured over loopback on patch 16.15:

    Artifact File On the wire Served
    synergies 7.88 MB 448 KB 48 ms
    counters 1.71 MB 110 KB 20 ms
    runes 1.32 MB 50 KB 12 ms

    A manifest poll that finds nothing changed is 1.1 KB and a 304 in 2.8 ms; a conditional GET of the 7.6 MB synergies file is a 304 in 9.8 ms.

    /v1/artifacts/{name} is also the only response that reproduces the committed bytes, because it serves payload rather than reassembling rows — Postgres normalises jsonb key order. scripts/verify-api.mjs checks exactly that, in both encodings, against each raw URL.

    Keys

    Per install, never one shared key baked into the app: an asar unpacks in minutes, and a shared key cannot be revoked without breaking every install that has not updated. Per-key issuance buys revocation, per-client rate limits and accounting. It does not buy secrecy, and nothing here needs any — these are anonymised aggregates of public match data, world-readable at the Forgejo URLs today.

    npm run key:create -- --label joey-desktop     # prints the key once
    npm run key:create -- --list
    npm run key:create -- --revoke <key_id>
    

    Secrets are stored as SHA-256 deliberately, not bcrypt or argon2: the secret is 256 bits of CSPRNG output, so there is no dictionary to run and a slow KDF would spend real CPU per request to buy nothing.

    The API gets its own Postgres role — SELECT on the published tables and api_key, UPDATE on last_used_at, no DDL and no INSERT. It never holds the crawler's DSN, so participant_stat is unreadable from the process serving HTTP rather than merely unqueried. The crawler therefore creates api_key, and the API asserts the tables exist at boot so a deployment ordering mistake reads as one sentence instead of a 500 per request.

    Two corrections to the plan, both found by building it

    • Keys parse with an anchored regex, not split('_'). base64url's alphabet contains _, so splitting on the last separator lands inside roughly half of all real secrets — and it would have surfaced as an intermittent authentication failure rather than a parse error. There is a test for exactly this case.
    • The api_key_active partial index is dropped. key_id is already the primary key.

    Fixed

    • Integration tests now run one file at a time. They share lolc_test and all truncate in beforeEach, so vitest's parallel workers had a second file deleting rows a first was midway through asserting on. Invisible until two files touched the same tables.

    Deploying

    Not deployed by this release. It needs a second service in the Swarm stack (same image, command: ["node", "dist/src/api/index.js"]), the lolc_api Postgres role, and at least one key minted in the crawler container. The stack file carries all three as comments.

    Keep it on the LAN: a bearer token over plaintext is a non-starter the moment it leaves the network.

    Downloads