Foundation
- A readiness endpoint
/readyznext to/health./healthonly says that the process
answers, and it stays200during a restore so that Docker does not restart the container in
the middle of one./readyzsays whether the instance can serve right now:200with
{"status":"ready"}, or503with{"status":"not_ready","reason":"..."}while a restore is
running, the database connection is closed, the database is older than this version expects or
the data folder refuses writes. It needs no login, tells nothing else about the instance and
does not write to the database./healthand every shipped healthcheck stay as they are; the
new path is for a load balancer or an uptime monitor. - Unexpected errors are answered and logged in one place. The central error handler now
keeps the status of a client error that is marked as safe to show; until now every error
that reached it was a 500. Its sentence andreasonreach the client only from a
HttpError(server/utils/http-error.js), whose text we write ourselves; a 4xx of the HTTP
libraries gets a fixed sentence such as "The request was refused (Bad Request).", because
body-parser wraps foreign text (zlib) that way. Requests the HTTP layer refuses now get their
4xx instead of a 500: a corrupt compressed body, an aborted upload or a body shorter than its
Content-Length400, a form nested too deep 400, too many form fields 413, an unsupported
Content-Encodingor charset 415. A path parameter that does not decode (%ZZ) is a 400.
Everything else stays a 500 with the fixed sentence, and the raw text stays in the log. The
log line names the method, the route pattern and the status instead of the path, and the
raw segment of an undecodable parameter is no longer written, so a calendar feed token in the
URL no longer lands in the log. The cause of an error is logged with it. - The status of the last backup survives a restart. After a restart the backup page said
"never" until the next scheduled run, because the last attempt lived in memory only. The time of
the last attempt, the time of the last success and the reason of a failed attempt are now kept
in the database, without a schema change, and a restore keeps the values of the installation it
runs on rather than those inside the restored file.GET /api/v1/backup/statusgains
scheduler.lastSuccess. - A restore drill checks that a release database restores and migrates on this version.
scripts/restore-drill.mjsrestores a database through the same restore command an operator
uses, migrates it to the current schema and checks its integrity, foreign keys, schema and row
counts before it starts the server on it and reads every module. The rollback probe that
tries the way back before a foundation release moved into the repository as
scripts/rollback-probe/. Nothing changes for an installation. - Releases that change the database schema carry a frozen demo database. A new workflow
attachesyuvomi-<version>-corpus.dband its.sha256to every release whose schema differs
from the last one with such a file, and to every foundation release. It is the demo seed of
that release, encrypted with a public test key, and the restore drill migrates each of them
to the current version. Nothing changes for an installation. - Every day and in every pull request, all of those databases are restored and migrated.
A new workflow downloads each release's frozen database, restores it the way an operator
would, migrates it to the code under test and starts the server on it. A migration that
breaks an older database now fails in CI, and the run is also red when the newest release
changed the schema but has no frozen database. Nothing changes for an installation. - Lint, layer and dead-code checks run on every pull request. A new
lintjob runs
oxlint, dependency-cruiser, knip and zizmor and compares their findings with
lint-baseline.json, one count per file and rule that only sinks: a new finding or a rising
count fails, as with the type baseline. An assignment toinnerHTMLand anawaitinside a
database transaction have no baseline and fail at the first finding. Contributors run
npm run lintbefore a push; CONTRIBUTING.md lists the rules. Nothing changes for an
installation. - Tests that read source code as text are listed and counted.
test/text-reader-inventory.jsonnames every suite that reads a file underpublic/or
server/as text, with the files it reads and what becomes of it. A new suite of that kind
without an entry failsnpm run test:text-reader-inventory, and the number of checks that
hold a spelling instead of a behaviour only sinks. docs/test-suites.md has the rules. Nothing
changes for an installation. - CI runs the test suites side by side and takes about ten minutes instead of thirty.
TheTestsjob now runsnpm run test-parallelwith four jobs instead ofnpm test. A red
suite no longer hides the ones after it: the job log ends with every red suite and its log,
and when one is red, the logs are attached to the run. Contributors runnpm run test-parallelbefore a push.
A new push to a pull request cancels the run it replaces. - The way back to 2.88.1 was tried before the tag. Image 2.88.1 and this build ran in turn
on the same demo database, in production mode with a generated encryption key. Neither start
applied a migration, every module answered the same through the API before and after, and
the service worker switched from 2.88.1 to 2.89.0 and back without an error screen or a
failed request, with the session staying signed in. A backup made on 2.89.0 restored on
2.88.1 through the admin restore, and 2.88.1 started on a database that carries the new
backup status without a word about it. To go back, pin the image tag2.88.1.
Added
- Admins can ask the server what it runs on.
GET /api/v1/system/diagnosticsanswers with
the version and build revision, the Node.js and SQLite versions, platform, schema version and
the time of the last migration, the size of the database and its write-ahead log, free space on
the data volume, uptime, the status of the last backup with the next scheduled run, where the
database key comes from, readiness and the number of installed modules. It never carries a key,
a path, a hostname, the value of an environment variable or anything about a member, and it is
refused for members and for signed-in devices.GET /api/v1/backup/statusgains
scheduler.nextRun. The settings page does not show any of this yet.
Changed
- The container image of a release is now built from its tag alone. Until now a release
build reused layers from the build cache that builds ofmainfill, the dependency install
and the app build included. A tagged build now ignores that cache and writes none; only the
rollingmainimage keeps it. Nothing changes for an installation. - A failed backup says why, with a reason instead of the text of the error (from #2525,
thereasonon the last error proposed by @nomi25home in #2540).scheduler.lastErrorin
GET /api/v1/backup/statusnow always carriesreason, one ofbackup_dir_not_writable,
disk_full,backup_verify_failedandunknown, and itsmessageis the fixed sentence of
that reason. Until nowmessagewas the raw text of the error, with the absolute path of the
backup folder in it; that text is in the server log only now, where it still names the folder
and what to check.POST /api/v1/backup/triggeranswers a failed run as before with200and
success: false, and carries the samereason. The backup page does not show the reason yet.
Fixed
- The demo seed finds the database key the server stored itself.
scripts/seed-demo.js --db <path>now readsDB_ENCRYPTION_KEY_FILEand the key a first start keeps under
.secrets/next to the database, in the same order as the server. Until now it stopped on an
encrypted database unless the key was in the environment or in.env.