Route registry
Admin HTTP API: desired route state in Postgres, live projection in etcd. Plat5’s only supported write path for the route registry.
The gateway loads and watches etcd.
Pipeline
Section titled “Pipeline”routes.yml → POST /apply (or PUT /services/{name}) → Postgres schema `routes` (revision) → etcd `edge/gateway/routes/{name}` → gateway watchPostgres is the source of truth. etcd is a projection. A reconciler retries if a put/delete is missed.
Local URLs
Section titled “Local URLs”| Surface | URL |
|---|---|
| Admin API (dev compose) | http://localhost:5002 |
| Health (internal) | container :5003 (/health/live, /health/ready) |
Prod compose does not publish the admin port by default.
Admin API routes require:
Authorization: Bearer <ADMIN_TOKEN>Local default: dev-admin-token (ADMIN_TOKEN env). Required and non-empty in all environments.
| Method | Path | Body | Notes |
|---|---|---|---|
POST |
/apply |
routes.yml shape (services: {…}) JSON or YAML |
Validate, expand, one PG transaction, project |
GET |
/services |
— | Current (non-deleted) services |
GET |
/services/{name} |
— | Current config plus name / revision |
PUT |
/services/{name} |
ServiceConfig JSON |
New revision + project |
DELETE |
/services/{name} |
— | Tombstone revision; remove etcd key |
GET |
/services/{name}/revisions |
— | History (includes delete revisions) |
GET |
/services/{name}/revisions/{rev} |
— | One revision (config is null if delete) |
POST |
/services/{name}/revisions/{rev}/restore |
— | New revision copying that config |
Apply is upsert of the services in the file. Services not in the file are left alone.
Named rate_limits live on each ServiceConfig. Apply and PUT /services/{name} validate shared: true policy names against all current services, not only the payload. Schema: routes.md.
Apply the identity catalog (services/identity/routes.yml) or a subset. Omitting a path hides it from the gateway. The identity process still serves that path on the network.
Apply example
Section titled “Apply example”CLI:
plat5 routes apply ./routes.ymlcurl:
curl -sS -X POST http://localhost:5002/apply \ -H "Authorization: Bearer dev-admin-token" \ -H "Content-Type: application/yaml" \ --data-binary @routes.ymlservices: widgets: url: http://host.docker.internal:3000 # http://host:port the gateway can reach user: routes: - path: /widgets methods: [GET, POST]url is from the gateway’s network view (in-cluster DNS, host.docker.internal, etc.). It must be exactly http://host:port (explicit port, no path or query; bare host:port, https:// and other schemes are rejected). Plat5 does not run your app process.
Response shape
Section titled “Response shape”Success apply (200) — desired state committed:
{ "results": [ { "service": "widgets", "status": "upserted", "revision": 1 } ]}Validation / auth / empty body failures use the Plat5 envelope (api-errors.md) and write nothing. A bad or missing service url is 422 VALIDATION_ERROR. A non-canonical path (trailing /, uppercase outside {params}) is 422. So is a service name, path, or path param name that an audit event cannot carry (routes.md). A method + path shape (params match params, literals match only literals) already owned by a different service rejects the whole apply with 409 ROUTE_CONFLICT (message lists each route and its owner). PUT /services/{name} and restore run exactly the same checks; see routes.md. Re-applying a service’s own routes replaces them and is not a conflict. A Postgres failure rolls the whole batch back (503). etcd projection is retried by the reconciler; apply does not fail after a successful commit.
Revisions
Section titled “Revisions”Each write (apply/put/delete/restore) appends a revision. Restore appends a new revision that copies an old config after validating it like an apply: an old revision with a now-invalid url or path is 422, and one whose routes another service owns now is 409 ROUTE_CONFLICT.
Delete stores config: null and clears the etcd key. History remains. Restore of a delete revision is 422.
etcd projection
Section titled “etcd projection”| Key | edge/gateway/routes/{service_name} |
| Value | JSON ServiceConfig with full paths and list-form methods (route_prefix and nested methods maps already expanded) |
Environment
Section titled “Environment”| Variable | Default | Purpose |
|---|---|---|
PORT |
5002 |
Admin API |
INTERNAL_PORT |
5003 |
Health |
ETCD_URL |
http://localhost:2379 |
etcd (projection) |
DATABASE_URL |
required | Postgres; schema routes |
ADMIN_TOKEN |
required | Bearer token |
SEED_ROUTES_DIR |
empty | Dev: apply YAML for services with no history yet |
Ready is 503 unless Postgres and etcd both answer.
Dev seed
Section titled “Dev seed”Compose mounts services/identity/routes.yml → /seed/identity.yml and sets SEED_ROUTES_DIR. On boot, missing services (no routes.services row) are applied. Existing rows — including deleted — are not overwritten.
Prod compose does not seed. Apply identity routes yourself (CLI or curl).
Validation
Section titled “Validation”Route types and validation live in each of gateway and route-registry (src/route_config.rs). Keep them aligned deliberately; etcd JSON and routes.md are the contract. Expand (route_prefix and nested methods maps) happens at write time only.
Related
Section titled “Related”routes.md— config format and scopesgateway-contract.md— request-time behavior