docs(website): record slice 2, and the three defects a browser found

The Modules screen as built (website#143), and the three things standing it
up against a live server exposed. Two of them are older than this phase and
neither was reachable from a test.

- A fresh install over a row the previous boot left `startup_failed` rendered
  the old failure - "module directory not present on the volume", one second
  after the files were written there - and, because that branch is not
  pending, suppressed the restart banner the install had just told the
  operator to use. Fixed by a derivation rather than a special case: the
  loader scans once at require time, so a module on the volume with no live
  record arrived after that scan and anything the row says predates it.

- The boot refresh had been nulling every install's provenance. source and
  sha256 exist for this screen and never survived a restart, because
  lifecycle.boot() re-records with neither and the upsert assigned both
  unconditionally. It could not have been found before Phase 4 wrote the first
  non-null value those columns ever had - and the model's test fake
  reproduced the defect faithfully, assigning exactly like the SQL.

- The restart killed the server outright on Windows. process.kill(pid,
  'SIGTERM') reaches the graceful handler on Linux and is unconditional
  termination where POSIX signals do not exist, so a Windows host got no
  module onShutdown, no pool close and no log flush. process.emit('SIGTERM')
  reaches the same listener everywhere. The test had stubbed process.kill and
  asserted the call - precisely the call whose meaning differs by platform.

That last one generalises: deployment is Linux containers and would never
have shown it. A smoke that only runs where the code ships cannot find a class
of defect that only bites the people developing it.

Also documents the COALESCE in BACKEND_DESIGN's installed_modules section.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-12 03:50:46 -05:00
parent 88b6257333
commit f753dfcff8
2 changed files with 77 additions and 1 deletions

View File

@@ -700,7 +700,7 @@ directory on the modules volume and its URL segment.
| `name`, `version` | the manifest's label and semver, for the admin Modules screen |
| `state` | ENUM `installed` / `enabled` / `disabled` / `started` / `startup_failed` |
| `failure_stage`, `failure_reason` | the stage a failure happened at (`manifest`, `core_api`, `mounts`, `extensions`, `schema`, `require`, `register`, `boot`) and its recorded reason |
| `source`, `sha256` | the release the bundle came from and the digest verified before unpacking; both NULL for a directory placed on the volume by hand |
| `source`, `sha256` | the release the bundle came from and the digest verified before unpacking; both NULL for a directory placed on the volume by hand. **Written only by an admin-panel install, and `COALESCE`d on upsert** — see below |
| `installed_at`, `started_at`, `updated_at` | `started_at` is the last **successful** start |
**This table never decides which routes exist.** The module loader scans the filesystem at require
@@ -722,6 +722,15 @@ on the volume (with NULL provenance for a hand-placed directory), marks any row
overwrite the operator's decision. Every one of those writes is individually caught: a row that will
not update is worse reporting, never a failed boot.
**Provenance is `COALESCE`d on upsert, and that is load-bearing.** The boot write above passes NULL
for `source` and `sha256` — honestly, since a scan finds a directory and never where it came from —
so a plain `source = VALUES(source)` overwrites both columns on *every* boot, and an admin-panel
install's provenance survives only until the restart that install asks for. The statement is
`source = COALESCE(VALUES(source), source)`: a value overwrites, a NULL leaves what is there. The cost
is that hand-placing a different bundle over a row installed from a URL keeps the old provenance,
which is stale rather than blank. Found in Phase 4 by installing a module and restarting; it could not
have been found earlier, because until then no caller had ever passed a non-null value.
Design of record: [`MODULE_SYSTEM.md`](MODULE_SYSTEM.md) §2.4; the loader's obligations are
[`MODULE_API.md`](MODULE_API.md) Part 4.