docs(modules): phase 7b as built — configuration from the site, and the undo that makes it safe
PROTOCOL.md §11 specifies protocol 5: the walk rooted at the framework's own config directory, the version a write must present back, the set-shaped write, the watched reload and the automatic restore. PLAN.md §21 is the phase as built — four org-lead decisions, the float trap and what avoiding it cost, and four defects a browser found that 179 green tests did not. PLAYER_WALK.md gains the configuration walk, because the acceptance line needs the sidecar and the game on one host and the rig cannot reach one yet. INTEGRATION.md gains the four things an operator should know before they edit a plugin's settings from a web page. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PMH6bw1jXMgbyF3ZWGEzSM
This commit is contained in:
@@ -174,3 +174,44 @@ this phase has two specific things to confirm there rather than assume:
|
||||
**What counts as a pass:** a non-admin player's access in game changes because of something typed on
|
||||
the website and nothing else; a hand edit is reported rather than undone; and a wipe costs the
|
||||
operator nothing.
|
||||
|
||||
## The configuration walk (protocol 5, phase 7b)
|
||||
|
||||
Added 2026-09-22. The website half was walked end to end against a real sidecar and a stand-in
|
||||
plugin over a real directory of real config files — the recursive walk, a form save, a rollback, a
|
||||
refusal, a version conflict and the locked keys — and the plugin half **compiles and loads on the
|
||||
live Oxide rig**, where `rg.config` answers
|
||||
`protocol=5 framework=oxide root=/home/container/oxide/config`.
|
||||
|
||||
**What is left is the sentence the phase exists for: a setting changed on the website takes effect
|
||||
in the running game.** It needs the sidecar and the game server on **one host**, because the game
|
||||
link is loopback by design (D2) — on the Pterodactyl rigs that is phase 18's egg, and on a
|
||||
development machine it is a firewall rule for the port the plugin dials.
|
||||
|
||||
| # | Do this | You should see |
|
||||
|---|---|---|
|
||||
| 1 | **Open Admin → Rust mod config** and pick the server | The tree the framework actually uses — `oxide/config` on Oxide, `carbon/configs` on Carbon — grouped by plugin, with every loaded plugin's version beside it |
|
||||
| 2 | **Open `ZoneManager.json`, change a setting, leave the reload target on its guess, and save** | "Saved, and the plugin reloaded." At the console, `oxide.show`/`c.show` is irrelevant — the proof is the plugin behaving differently, so pick a setting you can see: `Auto Show Zones`, or an entry message |
|
||||
| 3 | **Check a float nobody touched**, e.g. a rate ending `.0`, in the file on the host | It is still `1.0`, not `1`. This is the trap the whole editor exists for, and a server whose configs are full of whole-numbered floats is where it bites |
|
||||
| 4 | **Break a config on purpose** — in Raw JSON, give a numeric field a string, or anything the plugin's own class cannot deserialize — and save with that plugin as the reload target | Within about four seconds: *"The plugin did not come back, so the old file was put back automatically"*, the compiler's own line underneath it, and the file on the host back as it was. `oxide.plugins` shows the plugin **loaded** — because the restore was reloaded too |
|
||||
| 5 | **Save a nested file** (`Kits/kits.json`, or any `config/<Mod>/x.json`) **and confirm the reload target** | The right plugin reloads. Reloading the wrong one is the failure this field exists to prevent, and it reports success — so check `oxide.plugins`' timestamps, not the website's word |
|
||||
| 6 | **Open the bridge's own config** | `Host`, `Port` and `ServerId` are read-only with the reason; `QueueCap` saves; the reload dropdown does not offer this plugin. The save says it was written and **not** reloaded, which is the honest answer — our settings apply on the next deliberate reload |
|
||||
| 7 | **Edit a file on the host over SSH while the website has it open, then save from the website** | A conflict, with the current file offered — never an overwrite |
|
||||
| 8 | **Ask for a file outside the tree** (`../data/oxide.users.data`, an absolute path) with `curl` against the sidecar, with a valid token | Refused by the **plugin**, with a reason. The sidecar forwards paths and judges none of them; the guard is where the directory is |
|
||||
|
||||
**Run it on both frameworks.** From phase 3, done means done on Oxide and on Carbon (R19/R21), and
|
||||
this phase has two specific things to confirm rather than assume:
|
||||
|
||||
- **The reload path.** The plugin asks `Interface.Oxide` for `ReloadPlugin` by reflection and falls
|
||||
back to a console command — `c.reload` on Carbon, `oxide.reload` on Oxide, chosen by looking for a
|
||||
Carbon assembly at runtime. A wrong prefix on Carbon prints **nothing at all**, which looks
|
||||
exactly like a command that worked (`CARBON.md` §5), so the proof is `OnPluginLoaded` arriving,
|
||||
not the command being accepted.
|
||||
- **`OnPluginLoaded` / `OnPluginUnloaded` firing at all.** They are the rollback's only evidence. If
|
||||
either does not fire on a framework, every save there rolls itself back four seconds later and
|
||||
reports a plugin that is in fact perfectly fine. `rg.hooks` at the console is the standing answer:
|
||||
both names are in `ExpectedHooks`, so a framework that never raises one shows a zero.
|
||||
|
||||
**What counts as a pass:** a setting typed on the website changes what the running game does; a
|
||||
deliberately broken config leaves the plugin loaded and the operator holding the reason; and no file
|
||||
the save did not touch differs by a single byte.
|
||||
|
||||
Reference in New Issue
Block a user