Controller Contract Specification
A controller contract defines the complete interface for a controller: what it accepts, what it returns, what capabilities it needs, and how it should be monitored.
Overview
Every controller in Magic must have a contract. The contract is a YAML or JSON file that declares:
- Identity: Name and version
- Input Schema: JSON Schema for request validation
- Output Schema: JSON Schema for response validation
- Capabilities: What system resources the controller needs
- Resources: CPU, memory, and timeout limits
- Observability: Logging and metrics requirements
The runtime enforces these contracts at boundaries. Requests that don't match the input schema are rejected. Responses that don't match the output schema trigger errors. Capability usage without declaration is denied.
Contract Schema
The complete contract schema:
# Required fields
name: string # Controller identifier (PascalCase)
version: string # Semantic version (1.0.0)
# Input/Output (required)
input: # JSON Schema object
type: object
required: []
properties: {}
output: # JSON Schema object
type: object
required: []
properties: {}
# Capabilities (optional, default: none)
capabilities: [] # List of capability declarations
# Resources (optional, has defaults)
resources:
cpu: 0.5 # CPU cores (default: 0.5)
memory: 256Mi # Memory limit (default: 256Mi)
timeout: 30s # Execution timeout (default: 30s)
# Observability (optional)
observability:
log_fields: [] # Fields to include in structured logs
metrics: [] # Custom metrics to emit
Input & Output Schemas
Input and output schemas use JSON Schema (draft-07). The runtime validates all requests against the input schema and all responses against the output schema.
Input Schema
input:
type: object
required:
- customer_id
properties:
customer_id:
type: string
format: uuid
description: The customer's unique identifier
include_orders:
type: boolean
default: false
description: Whether to include recent orders
Output Schema
output:
type: object
required:
- customer
- request_id
properties:
customer:
type: object
required: [id, email, created_at]
properties:
id:
type: string
format: uuid
email:
type: string
format: email
name:
type: string
created_at:
type: string
format: date-time
orders:
type: array
items:
$ref: "#/definitions/Order"
request_id:
type: string
Schema Definitions
You can use $ref to reference shared definitions within your contract or from external schema files.
Capabilities
Capabilities declare what system resources a controller needs. The runtime uses a default-deny model: if a capability isn't declared, it can't be used.
| Capability | Description | Options |
|---|---|---|
db:read |
Structured read queries (self.db.query, self.db.query_one) |
tables (allowlist) |
db:write |
Structured write operations (self.db.insert, self.db.update, self.db.delete) |
tables (allowlist) |
db:rawsql |
Raw SQL escape hatch (self.db.raw). Denied by default in production policy. Requires explicit policy approval. |
— |
http:egress |
Make outbound HTTP requests | allow (domain list) |
secrets:read |
Access secrets by key | keys (allowlist) |
queue:publish |
Publish to message queues | queues (allowlist) |
queue:subscribe |
Consume from queues | queues (allowlist) |
cache:read |
Read from cache | prefix (key prefix) |
cache:write |
Write to cache | prefix, max_ttl |
fs:temp |
Temporary file access | max_size |
Capability Examples
capabilities:
# Simple capability (no options)
- db:read
# Database with table restrictions
- db:write:
tables:
- customers
- orders
# HTTP egress with domain allowlist
- http:egress:
allow:
- api.stripe.com
- hooks.slack.com
- "*.internal.company.com"
# Secrets with key restrictions
- secrets:read:
keys:
- STRIPE_API_KEY
- SLACK_WEBHOOK_URL
# Queue publishing
- queue:publish:
queues:
- order-events
- notifications
# Cache with prefix and TTL limit
- cache:write:
prefix: "customer:"
max_ttl: 3600
Capability Audit
All capability usage is audit-logged with the request's X-Request-ID. Enterprise deployments can stream these logs to SIEM systems.
Resource Limits
Resource limits constrain controller execution to prevent runaway processes.
| Resource | Default | Min | Max |
|---|---|---|---|
cpu |
0.5 cores | 0.1 | 2.0 |
memory |
256Mi | 64Mi | 2Gi |
timeout |
30s | 1s | 300s |
resources:
cpu: 1.0 # 1 CPU core
memory: 512Mi # 512 MB RAM
timeout: 60s # 1 minute timeout
Observability
Observability configuration ensures consistent logging and metrics across controllers.
Log Fields
Specify input fields to include in structured logs:
observability:
log_fields:
- customer_id # Always log customer_id
- order_id # Always log order_id
Custom Metrics
Declare custom Prometheus metrics the controller will emit:
observability:
metrics:
- name: invoice_sync_duration_seconds
type: histogram
description: Time spent syncing invoices
buckets: [0.1, 0.5, 1.0, 5.0, 10.0]
- name: invoice_sync_total
type: counter
description: Total invoice sync operations
labels: [status, customer_tier]
Complete Examples
Example 1: Invoice Sync Controller
name: InvoiceSync
version: 1.2.0
input:
type: object
required:
- customer_id
- invoice_data
properties:
customer_id:
type: string
format: uuid
invoice_data:
type: object
required: [amount, currency, line_items]
properties:
amount:
type: integer
minimum: 0
currency:
type: string
enum: [USD, EUR, GBP]
line_items:
type: array
minItems: 1
items:
type: object
required: [description, amount]
properties:
description:
type: string
maxLength: 200
amount:
type: integer
output:
type: object
required:
- sync_id
- status
- request_id
properties:
sync_id:
type: string
status:
type: string
enum: [queued, completed, failed]
stripe_invoice_id:
type: string
request_id:
type: string
capabilities:
- db:read:
tables: [customers, invoices]
- db:write:
tables: [invoices, invoice_events]
- http:egress:
allow:
- api.stripe.com
- secrets:read:
keys: [STRIPE_API_KEY]
- queue:publish:
queues: [invoice-events]
resources:
cpu: 0.5
memory: 256Mi
timeout: 30s
observability:
log_fields:
- customer_id
- sync_id
metrics:
- name: invoice_sync_duration_seconds
type: histogram
- name: invoice_sync_total
type: counter
labels: [status]
Example 2: Customer Lookup Controller (Read-Only)
name: CustomerLookup
version: 1.0.0
input:
type: object
required: [customer_id]
properties:
customer_id:
type: string
format: uuid
output:
type: object
required: [customer, request_id]
properties:
customer:
type: object
required: [id, email]
properties:
id:
type: string
email:
type: string
format: email
name:
type: string
request_id:
type: string
capabilities:
- db:read:
tables: [customers]
- cache:read:
prefix: "customer:"
resources:
cpu: 0.25
memory: 128Mi
timeout: 5s
observability:
log_fields: [customer_id]
Contract Validation
Validate contracts before deployment:
# Validate a single contract (via docker compose)
docker compose exec runtime magic validate ./controllers/invoice_sync/contract.yaml
# Validate all contracts in directory
docker compose exec runtime magic validate ./controllers/
# Validate with strict mode (warnings become errors)
docker compose exec runtime magic validate --strict ./controllers/
The validator checks:
- Schema syntax (valid YAML/JSON)
- Required fields present
- JSON Schema validity for input/output
- Capability syntax
- Resource limits within bounds
- No conflicting declarations