# Node Service Catalog Spec
The service catalog describes node-exported namespace services and their mount metadata. It is the control-plane source used to project `/nodes/<node_id>/services/*` and dynamic mount roots in Acheron namespace projection.
## Control Operations
- `control.node_service_upsert`
- `control.node_service_get`
- `control.node_service_watch`
- `control.node_service_unwatch`
- `control.node_service_event` (server push)
## `control.node_service_upsert`
### Request fields
- `node_id` (`string`, required)
- `node_secret` (`string`, required)
- `platform` (`object`, optional)
- `os` (`string`, optional)
- `arch` (`string`, optional)
- `runtime_kind` (`string`, optional)
- `labels` (`object<string,string>`, optional)
- `services` (`array`, optional)
Each service entry:
- `service_id` (`string`, required)
- `kind` (`string`, required)
- `version` (`string`, default `"1"`)
- `state` (`string`, required)
- `endpoints` (`array<string>`, required, absolute paths)
- `capabilities` (`object`, optional, default `{}`)
- `mounts` (`array<object>`, optional, default `[]`)
- `mount_id` (`string`, required)
- `mount_path` (`string`, required, absolute path)
- `state` (`string`, optional; defaults to service state)
- `ops` (`object`, optional, default `{}`)
- `invoke` (`string`, optional)
- `paths.invoke` (`string`, optional)
- `runtime` (`object`, optional, default `{}`)
- `permissions` (`object`, optional, default `{}`)
- `schema` (`object`, optional, default `{}`)
- `help_md` (`string`, optional)
### Example
```json
{
"node_id": "node-2",
"node_secret": "secret-...",
"platform": { "os": "linux", "arch": "amd64", "runtime_kind": "native" },
"labels": { "site": "hq-west", "tier": "edge" },
"services": [
{
"service_id": "camera",
"kind": "camera",
"version": "1",
"state": "online",
"endpoints": ["/nodes/node-2/camera"],
"capabilities": { "still": true },
"mounts": [
{
"mount_id": "camera",
"mount_path": "/nodes/node-2/camera",
"state": "online"
}
],
"ops": { "model": "namespace", "style": "plan9" },
"runtime": { "type": "native_proc", "abi": "namespace-driver-v1" },
"permissions": { "default": "deny-by-default" },
"schema": { "model": "namespace-mount" },
"help_md": "Camera namespace driver"
}
]
}
```
## `control.node_service_get`
Request fields:
- `node_id` (`string`, required)
Response fields:
- `node_id`
- `node_name`
- `platform`
- `labels`
- `services`
## `control.node_service_watch`
Subscribes to catalog events. Server responds with `control.node_service_event` frames for upserts and changes.
## Validation Notes
- Service IDs and kinds are identifier-safe strings.
- Service IDs must be unique within a single upsert payload.
- `endpoints` must be absolute-style paths.
- `capabilities` must be a JSON object.
- `mounts`, when present, must use absolute `mount_path` values.
- `ops`, `runtime`, `permissions`, and `schema` must be JSON objects.
- `ops.invoke` / `ops.paths.invoke`, when provided, override the default `control/invoke.json` resolve path.
## Namespace Permission Projection
`/nodes/<node_id>/services/*` visibility for non-admin sessions evaluates service `permissions` metadata:
- `allow_roles` (`array<string>`, optional)
- `default` (`string`, optional)
- `require_project_token` / `project_token_required` (`bool`, optional)
Admin sessions bypass service permission filtering.
## Workspace Mount Gating
`control.workspace_status` applies invoke-policy checks to mount projection for non-admin actors:
- If a mount maps to an invoke-capable service, it is omitted unless
- project access policy allows `invoke`
- service `permissions` allow the actor
## `spiderweb-fs-node` Provider Mapping
When `spiderweb-fs-node` runs in control daemon mode (`--control-url`), it auto-upserts service metadata:
- FS provider (enabled by default):
- `service_id`: `fs`
- `kind`: `fs`
- endpoint: `/nodes/<node_id>/fs`
- capabilities: `rw`, `export_count`
- mounts: `/nodes/<node_id>/fs`
- Terminal provider (`--terminal-id <id>`):
- `service_id`: `terminal-<id>`
- `kind`: `terminal`
- endpoint: `/nodes/<node_id>/terminal/<id>`
- capabilities: `pty=true`, `terminal_id`, `invoke=true`
- ops: `invoke=control/invoke.json` (`paths.exec` alias)
- runtime: `native_proc` (`entry=internal-terminal-invoke`)
- mounts: `/nodes/<node_id>/terminal/<id>`
- Extra namespace services (from `--service-manifest` / `--services-dir`):
- appended after built-in providers
- validated for shape and duplicates before publish
Use `--no-fs-service` to disable FS service advertisement and `--label <key=value>` to attach node labels.
Implementation pointers:
- `src/fs_node_service.zig`
- `src/node_service_catalog.zig`
- `src/server_piai.zig`