Add layered settings, and fix a migration mechanism that silently lied
Two things, because the second could not be built on the first. The schema stamp was part of the same execute_batch as the CREATE TABLE IF NOT EXISTS statements, and it wrote unconditionally. Opening an older file therefore added no columns but flipped the version forward anyway; opening a *newer* file stamped it back down and then wrote rows the newer build could not read. Both produced a database whose recorded version was a lie, and every future schema change would have inherited it. Now the version is read before anything is applied, migrations are ordered and forward-only inside one transaction, a newer file is refused with SchemaTooNew rather than downgraded, and a supported version raised without a step to reach it fails at the first open instead of claiming success. Tested by stamping a file at version 99 and asserting both the refusal and that the stamp is left untouched. lumbridge-settings resolves compiled default -> settings.toml -> environment. The environment sits above the file deliberately: decision 0016 calls LUMBRIDGE_CLAUDE_OAUTH=0 "one switch off", and a switch a config file can silently re-enable is not a switch. A pinned value renders disabled and names the variable, rather than accepting an edit that would do nothing. Every field carries a WriteAuthority. Routing all writes through Configure is the obvious design and would hand a layout-only agent the program every future pane launches — the guarantee decision 0006 exists to make. Anything naming a program, path or destination is Human-only, asserted by a test that reads the path rather than trusting the author. Four paths are permanently not settings, with the reason recorded beside each and a test asserting their absence: the usage endpoint URL, the credentials path, the client identity, and the shell program. A configuration file that can redirect where an access token is sent is a credential exfiltration path with a friendly name. Environment access is a trait rather than std::env, because the workspace forbids unsafe, set_var is unsafe in Rust 2024, and the layering rule has to be testable without mutating the process running the test. Verified live with LUMBRIDGE_CLAUDE_OAUTH=0: the account-endpoint row reads off, greyed, "pinned by LUMBRIDGE_CLAUDE_OAUTH". The Advanced page names every file, endpoint and child process Lumbridge touches and states that nothing is sent anywhere else — as a fact, not as a toggle nobody can flip. File loading, comment-preserving writes and editable controls are not in this pass; 0022 records why that order is the honest one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
72887cb4ab
commit
e5d7a3efd5
@@ -0,0 +1,86 @@
|
||||
# 0022: Settings are layered, and every field says who may write it
|
||||
|
||||
Status: accepted; the crate and the pane are live, file loading is not.
|
||||
|
||||
Everything Lumbridge could be configured by was an environment variable set
|
||||
inside `scripts/open-metal-lumbridge.sh`: invisible from inside the application,
|
||||
and unchangeable without editing the launcher.
|
||||
|
||||
## Layers
|
||||
|
||||
Compiled default → `settings.toml` → environment.
|
||||
|
||||
The environment sits **above** the file, which is the reverse of the usual
|
||||
arrangement and is deliberate. Decision 0016 calls `LUMBRIDGE_CLAUDE_OAUTH=0`
|
||||
"one switch off". A switch that a configuration file can silently re-enable is
|
||||
not a switch, so the file loses.
|
||||
|
||||
A value pinned by the environment renders **disabled**, naming the variable that
|
||||
pinned it. Not annotated — disabled. An interface that accepts an edit which
|
||||
will not take effect has lied about what it does.
|
||||
|
||||
Every value carries where it came from, for the same reason a usage number
|
||||
carries a provenance: a value you cannot trace is a value you cannot trust.
|
||||
|
||||
## Write authority
|
||||
|
||||
Routing every write through the `Configure` capability is the obvious design and
|
||||
it is wrong. It would hand a layout-only agent a way to set the program that
|
||||
every future pane launches, which is precisely the guarantee decision 0006
|
||||
exists to make.
|
||||
|
||||
So each field carries a `WriteAuthority`. Anything naming a **program, a path,
|
||||
or a network destination** is `Human`, and a non-human origin is refused rather
|
||||
than quietly downgraded. A test asserts the rule from the path itself, so a new
|
||||
setting whose name contains `path` or `feed` cannot ship as agent-writable.
|
||||
|
||||
## What is not a setting, permanently
|
||||
|
||||
`FORBIDDEN_PATHS` names them, with the reason beside each, and a test asserts
|
||||
they never appear:
|
||||
|
||||
- **`usage.oauth_endpoint`** — the URL an access token is sent to. A
|
||||
configuration file that can redirect it is a credential exfiltration path with
|
||||
a friendly name.
|
||||
- **`usage.credentials_path`** — the file the token is read from; the same attack
|
||||
from the other end.
|
||||
- **`usage.client_name`** — the identity Lumbridge presents. Decision 0016
|
||||
refuses to send the harness's own User-Agent because it would make our traffic
|
||||
indistinguishable from the harness's in the provider's logs; a settings key
|
||||
would undo that.
|
||||
- **`terminal.shell_program`** — decision 0006 again.
|
||||
|
||||
## Unknown keys are ignored, not rejected, and not silent
|
||||
|
||||
A file written by a newer build must still open in an older one, so the schema
|
||||
does not deny unknown fields. But a key that vanished without a word is a key the
|
||||
user thinks is in effect, so they are collected and reported on the Advanced
|
||||
page. A parse *error* is an error: the last good content is kept and a banner
|
||||
shown, because a typo must never silently reset a configuration to defaults.
|
||||
|
||||
## Advanced states facts, not toggles
|
||||
|
||||
"What Lumbridge reads" lists every file, endpoint and child process by name, and
|
||||
ends with "Nothing is sent anywhere else. There is no telemetry to turn off."
|
||||
That is a fact. Rendering it as a switch nobody can flip would be theatre.
|
||||
|
||||
## Paths
|
||||
|
||||
`~/.config/lumbridge/settings.toml` on Linux; on macOS under `Application
|
||||
Support` and explicitly **not** `~/Library/Preferences`, which is CFPreferences
|
||||
territory where `defaults` and the preference daemon rewrite files in their own
|
||||
format — fatal for a file whose comments are its documentation. Behind a trait
|
||||
with a shared contract test, so the macOS half is at least checked on Linux.
|
||||
|
||||
## Not in this pass
|
||||
|
||||
The file is not read yet: the pane resolves compiled defaults against the real
|
||||
environment, which is why the environment-pinned rows are already correct. Load,
|
||||
atomic 0600 write through `toml_edit` to preserve comments, and a directory
|
||||
watcher with echo suppression are the next step — a debounced write that bounces
|
||||
off its own watcher event loops forever, so the hash of the last written bytes
|
||||
has to be recorded and matched.
|
||||
|
||||
Editable controls are also pending. Every row shows its value, origin, authority
|
||||
and when it applies; none of them can yet be clicked. That is the honest order:
|
||||
the pane tells the truth about the configuration before it offers to change it.
|
||||
Reference in New Issue
Block a user