diff --git a/Cargo.lock b/Cargo.lock index d83b5a2..3a7a130 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3143,7 +3143,7 @@ dependencies = [ [[package]] name = "tw-api" -version = "0.60.0" +version = "0.61.0" dependencies = [ "serde", "serde_json", @@ -3154,7 +3154,7 @@ dependencies = [ [[package]] name = "tw-bedrock" -version = "0.60.0" +version = "0.61.0" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -3175,7 +3175,7 @@ dependencies = [ [[package]] name = "tw-breaker" -version = "0.60.0" +version = "0.61.0" dependencies = [ "serde", "serde_json", @@ -3183,7 +3183,7 @@ dependencies = [ [[package]] name = "tw-config" -version = "0.60.0" +version = "0.61.0" dependencies = [ "blake3", "libc", @@ -3208,7 +3208,7 @@ dependencies = [ [[package]] name = "tw-control" -version = "0.60.0" +version = "0.61.0" dependencies = [ "axum", "base64", @@ -3249,7 +3249,7 @@ dependencies = [ [[package]] name = "tw-dialect" -version = "0.60.0" +version = "0.61.0" dependencies = [ "serde", "serde_json", @@ -3257,7 +3257,7 @@ dependencies = [ [[package]] name = "tw-engine" -version = "0.60.0" +version = "0.61.0" dependencies = [ "serde", "serde_json", @@ -3270,7 +3270,7 @@ dependencies = [ [[package]] name = "tw-gateway" -version = "0.60.0" +version = "0.61.0" dependencies = [ "arc-swap", "async-stream", @@ -3319,7 +3319,7 @@ dependencies = [ [[package]] name = "tw-guard" -version = "0.60.0" +version = "0.61.0" dependencies = [ "base64", "bytes", @@ -3334,7 +3334,7 @@ dependencies = [ [[package]] name = "tw-link" -version = "0.60.0" +version = "0.61.0" dependencies = [ "serde", "serde_json", @@ -3348,7 +3348,7 @@ dependencies = [ [[package]] name = "tw-observe" -version = "0.60.0" +version = "0.61.0" dependencies = [ "tokio", "tracing", @@ -3357,7 +3357,7 @@ dependencies = [ [[package]] name = "tw-plugin" -version = "0.60.0" +version = "0.61.0" dependencies = [ "libc", "rand 0.10.2", @@ -3372,7 +3372,7 @@ dependencies = [ [[package]] name = "tw-pricing" -version = "0.60.0" +version = "0.61.0" dependencies = [ "arc-swap", "flate2", @@ -3385,14 +3385,14 @@ dependencies = [ [[package]] name = "tw-secret" -version = "0.60.0" +version = "0.61.0" dependencies = [ "thiserror", ] [[package]] name = "tw-store" -version = "0.60.0" +version = "0.61.0" dependencies = [ "blake3", "bytes", @@ -3412,7 +3412,7 @@ dependencies = [ [[package]] name = "tw-types" -version = "0.60.0" +version = "0.61.0" dependencies = [ "serde", "serde_json", @@ -3421,7 +3421,7 @@ dependencies = [ [[package]] name = "tw-watch" -version = "0.60.0" +version = "0.61.0" dependencies = [ "notify", "tempfile", @@ -3431,7 +3431,7 @@ dependencies = [ [[package]] name = "tw-yaml" -version = "0.60.0" +version = "0.61.0" dependencies = [ "saphyr-parser", "serde_yaml_ng", @@ -3441,7 +3441,7 @@ dependencies = [ [[package]] name = "twcore" -version = "0.60.0" +version = "0.61.0" dependencies = [ "anyhow", "axum", diff --git a/Cargo.toml b/Cargo.toml index 138c6bc..04e56e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,7 @@ categories = ["network-programming", "web-programming::http-server"] # 二进制走 CalVer,crate 走 SemVer —— 两者是两回事,卖给不同的人。 # 这里是 crate 的版本。 -version = "0.60.0" +version = "0.61.0" [workspace.dependencies] # ── 内部 crate ─────────────────────────────────────────── diff --git a/release-notes/0.61.0.md b/release-notes/0.61.0.md new file mode 100644 index 0000000..14c84f4 --- /dev/null +++ b/release-notes/0.61.0.md @@ -0,0 +1,24 @@ +This release makes safe mode work for the case it exists for: a configuration that does not load. Core serves the control plane anyway, says which line is wrong, and can repair the two commonest mistakes in one step. Before, a safe-mode core exited on the same error as a normal start, and the desktop app could only say that core had stopped. + +**Upgrade notes** + +- The control-plane protocol version (`CONTROL_API_VERSION`) goes from 36 to 37. ThinkWatch Lite connects only to a core with the same protocol version. ThinkWatch Lite 2026.10.2 includes 0.60.0 (protocol 36) and does not connect to 0.61.0. A server used with it stays on 0.60.0 until the app is updated to a release that includes 0.61.0; `sudo twcore upgrade --version 0.60.0 --restart` switches a server back. +- The request store's schema is unchanged (25): upgrading from 0.60.0 keeps the request history. +- The configuration format is unchanged. +- Changed in the protocol: + - New: `GET /config/repair` (`ConfigRepairPlan` → `ConfigRepair`: `base_version` and a list of `ConfigFix`) and `POST /config/repair` (`RepairConfig`: `ConfigRepairRequest { base_version }` → `ConfigWritten`). A web view may call both. + - `ConfigFix` has `kind` (`unknown_value` or `unknown_field`), `field` (a path such as `providers[0].protocol`), `line`, `value` (masked) and `now` (the default the field goes back to, when it is a single value). + - In safe mode, `Status.config_rejected` is set from the start when the configuration does not load, and `Status.gateway_addr` is null as before. +- Message codes, compared with 0.60.0: + - New: `config.unknown_variant` (`field`, `value`, `expected`), `config.unknown_field` (`field`, `expected`) and `control.config_not_repairable`. + - A value outside its choices and an unknown field used to come as `config.unparsable` with serde's sentence in `detail`; they now use the two new codes. Other parse errors still use `config.unparsable`. + +**Safe mode with a configuration that does not load.** `twcore serve --safe` used to load the configuration before anything else and exit when it did not load. It now serves the control plane with a stand-in configuration: the control key comes from the file, so a client that reads it from the same file can connect, and everything else is a default. The data plane does not start. +- `Status.config_rejected` says from the start which line is wrong, in the same form as for an edit that was refused while running: the stage, the message, the line and the line itself with secrets masked. +- Saving a configuration that loads (through the control plane, a rollback, a repair, or by editing the file) swaps it in as usual. A client then restarts core in normal mode. +- A file in which the control key cannot be found, such as one that is not valid YAML, still makes `serve --safe` exit, since nobody could connect to the control plane. + +**One-click repair.** `GET /config/repair` lists what a repair would change, and `POST /config/repair` makes those changes and writes the file. +- Only two kinds of mistake are repaired, and each repair removes one key. A value outside its choices, such as `titling: passthrough`, goes back to the field's default. An unknown field, such as a misspelled name, is removed. A section left empty is removed with its last key. Comments, layout and every other key are kept. +- A repair is offered only when the repaired configuration loads. A syntax error, a missing field and an error in the configuration as a whole (such as two upstreams with the same name) are left to the user. +- The repair is computed again from the file on disk when it is applied, and refused with 409 when the file has changed since the plan. It goes through the same path as any other write, so the previous version is in the history and can be rolled back.