From 4b74221cffb7f922637c6a44432b9089a97bc6fa Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Thu, 26 Mar 2026 23:31:07 -0700 Subject: [PATCH] Update deployment standards for MCP build lifecycle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add [build] section to service definition format, document mcp build and mcp sync auto-build, add Build and Release Workflow section. Pin example image tags to explicit versions — :latest is no longer permitted in service definitions. Co-Authored-By: Claude Opus 4.6 (1M context) --- engineering-standards.md | 153 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) diff --git a/engineering-standards.md b/engineering-standards.md index c0486d7..4c608af 100644 --- a/engineering-standards.md +++ b/engineering-standards.md @@ -72,6 +72,7 @@ but the top-level skeleton is fixed. │ ├── templates/ Go HTML templates │ └── static/ CSS, JS (htmx) ├── deploy/ +│ ├── -rift.toml MCP service definition (reference) │ ├── docker/ Docker Compose configuration │ ├── examples/ Example config files │ ├── scripts/ Install, backup, migration scripts @@ -217,6 +218,11 @@ Services expose two synchronized API surfaces: - Use `google.protobuf.Timestamp` for all time fields (not RFC 3339 strings). - Run `buf lint` and `buf breaking` against master before merging proto changes. +- **Input validation**: gRPC handlers must validate input fields (non-empty + required strings, positive IDs, valid enum values) and return + `codes.InvalidArgument` with a descriptive message on failure. This mirrors + the validation that REST handlers perform and ensures both API surfaces + reject bad input consistently. ### REST (Secondary) @@ -307,6 +313,9 @@ File permissions: `0600`. Created by the service on first run. sequentially at startup. - Each migration is idempotent — `CREATE TABLE IF NOT EXISTS`, `ALTER TABLE ... ADD COLUMN IF NOT EXISTS`. +- Seed data migrations must use `INSERT OR IGNORE` (not plain `INSERT`) + to ensure idempotency when the migration runs against a database that + already contains the seed rows. - Applied migrations are tracked in a `schema_migrations` table. - Never modify a migration that has been deployed. Add a new one. @@ -548,6 +557,20 @@ Multi-stage builds: 2. **Runtime**: `alpine:3.21`. Non-root user (``), minimal attack surface. +Runtime images MUST include `ca-certificates` (for TLS verification) and +`tzdata` (for timezone-aware logging and scheduling): + +```dockerfile +RUN apk add --no-cache ca-certificates tzdata \ + && addgroup -S \ + && adduser -S -G -h /srv/ -s /sbin/nologin \ + && mkdir -p /srv/ && chown : /srv/ +``` + +The image should declare `VOLUME /srv/` (to document the data +mount point) and `WORKDIR /srv/` (so relative paths resolve +correctly). + If the service has separate API and web binaries, use separate Dockerfiles (`Dockerfile.api`, `Dockerfile.web`) and a `docker-compose.yml` that wires them together with a shared data volume. @@ -597,6 +620,136 @@ The web UI unit should use `ReadOnlyPaths=/srv/` instead of 4. Install example config if none exists. 5. Install systemd units and reload the daemon. +### Deployment with MCP + +The Metacircular Control Plane (MCP) is the standard deployment tool for +container-based services. It manages container lifecycle on target nodes +using rootless Podman. + +#### Service Definition Format + +MCP service definitions are TOML files stored at +`~/.config/mcp/services/.toml` on the operator workstation. Each +file defines a service with one or more container components: + +```toml +name = "metacrypt" +node = "rift" +active = true +path = "metacrypt" + +[build] +uses_mcdsl = false + +[build.images] +metacrypt = "Dockerfile.api" +metacrypt-web = "Dockerfile.web" + +[[components]] +name = "api" +image = "mcr.svc.mcp.metacircular.net:8443/metacrypt:v1.0.0" +network = "mcpnet" +user = "0:0" +restart = "unless-stopped" +ports = ["127.0.0.1:18443:8443", "127.0.0.1:19443:9443"] +volumes = ["/srv/metacrypt:/srv/metacrypt"] +cmd = ["server", "--config", "/srv/metacrypt/metacrypt.toml"] +``` + +Top-level fields: + +| Field | Purpose | +|-------|---------| +| `name` | Service name (matches the project name) | +| `node` | Target host to deploy to | +| `active` | Whether MCP should keep this service running | +| `path` | Source directory relative to the workspace (for builds) | + +Build fields: + +| Field | Purpose | +|-------|---------| +| `build.uses_mcdsl` | Whether the build requires the mcdsl module | +| `build.images.` | Maps image name to its Dockerfile path | + +Component fields: + +| Field | Purpose | +|-------|---------| +| `name` | Component name within the service (e.g. `api`, `web`) | +| `image` | Full image reference including MCR registry and version tag | +| `network` | Podman network to attach to | +| `user` | Container user:group | +| `restart` | Restart policy | +| `ports` | Host-to-container port mappings | +| `volumes` | Host-to-container volume mounts | +| `cmd` | Command and arguments passed to the entrypoint | + +#### Convention + +Projects should include a reference service definition in +`deploy/-rift.toml` as the canonical deployment example. This +file is committed to the repository and kept in sync with the actual +deployment. + +#### Key Commands + +| Command | Purpose | +|---------|---------| +| `mcp build ` | Build and push images for a service | +| `mcp sync` | Push all service definitions to agents; builds missing images if source tree is available | +| `mcp deploy ` | Pull image and (re)create containers | +| `mcp stop ` | Stop running containers | +| `mcp restart ` | Restart containers in place | +| `mcp ps` | List all managed containers and their status | +| `mcp status [service]` | Show detailed status for a specific service | + +#### Container User Convention + +All containers run as `--user 0:0` (root inside the container). Security +isolation is provided by rootless Podman (the container engine runs as an +unprivileged host user) combined with systemd hardening on the host. This +avoids file permission issues with mounted volumes while maintaining a +strong security boundary at the host level. + +#### Image Convention + +Container images are pulled from the Metacircular Container Registry (MCR): + +``` +mcr.svc.mcp.metacircular.net:8443/: +``` + +Tags follow semver. Service definitions MUST pin an explicit version tag +(e.g., `v1.1.0`), never `:latest`. This ensures deployments are +reproducible and `mcp status` shows the actual running version. + +#### Build and Release Workflow + +The standard workflow for releasing a service: + +1. **Tag** the release in git (`git tag -a v1.1.0`). +2. **Build** the container images (`mcp build `). +3. **Update** the service definition with the new version tag. +4. **Sync and deploy** (`mcp sync` or `mcp deploy `). + +`mcp build` reads the `[build]` section of the service definition to +locate Dockerfiles and the source tree. The workspace root is configured +in `~/.config/mcp/mcp.toml`: + +```toml +[build] +workspace = "~/src/metacircular" +``` + +Each service's `path` field is relative to the workspace. For example, +`path = "mcr"` resolves to `~/src/metacircular/mcr`. + +`mcp sync` checks whether each component's image tag exists in the +registry. If a tag is missing and the source tree is available, it +builds and pushes automatically. If the source tree is not available, +it fails with a clear error directing the operator to build first. + ### TLS - **Minimum TLS version: 1.3.** No exceptions, no fallback cipher suites.