# Industrial Edge Computing Platform

## Commands

> **First-run prerequisites** — do both before any `docker compose` / build below,
> or builds fail with `edgebits/fasten-core:latest: pull access denied`:
> 1. Init the `fasten` git submodule — `git submodule update --init --recursive`
> 2. Build the shared native base image once from the repo root —
>    `docker build -t edgebits/fasten-core:latest -f Dockerfile.fasten-core .`
>    Every service across edge, edge-manager, and edge-analytics builds `FROM` it
>    (compose does not build it for you). Rebuild after a fasten bump that touches
>    `fasten/fasten-core/`. Details: [README](README.md#cloning).

```bash
# Edge (full stack with hot reload)
cd edge && docker compose -f docker-compose.dev.yml up --build    # Full edge stack
cd edge && docker compose -f docker-compose.dev.yml watch         # Hot reload on file changes
cd edge/ui && npm run dev                                         # Edge UI dev server (:3000) -- Vite HMR
cd edge/gateway && python -m uvicorn src.main:app --reload        # Gateway only -- auto-reload on .py changes

# Edge Manager (full stack with hot reload)
cd edge-manager && docker compose -f docker-compose.dev.yml up --build   # Full edge-manager stack (includes Redis)
cd edge-manager && docker compose -f docker-compose.dev.yml watch        # Hot reload on file changes
cd edge-manager/ui && npm run dev                                        # Edge Manager UI dev server (:4000) -- Vite HMR
cd edge-manager/api && go run ./cmd/server                               # Edge Manager API only

# SDK
cd sdk/edge && go test ./...                                      # Test edge SDK
```

**Dev mode**: Always use `docker-compose.dev.yml` (not `docker-compose.yml`). Dev compose includes volume mounts for source code hot reload, debug ports, and dev-only services (Redis with AOF for edge-manager).

**Build/test/verify always in Docker** — never use the host `python3`, `node`, `go`, `dpkg`, `rpmbuild`, or any other toolchain directly. The host is a Mac; it is not the source of truth.

```bash
# Packaging — build.sh spawns a debian:bookworm / rockylinux:9 container internally
bash packaging/build.sh blocks/connectors/rest deb arm64 bookworm   # RPi OS / Bookworm arm64
bash packaging/build.sh blocks/connectors/rest deb amd64 noble      # Ubuntu 24.04 amd64

# Type-check Edge UI
docker run --rm \
  -v "$(pwd)/edge/ui/src:/app/src:ro" \
  -v "$(pwd)/edge/ui/index.html:/app/index.html:ro" \
  -v "$(pwd)/edge/ui/tsconfig.json:/app/tsconfig.json:ro" \
  -v "$(pwd)/edge/ui/tsconfig.app.json:/app/tsconfig.app.json:ro" \
  -v "$(pwd)/edge/ui/vite.config.ts:/app/vite.config.ts:ro" \
  edge-ui-test \
  sh -c "npx tsc --noEmit && npx vite build"
# (requires: cd edge && docker build -f ui/Dockerfile.dev -t edge-ui-test .)

# Python tests
docker run --rm edge_core pytest

# Go tests
docker run --rm -v "$(pwd):/repo" -w /repo golang:1.23-alpine go test ./...
```

## Architecture

```
blocks/                             edge/                           edge-manager/
  connectors/ (protocol adapters)     gateway/  (FastAPI proxy :8080)  api/    (Go chi :9000)
  presets/    (processing stages)     core/     (Python: scheduler,   ui/    (React/Vite :4000)
  event-processors/ (rules+actions)              catalog, models)
  egress/     (output sinks)          pipeline-engine/               sdk/edge/  (Go HTTP client)
                                      event-engine/                  sdk/edge-manager/ (Go HTTP client)
catalog/    (GENERATED from blocks)   buffer/     (TimescaleDB writer)
scripts/    (publish-catalog.py)      edge-sync/  (Go: 4-step poll cycle → Edge Manager)
                                      ui/         (React/Vite :3000)

edge-analytics/                     (separate product — data plane, v1 built)
  api/   (FastAPI :8100 — ingest, tags, series, OEE config, SSE stream)
  ui/    (React/Vite :3100 — OEE dashboard, tag explorer, health monitor, reports)
  db/    (TimescaleDB — telemetry hypertable + 15m/1h continuous aggregates)
```

Edge Analytics is NOT part of Edge Manager. Edge Nodes push to it via REST egress. It has its own TimescaleDB, its own UI, its own API key auth. Edge Manager = control plane. Edge Analytics = data plane.

All services talk via **MQTT (UNS)**. Topics: `uns/{site}/{area}/{line}/{device}/{tag}`

## Tech Stack

| Layer | Edge | Edge Manager | Edge Analytics |
|-------|------|-------|-------|
| API | FastAPI + Pydantic | Go chi | FastAPI + Pydantic |
| DB | SQLite (config DB — always) · TimescaleDB (buffer — optional, `--profile buffer`) | PostgreSQL (always required — `DATABASE_URL`) · SQLite (fasten audit) | TimescaleDB (telemetry hypertable + continuous aggregates) · SQLite (OEE config) |
| Scheduler | APScheduler 3.x + SQLite | asynq + Redis | TimescaleDB continuous aggregate policies |
| Event Bus | Mosquitto MQTT | HTTP to edge gateways (via SDK) | SSE stream (`/api/v1/stream`) |
| UI | React 19 + Vite + vanilla CSS vars | React 19 + Vite |
| Auth | -- (single-node) | JWT (planned) |

## Platform support (locked)

Linux only, three host families, two container runtimes — full doc at
[docs/get-started/platform-support.md](docs/get-started/platform-support.md).

| | Pinned |
|---|---|
| Host OS | Raspberry Pi OS Bookworm 64-bit · Ubuntu **24.04 LTS** (not "latest") · Rocky Linux **9.4** / AlmaLinux **9.4** (free downstream of RHEL — paid RHEL not on the test matrix) |
| Container runtime | Docker ≥ 24.0 · Podman ≥ 4.0 |
| Base images | Python → `python:3.12-slim` · Go → `golang:*-alpine` builder + `alpine:*` runtime · Node → `node:*-alpine` · C/C++ → `debian:bookworm-slim` |
| CI runner | `ubuntu-24.04` (never `ubuntu-latest` — pin) |

**Don't** propose Windows/macOS as production hosts, Debian as a host OS, Alpine for Python services, or `ubuntu-latest` in CI. To expand the support matrix, open an RFC issue first.

## Conventions

- **Repository pattern**: Zero raw SQL in route handlers. All DB through service-layer repositories
- **Catalog generated from manifests**: `blocks/*/manifest.json` → `publish-catalog.py` → `catalog/{type}/{id}.json`. Never hand-edit catalog.
- **Building blocks** = independent packages in `blocks/`. 4 types: connector, preset, event_processor, egress. (A **preset** is a config-only catalog block that wraps an engine primitive via its manifest `function` field — `combine`, `expression`, `deadband`, …; it ships no code. Connectors/egress are real-code blocks. See [presets doc](docs/developers/building-blocks/presets.md).)
- **Buffer + Event Engine** = platform services, NOT building blocks
- **Install blocks** via API (`POST /api/v1/blocks/install`) or CLI (`edgebits install`)
- **Envelope** = universal data wrapper: `{id, source, tag, value, unit, timestamp, quality, metadata}`
- Files: `kebab-case.tsx` components, `camelCase.ts` utils, `snake_case.py` Python
- API types in frontend match backend field names (snake_case)
- Edge UI uses CSS custom properties (--accent, --bg-card, etc.), NOT Tailwind
- No emojis in UI -- use Lucide icons

## Commits

- No `Co-Authored-By` trailers ever

## CI / GitHub Actions

- Auto-triggers (`push`, `pull_request`) are disabled to avoid billed runs — they're commented out in `ci.yml`, not deleted, so they can be re-enabled later
- `ci.yml` keeps `workflow_dispatch` active so it can still be run manually from the GitHub Actions tab
- Any new workflow must follow this pattern: comment out auto-triggers, keep only `workflow_dispatch` active

## Don'ts

- Don't create custom API endpoints for things that fit existing primitives (connectors, functions, event_processors, egress)
- Don't hand-edit `catalog/` files -- always generate via `publish-catalog.py`
- Don't put building blocks inside `edge/` -- they live in `blocks/`
- Don't use `console.log`, `alert()`, or browser default fonts
- Don't write files > 200 lines -- decompose
- Don't bypass the config_tree for runtime configuration
- Don't run connector/egress logic in the gateway process -- always separate containers

## Key Files

| Purpose | Path |
|---------|------|
| Architecture principles | `.claude/rules/architecture.md` |
| Building blocks (source) | `blocks/` |
| Catalog (generated) | `catalog/` |
| Publish script | `scripts/publish-catalog.py` |
| Blocks REST API | `edge/gateway/src/routes/blocks.py` |
| Edge CLI (`edgebits-edge`) | `edge/cli/` |
| Edge TUI (`edgebits-edge-tui`) | `edge/tui/` |
| Edge Manager CLI (`edgebits-manager`) | `edge-manager/cli/` |
| Edge Manager TUI (`edgebits-manager-tui`) | `edge-manager/tui/` |
| Edge scheduler | `edge/core/scheduler.py` |
| Edge Manager scheduler | `edge-manager/api/internal/scheduler/scheduler.go` |
| Edge API client (TS) | `edge/ui/src/lib/api.ts` |
| Edge SDK (Go) | `sdk/edge/` |
| Edge Manager SDK (Go) | `sdk/edge-manager/` |
| Edge-Sync poller | `edge/edge-sync/internal/poller/poller.go` |
| Pipeline types | `edge/ui/src/components/pipeline/types.ts` |
| Function registry | `edge/ui/src/lib/function-registry.ts` |
