Files
headquarter/docs/tool-manifest-spec.md
T
Fusion f33a563003 feat(FN-003): add tool manifest registry with FastAPI CRUD and built-in manifests
- Add ToolManifest Pydantic models with validators for ports, mounts, health checks, and traefik config

- Implement in-memory ToolRegistry with YAML loading and built-in manifest scanning

- Add FastAPI CRUD routes for listing, retrieving, and creating tool manifests

- Include built-in manifests for runfusion and code-server

- Harden web Dockerfile with unprivileged nginx and port 8080

- Add tool manifest specification documentation and architecture updates

Fusion-Task-Id: FN-003
2026-05-14 09:15:22 +02:00

210 lines
8.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Tool Manifest Specification
> Canonical schema reference for Headquarter's manifest-driven tool registry.
> Version: 1.0.0 — aligned with FN-003.
## Overview
Headquarter is a manifest-driven platform: every containerized tool (RunFusion, code-server, and future tools) is declared by a YAML manifest. The orchestration backend reads these manifests to generate Docker Compose services, Traefik routing labels, volume mounts, and resource constraints.
**Design goal:** Adding a new standard container tool requires only a YAML manifest—no backend code changes.
## Manifest File Format
Manifests are YAML files with a single top-level mapping. They are validated on load by Pydantic v2 models.
### Built-in location
Built-in manifests live in `apps/api/app/tools/manifests/*.yml` and are loaded automatically on API startup.
### Minimal valid manifest
```yaml
id: my-tool
name: My Tool
image: my-org/my-tool:latest
ports:
- container_port: 8080
primary: true
traefik:
enabled: false
```
## Field Reference
### `ToolManifest` (top level)
| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `id` | `string` | yes | — | Lowercase slug with hyphens only (`^[a-z0-9\-]+$`). Used as the registry key. |
| `name` | `string` | yes | — | Human-readable tool name. |
| `description` | `string` | no | `""` | Short description of the tool. |
| `version` | `string` | no | `"1.0.0"` | Manifest version (semver-ish). |
| `image` | `string` | yes | — | Docker image reference. |
| `runtime_command` | `string[] \| null` | no | `null` | Override the container default command. |
| `runtime_entrypoint` | `string[] \| null` | no | `null` | Override the container entrypoint. |
| `runtime_user` | `string \| null` | no | `null` | User to run as inside the container. |
| `runtime_working_dir` | `string \| null` | no | `null` | Working directory inside the container. |
| `ports` | `PortConfig[]` | no | `[]` | Exposed ports. |
| `workspace_mounts` | `MountConfig[]` | no | `[]` | Workspace volume mounts (project-scoped). |
| `config_mounts` | `MountConfig[]` | no | `[]` | Config volume mounts (user or tool-scoped). |
| `env` | `dict<string, string>` | no | `{}` | Static environment variables. |
| `secrets` | `SecretRef[]` | no | `[]` | Secrets injected as environment variables. |
| `health_check` | `HealthCheckConfig \| null` | no | `null` | Health check definition. |
| `resource_limits` | `ResourceLimits \| null` | no | `null` | CPU and memory constraints. |
| `executable` | `ExecutableConfig \| null` | no | `null` | Node.js runtime metadata for executable environments. |
| `traefik` | `TraefikConfig \| null` | no | `null` | Traefik routing configuration. |
### `PortConfig`
| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `container_port` | `int` | yes | — | Port inside the container. Range: 165535. |
| `protocol` | `"tcp" \| "udp"` | no | `"tcp"` | Transport protocol. |
| `name` | `string \| null` | no | `null` | Logical name, e.g. `"http"`, `"websocket"`. |
| `primary` | `bool` | no | `false` | The port used for default routing and health checks. |
### `MountConfig`
| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `type` | `"volume" \| "bind"` | no | `"volume"` | Mount type. |
| `source_pattern` | `string` | yes | — | Template pattern resolved at spawn time, e.g. `"{project_repo}"`. |
| `target` | `string` | yes | — | Absolute path inside the container. Must start with `/`. |
| `read_only` | `bool` | no | `false` | Mount read-only. |
### `SecretRef`
| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `name` | `string` | yes | — | Secret identifier in the secret store. |
| `env_var` | `string` | yes | — | Name of the environment variable injected into the container. |
| `required` | `bool` | no | `true` | Whether the tool fails to start if the secret is missing. |
### `HealthCheckConfig`
| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `type` | `"http" \| "tcp" \| "command"` | no | `"http"` | Health check mechanism. |
| `path` | `string \| null` | no | `null` | HTTP path. Required when `type == "http"`. |
| `command` | `string[] \| null` | no | `null` | Command to execute. Required when `type == "command"`. |
| `port` | `int \| null` | no | `null` | Override port; defaults to the primary port if unset. |
| `interval_seconds` | `int` | no | `10` | Check interval. ≥ 1. |
| `timeout_seconds` | `int` | no | `5` | Check timeout. ≥ 1. |
| `retries` | `int` | no | `3` | Retries before marking unhealthy. ≥ 1. |
| `start_period_seconds` | `int` | no | `5` | Grace period before checks count. ≥ 0. |
### `ResourceLimits`
| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `cpus` | `float \| null` | no | `null` | CPU limit. If set, ≥ 0.01. |
| `memory_mb` | `int \| null` | no | `null` | Memory limit in MiB. If set, ≥ 16. |
| `memory_swap_mb` | `int \| null` | no | `null` | Swap limit in MiB. `-1` disables swap limit. |
### `ExecutableConfig`
| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `node_version` | `string \| null` | no | `null` | Expected Node.js version, e.g. `"22"`, `"lts"`. |
| `npm_version` | `string \| null` | no | `null` | Expected npm version. |
| `package_manager` | `"npm" \| "pnpm" \| "yarn" \| "bun"` | no | `"npm"` | Preferred package manager. |
| `bootstrap_commands` | `string[]` | no | `[]` | One-time setup commands run on first start. |
| `install_commands` | `string[]` | no | `[]` | Commands run before the main command. |
### `TraefikConfig`
| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `enabled` | `bool` | no | `true` | Whether Traefik routing is generated for this tool. |
| `subdomain_prefix` | `string \| null` | no | `null` | Subdomain prefix. Defaults to the tool `id`. |
| `port` | `int \| null` | no | `null` | Container port to route traffic to. |
| `middlewares` | `string[]` | no | `[]` | Traefik middleware names to apply. |
| `strip_prefix` | `bool` | no | `false` | Strip path prefix before forwarding. |
| `entrypoint` | `string \| null` | no | `null` | Override the environment default Traefik entrypoint. |
| `cert_resolver` | `string \| null` | no | `null` | Override the environment default cert resolver. |
## Validation Rules
1. `id` must match `^[a-z0-9\-]+$` (lowercase, digits, hyphens only).
2. `MountConfig.target` must be an absolute path (`starts with "/"`).
3. When `health_check.type == "http"`, `path` must be set and non-empty.
4. When `health_check.type == "command"`, `command` must be set and non-empty.
5. `container_port` must be between 1 and 65535.
6. `cpus`, if set, must be ≥ 0.01.
7. `memory_mb`, if set, must be ≥ 16.
8. If `traefik.enabled` is `true`, at least one port must have `primary: true`.
## Example: RunFusion Manifest
```yaml
id: runfusion
name: RunFusion
description: Executable Node.js environment for running and developing applications.
version: "1.0.0"
image: node:22-slim
runtime_working_dir: /workspace
ports:
- container_port: 8080
protocol: tcp
name: http
primary: true
workspace_mounts:
- type: volume
source_pattern: "{project_repo}"
target: /workspace
read_only: false
config_mounts:
- type: volume
source_pattern: "{user_config}/runfusion"
target: /home/node/.config
read_only: false
env:
NODE_ENV: development
health_check:
type: http
path: /
port: 8080
interval_seconds: 10
timeout_seconds: 5
retries: 3
start_period_seconds: 10
resource_limits:
cpus: 2.0
memory_mb: 2048
memory_swap_mb: -1
executable:
node_version: "22"
package_manager: npm
bootstrap_commands: []
install_commands: []
traefik:
enabled: true
subdomain_prefix: runfusion
port: 8080
middlewares: []
strip_prefix: false
```
## Extension Guide: Adding a New Tool
To add a new standard container tool:
1. Create a new YAML file in `apps/api/app/tools/manifests/{tool-id}.yml`.
2. Populate all required fields (`id`, `name`, `image`, `ports`).
3. Set `traefik.enabled: true` and mark one port as `primary: true` if the tool needs HTTP routing.
4. Declare `workspace_mounts` and `config_mounts` as needed.
5. Restart the API (or call `registry.load_builtin_manifests()`).
No backend code changes are required for standard containers that expose an HTTP port and need volume mounts.
## Registry API
The in-memory registry exposes FastAPI routes under `/api/v1/tools`:
- `GET /api/v1/tools` — list all registered manifests.
- `GET /api/v1/tools/{id}` — retrieve a single manifest.
- `POST /api/v1/tools` — register a new manifest (returns 409 if `id` already exists).
Built-in manifests are loaded automatically on application startup via the FastAPI lifespan context manager.