Skip to main content
Status: draft · Version 0.2 · Filed 2026-05-04 · Updated 2026-05-04 (Texi review v0.1 → v0.2)

version: 0.2 status: draft

Title: SPEC-084 v0.2 — prism_install_editor_host — AI-FIRST editor-host install verb

Origin

P2 install rehearsal on mini1 (Win 11, Node 22.17, pointing at mini4 LAN backend) surfaced an MCP-verb gap: the editor-host install lane has no MCP wrapper. Available install verbs:
VerbShapeEditor-host fit
prism_install_local(...)Local Docker compose stackNO — backend installer
prism_install_lan(target)Remote backend via SSH + DockerNO — backend installer
(none)Editor-host install (Node CLI + mcp-node shim, no Docker, points at remote backend)GAP
Today the editor-host install path is a manual sequence: SSH to host, scp cli/dist + mcp-node/dist + each package.json, npm install --ignore-scripts in each, then run prism install --mode lan --host <backend> --port <port> --api-key <key> on the host. This violates feedback_ai_first_end_to_end.md and forces the operator to remember the scp recipe. Validated end-to-end manually (mini1 cycles 1–4, 2026-05-04) — every step is mechanical and SSH-ready.

§1 — Goals

  1. One verb call → working editor host. From the operator’s machine: prism_install_editor_host(target=mini1) produces a Win/Mac/Linux editor host wired with the prism MCP shim talking to the named backend, with no manual SSH or scp.
  2. Re-uses the credential exfiltration chain shipped in PR #157: the operator’s ~/.prism/credentials.<backend_target>.json is the source of truth for the API key the new editor host needs. Operator never sees or copies the secret.
  3. Same per-invocation log file UX as install_lan. Writes ~/.prism/logs/install-editor-host-<target>-<UTC>.log with timestamped per-stage entries; tail -f-compatible.
  4. Idempotent. Re-running on an already-installed editor host equals an upgrade — refresh dist tarballs, re-run prism install, validate handshake.

Non-goals (v0.2)

  • Distribution mechanism beyond scp-from-operator. npm publish / fetch-on-demand / single-binary is the next install-lane SPEC.
  • Per-host fan-out / concurrent installs. v0.2 is one host per verb call.
  • PowerShell remote shell support. v0.2 requires cmd.exe on Windows targets (default for Win OpenSSH 9.x); PowerShell-shell support deferred to v0.3 with separate command builders.

§2 — Target Registry — Discriminated Union (Texi v0.1 finding 1)

~/.prism/targets.json is extended with an explicit kind discriminator. Verbs select targets by kind; cross-kind selection is a config error.

§2.1 — Schema

{
  "version": 2,                         // bumped from 1; see §2.4 migration
  "default_target": "<key-name-or-null>",
  "targets": {
    "mini4": {
      "kind": "backend_lan",            // REQUIRED; no implicit default
      "mode": "lan",
      "ssh": { "target": "...", "key": "..." },
      "remote": { "repo_dir": "...", "compose_file": "..." },
      "backend_url": "..."
    },
    "mini1": {
      "kind": "editor_host",            // REQUIRED; no implicit default
      "platform": "windows",            // REQUIRED for editor_host: "windows" | "linux" | "macos"
      "ssh": { "target": "...", "key": "..." },
      "remote": { "install_root": "C:\\Users\\lafonda\\Prism" },
      "backend_target": "mini4"         // REQUIRED for editor_host; MUST name a kind=backend_lan entry
    }
  }
}

§2.2 — Resolver Rules

resolveTarget(name, expected_kind) algorithm:
  1. Look up entry by name. Missing → target_not_found.
  2. Read entry.kind. If absent (legacy v1 registry), see §2.4.
  3. If entry.kind !== expected_kind, fail with kind_mismatch{got: <kind>, expected: <kind>}. No silent coercion.
  4. For kind=editor_host: also resolve entry.backend_target. Missing → editor_host_missing_backend_target. Resolved entry’s kind must be backend_lan → otherwise backend_target_not_backend_lan.

§2.3 — Verb / Kind Matrix (normative)

VerbAccepts kindRejection error
prism_install_lan / prism_upgrade_lan / prism_configure_lanbackend_lan onlykind_mismatch
prism_install_editor_hosteditor_host onlykind_mismatch
prism_list_targetsall kinds (read-only)n/a

§2.4 — Migration from registry v1

Pre-existing v1 entries (no kind field) are interpreted as kind=backend_lan for backward compatibility. The first write through prism_configure_lan upgrades the file: writes version: 2 and adds kind: "backend_lan" to existing entries. New editor_host entries are written via a new prism_configure_editor_host companion verb (out of scope for this SPEC; tracked as a §6 follow-up).

§3 — Verb Argument Contract (Texi v0.1 finding 3)

Two mutually-exclusive modes, validated at the input boundary:

§3.1 — Mode A: target (registered)

prism_install_editor_host(
  target: string                  // REQUIRED; must resolve to kind=editor_host
)
All other args MUST be empty. Backend resolution, ssh details, install_root, platform — all read from the registry entry.

§3.2 — Mode B: target_host (one-shot)

prism_install_editor_host(
  target_host: string,            // REQUIRED; ssh target, e.g. "Lafonda@mini1.home.lan"
  ssh_key: string,                // REQUIRED; path to private key
  install_root: string,           // REQUIRED; remote dir
  platform: string,               // REQUIRED; "windows" | "linux" | "macos"
  // Backend identity — exactly one of these two groups:
  backend_target?: string,        // EITHER: name of kind=backend_lan registry entry
  // ── OR ──
  backend_url?: string,           // both required if backend_target absent
  api_key?: string
)
target MUST be empty in Mode B.

§3.3 — Validation rules

  1. Exactly one of {target, target_host} non-empty. Both empty or both set → arg_mode_ambiguous.
  2. Mode A: all other args MUST be empty → arg_mode_a_extra_args.
  3. Mode B: target_host AND ssh_key AND install_root AND platform all required → missing_required_arg.
  4. Mode B backend identity: backend_target XOR (backend_url AND api_key). Both groups set → backend_identity_ambiguous. Neither set → backend_identity_missing.
  5. Backend identity precedence: in v0.2 the two backend-identity groups are mutually exclusive. A future v0.x extension may allow both with an explicit precedence flag.
  6. Resolved kind in Mode A and resolved backend_target in either mode MUST satisfy §2.2.

§4 — Stages

Mirrors upgrade_lan.ts flow but adapted for editor-host:
  1. preflight_local — local repo cli/dist + mcp-node/dist exist + are fresh. Fail-fast with actionable error if missing per feedback_preflight_checks (no auto-build inside the verb — caller invokes npm run build first; verb is side-effect-narrow).
  2. preflight_remote — see §5 for the OS-detection + shell-policy detail.
  3. distributionmkdir -p $install_root/{cli,mcp-node} (per §5 platform branch); scp cli/dist + cli/package.json and mcp-node/dist + mcp-node/package.json to remote.
  4. prereqsnpm install --ignore-scripts --no-audit --no-fund in cli/ and mcp-node/.
  5. installnode $install_root/cli/dist/index.js install --mode lan --host <backend_host> --port <backend_port> --api-key <api_key>.
  6. verify — fresh SSH session running JSON-RPC initialize handshake against node $install_root/cli/dist/mcp-launcher.js with the configured env block; assert version + capabilities; assert tools/call list_projects returns clean (real backend round-trip).
Failure at any stage aborts and surfaces a structured error with stage + detail + remediation hint. No partial cleanup — re-running idempotently fixes-forward.

§5 — Cross-Platform Policy (Texi v0.1 finding 2)

v0.2 selects approach (a) from the v0.1 sketch: single Windows shell required (cmd.exe), explicit detection at preflight, fail-fast otherwise. PowerShell support is v0.3 work with separate command builders.

§5.1 — Platform identification

entry.platform (Mode A) or the platform arg (Mode B) is normative — operator declares the target’s platform explicitly. The verb does NOT auto-detect from uname / ver because:
  • The probe itself differs per shell (cmd ver vs PowerShell $PSVersionTable vs POSIX uname -s).
  • Auto-detection masks misconfigured target entries; explicit declaration surfaces them at registration time.

§5.2 — Shell policy

platformRequired remote shellProbe at preflight_remoteFailure mode
windowscmd.exe (Win OpenSSH default)ssh ... "ver" returns “Microsoft Windows” lineIf empty / “PowerShell” detected → windows_powershell_unsupported_v0_2 with remediation: set OpenSSH DefaultShell registry to cmd.exe
linuxPOSIX shell (sh/bash)ssh ... 'uname -s' returns Linuxnon-Linux → linux_platform_mismatch
macosPOSIX shell (zsh/bash)ssh ... 'uname -s' returns Darwinnon-Darwin → macos_platform_mismatch

§5.3 — Command builders per platform

A RemoteCommands interface dispatches per platform:
interface RemoteCommands {
  mkdirP(path: string): string;          // Win: 'if not exist "X" mkdir "X"' (cmd.exe); POSIX: `mkdir -p $(shq path)`
  cd(path: string): string;              // Win: `cd /d "path"`; POSIX: `cd $(shq path)`
  envSet(name: string, value: string): string; // Win: `set NAME=value`; POSIX: `NAME=$(shq value)`
  joinAnd(parts: string[]): string;      // Win: `&&`; POSIX: `&&` — same operator but quoting differs
  shq(s: string): string;                // Win: `"<doubled-quote-escaped>"`; POSIX: existing single-quote shq
}
Implementations live in mcp-node/src/ops/_smoke/remote_commands_{windows,posix}.ts (or similar). The branch is decided once at preflight_remote; subsequent stages call methods on the dispatched instance.

§5.4 — scp destination handling

scp uses host:path syntax. Path semantics differ:
  • POSIX target: forward slashes, absolute path: Lafonda@mini1.home.lan:/home/lafonda/Prism/cli/dist/.
  • Windows target: forward slashes ALSO work in scp’s path argument (translated by Win OpenSSH). Use forward-slash form for the scp arg even when install_root in registry is backslash form. Path normalization: install_root.replace(/\\/g, "/") for scp args.
Quoting: scp args go through the local shell. shq (operator side, POSIX) wraps the destination string. The remote-side path interpretation is by Win OpenSSH, NOT cmd.exe — no double-quoting needed.

§6 — Open Questions (v0.2)

  1. PowerShell support timeline. v0.3 candidate. Question: detect at preflight and select builder, or require operator to declare shell in target entry? Lean toward declaration for the same reason as platform — explicit > magic.
  2. prism_configure_editor_host companion verb. Out of scope here but blocks fully programmatic registration of editor_host entries. File as separate small SPEC after this one ratifies.
  3. Daemon binary distribution. SPEC-072 daemon source is not part of cli/dist. v0.2 install path leaves daemon/ absent on editor host (CLI already handles it gracefully per cycle 3 output). Future v0.x SPEC for daemon-included editor-host installs.

§7 — Validation Plan

  1. Greenfield mini1 cycle: tear down C:\Users\lafonda\Prism\{cli,mcp-node}, then prism_install_editor_host(target=mini1) from operator → all stages green, MCP initialize handshake clean against mini4.
  2. Idempotency: re-run immediately → distribution refreshes dist, install reports “already wired”, verify still green.
  3. Failure surface: register a target with bad ssh key → preflight_remote fails fast with remediation hint; no partial state on remote.
  4. Kind mismatch: call prism_install_editor_host(target=mini4) (mini4 is backend_lan) → kind_mismatch{got: backend_lan, expected: editor_host}.
  5. PowerShell rejection: deliberately reconfigure mini1 OpenSSH to PowerShell → windows_powershell_unsupported_v0_2.
  6. Mode-validation: call with both target and target_hostarg_mode_ambiguous. Call with backend_target and backend_urlbackend_identity_ambiguous.

§8 — Implementation Notes

  • New file: mcp-node/src/ops/install_editor_host.ts (mirrors upgrade_lan.ts shape).
  • New files: mcp-node/src/ops/_smoke/remote_commands_{windows,posix}.ts for the §5.3 command builders.
  • Verb wiring: mcp-node/src/verbs/tier1c.ts adds registerVerb({ name: "prism_install_editor_host", ... }).
  • Re-uses POSIX shq() (from PR #157) for operator-shell quoting. Adds a Windows winq() helper alongside.
  • Re-uses log-file helper (newLogFile / logLine) — refactor into a shared util as a third consumer arrives.
  • Targets registry resolver gains expected_kind arg; existing callers updated to pass "backend_lan" explicitly.
  • Implements: the install-lane verb taxonomy gap surfaced in P2 mini1 rehearsal 2026-05-04.
  • Depends on: SPEC-055 (target registry) + PR #157 (stageBootstrapApiKey for credentials chain) + PR #159 (CLI consumer-install no-src guard).
  • Extends: SPEC-054 (Node MCP shim distribution model — explicit consumer install path).
  • Out-of-scope follow-up: distribution mechanism (npm publish vs scp vs single-binary) — separate SPEC TBD.

§10 — Review history

  • v0.1 → Texi review (PR #160 commit dda33a6): 3 findings — discriminated-union resolver rules (blocking), Win/POSIX shell-policy inconsistency (blocking), verb-arg-contract precedence (medium).
  • v0.2 → applied: §2.1 schema + §2.2 resolver rules + §2.3 kind/verb matrix + §2.4 migration; §5 single-shell Windows policy with explicit platform declaration + §5.3 RemoteCommands dispatch + §5.4 scp path normalization; §3 mutually-exclusive Mode A vs Mode B with normative validation rules.
Last modified on May 5, 2026