docs: plan Pi restart operator workflow
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user