Skip to main content
Status: draft · Version 0.1 · Filed 2026-04-29

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

{
  "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 homeprism-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:
{ "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

VerbArgsPurpose
prism_install_local(none)Bring up local Docker stack via docker-compose.personal.yml. Equivalent to prism install --mode local today.
prism_install_lantarget: strFirst 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_lantarget: str, mode: "full" | "build-only" | "verify-only" = "full"Update existing LAN install. Today’s prism_deploy semantics, target-driven.
prism_configure_lanname: str, ssh_target: str, ssh_key: str, repo_dir: str, backend_url: str, containers?: dictAdd/edit a target in the registry. Validates connectivity.
prism_clone_dbfrom_target: strMirror a remote target’s Postgres into the local stack (today’s prism_clone_from semantics, target-driven).
prism_backupdry_run: bool = falseBackup 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.
Last modified on May 3, 2026