fix(rust): protocol 13 — a configuration save that settles after its reload (F9, D179)
Some checks failed
PR Checks / server-tests (pull_request) Failing after 16s
PR Checks / client-build (pull_request) Successful in 17s
PR Checks / frozen-manifest (pull_request) Failing after 37s

The plugin now answers a save once the files are written, with pending and
a writeId, and reports the reload later as a config.outcome event. The save
is recorded as reloading with a settle_by of two plugin ceilings plus slack
on the database's clock; ingest settles the row by (server, writeId), only
while it is still reloading, so a replay moves nothing and a late outcome
still lands. A row past settle_by reads as lost.

GET /admin/rust/config/:serverId/writes/:writeId serves the poll; the page
polls it every two seconds, holds the Save button while it waits, and says
whether a rolled-back plugin came back on its old file. config.outcome is
a staff kind: it carries the server's log tail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
2026-09-26 17:16:45 -05:00
parent 36eae7ffa8
commit 654a24585d
13 changed files with 515 additions and 37 deletions

View File

@@ -651,7 +651,7 @@ CREATE TABLE IF NOT EXISTS rust_config_writes (
-- whole document.
tier VARCHAR(16) NOT NULL DEFAULT 'form',
user_id INT NULL,
-- `applied` | `rolled-back` | `refused` | `unreachable`
-- `applied` | `rolled-back` | `refused` | `unreachable` | `reloading` (protocol 13)
outcome VARCHAR(24) NOT NULL,
reloaded TINYINT(1) NOT NULL DEFAULT 0,
changes LONGTEXT NULL,
@@ -669,6 +669,28 @@ CREATE TABLE IF NOT EXISTS rust_config_writes (
KEY idx_rust_config_writes_server (server_id, created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Protocol 13 (PLAN_FIXES.md F9, D177–D179). A save that reloads a plugin no
-- longer waits for the reload: it is recorded as `reloading` the moment the files
-- land, and ingest settles it from the plugin's `config.outcome`.
--
-- • `write_id` is the plugin's own name for the write — the frame carries it,
-- and the sidecar's request ids restart with the sidecar, so they cannot.
-- • `settle_by` is when a write that never heard back is LOST rather than slow:
-- two of the plugin's ceilings (the edit's reload and a restore's) plus
-- slack. Reads report `lost` past it; the row itself stays `reloading`, so an
-- outcome that arrives late — a link that was down — still settles it.
-- • `log_tail` is the server's log at the moment a reload failed. It used to go
-- straight back to the browser that asked; nothing is waiting now, so the
-- page reads it from here. Operator console output: admin-only like the rest.
-- • `restored` is whether the plugin came back on its old file after a
-- rollback. NULL when there was no rollback.
ALTER TABLE rust_config_writes ADD COLUMN IF NOT EXISTS write_id VARCHAR(64) NULL;
ALTER TABLE rust_config_writes ADD COLUMN IF NOT EXISTS settle_by DATETIME NULL;
ALTER TABLE rust_config_writes ADD COLUMN IF NOT EXISTS settled_at DATETIME NULL;
ALTER TABLE rust_config_writes ADD COLUMN IF NOT EXISTS log_tail TEXT NULL;
ALTER TABLE rust_config_writes ADD COLUMN IF NOT EXISTS restored TINYINT(1) NULL;
CREATE INDEX IF NOT EXISTS idx_rust_config_writes_write ON rust_config_writes (server_id, write_id);
-- ── Who may see who is online (the presence fix, 2026-09-22) ──────────────
--