fix(rust): protocol 13 — a configuration save that settles after its reload (F9, D179)
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:
@@ -67,8 +67,8 @@ configRouter.post(
|
||||
'/:serverId/file',
|
||||
// #swagger.tags = ['Admin · Rust']
|
||||
// #swagger.summary = 'Save a configuration file and reload its plugin'
|
||||
// #swagger.description = 'Send `edits` (the generated form: pointers and literals, type-preserving) or `text` (the raw tier: the whole document). `version` must match what the host holds or the save is refused 409 with the current file. The game backs the file up, writes it, reloads the named plugin, and **restores the old file automatically** if the plugin does not come back — which is answered 200 with `report.rolledBack`, because a rollback is a round trip that worked and an edit that did not.'
|
||||
/* #swagger.responses[200] = { description: 'What happened: applied, or rolled back with the reason' } */
|
||||
// #swagger.description = 'Send `edits` (the generated form: pointers and literals, type-preserving) or `text` (the raw tier: the whole document). `version` must match what the host holds or the save is refused 409 with the current file. The game backs the file up, writes it, reloads the named plugin, and **restores the old file automatically** if the plugin fails to load. With a plugin to reload, the answer comes as soon as the file is written — `pending: true` and `write.id` — and the outcome is read from `GET …/writes/{writeId}` once the reload settles. Without one, the answer is final.'
|
||||
/* #swagger.responses[200] = { description: 'Written and reloading (`pending`, with the write’s id), or the final outcome when nothing was reloaded' } */
|
||||
/* #swagger.responses[400] = { description: 'Invalid body, an edit the form may not make, or a locked key' } */
|
||||
/* #swagger.responses[409] = { description: 'The file changed on the host since it was read' } */
|
||||
/* #swagger.responses[503] = { description: 'The sidecar or the game is unreachable' } */
|
||||
@@ -105,4 +105,18 @@ configRouter.get(
|
||||
config.history,
|
||||
)
|
||||
|
||||
configRouter.get(
|
||||
'/:serverId/writes/:writeId',
|
||||
// #swagger.tags = ['Admin · Rust']
|
||||
// #swagger.summary = 'One configuration write, while its reload settles'
|
||||
// #swagger.description = 'A save that reloads a plugin answers as soon as the file is written, with `pending: true` and this write’s id; the reload can take longer than a request should, behind a cold compile. Poll this until `outcome` leaves `reloading`: `applied`, or `rolled-back` with the reason, the server’s log, and `restored` (whether the plugin came back on its old file). `lost` means the plugin never reported — it was reloaded, or the link dropped — so re-read the file.'
|
||||
/* #swagger.responses[200] = { description: 'The write and how far it has got' } */
|
||||
/* #swagger.responses[404] = { description: 'No such write on that server' } */
|
||||
requireRole('admin'),
|
||||
param('serverId').matches(SERVER_ID),
|
||||
param('writeId').isInt({ min: 1 }),
|
||||
validate,
|
||||
config.writeStatus,
|
||||
)
|
||||
|
||||
module.exports = configRouter
|
||||
|
||||
Reference in New Issue
Block a user