feat(slice-22): two-daemon VM-preview bootstrap + sample fixture

release-0.3.1 Slice 22 founder-walk-vm-populated-env Phase D + E
(merged) — ships the operational artifacts for running two daemons
side-by-side in a Tart preview VM with fully isolated state.

OQ resolutions (per qitem-20260511113113-9fea3ab4):
  OQ-1 (UI switcher):   (a) multi-port + operator-picks-URL — zero UI change
  OQ-2 (fixture source): (b) static YAML + idempotent loader (scope-cut to
                            bootstrap script as the loader for v0)
  OQ-3 (bootstrap loc):  (c) both — scripts in repo + conventions in substrate
  OQ-4 (HOME isolation): (a) fully isolated per daemon

Adds:

- scripts/vm-bootstrap/two-daemon-start.sh — idempotent two-daemon
  spawn (refuses to clobber an existing daemon.json; operator must
  cleanly reset state dirs to retry). Each daemon gets its own
  --openrig-home / --port / --db so they share only the host's
  network namespace, not state. Defaults: blank → 7433 at
  $HOME/.openrig-blank; populated → 7434 at $HOME/.openrig-populated.
  Env-overridable: BLANK_HOME, POPULATED_HOME, BLANK_PORT,
  POPULATED_PORT, FIXTURE_DIR.

- packages/daemon/assets/vm-preview-fixtures/ — fixture-source
  directory with README documenting the canonical layout, scope, and
  placeholder conventions (/Users/example/, <substrate-<internal-docs>>/
  per banked feedback_test_fixture_example_path_placeholder). Ships
  one sample workflow fixture (renamed from the basic-loop starter to
  vm-preview-basic-loop so it does not collide with the bundled
  built-in when dropped into a populated daemon's workspace/
  specs_root). Slice 11 folder-discovery auto-discovers it.

Scope cuts (explicit, called out in IMPL-PRD §13):
- Phase C structured fixture-loader module: not needed for v0; the
  bootstrap script is the loader. Operator runs `rig up product-team`
  for sample rigs, drops workflow YAMLs into the populated daemon's
  specs_root for workflow specs. No new daemon module.
- Rich populated content (~13-node openrig-velocity rig, ~20 qitems,
  getting-started narrative): deferred to follow-up content slices.

Convention doc added in substrate at
<work-tree>/conventions/vm-preview/README.md
with operator workflow, OQ resolutions, future-evolution ideas, and
cross-links to slice 11 (folder-discovery) and auth-bearer-tailscale
-trust (bind model).

No SC-29 EXCEPTIONS: --openrig-home is pure ergonomics on the
pre-existing OPENRIG_HOME env contract; no allowlist additions.

Tests unchanged from Phase B (56/56 daemon-lifecycle; full sweep
4270/4272; one pre-existing env-only fail). tsc clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
mvschwarzandClaude Opus 4.7 committed 2026-05-11 04:42:04 -07:00
1 parent 3d9a6f6190
commit 5a14029739
3 files changed
+204

No files matched your search

@@ -0,0 +1,44 @@
# VM Preview Fixtures
Sample data the Tart preview VM uses to populate the "populated" daemon with
a representative "lived-in" environment. See
`<substrate-shared-docs>/openrig-work/conventions/vm-preview/README.md` for the
operator workflow and the rationale behind the two-daemon model.
## Contents
- `workflows/` — workflow_spec YAML files. The operator copies these into the
populated daemon's `<workspace.specs_root>/workflows/` directory; slice 11's
folder-discovery scanner auto-discovers them on the next
`GET /api/specs/library` call.
- `queue-items/` — placeholder for future qitem fixture content (TODO; slice
follow-up will add).
- `rigs/` — placeholder for future rig-instance fixtures (TODO; slice
follow-up will add). For v0, the operator manually instantiates a sample
rig via `rig up product-team` against the populated daemon (see bootstrap
script).
## Conventions
Fixture YAML files use the canonical placeholder paths per banked
`feedback_test_fixture_example_path_placeholder.md`:
- Username-bearing paths: `/Users/example/...` (never `wrandom` or other
contributor usernames)
- Substrate-relative refs: `<substrate-shared-docs>/...`
## v0 scope
Slice 22 (release-0.3.1) ships the INFRASTRUCTURE for the two-daemon model:
- `--openrig-home` CLI flag on `rig daemon start` and `rig up`
- Bootstrap script (`scripts/vm-bootstrap/two-daemon-start.sh`)
- This fixtures directory + a sample workflow YAML
- Convention doc in substrate
Authoring rich populated content (~13-node openrig-velocity rig + 20 qitems
+ getting-started mission narrative) is deferred to follow-up content slices
that have clearer ownership (slice 21 onboarding-conveyor will likely
contribute getting-started mission content).
@@ -0,0 +1,79 @@
# VM-preview fixture for slice 22 founder-walk-vm-populated-env.
# Derived from the 0.3.0 basic-loop starter; renamed to avoid colliding
# with the built-in spec when this fixture file is dropped into a
# populated daemon's workspace.specs_root/workflows/ directory.
# Slice 11's folder-discovery scanner auto-discovers it on next list.
workflow:
id: vm-preview-basic-loop
version: 1
objective: >
Walk one work packet through the conveyor rig slowly enough for a new user
to inspect each handoff.
target:
rig: conveyor
entry:
role: intake
coordination_terminal_turn_rule: hot_potato
roles:
intake:
skill_refs: [openrig-user, backlog-capture]
preferred_targets: [intake-lead@conveyor]
planner:
skill_refs: [openrig-user, writing-plans]
preferred_targets: [plan-planner@conveyor]
builder:
skill_refs: [openrig-user, executing-plans]
preferred_targets: [build-builder@conveyor]
reviewer:
skill_refs: [openrig-user, review-team]
preferred_targets: [review-reviewer@conveyor]
closer:
skill_refs: [openrig-user, verification-before-completion]
preferred_targets: [intake-lead@conveyor]
steps:
- id: intake
actor_role: intake
objective: Restate the objective and hand off one packet.
allowed_exits: [handoff, waiting, failed]
next_hop:
mode: require
suggested_roles: [planner]
- id: plan
actor_role: planner
objective: Produce a short plan for one build turn.
allowed_exits: [handoff, waiting, failed]
next_hop:
mode: require
suggested_roles: [builder]
- id: build
actor_role: builder
objective: Build or draft the planned output.
allowed_exits: [handoff, waiting, failed]
next_hop:
mode: require
suggested_roles: [reviewer]
- id: review
actor_role: reviewer
objective: Check the output and clear it for close.
allowed_exits: [handoff, waiting, failed]
next_hop:
mode: require
suggested_roles: [closer]
- id: close
actor_role: closer
objective: Close the walkthrough packet.
allowed_exits: [done, failed]
next_hop:
mode: forbid
invariants:
continuation_required: true
preserve_lineage: true
closure_required: true
allowed_exits: [handoff, waiting, done, failed]
loop_guards:
max_hops: 10
spawn_budget: 0
closure:
success: Walkthrough packet closed.
degraded: Walkthrough stopped with a named blocker.
failed: Walkthrough failed with evidence.
+81
View File
@@ -0,0 +1,81 @@
#!/usr/bin/env bash
# Slice 22 (release-0.3.1) — VM preview two-daemon bootstrap.
#
# Starts two OpenRig daemons in the same VM with fully isolated state
# (separate OPENRIG_HOME dirs, distinct ports, distinct SQLite paths)
# so a founder can compare a blank-slate "first install" UI to a
# pre-populated "lived-in" UI side-by-side.
#
# Operator workflow (per <substrate-shared-docs>/openrig-work/conventions/vm-preview/README.md):
# 1. Provision a Tart VM with OpenRig CLI installed.
# 2. Run this script.
# 3. Open two browser tabs:
# http://<vm-ip>:7433 → blank-slate (default, fresh install UX)
# http://<vm-ip>:7434 → populated (sample rig + qitems for "lived-in" UX)
# 4. To reset: kill both daemons + rm both OPENRIG_HOME dirs + re-run.
#
# Pre-conditions (operator install before running this):
# - `rig` CLI on PATH
# - Tailscale daemon active OR explicit LAN bind via OPENRIG_HOST (see
# auth-bearer-tailscale-trust slice for the bind rules)
#
# Idempotency: the script refuses to clobber existing daemons; operator
# must clean state dirs to fully reset.
set -euo pipefail
BLANK_HOME="${BLANK_HOME:-$HOME/.openrig-blank}"
POPULATED_HOME="${POPULATED_HOME:-$HOME/.openrig-populated}"
BLANK_PORT="${BLANK_PORT:-7433}"
POPULATED_PORT="${POPULATED_PORT:-7434}"
FIXTURE_DIR="${FIXTURE_DIR:-$(dirname "$0")/../../packages/daemon/assets/vm-preview-fixtures}"
echo "==> VM preview two-daemon bootstrap"
echo " BLANK_HOME=$BLANK_HOME (port $BLANK_PORT)"
echo " POPULATED_HOME=$POPULATED_HOME (port $POPULATED_PORT)"
echo " FIXTURE_DIR=$FIXTURE_DIR"
# Refuse to overwrite existing daemons — operator action required to reset.
if [ -f "$BLANK_HOME/daemon.json" ]; then
echo "ERROR: blank daemon already running per $BLANK_HOME/daemon.json"
echo " Run: rig daemon stop --openrig-home $BLANK_HOME"
echo " Then: rm -rf $BLANK_HOME && rerun this script."
exit 1
fi
if [ -f "$POPULATED_HOME/daemon.json" ]; then
echo "ERROR: populated daemon already running per $POPULATED_HOME/daemon.json"
echo " Run: rig daemon stop --openrig-home $POPULATED_HOME"
echo " Then: rm -rf $POPULATED_HOME && rerun this script."
exit 1
fi
mkdir -p "$BLANK_HOME" "$POPULATED_HOME"
# Start the blank-slate daemon — pristine first-launch UX.
echo "==> Starting blank-slate daemon (port $BLANK_PORT)"
rig daemon start \
--openrig-home "$BLANK_HOME" \
--port "$BLANK_PORT" \
--db "$BLANK_HOME/openrig.sqlite"
# Start the populated daemon — runs alongside the blank one with its own state.
echo "==> Starting populated daemon (port $POPULATED_PORT)"
rig daemon start \
--openrig-home "$POPULATED_HOME" \
--port "$POPULATED_PORT" \
--db "$POPULATED_HOME/openrig.sqlite"
echo
echo "==> Both daemons up. Seed the populated daemon with sample data:"
echo " OPENRIG_HOME=$POPULATED_HOME OPENRIG_PORT=$POPULATED_PORT \\"
echo " rig up product-team # instantiate a sample rig"
echo
echo " Copy workflow fixtures to operator workspace specs dir:"
echo " cp $FIXTURE_DIR/workflows/*.yaml \$OPENRIG_WORKSPACE_SPECS_ROOT/workflows/"
echo " (slice 11 auto-discovers them on GET /api/specs/library)"
echo
echo "==> Operator UI access:"
echo " Blank-slate: http://127.0.0.1:$BLANK_PORT"
echo " Populated: http://127.0.0.1:$POPULATED_PORT"
echo
echo "==> See conventions/vm-preview/README.md for the full workflow + how to extend."