§ 06 · Build

Developer quickstart

Bring the full chain up locally from source — data source, query plane, gateway — and ask Claude Desktop a question.

View as .md

This is the developer runbook — bringing the full chain up locally and asking Claude Desktop a question against real data. For the operator-grade walkthrough (Docker only, no Rust toolchain), see the operator quickstart.

This runbook drives one reference implementation — illustrative, not definitive. The canonical product is the role specification (read-only, one-way, outside copy); see the system architecture. The commands below are real for this reference build.

Prerequisites

  • macOS or Linux dev host
  • Docker + Docker Compose (for the data source)
  • Rust toolchain (rustup default stable, recent stable)
  • mkcert (brew install mkcert on macOS) — only needed if you’re testing mTLS
  • Optional: Claude Desktop or another MCP-aware client

The chain

Claude Desktop ──MCP──▶ gateway ──HTTP/mTLS──▶ query plane ──▶ local data source
 (off-box)              (:8091)   (the seam)    (:8090, i3X)    (Docker, :5001)

The gateway is the read-only front door. The query plane re-exposes the data source under a small, opinionated read API. Nothing in this chain has a write path to a device.

Step 1 — Bring up the data source

The data source runs as a Docker Compose stack (app + workers + Postgres + Redis). The one-time bootstrap handles secrets, schema migrations, the default org, and the API key the query plane uses:

make first-run

make first-run is idempotent. It populates services/witness/.env with dev secrets, brings up db/redis, runs alembic upgrade head, starts the app, provisions an org + API key, and writes WITNESS_API_KEY to ./.env. Confirm health:

make ps
curl -k http://127.0.0.1:5001/

Load sample data either by uploading a preset capture from services/witness/presets/ in the console at http://127.0.0.1:5001, or via the live-capture path if you’ve given the container an interface.

Step 2 — Bring up the query plane

In a new terminal at the repo root. make run-query-plane honors any env you’ve exported:

WITNESS_URL=http://127.0.0.1:5001 \
  WITNESS_API_KEY="$(grep ^WITNESS_API_KEY= .env | cut -d= -f2-)" \
  make run-query-plane

Verify connectivity:

curl -s http://127.0.0.1:8090/v1/info | jq
# expect capabilities.query.history = true (data source wired)

Step 3 — Bring up the gateway

QUERY_PLANE_URL=http://127.0.0.1:8090 \
  AUDIT_LOG_PATH=/tmp/cf-audit.jsonl \
  make run-gateway

Smoke check:

curl -s http://127.0.0.1:8091/health
curl -s http://127.0.0.1:8091/tools | jq '.tools | length'

Step 4 — Make a real query (HTTP path)

curl -s http://127.0.0.1:8090/v1/objects | jq '.result[].elementId' | head -10

ELEMENT_ID="<paste-an-elementId-from-above>"

curl -s -X POST http://127.0.0.1:8091/query \
  -H 'content-type: application/json' \
  -d "$(jq -nc --arg eid "$ELEMENT_ID" '{
    request_id: "00000000-0000-0000-0000-000000000001",
    intent: "get-current-state",
    elementId: $eid,
    maxDepth: 2
  }')" | jq

Inspect the audit chain:

tail -1 /tmp/cf-audit.jsonl | jq

Step 5 — Connect Claude Desktop (optional, MCP path)

Run the gateway as a stdio subprocess instead of HTTP:

cargo build --release -p conversational-gateway

Edit ~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "factory": {
      "command": "/path/to/conversational-factory/target/release/conversational-gateway",
      "args": ["--stdio"],
      "env": {
        "QUERY_PLANE_URL": "http://127.0.0.1:8090",
        "AUDIT_LOG_PATH": "/tmp/cf-audit.jsonl"
      }
    }
  }
}

(make run-gateway-stdio runs the same thing from source for a quick check.) Restart Claude Desktop. Ask: “Use the factory MCP server. What objects exist on Cell 5? Get the current state of one of them.”

Step 6 — Verify the audit chain

cat /tmp/cf-audit.jsonl | jq -s 'group_by(.tool) | map({tool: .[0].tool, count: length})'

The audit log carries: the natural-language question, the exact tool dispatched, the parameters, the downstream call(s), and the status returned to the AI. Every claim traces back to a read — there is no write path to trace.

Dev mode vs production

ConcernDev (this guide)Production
Query plane transportPlain HTTP loopbackmTLS with operator-issued certs
Gateway locationSame host as query planeOff-box on operator workstation / adjunct host
Data source API keyRead from .envProvisioned via signed-config artifact
Audit log path/tmp/cf-audit.jsonl/var/log/cf/gateway-audit.jsonl with rotation
Sample dataLoaded from presetsReal plant traffic

Troubleshooting

Run make smoke-run to probe the full chain end-to-end. Common surprises:

  • make up exits with app crashlooping — schema not migrated. make first-run runs alembic upgrade head; re-run it.
  • Compose complains about missing required env — the five secrets in services/witness/.env (DB_PASSWORD, SECRET_KEY, ADMIN_PASSWORD, ARTIFACT_ENCRYPTION_KEY, SMTP_FROM) are NOT NULL; make first-run writes dev defaults.
  • Query plane reports query.history: falseWITNESS_URL or WITNESS_API_KEY is wrong; the startup ping failed.
  • get_current_state returns GoodNoData for everything — no data loaded yet; upload a preset capture in the console.
  • tools/call returns audit-failure — the AUDIT_LOG_PATH directory isn’t writable by the gateway process.
  • Gateway can’t reach query plane — port mismatch (default 8090) or one process bound to localhost only.
  • Data source rejects the bearer token — key was regenerated but the env var still has the old value; re-source .env.