Orchestrata Ultimate / Specs / 108-environment-installer
108-environment-installer: Environment Replication Installer
- What: A tracked, tested installer suite that replicates the complete orchestrata-ultimate working environment on a fresh machine: the Claude Code global layer (
claude-env/install.sh), and the infrastructure fleet (claude-env/install-infra.sh) on either an Incus or a Docker backend, with Terraform as the IaC engine for the Incus path. - Why: The environment previously lived only on one workstation — global~/.claudetools/hooks, session state, custom test runners,...
At a glance
| Item | Value |
|---|---|
| Spec directory | specs/108-environment-installer/ |
| Status line in the spec | Complete |
| Requirement IDs defined | 25 |
With a @traces citation in src/ |
0 (0.0%) |
With a @traces citation in tests/ |
0 (0.0%) |
| Tracked files | 2 |
| RTM page | 108-environment-installer |
Reading this spec
This spec defines a tracked, tested installer suite that replicates the complete orchestrata-ultimate working environment on a fresh machine. The environment previously lived on one workstation only: the global ~/.claude tools and hooks, session state, custom test runners and a hand-built 13-node Incus fleet. Nothing outside the repository was reproducible, so a disk failure or a second machine meant rebuilding from memory.
The design is two idempotent bash installers plus tracked assets. claude-env/install.sh sets up the Claude Code global layer and delegates the config layer to deploy/install-claude-config.sh, the SPEC-107 FR-013 deployment of claude/. claude-env/install-infra.sh sets up the infrastructure fleet on either an Incus or a Docker backend, with Terraform as the infrastructure-as-code engine on the Incus path, using the infra/rehearsal/ harness, and infra/docker/infra-compose.yml for the Docker fleet. Four user stories cover a fresh workstation bootstrap, backend choice between Incus and Docker, a safe re-run on a machine with an existing environment, and a raw machine on another network without access to private images.
The 25 requirement IDs (REQ-108-NNN) state the behaviour. The config layer deploys from a pinned git ref and verifies every file byte for byte. A second identical run is a no-op, verified in a clean Ubuntu 24.04 container. --dry-run prints every planned action and changes nothing. The infrastructure installer detects both Incus and Docker and reports both states before choosing. The compose file brings up Redis, a primary Neo4j, three workers and Memgraph with health checks, and waits until all are healthy. Subnet, cloud image and ready-timeout overrides are available through ORCHESTRATA_SUBNET, ORCHESTRATA_CLOUD_IMAGE and ORCHESTRATA_READY_TIMEOUT. No Memgraph-Enterprise artifact appears in any public or tracked binary form, so the Docker path uses the community image and documents the divergence. A semantic-search test fails loudly on an empty semantic graph instead of passing vacuously. Per-service backend selection (mix and match) is declared as a follow-up.
No code module cites this spec, since the deliverables are shell scripts, compose and Terraform files. The requirements traceability matrix lists every ID. The spec's own status line reads Complete and it tracks 2 files. No src/ symbol cites any of the 25 requirements, so measured trace coverage is 0.0%, and in tests/, 0 requirements are cited by a test-side @traces. All 25 requirements have no citing symbol; untraced means no @traces names the ID, and the behaviour may still exist in code that was never annotated.
Requirements
Checkbox is the state recorded in the requirements checklist; Traced by is the count of src/ symbols whose @traces names the ID.
| ID | Done | Requirement | Traced by |
|---|---|---|---|
REQ-108-001 |
yes | install.sh delegates the config layer to deploy/install-claude-config.sh via --prefix, which deploys claude/ from a pinned git ref and verifies every file byte-for-byte. It no longer rsyncs a bundle of its own — claude-env/global/ is deleted |
none |
REQ-108-002 |
yes | settings.json is merged by claude/scripts/patch_settings.py — additive, idempotent, and it rewrites hook commands to the deployed venv's interpreter. Replaces the settings.json.new write-alongside |
none |
REQ-108-003 |
yes | Python deps go into ~/.claude/.venv, the deployment's own uv venv on python3.14t (platform convention 2), and the delegate verifies they import FROM that venv. The pip --user / --break-system-packages path is retired |
none |
REQ-108-004 |
yes | A second identical run is a no-op (verified on clean Ubuntu 24.04 container) | none |
REQ-108-005 |
yes | Missing required tools (git rsync cmp uv python3) abort BEFORE any mutation, listing all of them with per-distro install hints |
none |
REQ-108-006 |
yes | The missing-uv path aborts with the astral.sh install hint, and a missing free-threaded python3.14t aborts naming scripts/build_ft_cpython.py — a build hint, since that is the one blocker no package manager fixes |
none |
REQ-108-007 |
yes | --dry-run prints every planned action and mutates nothing |
none |
REQ-108-008 |
yes | Every bundled Python file parses on Python 3.12 (17 PEP 758 parenthesis-free except sites parenthesized; verified in-container) |
none |
REQ-108-009 |
yes | tools/orchestrata -> session ships as a RELATIVE symlink so it survives any home layout |
none |
REQ-108-010 |
yes | install-infra.sh detects incus AND docker and reports both states before choosing |
none |
REQ-108-011 |
yes | --backend incus|docker + --yes is fully non-interactive; without flags an interactive prompt offers both |
none |
REQ-108-012 |
yes | Terraform installs from the hashicorp apt repo (Debian/Ubuntu) or pacman (Arch) when absent | none |
REQ-108-013 |
no | Mix-and-match (per-service backend selection) — declared follow-up, enabled by per-service .env derivation |
none |
REQ-108-014 |
yes | The 8-node public tier builds from infra/rawmachine (terraform, images:debian/12/cloud, pinned IPs) with cloud-init provisioning — verified end-to-end in an ISOLATED VM (host fleet ACL-blocked and proven unreachable), incus installed in-VM, protocol-level checks green on every node |
none |
REQ-108-015 |
yes | All provisioned services are systemd-supervised, fleet-bound, and survive container restart; the app unit on prolog nodes is ConditionPathExists-guarded (inactive, never crash-looping) | none |
REQ-108-022 |
yes | ORCHESTRATA_SUBNET / ORCHESTRATA_CLOUD_IMAGE / ORCHESTRATA_READY_TIMEOUT overrides work (image override exercised in verification — the images: remote stalls on constrained networks) |
none |
REQ-108-023 |
yes | Disk preflight: < 12GiB free aborts before any build (ORCHESTRATA_MIN_DISK_GB override) — a 10GiB root exhausted mid-provision in verification |
none |
REQ-108-024 |
no | installer + bootstrap_graphs.py --all + infra-subset suite green on the machine's own fleet — trigger: first raw-machine bring-up with the app venv (uv sync --dev + FT builds) installed |
none |
REQ-108-016 |
yes | infra-compose.yml brings up redis + neo4j primary + 3 workers + memgraph 3.11.0 with healthchecks; up -d --wait gates on ALL healthy (verified in clean Ubuntu 24.04 VM) |
none |
REQ-108-017 |
yes | Data-path smoke passes: redis PONG, cypher round-trip on primary AND a worker, memgraph bolt write+read | none |
REQ-108-018 |
yes | .env.docker is emitted with the full localhost port map and the community-stand-in caveat in-file |
none |
REQ-108-019 |
yes | No Memgraph-Enterprise/orchgraph artifact appears in any public or tracked binary form; the docker path uses the community image with the divergence documented | none |
REQ-108-020 |
yes | context.json/context.md/context.history.md are tracked in the repo |
none |
REQ-108-021 |
yes | session_cli.py init bootstraps a NEW project from cwd when no root exists (circular-error defect fixed); init → begin → log-work → status verified on the clean container |
none |
REQ-108-025 |
yes | test_semantic_search.py fails loudly on an empty semantic graph instead of passing vacuously (empty result list / "[]" / "No results." no longer satisfy the assertions); 12/12 observed on the populated fleet |
none |
Code that traces this spec
No src/ symbol cites a requirement of this spec.
Checklists
checklists/requirements.md
The requirement list above comes from this file; 23 of 25 checkbox items are ticked.
Full spec text
Verbatim from specs/108-environment-installer/spec.md; headings are pushed down two levels.
SPEC-108: Environment Replication Installer
Metadata
- Spec ID: 108-environment-installer
- Title: Environment Replication Installer (claude-env)
- Author: Mohamed
- Status: Complete
- Created: 2026-07-21
- Last Updated: 2026-07-21
- Priority: P1
- Version: 1.0.0
- Extends: reproducibility plan R5 (
plans/reproducibility-plan.md), infra IaC (infra/)
Overview
- What: A tracked, tested installer suite that replicates the complete
orchestrata-ultimate working environment on a fresh machine: the Claude Code
global layer (
claude-env/install.sh), and the infrastructure fleet (claude-env/install-infra.sh) on either an Incus or a Docker backend, with Terraform as the IaC engine for the Incus path. - Why: The environment previously lived only on one workstation —
global
~/.claudetools/hooks, session state, custom test runners, and a hand-built 13-node Incus fleet. Nothing outside the repo was reproducible; a disk failure or second machine meant manual reconstruction from memory. - How: Two idempotent bash installers plus tracked assets:
claude-env/install.sh, which delegates the config layer todeploy/install-claude-config.sh(the SPEC-107 FR-013 deployment ofclaude/),infra/docker/infra-compose.yml(docker fleet), and the proveninfra/rehearsal/Terraform harness (incus fleet). Both installers are verified end-to-end on clean Ubuntu 24.04 (container for the global layer, VM for the infra layer).
Amended 2026-08-14 —
claude-env/global/is deleted. This spec originally made that directory a second, verbatim copy of the~/.claudelayer, rsynced into place byinstall.sh. SPEC-107 FR-013 then madeclaude/the source of truth, deployed bydeploy/install-claude-config.shfrom a pinned git ref with a byte-exactness check that refuses success on any mismatch. Two installers were writing$HOME/.claudeand only one verified its work; whichever ran last won.Measured before removal:
claude-env/global/held zero filesclaude/did not also have — every path unique to it was a.ruff_cacheor.pytest_cachebuild artifact — and it had already drifted fromclaude/on ~22 files within five hours of that tree existing. Its only distinct content was the drift.Mohamed ruled
claude/canonical. FR-001 and SEC-001 below are amended accordingly; the fresh-machine entry point, the preflight and the infra half are unchanged, which is what this spec uniquely owns.
User Scenarios
US-108-001: Fresh workstation bootstrap
- As a developer with a new machine
- I want to clone the repo and run two scripts
- So that the full working environment (Claude tooling + infra fleet) exists without manual reconstruction
- Acceptance Criteria:
bash claude-env/install.shpopulates~/.claude(tools, hooks, commands, skills, settings) by delegating todeploy/install-claude-config.sh, which also creates~/.claude/.venvonpython3.14tand installs the tools' Python deps into it (amended 2026-08-14: the deps go into the deployment's own venv, notpip install --user).bash claude-env/install-infra.shbuilds a working infra fleet on the chosen backend.- The session CLI passes an
init → begin → log-work → statusround-trip on the fresh machine.
US-108-002: Backend choice (incus vs docker)
- As a developer on a machine where only one container runtime is viable
- I want to be shown which backends are present and choose one
- So that I am not forced into a runtime my machine cannot host
- Acceptance Criteria:
- The installer detects BOTH incus and docker and reports each as present/absent.
- Interactive mode prompts for the choice;
--backend incus|docker+--yesis fully non-interactive. - The unchosen backend is untouched. Mix-and-match (per-service backend) is a declared follow-up, enabled by per-service
.envderivation.
US-108-003: Safe re-run on a machine with an existing environment
- As a developer re-running the installer after an update
- I want existing files backed up and my customized settings never clobbered
- So that re-running is always safe
- Acceptance Criteria:
- (amended 2026-08-14) Safety comes from the delegate rather than from backups: it deploys from a pinned git ref into a staging dir, then verifies every deployed file byte-for-byte against that commit and REFUSES success on any mismatch. A re-run that would change something says so; one that would not is a no-op.
settings.jsonis merged byclaude/scripts/patch_settings.py, which is additive and idempotent and rewrites hook commands to the deployed venv's interpreter — replacing the oldersettings.json.newwrite-alongside convention.- A second identical run is a no-op (verified).
US-108-004: Raw machine on another network, no private-image access
- As a developer bootstrapping a machine that is NOT the production host
- I want the incus path to build the full public tier from checked-in cloud-init, with the private orchgraph tier's addition path stated
- So that a raw machine reaches a working fleet without any hand provisioning
- Acceptance Criteria:
- The 8-node public tier (redis, neo4j primary + 3 replicas, 3 prolog/app) builds from
infra/rawmachine+infra/cloud-initwith zero manual steps. - Every service is systemd-supervised and survives a container restart.
- The installer prints the exact private-release path for adding the orchgraph tier (never bundles it — Memgraph Enterprise License).
- The build is proven on a machine that CANNOT reach the production fleet (network-enforced isolation, not honor-system).
- The 8-node public tier (redis, neo4j primary + 3 replicas, 3 prolog/app) builds from
Data Models
{
"env_docker": {
"description": ".env.docker emitted by the docker backend",
"type": "object",
"required": ["REDIS_HOST", "REDIS_PORT", "KG_ROUTING_URI", "KG_DATABASE", "MEMGRAPH_URI"],
"properties": {
"REDIS_HOST": {"type": "string", "const": "127.0.0.1"},
"REDIS_PORT": {"type": "string", "const": "6379"},
"KG_ROUTING_URI": {"type": "string", "pattern": "^bolt://"},
"KG_DATABASE": {"type": "string", "const": "orchkg"},
"MEMGRAPH_URI": {"type": "string", "pattern": "^bolt://"},
"PROLOG_NODES": {"type": "string", "const": "", "description": "empty: janus in-process"}
}
}
}
Functional Requirements
- FR-001 Global-layer install (amended 2026-08-14):
install.shdelegates the config layer todeploy/install-claude-config.sh, invoking it as--prefix "$CLAUDE_HOME"— the delegate readsCLAUDE_PREFIX/--prefix, so passingCLAUDE_HOMEthrough the environment would be silently ignored and land every install in$HOME/.clauderegardless of the caller's override. The delegate deploysclaude/from a pinned git ref, gives~/.claudeits ownuvvenv on free-threadedpython3.14t, rewrites hook shebangs andsettings.jsoncommands to that interpreter, installs no symlinks, and refuses success unless every deployed file is byte-identical to the recorded commit.install.shno longer copies files, installs Python deps, or mergessettings.json: the venv replaces thepip install --userpath (platform convention 2 — a deployment owns its venv) andpatch_settings.pyowns the settings merge. - FR-002 Tool preflight (amended 2026-08-14):
install.shfails fast, before any mutation, listing every missing required tool (git rsync cmp uv python3) with per-distro install hints, and separately checks for the free-threaded interpreter ($FT_PYTHON, default~/.local/bin/python3.14t) with a build hint rather than a package hint — on a fresh machine that is the likeliest blocker and the only one whose fix is a build. The list is now what the DELEGATE needs:uvreplacedpip, andrsync/cmpare the delegate's copy and drift-check tools rather than this script's. A clean Ubuntu 24.04 container reproduces this path (rsync and git are both absent there). - FR-003 Dry-run:
--dry-runon both installers prints every action and mutates nothing. - FR-004 Portability of bundled Python: every bundled tool/hook parses on Python ≥ 3.10 (PEP 758 parenthesis-free
exceptis forbidden in the bundle — it is 3.14-only; the parenthesized form is used everywhere). - FR-005 Backend detection and choice:
install-infra.shdetects both incus and docker, reports both, and resolves the backend from--backend, else an interactive prompt, else (with--yes) prefers whichever is installed (incus on ties/none). - FR-006 Terraform provisioning: terraform is installed (hashicorp apt repo / pacman) when absent, regardless of backend, because
infra/terraformis the fleet's source of truth. - FR-007 Incus fleet (raw machine): the 8-node public tier builds via
terraform -chdir=infra/rawmachine applywith cloud-init provisioning (infra/cloud-init/*.yaml) onimages:debian/12/cloud— the plain image variant has no cloud-init and MUST NOT be used. Overrides:ORCHESTRATA_SUBNET(default10.95.0),ORCHESTRATA_CLOUD_IMAGE(locally imported alias for networks where theimages:remote stalls),ORCHESTRATA_READY_TIMEOUT(default 1800s — four parallel neo4j tarball pulls dominate first boot). The production host's disaster drill remainsinfra/rehearsal(exec provisioning) — a separate concern. - FR-011 Cloud-init service contract: every provisioned service runs under systemd supervision, binds the fleet interface (never localhost-only), and comes back after a container restart. Units that depend on a not-yet-deployed application are
ConditionPathExists-guarded — inactive, never crash-looping. - FR-012 Suite-on-fresh-fleet acceptance (trigger-armed): after the installer plus
python scripts/bootstrap_graphs.py --all(the three seed pre-conditions established by the 2026-07-21 seed-data audit: canonical constraints on primary + each worker DB, the AST code-intelligence graph, non-empty Memgraph), the infra-subset suite passes on the machine's own fleet. Trigger: first raw-machine bring-up with the app venv installed (needsuv sync --dev+ the FT janus/grpcio builds on that machine). - FR-008 Docker fleet:
infra/docker/infra-compose.ymlbrings up redis + neo4j primary + 3 workers + memgraph-community with healthchecks;up -d --waitgates on health;.env.dockeris emitted pointing at the localhost port map. - FR-009 License boundary: the orchgraph fork (Memgraph Enterprise) never enters a public artifact: the docker path uses the community image with the divergence documented in-file; the incus path requires the operator to supply the private image.
- FR-010 Session-state replication:
context.json/context.md/context.history.mdare tracked in the repo; the session CLI'sinitbootstraps a NEW project from cwd when no root exists (the pre-existing circular "run init → init needs a root" defect is fixed).
Error Conditions
| ID | Condition | Handling |
|---|---|---|
| ERR-001 | Required CLI tool missing (git rsync cmp uv python3) |
Exit 1 before any mutation; list all missing tools + per-distro install command |
| ERR-002 | (amended 2026-08-14) uv absent |
Exit 1 with the astral.sh install hint. The pip path is gone: the deployment owns a uv venv (platform convention 2) |
| ERR-003 | (amended 2026-08-14) Free-threaded python3.14t absent |
Exit 1 naming $FT_PYTHON and scripts/build_ft_cpython.py — a BUILD hint, not a package hint. The delegate still refuses success on a broken toolchain; it verifies importability from the deployed venv |
| ERR-004 | (amended 2026-08-14) Existing settings.json differs |
patch_settings.py merges additively and idempotently, rewriting hook commands to the deployed venv. The settings.json.new write-alongside is retired |
| ERR-005 | Unknown --backend value |
Exit 2 with usage |
| ERR-006 | Neither backend chosen interactively (bad input) | Exit 1 "invalid choice" |
| ERR-007 | Nested/constrained network stalls the images: remote fetch |
Operator imports the image locally and points ORCHESTRATA_CLOUD_IMAGE at the alias (documented in the installer header) |
| ERR-008 | terraform apply non-zero |
Exit 1; terraform's own error stream is the diagnosis |
| ERR-009 | Node provisioning failure (any of the parallel provision_node.sh) |
Collect all PIDs, exit 1 if any failed |
| ERR-010 | docker compose up --wait timeout (600s) |
Exit 1; containers left in place for inspection |
| ERR-011 | Not root and no sudo | Exit 1 "not root and no sudo available" |
| ERR-012 | Unsupported distro (no apt/pacman) | Exit 1 naming the requirement |
| ERR-013 | Less than 12GiB free disk (ORCHESTRATA_MIN_DISK_GB override) |
Exit 1 BEFORE any build — a 10GiB root exhausted mid-provision in verification |
| ERR-014 | Services not answering within ORCHESTRATA_READY_TIMEOUT |
Exit 1 with the per-node cloud-init status --long inspection command |
API Contracts
claude-env/install.sh [--dry-run] # exit 0 success, 1 preflight/dep failure
claude-env/install-infra.sh
[--backend incus|docker] # else interactive prompt
[--yes|-y] # non-interactive accept
[-h|--help]
# env: CLAUDE_HOME (install.sh dest override),
# ORCHESTRATA_SUBNET (incus fleet subnet, default 10.95.0),
# ORCHESTRATA_CLOUD_IMAGE (base image override, default images:debian/12/cloud),
# ORCHESTRATA_READY_TIMEOUT (service gate seconds, default 1800),
# ORCHESTRATA_MIN_DISK_GB (disk preflight floor, default 12)
Security Requirements
- SEC-001 (amended 2026-08-14): No secret ever enters the deployed config layer — all credentials in
claude/areos.environreferences, anddeploy/install-claude-config.shdeploys from a pinned git ref, so anything committed is reviewable in a diff. (Formerly scoped toclaude-env/global/, which is deleted; the grep sweep it described applied to a bundle that no longer exists.) - SEC-002: The orchgraph Enterprise binary/image is never published publicly and never enters the docker compose file; the private GitLab release is the only distribution channel.
- SEC-003:
.envfiles (real credentials) stay untracked; only.env.exampleand the generated.env.docker(localhost, default dev password) are written by tooling. - SEC-004:
install.shruns unprivileged;install-infra.shuses sudo only for package installs and runtime enablement.
Verification
The session-250 record previously here overclaimed: the docker run was container-level smoke only (fleet health +
docker execprobes), with no app-level config wiring and no network isolation — the suite could have silently reached production infra. Retracted and replaced by the session-251 record below, which enforces isolation at the network layer.
Executed 2026-07-21 (sessions 250-251), all on fresh Ubuntu 24.04 images:
| Layer | Environment | Result |
|---|---|---|
| install.sh preflight/dry-run/install/idempotency/settings-merge | Incus container | 12 scripted checks pass |
Session CLI init → begin → log-work → status |
same container (Python 3.12) | pass — after fixing 17 PEP 758 except sites + the init root-bootstrap defect |
| Isolation enforcement | dedicated incus network + ACL | 10.53.1.0/24 hard-dropped; proven unreachable from inside the VM before AND after the build, internet intact |
| incus raw-machine path, end-to-end | isolated VM, incus installed IN-VM | terraform applied the 8-node fleet from cloud-init; protocol-level checks pass on every node (authenticated cypher-shell RETURN 1 on all 4 neo4j, redis PONG, swipl 9.0.4 ×3); services survive container restart; canonical .env installed pointing at the VM's own 10.95.0.x fleet |
| Cloud-init first-boot cost | same VM | 2 iterations to green: 10GiB root exhausted mid-provision (→ ERR-013 preflight), 900s gate too tight for 4 parallel neo4j pulls (→ ORCHESTRATA_READY_TIMEOUT) |
| docker path | VM (session 250) | fleet healthy + data-path smoke (redis PONG, cypher on primary+worker, memgraph write/read) + canonical .env; infra-level only — the FR-012 suite-level acceptance is trigger-armed for both backends |
| Semantic-search false-green (seed-data audit) | host | fixed: empty-graph results now FAIL loudly instead of passing vacuously; 12/12 observed |