CrowdVision · source-available, not open source · © 2026 Nicolò Ghignatti
CrowdVision can be run in two distinct modes: Docker Compose for everyday local development, and Kubernetes for production-like environments. Both modes are managed via just recipes from the monorepo root.
For active development with live code reloading, use:
just stack devWhat happens:
just stack env runs first to verify your .env and generate any missing keys (see Setting Up the Environment for the full list of what it generates).--watch — saving a file triggers an instant hot-reload inside the running container.Run a lighter stack by excluding services you are not working on:
# Named flags for the common groups:
just stack dev --no-simulators
just stack dev --no-agent --no-metrics
# Raw substring for anything without a flag:
just stack dev frontend
# Combine them:
just stack dev --no-simulators frontend
# See what would run, without running it:
just stack dev --no-agent --dry-runA raw substring matches both folder names and compose service names. simulator skips aq-simulator and sensor-simulator; use a longer substring (aq) to target one. just stack dev --help lists every flag.
| Interface | URL | Description |
|---|---|---|
| Vue Frontend | http://localhost:8080 | The main CrowdVision user interface. |
| API Gateway | http://localhost:80 | Caddy proxy — routes to all microservices. |
| Keycloak | http://localhost:8090 | Hosted login/registration UI, and its own admin console (generated admin credentials — see Setting Up the Environment. Not routed through Caddy. |
| Langfuse | http://localhost:3030 (or http://localhost/langfuse, which redirects there) | LLM trace UI for the agent. Login admin@crowd-vision.local / langfuse-admin. |
| Twin DB GUI | http://localhost/twin-db-gui/ | Mongo Express for the twin database. Dev mode only. |
| Sensor DB GUI | http://localhost/telemetry-db-gui/ | Mongo Express for the sensor database. Dev mode only. |
| Notification DB GUI | http://localhost/notification-db-gui/ | Mongo Express for the notification database. Dev mode only. |
| Contracts DB GUI | http://localhost/dashboard-db-gui/ | Mongo Express for the contracts database. Dev mode only. |
| Chat DB GUI | http://localhost/chat-db-gui/ | Mongo Express for the chat database. Dev mode only. |
| Grafana | http://localhost/grafana/ | Metrics dashboards. Dev mode only. |
| Prometheus | http://localhost/prometheus/ | Raw metrics scraper. Dev mode only. |
just stack startRuns the stack using the base compose configuration in detached mode (-d). No hot-reloading, no database GUIs. Same exclude= syntax works here too.
# Graceful shutdown — stops all containers and removes orphans:
just stack down
# Follow logs for all services:
just stack logs
# Follow logs for one service:
just stack logs digital-twin
# Drop the chat, twin, notification and agent databases (destructive):
just db clear
# Truncate only the telemetry tables:
just db clear-telemetryjust db clear drops those databases’ data; the Docker volumes stay. Both recipes act on the stack the last just stack dev or just stack start generated, so start it through just first.
Login and registration are Keycloak’s hosted UI now — there is no seed script for an admin account. To bootstrap a fresh stack: register through the frontend’s normal sign-in flow (redirects to Keycloak), then create your own domain through the Domains page (POST /tenancy/domains); the creator automatically becomes that domain’s business_admin. The old scripts/ops/provision.js (which called auth-service’s /business/register) has been removed along with auth-service — enterprise self-registration has no current equivalent, see Identity & Tenancy Architecture.
See the Kubernetes Configuration page for the full reference. Quick summary:
just k8s createSee Kubernetes Configuration for exactly what this creates and why (single-server k3d, Traefik disabled — Istio’s Gateway API is the local ingress path instead).
# First-time full setup (create cluster → Gateway API + Istio → namespace → secrets → manifests):
just k8s setup
# Subsequent deploys — re-apply manifests:
just k8s applyjust k8s build # build all images locally
just k8s load # import into k3d — no GHCR needed
just k8s apply # apply manifests# After code changes — rebuild the image, then rolling restart:
docker build -t ghcr.io/nickghignatti/crowdvision-twin:latest -f backend/digital-twin/Dockerfile .
just k8s load
just k8s restart digital-twin
# Watch the rollout:
just k8s rollout digital-twindigital-twin, telemetry, agent, claims-gateway, tenancy, registry, and provisioner all build with -f backend/<service>/Dockerfile . from the repository root, not ./backend/<service>. Reason differs by language but is the same shape both times: digital-twin/telemetry/agent read the shared Cedar authorization policy bundle at backend/libs/auth-policy and the role-weight ladder at backend/libs/auth-contracts/roles.json, both siblings of their own directory; the four Go control-plane services depend on sibling Go modules (auth-contracts, auth-middleware, auth-policy) via a local replace directive rather than a registry, which only resolves if those siblings are inside the build context. chat, dashboard, and the rest build from their own directory as shown above for the general case. just k8s build already gets every one of the twelve images right — see just/k8s.just for the exact command per service.
# Pause the cluster (preserves all pods, volumes, and secrets):
just k8s stop
# Resume from exactly where you left off (~30 seconds):
just k8s start
# Verify everything came back up:
just k8s status
# Destroy the cluster completely (data is permanently lost):
just k8s deleteUse just k8s stop at the end of your working day. All pod state and persistent volume data survives the stop/start cycle — your databases are intact.
# All pods should show 1/1 Running:
just k8s status
# Check persistent volumes are Bound:
kubectl get pvc -n crowdvision
# Check the Gateway and its routes are accepted (there is no Ingress — Istio's
# Gateway API is the only k8s ingress path):
kubectl get gateway,httproute -n crowdvision
# Tail logs for a service:
just k8s logs digital-twin
# Describe a pod for detailed events (useful for debugging):
kubectl describe pod <pod-name> -n crowdvision# Run all unit tests across all services (moon caches results — unchanged services are instant):
just test all
# Run only tests for services touched by your current branch:
just test affected
# Per-service unit tests:
just test chat
just test twin
just test telemetry
just test notification
just test socket
just test frontend
just test agent
# Agent-only Python integration suite (separate from the backend integration tests below):
just test agent-integration
# Full backend integration tests (spins up a composed stack, then tears it down):
just test integrationjust test all and just lint all both delegate to moon, which caches task results by input hash. If nothing in a service has changed since the last run, the cached result is replayed in milliseconds. Use just test affected / just lint affected on a feature branch to only process the services your changes actually touch. There is no just test <service> recipe for dashboard or the four Go control-plane services (claims-gateway, provisioner, registry, tenancy) — they’re still covered by just test all / just test affected (both run moon run :test, which includes every registered project regardless of language), just not individually by name.
just test eval [models="..."] [judge="..."] runs the agent’s golden-dataset evaluation against your local stack, auto-minting the JWTs it needs. This isn’t a pass/fail test — it scores model responses (optionally with an LLM judge) and is meant to be run manually when tuning prompts or comparing models, not as part of the regular test loop. See backend/agent/evals/run_evals.py.
Two recipes run a security audit and a lockfile-sync check across every project. Both delegate to moon (moon run :audit / :deps), so they are cached and accept --affected to scope to the projects changed on your branch. See The Toolchain: mise & moon for how these tasks are defined.
# Security audit — npm audit (--audit-level=high --omit=dev) + uv audit + cargo audit:
just setup audit
# Lockfile-sync check — npm ci + uv sync --locked + cargo check:
just setup deps-check
# Scope either to only the projects changed on your branch:
mise exec -- moon run :audit --affectedjust setup deps-check is the fastest way to catch a lockfile drift before pushing (the classic npm ci can only install packages when your package.json and package-lock.json are in sync). If it fails, regenerate the offending lockfile with just setup clean-install (see Setting Up the Environment → Lockfiles & Cross-Platform Regeneration). The Rust cargo audit leg is marked allow-failure: if cargo-audit isn’t installed it is reported but does not fail the run (cargo install cargo-audit to enable it).
.moon/tasks/go.yml defines build, test, lint (go vet), and deps (go mod download) for the four Go control-plane services, but no audit task — so just setup audit silently skips them (moon only runs a task for projects that define it). There’s no govulncheck-equivalent leg yet for claims-gateway, tenancy, registry, or provisioner.
| Recipe | What it does |
|---|---|
just stack dev [flags] | Full stack with hot-reloading. --no-agent / --no-metrics / --no-simulators, raw substrings, --dry-run. |
just stack start [flags] | Full stack in detached/production mode. Same flags as dev. |
just stack down | Graceful shutdown, removes orphan containers. |
just stack logs [service] | Follow logs. Omit service to tail all. |
just db clear | Drop the chat, twin, notification and agent databases (destructive). |
just db clear-telemetry | Truncate only the telemetry tables. |
just test all | Run all unit tests (moon-cached; unchanged services are instant). |
just test affected | Run unit tests only for services changed on the current branch. |
just test <service> | Run unit tests for one service (chat, twin, telemetry, notification, socket, frontend, agent — not contracts or the Go services, see the note above). |
just test agent-integration | Agent-only Python integration suite (real Postgres, no full compose stack). |
just test eval [models=...] [judge=...] | Run the agent’s golden-dataset evaluation against the local stack. |
just test integration | Run backend integration tests. |
just agent ingest | Re-ingest all documentation into the agent’s knowledge base — run after editing anything under documentation/. |
just lint all | Lint every service (moon-cached). |
just lint affected | Lint only services changed on the current branch. |
just lint fix | Auto-fix linting issues across every service. |
just setup audit | Security audit across all projects via moon run :audit (npm + uv + cargo). |
just setup deps-check | Lockfile-sync check across all projects via moon run :deps (npm ci + uv sync —locked + cargo check). |
just setup clean-install | Wipe all node_modules + lockfiles and regenerate them for Linux, then reinstall. |
just docs build | Build the whole published site into landing-page/: the Quarkdown guide at the site root, plus the OpenAPI specs under api/. The Material 3 skin in documentation/theme/crowdvision.css is appended to the generated theme/theme.css, which every page already links at the right depth. Runs with --allow native-content, which the header icon bar and the Coverage page’s live fetch both need. CI runs this same recipe, so there is one definition of the build. Refuses to build if a page or a spec would publish unreachable — see the box below. |
just docs preview | Quarkdown live-reloading server for the guide while editing. |
just docs serve | Build the site, then live-serve the whole landing-page/ site at :8080. |
The navigation lists are hand-maintained. A new .qd page is not picked up by any menu, and a new api/*.yaml is not linked from the API Reference page — every list is written by hand (documentation/_nav.qd for the sidebar, documentation/_setup.qd for the top nav, documentation/reference/api-reference.qd for the specs). A page needs to be in one of the two navigations, not both: reference/ pages are kept out of the sidebar on purpose and reached from the top nav. Those pages also include _standalone.qd rather than docs, which keeps the doctype and top bar but drops the sidebar and the on-this-page panel.
Output directories are named after the page’s file, not its .docname. A page at documentation/**/api.qd therefore builds to <root>/api/ and is then overwritten by the OpenAPI specs copied in afterwards.
Either one compiles green and publishes something nobody can reach. scripts/check-docs-links.sh runs first in just docs build and fails the build on both.
The Caddy proxy forwards to the service container but nothing is listening. Most common causes:
1. The dev script in package.json points to a non-existent file. Every Node.js/TypeScript service must reference src/index.ts via ts-node/esm, not a compiled .js file. Check the service’s package.json:
# Correct pattern (all TypeScript services):
"dev": "nodemon --watch src --exec node --no-warnings=ExperimentalWarning --loader ts-node/esm src/index.ts"If it says src/index.js and that file does not exist, nodemon exits silently and the service never starts — manifesting as 502.
2. Environment variable is empty or wrong. Open the service logs: just stack logs <service-name>. Look for connection errors to MongoDB or Redis at startup.
3. Missing .env variable. Run just stack env to fill any gaps. The script skips variables already present, so it is safe to re-run.
Check the spelling first — a mistyped flag is rejected outright (unrecognized arguments: --no-metrcs), but a mistyped raw substring matches nothing and is silently ignored. Prefer the flags where one exists:
just stack dev --no-simulators # skips aq-simulator and sensor-simulator
just stack dev --no-simulators frontend # also skips the Vue frontend
just stack dev --no-agent --dry-run # confirm the service list before startingdown and build deliberately ignore all exclusions and operate on the full set.
If exclusion stops working after editing the Justfile: the dev, start, and down recipes must keep their variadic *args parameter, which is what forwards flags through to compose-run.py.
kubectl fails to connect after just k8s startAfter a cluster restart the kubeconfig may point to a stale port. Fix it:
# Find the actual port the k3d load balancer is using:
docker ps --filter name=k3d-crowdvision-serverlb
# Look for the port mapped to 6443 in the PORTS column, e.g. 0.0.0.0:32774->6443/tcp
# Update the kubeconfig to use the correct port:
kubectl config set-cluster k3d-crowdvision --server=https://127.0.0.1:<PORT>just k8s secrets fails: “namespaces not found”Secrets are scoped to the crowdvision namespace — it must exist first. Create it manually:
kubectl apply -f k8s/namespace.yml
just k8s secretsOr use just k8s setup which handles the ordering automatically.
If pods are stuck in ImagePullBackOff, Kubernetes is trying to pull from GHCR instead of using the locally loaded image.
# Load images from your local Docker daemon into k3d:
just k8s build
just k8s loadAlso check that the deployment manifest sets imagePullPolicy: IfNotPresent, not Always — Always forces a remote pull even when the image exists locally.