Files
ThothII/docs/contracts/thothctl-pi.md
T

8.2 KiB

thothctl pi lifecycle contract

thothctl is the only component that drives Docker lifecycle operations. The core container does not mount a Docker socket, and Pi is never updated in a running container.

Inspection and configuration

thothctl --installation /absolute/path/thothii-installation.yaml pi status
thothctl --installation /absolute/path/thothii-installation.yaml pi doctor
thothctl --installation /absolute/path/thothii-installation.yaml pi test
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 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 selected provider. In non-interactive use, all choices must be explicit:

thothctl --installation /absolute/path/thothii-installation.yaml pi configure \
  --provider zai --model glm-5.2 --thinking medium

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:

<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

Every update requires a pinned version, an explicit source, and confirmation:

thothctl --installation /absolute/path/thothii-installation.yaml pi update \
  --version 0.81.0 --source build --yes

thothctl --installation /absolute/path/thothii-installation.yaml pi update \
  --version 0.81.0 --source pull \
  --image registry.example.invalid/thothii-core@sha256:<64-lowercase-hex-digits> --yes

--source build rebuilds only core with PI_VERSION=<version>. --source pull requires an immutable digest reference; mutable tags, URL forms, and credential-bearing references are rejected. --source is never inferred.

Before inventory, update activates the durable maintenance gate. Activation writes /data/settings/maintenance.json in the mounted settings volume, closes admission, and waits for all leases. A recreated candidate reads that marker at startup and therefore starts gated. The loopback-only control endpoints cannot be reached through the frontend proxy and do not depend on the configured authentication principal mode. Lost activation/deactivation responses are resolved by querying gate status.

Open, unarchived sessions stop an update. After an operator has completed or otherwise drained their work, --drain makes the command poll the authenticated bare-array GET /sessions?scope=all response until no active sessions remain.

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. 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 provider/model/settings smoke, unchanged non-secret rendered configuration, and the complete persistence-mount fingerprint.

Recovery, rollback, and maintenance cleanup

Recovery state and lock diagnostics live under:

<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 fingerprints, target version/source, configuration digest, phase, and timestamp. It contains no credentials, endpoint values, secret paths, Compose output, or logs. A cross-platform OS advisory file lock serializes lifecycle operations; a crashed owner releases the lock automatically. Owner metadata is diagnostic only and cannot wedge acquisition if empty, partial, or stale.

Any post-candidate failure explicitly confirms or reactivates maintenance and rescans sessions before compensation. Automatic rollback selects the transaction's previous image through the lifecycle override and clears maintenance only after the previous image, configuration, mounts, health, Pi smoke, and terminal recovery write are verified. Ambiguous compensation remains gated.

For an interrupted transaction, first run:

thothctl --installation /absolute/path/thothii-installation.yaml pi rollback --yes

Inspect and clean a stale durable gate with:

thothctl --installation /absolute/path/thothii-installation.yaml pi maintenance status
thothctl --installation /absolute/path/thothii-installation.yaml pi maintenance recover --yes

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.