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:
| Verb | Shape | Editor-host fit |
|---|
prism_install_local(...) | Local Docker compose stack | NO — backend installer |
prism_install_lan(target) | Remote backend via SSH + Docker | NO — 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
- 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.
- 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.
- 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.
- 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:
- Look up entry by name. Missing →
target_not_found.
- Read
entry.kind. If absent (legacy v1 registry), see §2.4.
- If
entry.kind !== expected_kind, fail with kind_mismatch{got: <kind>, expected: <kind>}. No silent coercion.
- 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)
| Verb | Accepts kind | Rejection error |
|---|
prism_install_lan / prism_upgrade_lan / prism_configure_lan | backend_lan only | kind_mismatch |
prism_install_editor_host | editor_host only | kind_mismatch |
prism_list_targets | all 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
- Exactly one of
{target, target_host} non-empty. Both empty or both set → arg_mode_ambiguous.
- Mode A: all other args MUST be empty →
arg_mode_a_extra_args.
- Mode B:
target_host AND ssh_key AND install_root AND platform all required → missing_required_arg.
- Mode B backend identity:
backend_target XOR (backend_url AND api_key). Both groups set → backend_identity_ambiguous. Neither set → backend_identity_missing.
- 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.
- 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:
- 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).
- preflight_remote — see §5 for the OS-detection + shell-policy detail.
- distribution —
mkdir -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.
- prereqs —
npm install --ignore-scripts --no-audit --no-fund in cli/ and mcp-node/.
- install —
node $install_root/cli/dist/index.js install --mode lan --host <backend_host> --port <backend_port> --api-key <api_key>.
- 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.
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.
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
| platform | Required remote shell | Probe at preflight_remote | Failure mode |
|---|
windows | cmd.exe (Win OpenSSH default) | ssh ... "ver" returns “Microsoft Windows” line | If empty / “PowerShell” detected → windows_powershell_unsupported_v0_2 with remediation: set OpenSSH DefaultShell registry to cmd.exe |
linux | POSIX shell (sh/bash) | ssh ... 'uname -s' returns Linux | non-Linux → linux_platform_mismatch |
macos | POSIX shell (zsh/bash) | ssh ... 'uname -s' returns Darwin | non-Darwin → macos_platform_mismatch |
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)
- 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.
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.
- 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
- 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.
- Idempotency: re-run immediately → distribution refreshes dist, install reports “already wired”, verify still green.
- Failure surface: register a target with bad ssh key → preflight_remote fails fast with remediation hint; no partial state on remote.
- Kind mismatch: call
prism_install_editor_host(target=mini4) (mini4 is backend_lan) → kind_mismatch{got: backend_lan, expected: editor_host}.
- PowerShell rejection: deliberately reconfigure mini1 OpenSSH to PowerShell →
windows_powershell_unsupported_v0_2.
- Mode-validation: call with both
target and target_host → arg_mode_ambiguous. Call with backend_target and backend_url → backend_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.