> ## Documentation Index
> Fetch the complete documentation index at: https://prism.ntecdev.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# SPEC-055 v0.1: SPEC-055 v0.1 — Generic Operator Lifecycle: Target-Registry-Driven LAN install/upgrade/clone-db/backup, No Hardcoded Host Identifiers

> SPEC-055 v0.1 — Generic Operator Lifecycle: Target-Registry-Driven LAN install/upgrade/clone-db/backup, No Hardcoded Host Identifiers

<Info>**Status:** `draft` · **Version** `0.1` · Filed 2026-04-29</Info>

***

version: 0.1
status: draft
-------------

# Title: SPEC-055 v0.1 — Generic Operator Lifecycle: Target-Registry-Driven LAN install/upgrade/clone-db/backup, No Hardcoded Host Identifiers

## Version

0.1

## Status

draft

## Origin

The current operator lifecycle (`prism_deploy`, `prism_clone_from`, `prism_backup` MCP verbs + their shell wrappers `bin/prism-deploy-server1.sh`, `scripts/clone-server1-to-local.sh`) is hardcoded to **one specific deployment**: `donna@server1.home.lan`, `/home/frank/Prism`, `prism-server-postgres`, neo4j password `prism_server`, etc. SPEC-051 Phase 1 v0.1 explicitly punted on multi-target ("only `target='server1'` supported"). The Python `server1_config()` function is a literal: anyone else running Prism on their own server has to set \~10 env vars or fork the source.

Frank's correction during the SPEC-054 tier 1C-redux design discussion: this is a **commercial product**. The operator-side lifecycle must be commercial-grade — generic enough that any user, on any server, can use it without forking. Names describe the operation, not the host. Per-deployment data lives in config/env/db, not source.

This spec defines the multi-target replacement and retires the server1-shaped verbs.

## §1 — Goals

1. **Zero machine-specific identifiers in source.** No literal `server1`, `donna@`, `/home/frank`, `prism-server-postgres` as defaults in any TS or Python file under `cli/`, `mcp-node/`, or anywhere a user pulling the repo would inherit them.
2. **Operation-based naming.** Verbs and CLI subcommands describe what they do (`install-lan`, `upgrade-lan`, `clone-db`, `backup`), not which deployment they target.
3. **Config-driven targets.** A per-operator target registry (`~/.prism/targets.json`) lists all named LAN/cloud destinations the user has configured. Verbs and CLI subcommands take a `--target <name>` arg that names a key in the registry. Empty registry → operator runs `prism configure-lan` (or edits the file) before any install/upgrade verb works.
4. **Intent-first verbs.** `prism_install_local`, `prism_install_lan`, `prism_upgrade_lan`, `prism_clone_db`, `prism_backup` — separate verbs per intent, not one multiplexed `prism_deploy(target=...)`. Aligns with feedback memory `feedback_intent_is_first_class.md`.
5. **CLI is the primary surface.** The operator interface is `prism install-lan`, `prism upgrade-lan`, etc. — the same CLI that owns `prism install --mode local` today. MCP verb wrappers exist for AI-FIRST coverage but delegate to the CLI implementation; both share the target-registry lookup.
6. **Frank's existing setup keeps working.** Migration: a one-time `prism configure-lan --import-legacy` pulls his current `donna@server1.home.lan` config out of env-var defaults and writes it into `~/.prism/targets.json` as a named target (default name `home-lan` or operator-chosen).

### Non-goals (v0.1)

* Cloud / SaaS targets. Mode `cloud` stays deferred per ADR-021 — same v0.1 cutoff.
* Backend changes. Same as SPEC-054: backend Docker stack untouched.
* Multi-tenant target visibility. v0.1: targets are per-operator, in the operator's home dir. Tenant-shared target registry is future work.
* Auto-discovery. Operator declares targets explicitly via `prism configure-lan`; nothing auto-detects LAN servers.

## §2 — Target Registry

### Location

`~/.prism/targets.json` (overridable via `PRISM_TARGETS_FILE` env). Same dir as `credentials.personal.json`.

### Schema

```jsonc theme={null}
{
  "version": 1,
  "default_target": "<key-name-or-null>",
  "targets": {
    "<operator-chosen-name>": {
      "mode": "lan",                              // "lan" | "cloud" (cloud=deferred)
      "ssh": {
        "target": "user@host.example.com",        // required
        "key": "~/.ssh/<keyfile>",                // required, expandable
        "control_persist_seconds": 60             // optional, default 60
      },
      "remote": {
        "repo_dir": "/path/to/Prism",             // required (where the deploy lives)
        "compose_file": "docker-compose.server.yml" // optional, default value
      },
      "containers": {
        "postgres": "prism-<name>-postgres",      // optional, defaults from "name"
        "neo4j":    "prism-<name>-neo4j",
        "backend":  "prism-<name>-backend"
      },
      "backend_url": "http://host.example.com:41765", // required
      "neo4j_password_env": "PRISM_NEO4J_PASSWORD_<NAME>" // optional, env-var name
    }
  }
}
```

Container names default-derive from the target key (e.g., target `home` → `prism-home-postgres`). Operator can override per-target.

### CLI — `prism configure-lan`

Interactive add/edit. Prompts for ssh target, key, repo dir, backend URL, container-name pattern. Validates ssh connectivity (try a `ssh user@host echo ok` with `BatchMode=yes`) before persisting. Writes the new target into `targets.json`; offers to set as `default_target` if it's the first.

Non-interactive form: `prism configure-lan --name=<n> --ssh-target=<...> --ssh-key=<...> --repo-dir=<...> --backend-url=<...>` for scripted setup.

### CLI — `prism configure-lan --import-legacy`

One-shot migration. Reads `PRISM_DEPLOY_*` env vars + `credentials.personal.json` and writes them as a named target in `targets.json`. Default name `home-lan`; `--name` overrides. Idempotent. After this lands and Frank runs it once, his existing setup works unchanged through the new verb names.

## §3 — Verb Surface

### Retired verbs

* `prism_deploy(target='server1', mode='full')` → **retired**. Replaced by `prism_upgrade_lan(target=<name>, mode='full')`.
* `prism_clone_from(target='server1')` → **retired**. Replaced by `prism_clone_db(from_target=<name>)`.

When the Node shim sees a call to a retired verb name, it returns:

```json theme={null}
{ "error": "verb_retired", "retired_in": "SPEC-055", "use_instead": "prism_upgrade_lan", "message": "..." }
```

For one release. After that, the verb name is freed.

### New verbs

| Verb                  | Args                                                                                           | Purpose                                                                                                                                                                                                                                                          |
| --------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `prism_install_local` | (none)                                                                                         | Bring up local Docker stack via `docker-compose.personal.yml`. Equivalent to `prism install --mode local` today.                                                                                                                                                 |
| `prism_install_lan`   | `target: str`                                                                                  | First install on a LAN target. Resolves target via registry, ssh+rsync repo, mints API key, writes `credentials.personal.json` (mode=lan), brings up `docker-compose.server.yml` on remote. Idempotent — re-running detects existing install and short-circuits. |
| `prism_upgrade_lan`   | `target: str, mode: "full" \| "build-only" \| "verify-only" = "full"`                          | Update existing LAN install. Today's `prism_deploy` semantics, target-driven.                                                                                                                                                                                    |
| `prism_configure_lan` | `name: str, ssh_target: str, ssh_key: str, repo_dir: str, backend_url: str, containers?: dict` | Add/edit a target in the registry. Validates connectivity.                                                                                                                                                                                                       |
| `prism_clone_db`      | `from_target: str`                                                                             | Mirror a remote target's Postgres into the local stack (today's `prism_clone_from` semantics, target-driven).                                                                                                                                                    |
| `prism_backup`        | `dry_run: bool = false`                                                                        | Backup local user state. Unchanged from today.                                                                                                                                                                                                                   |
| `prism_list_targets`  | (none)                                                                                         | Return the registry contents (paths only — no secrets).                                                                                                                                                                                                          |

### Bootstrap-skip

All seven verbs are bootstrap-skipped (operator-side, not project-session-scoped). Same posture as `prism_persona_create` and `prism_set_force_credentials` today.

## §4 — CLI Surface

```
prism install-local                              # alias for `prism install --mode local`
prism install-lan --target <name>
prism upgrade-lan --target <name> [--build-only|--verify-only]
prism configure-lan [--name <n> --ssh-target ... --ssh-key ... --repo-dir ... --backend-url ...]
prism configure-lan --import-legacy [--name home-lan]
prism clone-db --from <target>
prism backup [--dry-run]
prism list-targets
```

The legacy `prism install --mode local|lan` form stays for one release as an alias. After that, the dashed forms (`install-local`, `install-lan`) are canonical.

`prism install-lan` requires the target to already exist in the registry. Error message points at `prism configure-lan`.

## §5 — Implementation Layout

```
cli/src/
  index.ts                # add subcommand dispatch for the new verbs
  targets.ts              # NEW — load/save/validate ~/.prism/targets.json
  ops/                    # NEW — TS implementations of each operation
    install_lan.ts
    upgrade_lan.ts
    clone_db.ts
    backup.ts             # ports mcp/backup.py (no target arg)
    ssh_session.ts        # ports mcp/ssh_session.py (ControlMaster wrapper)
    credentials.ts        # ports mcp/credentials.py
mcp-node/src/
  verbs/
    tier1c.ts             # UPDATE — register new verb names, handlers delegate
                          # to cli/src/ops/* (or import directly via workspace).
                          # Drop the shell-out wrappers entirely.
    retired.ts            # NEW — registers prism_deploy + prism_clone_from with
                          # retirement payloads pointing at the new verbs.
```

The CLI workspace is the canonical home for operation logic; MCP verbs become thin wrappers. This split mirrors how `prism install --mode local` already works (CLI implements; no MCP verb today).

## §6 — Cutover Plan

### Phase 1 — Land SPEC-055 implementation in cli/ + mcp-node/ (this spec)

* New `cli/src/targets.ts` + `cli/src/ops/*` modules.
* New CLI subcommands (`install-lan`, `upgrade-lan`, `configure-lan`, `clone-db`, `backup`, `list-targets`).
* New MCP verbs in `mcp-node/src/verbs/tier1c.ts` replacing the shell-out stubs.
* `prism_deploy` + `prism_clone_from` registered as retired (return retirement payload).
* Legacy `mcp/deploy.py` + `mcp/backup.py` + `mcp/clone_from.py` + `bin/prism-deploy-server1.sh` + `bin/prism-backup.sh` + `scripts/clone-server1-to-local.sh` deleted.

### Phase 2 — Frank's migration

* Frank runs `prism configure-lan --import-legacy --name home-lan` once.
* Verifies `prism upgrade-lan --target home-lan` works.

### Phase 3 — Retire the retirement (next release)

* Delete `mcp-node/src/verbs/retired.ts`.
* Drop `prism_deploy` + `prism_clone_from` from `mcp-node/src/verbs.ts` registry list.

## §7 — Verification

1. `git grep -nE 'server1|donna@|/home/frank' -- cli/ mcp-node/` returns zero source references after Phase 1 (matches in `docs/`, `archive/`, changelog/retros are fine — those are historical record).
2. `~/.prism/targets.json` after `prism configure-lan --import-legacy` has Frank's existing setup as a named target.
3. `prism upgrade-lan --target home-lan --mode full` is functionally identical to today's `prism_deploy(target='server1', mode='full')` — same rsync/build/restart/health/verify stages, same structured return.
4. `prism upgrade-lan --target <other-target>` (e.g., a hypothetical `staging` target) works without code changes — only registry entry needed.
5. The MCP verb `prism_deploy` returns the retirement payload with `use_instead: prism_upgrade_lan`.
6. Empty registry: `prism upgrade-lan` errors with a clear message naming `prism configure-lan` as the fix.

## §8 — Decisions

* **D1 — CLI is primary, MCP verb is wrapper.** Same posture as `prism install --mode local` today (CLI-only, no MCP verb today). MCP wrappers exist for AI-FIRST.
* **D2 — Intent-first verb naming.** Per `feedback_intent_is_first_class.md`. Separate `prism_install_lan` / `prism_upgrade_lan` / `prism_clone_db` rather than multiplexed `prism_deploy(action=...)`.
* **D3 — Per-operator registry, not tenant-shared.** v0.1 keeps targets in `~/.prism/`. Tenant-shared registry is future work — needs auth + permission model that doesn't exist yet.
* **D4 — Container names derive from target key by default.** Override per-target. Avoids the operator having to specify three identical-shape container names every time.
* **D5 — Retirement payload, not silent rename.** `prism_deploy` returns a structured `verb_retired` error with `use_instead` for one release. Hard removal in next release. Gives any external scripts a deterministic upgrade path.
* **D6 — `--import-legacy` migration.** Frank gets a one-shot to convert his existing setup. Avoids "dies on first call after merge."
* **D7 — Target registry schema is versioned.** `version: 1` field allows future schema migrations without ambiguity.

## §9 — Open Questions

* **Q1 — Default target convention.** If `--target` omitted and registry has exactly one entry: use it (operator convenience). If multiple: error with list. **Resolution: confirmed during implementation; this is the behavior unless Frank objects.**
* **Q2 — Target validation depth.** `prism configure-lan` validates ssh connectivity. Should it also validate `repo_dir` exists on remote? Probably yes — costs one extra ssh round trip. **Resolution: yes.**
* **Q3 — Secrets in registry.** `targets.json` deliberately holds *no* secrets — only paths and IDs. API keys stay in `credentials.personal.json` (one set per local install). neo4j\_password resolved via env-var name in registry. **Resolution: confirmed; secrets stay separated by file.**
* **Q4 — `prism_install_lan` vs `prism_upgrade_lan` first-time semantics.** `install-lan` should be safe to run on a target that already has Prism installed (idempotent — short-circuits to upgrade). Or strict (errors if already installed). **Resolution during implementation: idempotent.**

## §10 — Performance Consciousness

* Target registry is small (single JSON file, \< 10KB even with many targets). Read-once-per-verb-call cost is negligible.
* ssh ControlMaster reuse from SPEC-051 carries forward: still one master socket per verb invocation.
* No new HTTP round-trips vs today's `prism_deploy` semantics.
* CLI subcommand dispatch adds one extra layer for MCP verbs (`mcp_node/verbs → cli/ops`); offset by the elimination of shell-out subprocess fork cost.

## §11 — Authorship

* **Author:** Donna (Claude Code, session `c1d19f01`) 2026-04-29.
* **Trigger:** Frank's correction during SPEC-054 tier 1C-redux design discussion — exact words: "this is a commercial product and we continue to take the easy way out." The hardcoded `server1_config()` and `bin/prism-deploy-server1.sh` smell triggered the redesign.
* **Implementation owner:** Lafonda (install lane). Donna authors the spec and hands off via signal.
* **Implementation gate:** Frank's GO. Lafonda's PR #21 (SPEC-054 Phase 3) ships first; this spec's implementation is a follow-up.

## §12 — Relationship to Other Specs

* **Supersedes** SPEC-051 Phase 1 + Phase 2 multi-target hardcoding. SPEC-051 itself stays in history; the verbs it shipped (`prism_deploy`, `prism_clone_from`) move to retirement.
* **Aligns with** SPEC-053 (consumer-grade install consolidation, ADR-030 — currently open as PR #12). SPEC-053 covers consumer bootstrap one-liners + `prism update` + statusline wiring; SPEC-055 covers the multi-target server-side install lifecycle. Lafonda noted in her SPEC-054 ack that PR #12 will rebase onto Phase 3 + this spec.
* **Inherits** target-registry pattern from credentials handling — same `~/.prism/` location, same per-operator scope.
* **Consistent with** `feedback_no_hardcoded_machine_values.md` and `feedback_commercial_grade_naming.md` — both say zero machine-specific identifiers in source.
* **Does not modify** `feedback_intent_is_first_class.md` posture — separate verbs per intent, not multiplexed flags.
