docs: plan Pi restart operator workflow

This commit is contained in:
2026-08-14 17:32:07 +02:00
parent 87cefd120c
commit a227cbe755
@@ -0,0 +1,764 @@
# Pi Management Operator Workflow Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Add a safe `thothctl pi restart` command and replace the Pi Management dialog's duplicated technical copy with a structured Docker-only operator workflow.
**Architecture:** Keep image changes in the existing recoverable `pi update` transaction. Implement configuration reload as a separate restart transaction that retains the current image, shares the Pi lifecycle lock, and uses a separate recovery file so the latest update remains rollback-capable. The React dialog remains a settings/diagnostics surface and only explains platform-specific host actions.
**Tech Stack:** Go 1.26, Docker Compose v2, React 18, TypeScript, TanStack Query, Tailwind CSS, Vitest, Testing Library, shell documentation gates.
**Spec:** `docs/superpowers/specs/2026-08-14-pi-management-operator-workflow-design.md`
## Global Constraints
- Pi runs only inside the Docker Compose `core` service; add no host-Pi or raw-Compose operator guidance.
- `pi restart` requires `--yes`; without `--drain` it refuses active sessions, and with `--drain` it waits without terminating them.
- Restart retains the currently selected image and never builds, pulls, or promotes an image.
- Restart and update use separate recovery files but one installation-scoped lifecycle lock.
- The browser receives no Docker access, shell, credential values, or write access to `deploy/pi/*.json`.
- Use `~` for GUI home-directory examples. All GUI strings remain English.
- Platform tabs remain closed initially; dialog and instruction scrolling remain functional.
- Do not stage or modify the unrelated existing changes in `frontend/src/shell/WorkspaceManager.tsx` and `frontend/src/shell/WorkspaceManager.test.tsx`.
- `PiManagement.tsx` and its test contain approved uncommitted work from earlier revisions; edit and commit them only in the frontend task.
## File map
- Create `tools/thothctl/internal/pi/restart.go` and `restart_test.go` for the restart transaction.
- Modify `tools/thothctl/internal/pi/state.go` and `state_test.go` for one shared lifecycle lock.
- Modify `tools/thothctl/internal/config/installation.go` and its test for `restart-state.json`.
- Modify `tools/thothctl/internal/pi/update.go` and its test to share the active-session drain helper and compose restart recovery.
- Modify the `Target.Source` comment in `tools/thothctl/internal/pi/state.go` when `"restart"` becomes a valid non-image lifecycle source.
- Modify `tools/thothctl/cmd/thothctl/main.go` and its test for usage, parsing, dispatch, and recovery.
- Modify `frontend/src/shell/PiManagement.tsx` and its test for the structured workflow.
- Modify `docs/contracts/thothctl-pi.md`, `docs/install/pi-management.md`, `docs/general/pi-configuration.md`, and `scripts/verify-workspace-install-docs.sh`.
- Modify `README.md` only if it claims to list the complete Pi command surface.
---
### Task 1: Separate restart state and share the lifecycle lock
**Files:**
- Modify: `tools/thothctl/internal/config/installation.go:234-249`
- Test: `tools/thothctl/internal/config/installation_test.go`
- Modify: `tools/thothctl/internal/pi/state.go:171-224`
- Test: `tools/thothctl/internal/pi/state_test.go`
**Interfaces:**
- Produces: `func (Installation) RestartStatePath() string`
- Produces: `func lifecycleLockPath(statePath string) string`
- Preserves: `func acquireLock(statePath string) (*updateLock, error)`
- [ ] **Step 1: Write failing path and cross-operation lock tests**
Add to `installation_test.go`:
```go
if got, want := installation.RestartStatePath(), filepath.Join(installation.ControlDirectory(), "restart-state.json"); got != want {
t.Fatalf("RestartStatePath() = %q, want %q", got, want)
}
```
Add to `state_test.go`:
```go
func TestUpdateAndRestartStatePathsShareOneLifecycleLock(t *testing.T) {
dir := t.TempDir()
first, err := acquireLock(filepath.Join(dir, "update-state.json"))
if err != nil {
t.Fatal(err)
}
defer first.Release()
second, err := acquireLock(filepath.Join(dir, "restart-state.json"))
if !errors.Is(err, ErrLockHeld) || second != nil {
t.Fatalf("second lock = %#v, %v; want nil, ErrLockHeld", second, err)
}
}
```
- [ ] **Step 2: Run the focused tests and verify RED**
```bash
cd tools/thothctl
go test ./internal/config ./internal/pi -run 'Test.*(RestartStatePath|ShareOneLifecycleLock)' -count=1
```
Expected: compile failure for `RestartStatePath`, then lock-test failure until both files resolve to one lock.
- [ ] **Step 3: Implement the installation path and common lock**
Add to `installation.go`:
```go
func (i Installation) RestartStatePath() string {
return filepath.Join(i.ControlDirectory(), "restart-state.json")
}
```
Change lock derivation in `state.go`:
```go
func lifecycleLockPath(statePath string) string {
return filepath.Join(filepath.Dir(statePath), "pi-lifecycle.lock")
}
var ErrLockHeld = errors.New("another Pi update, restart, or rollback is already in progress")
```
Keep `acquireLock(statePath)` but set `path := lifecycleLockPath(statePath)`. Preserve owner metadata and durable cleanup.
- [ ] **Step 4: Run config, state, update, and rollback tests**
```bash
cd tools/thothctl
go test ./internal/config ./internal/pi -count=1
```
Expected: PASS.
- [ ] **Step 5: Commit**
```bash
git add tools/thothctl/internal/config/installation.go tools/thothctl/internal/config/installation_test.go tools/thothctl/internal/pi/state.go tools/thothctl/internal/pi/state_test.go
git commit -m "refactor(thothctl): share Pi lifecycle lock"
```
---
### Task 2: Implement the core-only restart transaction
**Files:**
- Create: `tools/thothctl/internal/pi/restart.go`
- Create: `tools/thothctl/internal/pi/restart_test.go`
- Modify: `tools/thothctl/internal/pi/update.go:108-145,802-849`
- Test: `tools/thothctl/internal/pi/update_test.go`
**Interfaces:**
- Consumes existing `Runner`, `lifecycleHooks`, lock, maintenance, session inventory, `Doctor`, `Status`, `renderedCore`, `runningImage`, `recreateCore`, state, and recovery helpers.
- Produces:
```go
type RestartRequest struct {
StatePath string
UpdateStatePath string
Confirm bool
Drain bool
}
type RestartResult struct {
StatePath string
Version string
}
func Restart(context.Context, Runner, RestartRequest) (RestartResult, error)
func RecoverLifecycleMaintenance(context.Context, Runner, string, string, bool) error
```
- [ ] **Step 1: Write failing restart transaction tests**
Create `restart_test.go` in package `pi`, reusing `newFakeRunner`, `assertCalled`, and `assertNotCalled`:
```go
func TestRestartRequiresConfirmationWithoutInvokingCompose(t *testing.T) {
dir := t.TempDir()
fake := newFakeRunner()
_, err := Restart(context.Background(), fake, RestartRequest{
StatePath: filepath.Join(dir, "restart-state.json"),
UpdateStatePath: filepath.Join(dir, "update-state.json"),
})
if !errors.Is(err, ErrConfirmationRequired) {
t.Fatalf("Restart() error = %v, want ErrConfirmationRequired", err)
}
assertNotCalled(t, fake.calls, "compose")
}
func TestRestartDrainsRecreatesOnlyCoreAndRetainsImage(t *testing.T) {
fake := newFakeRunner()
fake.activeSessions = true
dir := t.TempDir()
hooks := defaultLifecycleHooks
hooks.sleep = func(time.Duration) { fake.activeSessions = false }
result, err := restartWithHooks(context.Background(), fake, RestartRequest{
StatePath: filepath.Join(dir, "restart-state.json"),
UpdateStatePath: filepath.Join(dir, "update-state.json"),
Confirm: true,
Drain: true,
}, hooks)
if err != nil {
t.Fatal(err)
}
if result.Version != fake.version {
t.Fatalf("version = %q, want %q", result.Version, fake.version)
}
assertCalled(t, fake.calls, "up --detach --wait --wait-timeout 45 --no-deps --force-recreate core")
assertNotCalled(t, fake.calls, "build --pull")
assertNotCalled(t, fake.calls, "pull ")
assertNotCalled(t, fake.calls, "frontend")
if _, err := os.Stat(result.StatePath); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("successful restart state still exists: %v", err)
}
}
```
Also add:
- `TestRestartRefusesActiveSessionsWithoutDrain`
- `TestRestartRefusesInterruptedUpdateOrRestartState`
- `TestRestartPreflightFailureNeverRecreatesCoreAndClearsMaintenance`
- `TestRestartPostRecreateFailureKeepsMaintenanceAndRecoveryState`
- `TestRecoverLifecycleMaintenanceVerifiesAndClearsRestartState`
- `TestRestartRejectsImageConfigurationAndMountDrift`
Extend the shared fake runner with a `recreated bool` field, set it when the force-recreate call is
observed, and make the existing post-candidate failure branches apply when `fake.built ||
fake.recreated`. This lets restart failures occur after mutation without pretending an image build
happened. For post-recreate failure set `fake.fail = "health"`, then assert maintenance remains
true and `restart-state.json` remains.
- [ ] **Step 2: Run restart tests and verify RED**
```bash
cd tools/thothctl
go test ./internal/pi -run 'TestRestart|TestRecoverLifecycleMaintenance' -count=1
```
Expected: compile failure for the missing restart interfaces.
- [ ] **Step 3: Extract the existing active-session loop**
Move the 30-second loop from `updateWithHooks` into `update.go`:
```go
func waitForInactiveSessions(ctx context.Context, runner Runner, drain bool, sleep func(time.Duration)) error {
running, err := activeSessions(ctx, runner)
if err != nil {
return err
}
if !running {
return nil
}
if !drain {
return ErrActiveSessions
}
for attempts := 0; attempts < 30; attempts++ {
running, err = activeSessions(ctx, runner)
if err != nil {
return err
}
if !running {
return nil
}
sleep(time.Second)
}
return ErrActiveSessions
}
```
Replace the original update loop with `waitForInactiveSessions(...)`. Preserve the second inventory check immediately before mutation.
- [ ] **Step 4: Implement `Restart` in `restart.go`**
Implement `Restart` as a wrapper around `restartWithHooks`. The transaction must execute in this exact order:
1. validate both state paths;
2. acquire the shared lock using `RestartStatePath`;
3. require confirmation;
4. reject recovery-required update or restart state;
5. activate maintenance and arrange cleanup for pre-mutation returns;
6. wait/refuse through `waitForInactiveSessions`;
7. run `Doctor` as preflight;
8. read current version, rendered core, running image, configuration SHA, and mount identity;
9. write restart state with `Target{Version: version, Source: "restart"}`;
10. recheck active sessions;
11. durably mark `MutationStarted`;
12. call `recreateCore(ctx, runner)` directly, without image override;
13. prove maintenance remains active;
14. record `PhaseRecreated`;
15. run `verifyRestart(ctx, runner, version, previous)`;
16. record `PhaseVerified`, remove only `restart-state.json`, and clear maintenance.
Implement `verifyRestart` to run `Doctor`, reload rendered configuration and running image, require
the same image ID, require the same `ConfigurationSHA`, and require `sameMounts(previous.Mounts,
after.Mounts)`. Use stable errors for image, external-configuration, and persistence-mount drift.
Update the `Target.Source` comment in `state.go` to include the non-image `restart` operation.
Use:
```go
func Restart(ctx context.Context, runner Runner, request RestartRequest) (RestartResult, error) {
return restartWithHooks(ctx, runner, request, defaultLifecycleHooks)
}
```
Use `recoveryRequired("Pi restart ...", err)` for every post-mutation failure and set the deferred maintenance cleanup flag to false. Do not overwrite or remove a verified `update-state.json`; it remains the image rollback record.
- [ ] **Step 5: Implement combined maintenance recovery**
Add:
```go
func RecoverLifecycleMaintenance(
ctx context.Context,
runner Runner,
updateStatePath string,
restartStatePath string,
confirm bool,
) error
```
When restart state requires recovery, run `verifyRestart` using the recorded target version and
previous image contract, and remove `restart-state.json` only after verification. Then call existing
update-state `RecoverMaintenance` without opening admission between the two checks. Missing restart
state is allowed; malformed restart state fails closed.
- [ ] **Step 6: Run internal Pi tests and verify GREEN**
```bash
cd tools/thothctl
go test ./internal/pi -count=1
```
Expected: PASS, including existing update, rollback, mount identity, durability, and transport-loss tests.
- [ ] **Step 7: Commit**
```bash
git add tools/thothctl/internal/pi/restart.go tools/thothctl/internal/pi/restart_test.go tools/thothctl/internal/pi/update.go tools/thothctl/internal/pi/update_test.go tools/thothctl/internal/pi/state.go
git commit -m "feat(thothctl): add safe Pi core restart"
```
---
### Task 3: Expose `pi restart` through thothctl
**Files:**
- Modify: `tools/thothctl/cmd/thothctl/main.go:25-53,263-363,470-520`
- Test: `tools/thothctl/cmd/thothctl/main_test.go:68-110,567-666`
**Interfaces:**
- Consumes Task 2's restart and recovery interfaces and Task 1's state paths.
- Produces `func parsePiRestartArgs([]string, string, string) (pi.RestartRequest, error)`.
- Public syntax: `thothctl --installation <path> pi restart --yes [--drain]`.
- [ ] **Step 1: Write failing usage, parser, and dispatch tests**
Extend the usage test:
```go
for _, expected := range []string{
"pi restart --yes [--drain]",
"Recreate only core with the currently selected Pi image",
} {
if !strings.Contains(usage, expected) {
t.Fatalf("usage missing %q", expected)
}
}
```
Add a table test for:
```go
{name: "confirmed", args: []string{"--yes"}, want: pi.RestartRequest{StatePath: restartPath, UpdateStatePath: updatePath, Confirm: true}}
{name: "drain", args: []string{"--yes", "--drain"}, want: pi.RestartRequest{StatePath: restartPath, UpdateStatePath: updatePath, Confirm: true, Drain: true}}
{name: "duplicate yes", args: []string{"--yes", "--yes"}, wantErr: "--yes may be supplied once"}
{name: "duplicate drain", args: []string{"--drain", "--drain"}, wantErr: "--drain may be supplied once"}
{name: "unknown", args: []string{"--force"}, wantErr: "unknown pi restart option"}
```
Add command tests proving missing `--yes` exits 2 before mutation, success is sanitized, and maintenance recovery passes both state paths.
- [ ] **Step 2: Run focused CLI tests and verify RED**
```bash
cd tools/thothctl
go test ./cmd/thothctl -run 'Test.*(Restart|Usage|Maintenance)' -count=1
```
Expected: failure because parsing and dispatch are absent.
- [ ] **Step 3: Add usage, parser, dispatch, and recovery routing**
Add:
```text
pi restart --yes [--drain]
Recreate only core with the currently selected Pi image and verify readiness.
```
Dispatch before `case "update"`:
```go
case "restart":
request, err := parsePiRestartArgs(
args[1:],
installation.RestartStatePath(),
installation.UpdateStatePath(),
)
if err != nil {
return commandUsageError(stderr, err.Error())
}
result, err := pi.Restart(ctx, controlled, request)
if err != nil {
return piFailure(stderr, err, secretValues)
}
fmt.Fprintf(stdout, "Pi core restarted with the existing image; version %s readiness and smoke checks passed.\n", result.Version)
return 0
```
Implement exact duplicate and unknown-option errors from the tests. Route `pi maintenance recover --yes` through `RecoverLifecycleMaintenance` with both state paths.
- [ ] **Step 4: Run CLI and full Go tests**
```bash
cd tools/thothctl
go test ./cmd/thothctl -count=1
go test ./... -count=1
```
Expected: PASS with no secret leakage.
- [ ] **Step 5: Build supported binaries**
```bash
cd ../..
./scripts/build-thothctl.sh
```
Expected: supported artifacts under `dist/thothctl/`, exit 0.
- [ ] **Step 6: Commit**
```bash
git add tools/thothctl/cmd/thothctl/main.go tools/thothctl/cmd/thothctl/main_test.go
git commit -m "feat(thothctl): expose Pi restart command"
```
---
### Task 4: Update contracts, operator docs, and documentation gates
**Files:**
- Modify: `docs/contracts/thothctl-pi.md`
- Modify: `docs/install/pi-management.md`
- Modify: `docs/general/pi-configuration.md`
- Modify: `scripts/verify-workspace-install-docs.sh:1160-1188`
- Modify: `README.md` only if it claims command completeness.
**Interfaces:**
- Consumes the exact Task 3 syntax and Task 2 recovery behavior.
- Produces one consistent distinction among GUI defaults, host files, credentials, reload, update, and recovery.
- [ ] **Step 1: Strengthen the documentation gate first**
Require:
```bash
"pi restart --yes --drain" \
"restart only core" \
"deploy/pi/models.json" \
"deploy/pi/settings.json" \
"PI_AUTH_FILE" \
"pi update" \
"pi rollback --yes" \
"pi maintenance recover --yes"
```
Add this negative gate:
```bash
if grep -Fq '~/.pi/agent/' "$guide"; then
echo "Pi management guide must not direct ThothII operators to native Pi paths" >&2
return 1
fi
```
- [ ] **Step 2: Run the documentation verifier and verify RED**
```bash
./scripts/verify-workspace-install-docs.sh
```
Expected: FAIL because restart and separated workflows are not documented.
- [ ] **Step 3: Rewrite the two authoritative operator documents**
In `docs/contracts/thothctl-pi.md`, document `pi restart --yes [--drain]`, confirmation, maintenance, bounded drain, current-image retention, core-only recreation, verification, separate restart state, shared lock, and recovery.
In `docs/install/pi-management.md`, use these headings:
- **Choose application defaults** — GUI Save defaults or CLI configure, not both.
- **Edit the provider catalog and enabled-model policy** — project-root `deploy/pi/` files.
- **Store provider credentials** — `PI_AUTH_FILE` from the installation environment file.
- **Reload changed configuration** — one restart command.
- **Update the bundled Pi version** — build command and digest-pinned pull alternative.
- **Recover a failed lifecycle operation** — status, logs, rollback, maintenance recovery.
- [ ] **Step 4: Add the Docker-operator callout**
Add below the introduction of `docs/general/pi-configuration.md`:
```markdown
> **ThothII operator note:** ThothII runs Pi only in Docker Compose. Paths under
> `~/.pi/agent/` in this document describe Pi's container-side behavior. Operators edit
> `deploy/pi/models.json` and `deploy/pi/settings.json` in the ThothII project root and use
> the protected host credential file selected by `PI_AUTH_FILE`; they do not edit files inside
> the running container.
```
- [ ] **Step 5: Run documentation gates**
```bash
./scripts/verify-workspace-install-docs.sh
./scripts/test-thothctl-build-contract.sh
```
Expected: both exit 0.
- [ ] **Step 6: Commit**
```bash
git add docs/contracts/thothctl-pi.md docs/install/pi-management.md docs/general/pi-configuration.md scripts/verify-workspace-install-docs.sh
git commit -m "docs: clarify Pi reload and update workflows"
```
Add `README.md` only if it changed.
---
### Task 5: Restructure the Pi Management dialog
**Files:**
- Modify: `frontend/src/shell/PiManagement.tsx:1-225,286-305,376-449`
- Test: `frontend/src/shell/PiManagement.test.tsx:170-230`
**Interfaces:**
- Consumes the public Task 3 commands.
- Preserves API calls, readiness rail, defaults, test, logs, all-tabs-closed state, dialog height, and scrolling.
- Removes `UPDATE_COMMAND`, `copyUpdateCommand`, global clipboard assertions, and the bottom Host update section.
- [ ] **Step 1: Rewrite the frontend test first**
Add these assertions:
```tsx
expect(screen.getByText("Using the host terminal:")).toBeVisible();
expect(screen.queryByText(/not this browser page/i)).not.toBeInTheDocument();
expect(screen.queryByRole("region", { name: "Host update" })).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Copy update command" })).not.toBeInTheDocument();
expect(screen.queryByText(/:5173/)).not.toBeInTheDocument();
await user.click(within(tablist).getByRole("tab", { name: "Linux" }));
const linux = screen.getByRole("tabpanel", { name: "Linux" });
expect(within(linux).getAllByRole("listitem")).toHaveLength(7);
expect(linux).toHaveTextContent("The deploy directory is in the ThothII project root, beside compose.yaml");
expect(linux).toHaveTextContent("deploy/pi/models.json");
expect(linux).toHaveTextContent("deploy/pi/settings.json");
expect(linux).toHaveTextContent("baseUrl is the provider API endpoint");
expect(linux).toHaveTextContent("enabledModels uses provider/model identifiers");
expect(linux).toHaveTextContent("PI_AUTH_FILE is a setting in the installation environment file");
expect(linux).toHaveTextContent("~/bin/thothctl --installation ~/thothii-installation.yaml pi restart --yes --drain");
expect(linux).toHaveTextContent("pi update --version <VERSION> --source build --yes --drain");
expect(linux).toHaveTextContent("pi rollback --yes");
expect(linux).not.toHaveTextContent("~/.pi/agent/");
```
Add equivalent macOS and Windows path/command assertions. Preserve tests for tabs closed, dialog `max-h-[calc(100vh-6rem)]`, and `overflow-y-scroll`.
Add:
```tsx
expect(screen.getByText("Select the provider, model, and reasoning used for new Pi work. Credentials stay in protected host files.")).toBeVisible();
expect(screen.getByText("Shows at most 200 recent lines with declared secret values removed.")).toBeVisible();
```
- [ ] **Step 2: Run the focused test and verify RED**
```bash
cd frontend
npx vitest run src/shell/PiManagement.test.tsx
```
Expected: FAIL on the new lead-in, ordered workflow, restart command, explanations, removed duplicate section, and revised descriptions.
- [ ] **Step 3: Introduce shared structured platform data**
Define:
```tsx
type PiPlatformDetails = {
modelsPath: string;
settingsPath: string;
terminal: string;
credentialProtection: string;
restartCommand: string;
updateCommand: string;
pullCommand: string;
recoveryCommands: string;
};
```
Render `PiInstructionSteps` as an ordered list with exactly these seven headings:
1. Open the project root
2. Edit the provider catalog
3. Enable the model
4. Set the provider credential
5. Reload Pi configuration
6. Update the Pi version
7. Recover a failed update
Use these normal commands:
```text
Linux/macOS:
~/bin/thothctl --installation ~/thothii-installation.yaml pi restart --yes --drain
~/bin/thothctl --installation ~/thothii-installation.yaml pi update --version <VERSION> --source build --yes --drain
Windows PowerShell:
& (Resolve-Path "~\bin\thothctl-windows-amd64.exe") --installation (Resolve-Path "~\thothii-installation.yaml") pi restart --yes --drain
& (Resolve-Path "~\bin\thothctl-windows-amd64.exe") --installation (Resolve-Path "~\thothii-installation.yaml") pi update --version <VERSION> --source build --yes --drain
```
Explain `baseUrl`, `api`, `models`, `id`, `name`, `enabledModels`, and `PI_AUTH_FILE` in compact lists. The lead-in must be exactly:
```tsx
<p className="text-muted-foreground">Using the host terminal:</p>
```
Keep digest-pinned pull and recovery commands visually subordinate.
- [ ] **Step 4: Remove duplicate and developer-only content**
Delete:
- `UPDATE_COMMAND`
- `copyUpdateCommand`
- the bottom `Host update` section
- `Clipboard` import if unused
- Vite, `:5173`, frontend rebuild, native Pi, and container-edit text
- mandatory configure, stop/start, status/doctor/test sequences
Keep the positive statement that Docker mounts the selected host credential file read-only for Pi.
- [ ] **Step 5: Clarify defaults and diagnostics**
Use exactly:
```text
Select the provider, model, and reasoning used for new Pi work. Credentials stay in protected host files.
Shows at most 200 recent lines with declared secret values removed.
```
Do not change API behavior or readiness semantics.
- [ ] **Step 6: Run focused and full frontend gates**
```bash
cd frontend
npx vitest run src/shell/PiManagement.test.tsx
npx vitest run
npx tsc -b
npm run build
```
Expected: focused tests, full suite, typecheck, and production build pass.
- [ ] **Step 7: Commit only Pi Management files**
```bash
git add frontend/src/shell/PiManagement.tsx frontend/src/shell/PiManagement.test.tsx
git commit -m "feat(frontend): simplify Pi operator workflow"
```
Confirm Workspace Manager files remain unstaged.
---
### Task 6: Final verification and local deployment
**Files:**
- Verify only; change source only to correct a failing gate attributable to Tasks 1-5.
**Interfaces:**
- Produces fresh evidence that CLI, docs, frontend, and port 8080 agree.
- [ ] **Step 1: Run all relevant gates**
```bash
cd tools/thothctl
go test ./... -count=1
cd ../..
./scripts/build-thothctl.sh
./scripts/test-thothctl-build-contract.sh
./scripts/verify-workspace-install-docs.sh
cd frontend
npx vitest run
npx tsc -b
npm run build
cd ..
git diff --check
```
Expected: every command exits 0. Existing Vite chunk-size warnings are acceptable.
- [ ] **Step 2: Verify the built CLI**
```bash
dist/thothctl/thothctl-darwin-arm64 --help
```
Expected: output contains `pi restart --yes [--drain]` plus update, rollback, and maintenance commands.
- [ ] **Step 3: Rebuild only the active frontend service**
Use project `thothii-9307255178c1`, the current installation environment values, and these Compose files:
```bash
docker compose --project-name thothii-9307255178c1 \
-f compose.yaml \
-f deploy/compose.local.yaml \
-f deploy/compose.git-ssh.yaml \
-f deploy/psd/connector-secrets.yaml \
up -d --build --no-deps frontend
```
Never print or inline credential contents.
- [ ] **Step 4: Verify port 8080 and health**
Fetch `http://127.0.0.1:8080/` and its JavaScript assets. Require:
```text
Using the host terminal:
pi restart --yes --drain
PI_AUTH_FILE is a setting in the installation environment file
The deploy directory is in the ThothII project root
```
Reject:
```text
not this browser page
For native Pi outside Compose
Copy update command
:5173
```
Run:
```bash
docker ps --format '{{.Names}}|{{.Status}}'
```
Expected: `thothii-9307255178c1-frontend-1` is healthy.
- [ ] **Step 5: Inspect final scope**
```bash
git status --short
git log --oneline -6
```
Expected: lifecycle state, restart transaction, CLI, docs, and frontend commits are present. Only unrelated pre-existing Workspace Manager changes remain.