-
v0.25.0
Stablereleased this
2026-08-07 21:42:54 -05:00 | 668 commits to main since this releaseAdded
-
The query API proves its dependencies at boot instead of announcing its
configuration. Phase 4 ofdocs/query-api-plan.md.Until now the service said one thing about itself when it started:
INF api/config > lol-companion-data api started | version=0.24.1 commit=7ef8062 listen=0.0.0.0:8080 database=postgres:5432/lolc_damage rate=120/min burst 60Every field on that line is configuration read back. Not one is evidence that
anything works. The crawler's block has been the opposite since 0.19.0 —
server=,entries=205,patch=16.15are all answers something gave it —
and the argument for one here is stronger, not weaker: the crawler's bad
moment is an unattended window at 02:00, this one's is a 500 served into champ
select, to a client on a draft timer.Six checks, one round trip each, run concurrently:
INF preflight/db | ok server=PostgreSQL 18.4 INF preflight/role | ok role=lolc_api INF preflight/schema | ok tables=4 INF preflight/grants | ok select=true update=last_used_at INF preflight/keys | ok active=1 revoked=0 INF preflight/artifacts | ok count=10 expected=10 patch=16.15 oldest=19hpreflight/roleis the one that matters most, and the crawler has no need
for it. The whole read-only design rests on this process connecting as
lolc_apirather than the crawler'slolc— that is what makes
participant_statunreadable rather than merely unqueried. Nothing already
logged can tell you which it got:databaseLabelstrips the credentials from
the DSN on purpose, so a stack file edited in a hurry could point this process
at the crawler's role and every line would look exactly as it does now. It has
to be asked, so it is:SELECT current_user, compared against the new
API_DB_ROLE, which defaults tololc_apiso a stack file that omits it
is still checked. Running against a development database aslolcmeans
setting it deliberately.preflight/keysis the cheapest real outage this catches. An API with
zero active keys boots clean, answers/v1/health, and 401s every real
request — which from the client's side is indistinguishable from a client bug.preflight/grantsprobes the write it never makes at boot. The
last_used_atUPDATE happens on a flush timer minutes later, so a missing
grant would otherwise surface as a background failure long after the deploy
that caused it.UPDATE api_key SET last_used_at = last_used_at WHERE false
matches no rows and writes nothing; Postgres checks table and column
privileges when the statement starts regardless, so the refusal is identical
to the one a real flush would get.Two of the six are fatal, and that is the one way this differs from the
crawler's block.preflight.tsis emphatic that nothing may stop the
crawler starting — a dead Forgejo token must not cost a night of Riot data a
fixed token could publish tomorrow. A read service has no equivalent argument:
it holds no work in progress, so refusing to start costs nothing that starting
broken would not cost more of.schemais fatal because a read service
against an unmigrated database 500s forever and cannot fix itself;roleis
fatal because it is a security boundary rather than a dependency, and
degrading gracefully into an HTTP surface with SELECT onparticipant_statis
not degrading gracefully. Grants, keys and artifacts stay loud and non-fatal —
each is fixable while the process runs.
Changed
-
assertSchemastopped being the reporter for faults it knows nothing
about. It was the first statement to touch the database, so every
connection-level failure surfaced there regardless of cause — which is how
0.24.0's malformedDATABASE_URLcame out as aTypeErrorfrom four frames
insidepgwhile its careful "the crawler owns these tables" message was
never reached. It is now one check of six, behindpreflight/db, and only
ever speaks about tables. -
The shape of a boot check moved to
src/check.ts, shared by both
processes.CheckOutcome,CheckStatus, the 10-second per-check timeout and
thepreflight/summaryline are now defined once. The two blocks are read in
the same Portainer viewer, often one above the other, so a parallel structure
would have drifted into two timeouts, two summary lines and two spellings of
"failed". What is deliberately not shared is fatality — that is a policy
about the checks rather than their shape, sologCheckBlocktakes the summary
wording instead of deciding it. -
ApiStore.ping()returns the server version rather than nothing, matching
Store.ping(), so both satisfy one check interface and the boot line can say
server=PostgreSQL 18.4instead of proving a query returned.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-