Orchestrata Ultimate › Plans › reproducibility-plan

Reproducibility & Infrastructure-as-Code Plan

Source: plans/reproducibility-plan.md at 8a2cb42ad · last changed 2026-07-27 · first committed 2026-07-10 · 257 lines.

Prose pending.

Plan text

Reproducibility & Infrastructure-as-Code Plan

Drafted 2026-07-10 (session 224) from a live fleet audit (incus list, per-container incus config show, in-container version probes). Goal: a fresh machine — or this machine after a disk loss — reaches "full suite green" from git clone + one bootstrap command, with zero undocumented hand-built state.

1. Ground truth — as-built (audited 2026-07-10; re-audited + R1 applied 2026-07-11)

18 RUNNING Incus (LXD 7.2) containers on bridge network 10.92.53.0/24. The suite depends on the starred rows. The memgraph-1/2/3/-ha/-db names in the 2026-07-10 draft were STALE — that named cluster no longer exists; the live Memgraph HA cluster is the orchgraph-* fleet below (matches docs/LEVEL0.md). All starred rows are now PINNED (R1, 2026-07-11).

Container IP Pinned? Service Role
★ redis-queue 10.92.53.10 YES (R1) Redis 7.x hot store, queues, broker
★ neo4j-db 10.92.53.11 YES (R1) Neo4j 5.26.x operational KG (primary)
★ neo4j-node-2/3/4 .13/.14/.19 YES Neo4j 5.26.x per-worker test replicas (NEO4J_URI_GW*)
★ prolog-node-1/2/3 .16/.17/.18 YES SWI-Prolog 9.0.4 policy engine (4 CPU / 4 GB)
★ orchgraph-coord-1/2/3 .160/.109/.198 YES (R1) Memgraph HA coordinator (bolt 7691) Raft coordinators; routing = .160:7691
★ orchgraph-data-1/2 .128/.123 YES (R1) Memgraph HA data (bolt 7687) data instances; MAIN is DYNAMIC (see below)
janus-test 10.92.53.15 YES janus-swi Prolog adapter testbed
orchestrata-arch 10.92.53.20 YES — architecture sandbox
claude-orchestrata 10.92.53.12 YES (R1) — agent sandbox
docker-build 10.92.53.30 YES docker build box
gatekeeper-neo4j — — STOPPED retired

The two findings that motivated this plan

  1. DHCP-lease IP fragility (RESOLVED by R1, 2026-07-11). The two most load-bearing addresses in .env — Redis .10 and Neo4j .11 — and the ENTIRE orchgraph/Memgraph HA fleet held their IPs only by lease longevity. R1 pinned all 8 to their current IPs via incus config device override <c> eth0 ipv4.address=<ip> and restart-tested each one at a time: every IP persisted.
  2. Memgraph HA MAIN is dynamic — confirmed live. Restart-testing the MAIN data instance during R1 triggered a real failover (MAIN moved instance_2 .123 → instance_1 .128; the cluster recovered to 5/5 after the old MAIN finished its ~90s vector-index WAL recovery). Consumers MUST discover MAIN via the HA controller / SHOW INSTANCES on a coordinator, never by pinned address — the whole point of R3. (Also observed: many leftover test_chunk_idx__TEST_* vector indexes slow recovery — test hygiene debt, tracked separately.)

2. Phase R1 — pin what must not move (½ day, do first)

For every suite-critical container, make the leased IP the configured IP (idempotent; no renumbering, so nothing else changes):

for c in redis-queue:10.92.53.10 neo4j-db:10.92.53.11 \
         memgraph-1:10.92.53.111 memgraph-2:10.92.53.192 \
         memgraph-3:10.92.53.159 memgraph-ha:10.92.53.153 \
         memgraph-db:10.92.53.174 claude-orchestrata:10.92.53.12; do
  name=${c%%:*}; ip=${c##*:}
  incus config device override "$name" eth0 ipv4.address="$ip"
done

Acceptance: incus config show <c> --expanded | grep ipv4.address shows the pin for all 8; containers restart-tested one at a time; suite stays green.

3. Phase R2 — .env.example + config contract (½ day)

4. Phase R3 — Memgraph MAIN discovery (1 day)

5. Phase R4 — graph state is DERIVED, never hand-built (1–2 days)

Both graphs must be rebuildable from source + seeds:

6. Phase R5 — Infrastructure as Code (the detailed plan)

Tool choice: Terraform + the official lxd/incus provider for container lifecycle, plus cloud-init per container for in-guest provisioning. Rationale: declarative state diffing (terraform plan shows drift — the exact failure mode we just audited by hand), the provider supports ipv4.address pins, profiles, and limits natively; no agent runtime needed on guests. Ansible is the fallback if in-guest config grows beyond cloud-init (Neo4j clustering, Memgraph HA enrollment already flirt with that line — start with cloud-init, promote to Ansible roles only when a guest needs >1 config file templated).

Layout (new top-level infra/):

infra/
  terraform/
    versions.tf          # incus provider, pinned version
    network.tf           # incusbr0 10.92.53.0/24, DHCP range excluding pins
    profiles.tf          # base-debian (bookworm), base-ubuntu (noble), limits
    redis.tf             # redis-queue: image, pin .10, cloud-init installs redis 7.x
    neo4j.tf             # neo4j-db .11 + count=3 worker replicas .13/.14/.19
    memgraph.tf          # 3 HA nodes + controller + (legacy) single node
    prolog.tf            # 3 nodes, 4CPU/4GB limits, swipl 9.x
    outputs.tf           # every service IP:port → consumed by .env generation
  cloud-init/
    redis.yaml neo4j.yaml memgraph.yaml prolog.yaml
  gen_env.py             # terraform output -json → .env (bridges IaC → app config)
  README.md              # bootstrap: terraform apply && python infra/gen_env.py
                         #            && python scripts/bootstrap_graphs.py --all

Sequenced adoption (never big-bang):

  1. R5.1 import, don't recreate — terraform import each running suite-critical container into state; terraform plan must converge to zero-diff before any apply is trusted. (~1 day)
  2. R5.2 drift gate — CI job (and health_check.py step) runs terraform plan -detailed-exitcode; non-zero = infra drifted from code = red. (~½ day)
  3. R5.3 rebuild rehearsal — on a scratch Incus project (incus project create rehearsal), terraform apply from nothing + gen_env.py + bootstrap_graphs.py + full suite. The rehearsal IS the acceptance test; document wall-clock. (~1 day, repeatable)
  4. R5.4 secrets — API keys/passwords never in tfvars committed to git: secrets.auto.tfvars gitignored; gen_env.py merges them from ~/.config/orchestrata/secrets.env (0600). GITLAB_TOKEN stays OUT of generated .env (the expired-token-shadows-glab incident).
  5. R5.5 versions — service versions pinned in cloud-init (redis=7.0.x, neo4j=5.26.x, memgraph latest-noble, swipl=9.0.x); upgrades are PRs that change a pin, run the rehearsal, then apply to prod containers one node at a time.

7. Phase R6 — CI / local runner parity (1 day)

7b. As-executed status (session 225, 2026-07-11)

8. Acceptance criteria (the plan is DONE when)

  1. All 8 suite-critical unpinned containers carry ipv4.address pins and survive a restart with the same IP (R1).
  2. .env.example committed; validate_env preflight red on any missing key (R2).
  3. No consumer writes to Memgraph by pinned MAIN address; resolve_main() contract test green against the live cluster (R3).
  4. bootstrap_graphs.py --all --check exits 0 on a freshly rebuilt pair of graphs; both graphs demonstrably rebuild from source (R4).
  5. terraform plan zero-diff on the imported fleet; drift gate wired into CI + health_check (R5.1–R5.2).
  6. One documented rehearsal from empty Incus project to full-suite green, with measured wall-clock (R5.3).
  7. CI and local both run scripts/run_tests.py; validate_ci enforces it (R6).

9. Sequencing & effort

Phase Effort Depends on
R1 pins ½ day — (do immediately; removes the standing hazard)
R2 env contract ½ day —
R3 MAIN discovery 1 day R2
R4 graph bootstrap 1–2 days R2
R5 IaC 3–4 days R1 (pins become tf attributes), R2
R6 CI parity 1 day R2, R4

Total ≈ 7–9 focused days. R1+R2 are the cheap insurance and should land this week regardless of when R5 starts.