fix: finalize durable Pi lifecycle

This commit is contained in:
2026-08-04 21:34:08 +02:00
parent 5ba2821a1b
commit a368889838
20 changed files with 1016 additions and 147 deletions
+53 -16
View File
@@ -13,10 +13,12 @@ thothctl --installation /absolute/path/thothii-installation.yaml pi logs
thothctl --installation /absolute/path/thothii-installation.yaml pi configure
```
`status` executes the image-bundled `pi --version`. `doctor` and `test` require a healthy core,
a successful Pi smoke, valid settings, and an exact selected provider/model pair from the
backend's available model entries. `pi check` remains an alias for `pi test`. Logs are always a
bounded, sanitized 200-line snapshot; there is no follow mode.
`status` executes the image-bundled `pi --version`. `doctor` compares that value with both the
container's `PI_VERSION` contract and the `io.thothii.pi.version` image label; a merely nonempty
version is not sufficient. `doctor` and `test` also require a healthy core, a successful Pi smoke,
valid settings, and an exact selected provider/model pair from the backend's available model
entries. `pi check` remains an alias for `pi test`. Logs are always a bounded, sanitized 200-line
snapshot; there is no follow mode.
On a TTY, `pi configure` presents numbered provider, model, and thinking choices. Providers and
models come from the backend's closed model list, and the model choices are restricted to the
@@ -27,10 +29,29 @@ thothctl --installation /absolute/path/thothii-installation.yaml pi configure \
--provider zai --model glm-5.2 --thinking medium
```
The helper snapshots the previous settings, applies the new values atomically, and verifies the
readback and rendered-configuration digest. A helper, readback, or digest failure triggers an
attempted restore followed by another readback. The command reports the actual host path from
`PI_AUTH_FILE`; credentials remain in that protected host file and must never be passed as flags.
The helper snapshots exact settings-file existence and raw bytes, applies the new values atomically,
and verifies the readback and rendered-configuration digest. A helper, readback, or digest failure
restores those exact bytes when the prior file existed; on a clean installation it removes the new
file and verifies the absent/default state. Empty prior files are supported. The command reports
the actual host path from `PI_AUTH_FILE`; credentials remain in that protected host file and must
never be passed as flags.
## Supported Compose entry points and current image
Use `thothctl start`, `stop`, `status`, `logs`, and `doctor` for ordinary installation lifecycle
operations. All `thothctl` Compose commands automatically include the installation-specific
durable selector when it exists:
```text
<projectDirectory>/.thothctl/<installation-id>/current-image.yaml
```
This selector is part of the supported installation state: it keeps a verified Pi image selected
across a fresh `thothctl` process, stop/start, reconcile, and source checkout whose base image is
digest-pinned. Do not delete or hand-edit it. Direct raw `docker compose` lifecycle commands bypass
this protection and are unsupported. Advanced documented Compose rendering must use
`scripts/compose-with-preflight.sh` and include the same selector with `-f` when present; connector
secret overrides must never bypass that preflight wrapper.
## Updating Pi
@@ -63,7 +84,11 @@ their work, `--drain` makes the command poll the authenticated bare-array
The configured `core.image` is never retagged or mutated. Each installation transaction creates
unique candidate and previous tags, including when two installations share a configured tag or
the configured image is digest-pinned. A temporary lifecycle-only Compose override selects those
tags for build, recreate, and rollback. Terminal success removes the override.
tags for build, recreate, and rollback. Candidate build, pull, or candidate-tag failures happen
before `mutation_started` and therefore never recreate or roll back core. After verification, the
temporary candidate selector is atomically promoted to the durable current-image override.
Rollback atomically promotes the previous selector. Terminal cleanup removes only transaction
files and never deletes the durable selector.
Only `core` is recreated, with `--no-deps --force-recreate`; `frontend` is not recreated and no
volume-replacement flags are used. Verification checks health, exact requested Pi version, the
@@ -75,8 +100,8 @@ persistence-mount fingerprint.
Recovery state and lock diagnostics live under:
```text
<projectDirectory>/.thothctl/update-state.json
<projectDirectory>/.thothctl/update-state.json.lock.owner.json
<projectDirectory>/.thothctl/<installation-id>/update-state.json
<projectDirectory>/.thothctl/<installation-id>/update-state.json.lock.owner.json
```
The recovery file is mode `0600` and records transaction-scoped image identities, mount
@@ -103,12 +128,24 @@ thothctl --installation /absolute/path/thothii-installation.yaml pi maintenance
thothctl --installation /absolute/path/thothii-installation.yaml pi maintenance recover --yes
```
`maintenance recover` refuses a pending transaction. For terminal or absent recovery state, it
removes a stale lifecycle override, verifies the running installation when the gate is active, and
only then removes the durable marker and reopens admission. If rollback or recovery fails, leave
the marker in place, preserve `update-state.json`, repair the reported Docker/configuration issue,
and rerun rollback or maintenance recovery.
`maintenance recover` completes an interrupted verified-image promotion, safely finalizes a
preparation interrupted before core mutation, and refuses other pending mutations. For terminal or
absent recovery state, it removes only a stale transaction override, verifies the running
installation when the gate is active, and only then removes the durable marker and reopens
admission. It never removes `current-image.yaml`. If rollback or recovery fails, leave the marker
in place, preserve `update-state.json`, repair the reported Docker/configuration issue, and rerun
rollback or maintenance recovery.
Missing confirmation, invalid arguments, active sessions, and an interrupted transaction exit
`2`. Docker and verification failures exit nonzero with concise, redacted guidance. Direct
read-only/log commands preserve the original Docker child exit code.
## Go 1.24 dependency security boundary
`golang.org/x/sys` is a direct dependency for the Windows durable-replace implementation. The
newest release compatible with the required Go 1.24 toolchain is pinned (`v0.41.0`). Releases
`v0.42.0` through `v0.44.0` require Go 1.25, so `v0.44.0` cannot be selected without changing the
product toolchain contract. Govulncheck reports GO-2026-5024 at module level for `v0.41.0`, but no
thothctl call trace reaches the vulnerable `windows.NewNTUnicodeString`; thothctl calls only
`UTF16PtrFromString` and `MoveFileEx` in that package. Upgrade to at least `v0.44.0` together with
the planned Go 1.25-or-newer toolchain migration.