README
¶
FRED - Flexible Resource Execution Daemon
A Go daemon for Manifest Network providers that manages the complete lease lifecycle with pluggable backend integration, event-driven provisioning, and automatic resource management.
Features
- Lease Lifecycle Management: Watches chain events and orchestrates provisioning through backends
- Multi-Backend Support: Route leases to different backends based on exact SKU UUID list, distributing new provisions to the least-loaded matching backend (lowest allocated-CPU ratio)
- Event-Driven Architecture: Uses Watermill for internal event routing with retries and middleware
- Tenant Authentication API: HTTP/HTTPS API with ADR-036 signature verification for tenant access
- Periodic Withdrawals: Configurable scheduled withdrawal of accumulated fees from active leases
- Credit Monitoring: Tracks tenant credit balances and auto-closes leases when credit is depleted
- Cross-Provider Credit Detection: Responds to credit depletion events from other providers
- Live Operations: Restart containers or deploy new manifests (update) on active leases with full release history tracking
- Data Retention & Restore: Soft-delete a lease's volumes on close and restore them into a new lease within a grace window, optionally onto a different SKU tier
- Security: Rate limiting, request size limits, input validation, and optional TLS
Architecture Overview
MANIFEST CHAIN
|
| WebSocket (events)
v
+------------------------------------------------------------------+
| FRED |
| |
| +------------------+ |
| | Event Subscriber | (fan-out: each consumer gets all events) |
| | (WebSocket) |-----+------------------+ |
| +------------------+ | | |
| v v |
| +------------------+ +------------------+ +------------------+ |
| | Event Bridge | | Watcher | | (other future | |
| | -> Watermill | | (cross-provider) | | consumers) | |
| +------------------+ +------------------+ +------------------+ |
| | |
| v |
| +------------------+ +------------------+ |
| | Watermill Router |---->| Provision | |
| | (event routing) | | Manager | |
| +------------------+ +------------------+ |
| | |
| +------------------+ | |
| | API Server |<------------+ |
| | (tenant access) | | |
| +------------------+ v |
| +------------------+ |
| | Backend Router | |
| | (SKU routing + | |
| | least-loaded) | |
| +------------------+ |
| | |
+------------------------------------------------------------------+
|
+---------------------+---------------------+
v v v
+---------------+ +---------------+ +---------------+
| Docker-1 | | Docker-2 | | Docker-3 |
| Backend | | Backend | | Backend |
| (skus: [uuid])| | (skus: [uuid])| | (skus: [uuid])|
+---------------+ +---------------+ +---------------+
Inside the provider, construction binds the durable placement Store, the
process-local operation Registry, one backend router, and one validated callback
factory exactly once. Purpose-specific placement applications own complete
provision, restore, maintenance, callback, timeout, deprovision, and
reconciliation sequences; Watermill and HTTP handlers supply opaque intents and
consume closed results rather than assembling claims or selecting outcomes.
The Registry exposes only its one-shot settlement-authority binder; after
composition, Manager retains an operation.RuntimeController for status and
graceful drain, not Registry mutation authority.
Event Fan-Out
The Event Subscriber uses a fan-out pattern where each consumer (Event Bridge, Watcher, etc.) gets its own channel and receives all events independently. This ensures that:
- The provisioner never misses lease events
- The watcher always sees cross-provider credit depletion events
- New consumers can be added without affecting existing ones
Lease Lifecycle
sequenceDiagram
participant T as Tenant
participant C as Chain
participant F as Fred
participant B as Backend
Note over T,B: Lease Creation & Provisioning
T->>C: Create Lease (with SKU)
C-->>F: lease_created event
F->>F: Route by SKU to backend
F->>B: POST /provision
B->>B: Provision resource (async)
B->>F: POST /callbacks/provision (success)
F->>C: MsgAcknowledgeLease
C-->>F: lease_acknowledged event
Note over T,B: Tenant Access
T->>T: Sign auth token (ADR-036)
T->>F: GET /v1/leases/{uuid}/connection
F->>F: Verify signature & lease ownership
F->>B: GET /info/{uuid}
F-->>T: Connection details
Note over T,B: Restart (same manifest)
T->>F: POST /v1/leases/{uuid}/restart
F->>B: POST /restart
B->>B: Stop, recreate containers (async)
B->>F: POST /callbacks/provision (success)
Note over T,B: Update (new manifest)
T->>F: POST /v1/leases/{uuid}/update
F->>B: POST /update
B->>B: Pull image, replace containers (async)
B->>F: POST /callbacks/provision (success)
Note over T,B: Release History
T->>F: GET /v1/leases/{uuid}/releases
F->>B: GET /releases/{uuid}
F-->>T: Release history
Note over T,B: Lease Closure
T->>C: Close Lease (or credit depleted)
C-->>F: lease_closed event
F->>B: POST /deprovision
B->>B: Cleanup resources
Building
# Build all binaries (including placement-preflight and placement-repair)
make all
# Build only providerd
go build -o build/providerd ./cmd/providerd
# Build only mock-backend
go build -o build/mock-backend ./cmd/mock-backend
# Build only docker-backend
go build -o build/docker-backend ./cmd/docker-backend
# Build only k3s-backend
go build -o build/k3s-backend ./cmd/k3s-backend
# Build the offline placement tools
go build -o build/placement-preflight ./cmd/placement-preflight
go build -o build/placement-repair ./cmd/placement-repair
k3s-backend is currently an experimental, non-functional scaffold (ENG-133): the binary boots, serves the backend HTTP contract, and signs/verifies callbacks, but its provisioner returns
status=failed, error="not implemented"for every provision. It is not usable in production; real Kubernetes provisioning lands in ENG-134+.
Local Development Setup
For a one-shot dev environment against a running local chain, use:
bash scripts/dev-init.sh
This registers a provider and SKUs on-chain, generates config.docker.yaml (the providerd config) and docker-backend.yaml, and writes a callback secret. Its final output gives the required fresh-start order: seal the empty backend, start it, drain mutation/callback work while leaving its inventory endpoints live, run placement-preflight -initialize-fresh with providerd plus tenant/chain mutation ingress fenced, and only then start providerd. Normal startup deliberately does not create placements.db. The script is intentionally one-shot and refuses to overwrite generated configuration or existing authority. All settings are overridable via environment variables — see the script header for the full list. Requires manifestd, jq, curl, openssl, and findmnt (from util-linux) on PATH plus a running local chain.
Configuration
Copy the example configuration and customize:
cp config.example.yaml config.yaml
Required Configuration
All required fields are validated at startup. The daemon will fail to start with a clear error message if any required configuration is missing or invalid. Provider, Docker and K3s YAML configuration files must contain exactly one document; unknown keys, including nested keys, are rejected. Provider YAML and JSON configuration also reject case-insensitive key collisions, so aliases cannot silently override a security setting.
| Option | Description |
|---|---|
provider_uuid |
Your registered provider UUID (must be valid UUID format) |
provider_address |
Provider management address |
keyring_dir |
Directory containing keyring |
key_name |
Key name for signing transactions |
backends |
At least one backend must be configured (multiple backends may share skus for load-based routing); production requires a distinct hmac_secret on every entry |
callback_base_url |
URL where backends send callbacks (absolute HTTP(S); HTTPS is required in production mode) |
placement_store_db_path |
Critical provider-bound lease-to-backend authority. It must be an existing prepared database at startup and is not hot-swappable |
billing_only_skus |
Optional. Every entry must be a lowercase canonical UUID, listed once, and served by no backend |
Backend Configuration
Backends are services that handle the actual resource provisioning. Backend and callback URLs must be absolute HTTP(S) URLs. Provider production_mode: true requires HTTPS in both directions and verifies backend peers using the configured private CA (or system roots); bundled backends must also enable production mode so callback peer verification cannot be disabled. HTTP is development-only because request HMAC does not authenticate backend responses and callback HMAC does not provide transport confidentiality.
The currently deployed and production-validated execution envelope is
docker-backend on XFS. K3s is a non-functional scaffold, and Docker's Btrfs
and ZFS volume implementations have automated coverage but are experimental and
not deployed; use XFS for production. See Deployment.
Leases are routed to backends using the skus field — an exact list of on-chain SKU UUIDs. A backend with no skus matches nothing. A SKU that no backend lists goes to the fallback backend: the one with default: true (at most one may set it), or the first configured backend when none does. A fenced fallback receives no new provisions. When multiple backends match the same SKU, Fred routes each new provision to the least-loaded matching backend — the SKU-matching backend reporting the lowest allocated-CPU ratio from its /stats endpoint (ENG-318), preferring backends that do not report disk_withheld. Ties break by fewest in-flight provisions, then by a round-robin counter; round-robin is also the fallback when no matching backend exposes usable load stats.
backends:
# Give every backend the same skus list so they all match,
# then Fred routes each provision to the least-loaded one.
- name: docker-1
url: "http://10.0.0.1:9001"
# Must equal docker-1's own callback_secret; never reuse on another backend.
hmac_secret: "docker-1-unique-32-byte-minimum-secret"
skus:
- "a1b2c3d4-e5f6-7890-abcd-1234567890ab"
- "b2c3d4e5-f6a7-8901-bcde-2345678901bc"
default: true
- name: docker-2
url: "http://10.0.0.2:9001"
# Must equal docker-2's own callback_secret.
hmac_secret: "docker-2-unique-32-byte-minimum-secret"
skus:
- "a1b2c3d4-e5f6-7890-abcd-1234567890ab"
- "b2c3d4e5-f6a7-8901-bcde-2345678901bc"
callback_base_url: "http://fred.provider.example.com:8080"
# Required. Records durable attempts and confirmed ownership so reads,
# reconciliation, restore, and deprovision reach the right backend.
placement_store_db_path: "/var/lib/fred/placements.db"
Per-backend fields:
| Field | Description | Default |
|---|---|---|
name |
Stable, case-sensitive durable backend identity; must be non-blank and unique | (required) |
url |
Absolute http:// or https:// origin with a usable ASCII hostname (punycode for IDNs); an explicit port must be 1–65535 |
(required) |
hmac_secret |
Bidirectional HMAC key for this backend; set the backend process's callback_secret to the same value. Selecting per-backend authentication requires at least 32 bytes per key and pairwise uniqueness in every mode. |
(required in production; all backends or none in development) |
hmac_secret_previous |
Verify-only key accepted on this backend's callbacks during a key rotation; providerd never signs with it. Requires hmac_secret on the same backend and at least 32 bytes, and must not duplicate any other configured key. See Rotating a backend's HMAC key |
"" |
skus |
Exact list of on-chain SKU UUIDs this backend serves | [] |
default |
Fallback for SKUs no backend lists. At most one backend may set it; without one, the first configured backend is the fallback | false |
timeout |
HTTP request timeout for calls to this backend | 30s |
tls_ca_file |
PEM CA that signed the backend's server certificate; empty uses the system roots. Requires an https:// url |
"" |
tls_skip_verify |
Skip backend certificate verification (development only; rejected when production_mode: true). Requires an https:// url |
false |
tls_client_cert_file, tls_client_key_file |
Client certificate and key for mTLS to the backend; set both or neither. Require an https:// url |
"" |
fenced |
Keep a backend you no longer trust in the topology: providerd sends it nothing, refuses its callbacks, routes no new lease to it, and ignores its inventory, while its leases keep their placement and wait. Requires hmac_secret on every backend and at least one unfenced backend. See Containing a compromised backend |
false |
Validation rules:
- Backend names must be valid printable UTF-8 with no leading/trailing whitespace and exactly unique (comparison is case-sensitive)
- Treat a backend name as an immutable storage identity: removing or renaming it while a durable placement refers to it is rejected. A drained name may leave the active topology and later return only for the same storage identity; replacement storage must use a new unique name. Removal also requires the latest complete, topology-bound raw provision and retention inventories to prove that backend empty; silence is not a drain proof
- Backend URLs must be absolute
http://orhttps://origins with a non-empty, non-dot ASCII hostname (use punycode for internationalized names), no path/query/fragment/user info, and any explicit port in the range 1–65535. Empty?and#markers are rejected rather than silently normalized away; production mode requires peer-verifiedhttps://(a configured private CA is supported) callback_base_urlmust be an absolutehttp://orhttps://URL without user info, a fragment, malformed query syntax, or reservedoperation_id/lifecycle_idquery keys. Its authority follows the same hostname and port rules as backend origins. Its path must contain no internal empty/dot/parent segments, backslashes, or percent-encoded separators- Trailing path slashes on
callback_base_urlare automatically stripped; its path escaping is canonicalized. Unrelated query bytes are preserved for callback HMAC signing, so spaces, non-ASCII bytes, and other unsafe raw bytes must be percent-encoded before configuration callback_canonical_path_prefixmust exactly equal the normalized escaped path ofcallback_base_url(both are empty for a root/direct URL). This fail-fast check prevents a path-stripping proxy configuration from making every HMAC callback fail at runtime- Complete callback destinations are accepted and persisted only when their
canonical path ends in
/callbacks/provision; dot segments, encoded path separators, fragments, user info, empty query markers, and unstable raw query bytes fail closed
Full Configuration Reference
| Option | Description | Default |
|---|---|---|
log_level |
Log verbosity (debug, info, warn, error) | info |
production_mode |
Enforce security requirements at startup (TLS, replay protection, SSRF) | false |
chain_id |
Chain identifier | manifest-1 |
grpc_endpoint |
Chain gRPC endpoint | localhost:9090 |
websocket_url |
CometBFT WebSocket URL | ws://localhost:26657/websocket |
grpc_tls_enabled |
Enable TLS for gRPC to the chain | false |
grpc_tls_ca_file |
Custom CA certificate file for gRPC TLS | "" (system CAs) |
grpc_tls_skip_verify |
Skip gRPC TLS certificate verification (development only) | false |
provider_uuid |
Your registered provider UUID | (required) |
provider_address |
Provider management address | (required) |
keyring_backend |
Keyring backend (file, os, test) | file |
keyring_dir |
Directory containing keyring | (required) |
key_name |
Key name for signing transactions | (required) |
api_listen_addr |
API server listen address | :8080 |
tls_cert_file |
TLS certificate file (PEM). Must be set with tls_key_file or neither. |
"" |
tls_key_file |
TLS private key file (PEM). | "" |
withdraw_interval |
How often to withdraw funds | 1h |
bech32_prefix |
Address prefix for validation | manifest |
rate_limit_rps |
Per-IP tenant API rate limit (req/s); callbacks use separate buckets | 10 |
rate_limit_burst |
Per-IP rate limit burst size | 20 |
tenant_rate_limit_rps |
Per-tenant rate limit (requests/second). 0 disables per-tenant limiting |
5 |
tenant_rate_limit_burst |
Per-tenant burst size. While per-tenant limiting is enabled, 0 passes validation but rejects every authenticated tenant request with 429 |
10 |
trusted_proxies |
CIDR blocks of trusted proxies for X-Forwarded-For | [] |
cors_origins |
Allowed CORS origins for browser clients. ["*"] allows all; [] disables CORS. |
["*"] |
backends |
List of backend configurations | (required) |
billing_only_skus |
On-chain SKU UUIDs the provider bills for but never deploys. Fred acknowledges a lease whose items all use these SKUs, which starts its billing, and never provisions, rejects, or closes it for a provisioning reason. Withdrawals and the credit check still apply. From the environment, PROVIDER_BILLING_ONLY_SKUS takes a comma-separated list. See Billing-only leases. |
[] |
callback_base_url |
Base URL for backend callbacks | (required) |
callback_secret |
Legacy fleet-wide HMAC key accepted only outside production; do not combine with backends[].hmac_secret |
"" |
callback_canonical_path_prefix |
Path prefix prepended to inbound callback URIs before HMAC verification. It must exactly equal the normalized escaped path in callback_base_url and the prefix stripped by the trusted proxy (e.g., /api/fred). Both values are empty for a root/direct URL. See SECURITY.md and docs/security-callback-auth.md. |
"" |
reconciliation_interval |
How often to run reconciliation | 5m |
token_tracker_db_path |
Path to bbolt database for token replay protection | (optional; required if production_mode) |
payload_store_db_path |
Path to bbolt database for payload storage. Without it, /data uploads are refused with 409 and updates with 503 |
(optional) |
placement_store_db_path |
Path to the critical provider-bound durable lease→backend placement authority used for write-ahead placement, routing, restore, and restart recovery. Normal startup opens only an existing prepared, unsymlinked, single-link regular file with exact mode 0600 and never creates or migrates it. Never replace, unlink, rename, or restore the path while providerd is running |
(required) |
placement_snapshot_dir |
Directory for online snapshots of placements.db and payloads.db; empty disables them. Must be an absolute, clean path that is not the directory of either live database, and requires payload_store_db_path. See Online snapshots |
"" |
placement_snapshot_interval |
How often to snapshot; at least 5m. Validated only when placement_snapshot_dir is set |
1h |
placement_snapshot_retain |
Number of complete snapshot sets to keep, 1–1000. Validated only when placement_snapshot_dir is set |
24 |
maintenance_legacy_idempotency_tenants |
Tenant addresses whose restart/update requests may omit Idempotency-Key (see Restart Lease). Entries must be distinct canonical bech32 account addresses with bech32_prefix |
[] |
max_request_body_size |
Maximum request body size in bytes | 1048576 (1MB) |
Note: The Docker backend has additional configuration (
releases_db_path,releases_max_age,container_stop_timeout, etc.) documented indocker-backend.example.yaml.
Bundled backends also require a one-shot storage-lineage initialization before
their first normal start. For a genuinely new empty Docker backend, run
docker-backend -config docker-backend.yaml -initialize-storage-identity new;
the k3s scaffold uses the corresponding k3s-backend command. Normal startup is
verification-only and will not create or repair markers or authoritative
journals. Existing v0.13 Docker storage instead uses the stopped-and-drained
adopt cutover in Deployment.
Bundled Docker and k3s backends require a positive callback_max_age (default
24h). The age applies to typed lifecycle observations;
exact operation and maintenance completions never expire because they may be the
only evidence that can settle Fred's durable write-ahead attempt or an exact
replacement. Operation/maintenance intents and Docker close intents likewise do
not age out. An operation row transitions atomically from Pending to Succeeded or
Failed when its callback is enqueued and remains after that delivery succeeds;
the outbox row and durable terminal decision have deliberately separate
lifetimes. A later authorized lease transition may atomically supersede or
retire the terminal history. Strict per-lease FIFO means an undeliverable exact completion remains
a permanent ordering barrier until it is delivered or repaired. Zero or negative
values are rejected at startup.
Advanced Configuration
These options have sensible defaults but can be tuned for specific environments:
| Option | Description | Default |
|---|---|---|
http_read_timeout |
HTTP server read timeout | 15s |
http_write_timeout |
HTTP server write timeout | 15s |
http_idle_timeout |
HTTP server idle timeout | 60s |
websocket_ping_interval |
WebSocket ping interval | 30s |
websocket_reconnect_initial |
Initial WebSocket reconnect delay | 1s |
websocket_reconnect_max |
Maximum WebSocket reconnect delay | 60s |
tx_poll_interval |
Transaction confirmation poll interval | 500ms |
tx_timeout |
Deadline for the entire public chain write: signer permit queue, all sub-batches/retries, confirmation, and withdrawal response lookup. Also bounds each acknowledgement flush, including its initial query and fallback attempts | 30s |
query_page_limit |
Page size for chain queries | 100 |
max_withdraw_iterations |
Max pages per provider-wide withdrawal cycle (cursor pagination) | 100 |
withdraw_limit |
Leases settled per provider-wide withdrawal tx (MsgWithdraw.Limit); trades tx count vs per-tx gas. Must be 1..the chain's max batch size (currently 100) |
100 |
gas_limit |
Fallback gas used only when a per-tx gas simulation fails or is unavailable; every tx is otherwise gas-simulated per-tx. | 1500000 |
gas_adjustment |
Multiplier applied to the simulated gas estimate (Cosmos --gas-adjustment convention), giving headroom above the estimate, and to gas_limit on the fallback path. Matches the Cosmos CLI flag. Range: 1.0–3.0. |
1.2 |
max_gas_limit |
Absolute reject-cap: a tx whose adjusted simulated estimate exceeds it is terminally rejected before broadcast (never sent); it also clamps the out-of-gas retry ladder. 0 = uncapped. Must be ≥ gas_limit when set. |
0 |
gas_price |
Gas price (micro-units of fee_denom per gas unit). Each tx pays fee = ceil(gas × gas_price / 1_000_000), at least 1, where gas is the limit declared on that tx: normally the simulated estimate × gas_adjustment, otherwise the adjusted gas_limit fallback or an out-of-gas retry value |
25 |
fee_denom |
Fee denomination | umfx |
sub_signer_count |
Number of authz sub-signers for parallel tx signing. 0 = single-signer mode. |
0 |
sub_signer_min_balance |
Minimum balance before a sub-signer is topped up. | 10000000umfx |
sub_signer_top_up_amount |
Amount transferred per top-up. | 50000000umfx |
sub_signer_fund_check_interval |
How often balances are checked. | 1h |
credit_check_interval |
How often the scheduler wakes to run the credit check, independent of withdraw_interval. 0s couples it to withdraw_interval; when set >0 it must be ≤ withdraw_interval. A smaller value polls credit faster while the paid withdrawal stays rate-limited to withdraw_interval (the ENG-524 withdraw-cadence guard). |
0s |
credit_check_error_threshold |
Consecutive failed credit reads for one tenant after which the scheduler schedules an earlier re-check (after credit_check_retry_interval). Read errors never close leases or disable monitoring |
3 |
credit_check_retry_interval |
Delay before an earlier follow-up credit check. Applies in two cases: once a tenant's consecutive credit-check errors reach credit_check_error_threshold, and while a zero-balance closure is being deferred inside its credit_check_zero_grace_period window (so the empty balance is re-confirmed promptly rather than at the next full credit_check_interval). |
30s |
credit_check_zero_grace_period |
How long a tenant's credit must stay empty before its leases are auto-closed. A single stale zero read (e.g. the chain node briefly lagging a top-up) is absorbed: closure only fires once the empty balance persists for this whole window, and any non-zero read clears it. Lower = faster reclaim of unpaid leases; higher = more tolerance for transient chain-node lag before soft-deleting tenant data. 0s uses the 5m default. |
5m |
shutdown_timeout |
Maximum time for graceful shutdown (drain + cleanup) | 30s |
The callback route has a separate two-minute application budget because a
terminal result may include chain settlement. It extends the connection write
deadline for that request only; http_write_timeout and the generic 30-second
request middleware continue to govern the other HTTP routes. Bundled backends
give the complete delivery retry chain an additional 15 seconds. A fresh first
attempt therefore normally leaves time for providerd to return its retryable
503 after the application budget expires. Quick retries and their 0/1s/5s
backoffs share that one two-minute-fifteen-second deadline rather than resetting
it; after an earlier failure consumes part of the budget, a later attempt may
reach the sender's remaining deadline before providerd's per-request timer.
Either result keeps the durable FIFO head for a later replay instead of holding
the same lease for another full application budget. Bundled backends commit
operation, maintenance, and lifecycle callback facts before sending a
non-blocking wake to their tracked replay loop. Only that loop performs HTTP, so
the retry chain never extends a lease actor, API handler, or startup-recovery
critical section. A slow callback can occupy one replay worker and its per-lease
FIFO lock, without stalling another lease or backend node. This callback budget
is deliberately independent of providerd's backends[].timeout, which governs
Fred-to-backend requests.
TLS Configuration
See SECURITY.md for TLS configuration details (API server HTTPS, gRPC to chain).
Environment Variables
An environment variable named PROVIDER_ plus the upper-cased key overrides that key, but only when the key has a built-in default or appears in the config file. These keys have no default and must therefore appear in the config file: provider_uuid, provider_address, keyring_dir, key_name, tls_cert_file, tls_key_file, trusted_proxies, backends, callback_base_url, callback_secret, callback_canonical_path_prefix, maintenance_legacy_idempotency_tenants, token_tracker_db_path, payload_store_db_path, and placement_store_db_path. providerd cannot start from environment variables alone.
export PROVIDER_CHAIN_ID=manifest-1 # has a default
export PROVIDER_CALLBACK_BASE_URL=http://fred.example.com:8080 # only if the config file sets callback_base_url
FRED_KEYRING_PASSPHRASE and FRED_MNEMONIC are separate secrets, not config keys; see Deployment.
Usage
# Run with config file
./build/providerd -c config.yaml
# Print version (providerd, backend binaries, and placement tools support --version)
./build/providerd --version
./build/docker-backend --version
./build/k3s-backend --version
k3s-backend is currently an experimental, non-functional scaffold (ENG-133): the binary boots, serves the backend HTTP contract, and signs/verifies callbacks, but its provisioner returns
status=failed, error="not implemented"for every provision. It is not usable in production; real Kubernetes provisioning lands in ENG-134+.
API Endpoints
Endpoint Reference
Tenant API
| Method | Path | Auth | Replay | Lease State | Notes |
|---|---|---|---|---|---|
GET |
/v1/leases/{uuid}/connection |
ADR-036 | Yes | Active | Returns sensitive connection details |
GET |
/v1/leases/{uuid}/status |
ADR-036 | No | Any | Idempotent read |
GET |
/v1/leases/{uuid}/provision |
ADR-036 | No | Any | Idempotent read |
GET |
/v1/leases/{uuid}/logs |
ADR-036 | No | Any | Idempotent read |
GET |
/v1/leases/{uuid}/releases |
ADR-036 | No | Any | Idempotent read |
POST |
/v1/leases/{uuid}/data |
ADR-036 | No | Pending | Has own idempotency (409 on duplicate) |
POST |
/v1/leases/{uuid}/restart |
ADR-036 + Idempotency-Key |
Yes | Active | Durable, lease-scoped idempotent maintenance command |
POST |
/v1/leases/{uuid}/update |
ADR-036 + Idempotency-Key |
Yes | Active | Durable, lease-scoped idempotent maintenance command |
POST |
/v1/leases/{uuid}/restore |
ADR-036 | Yes | Pending | Restore a soft-deleted lease's data into this fresh lease |
GET |
/v1/leases/{uuid}/events |
ADR-036 | No | Any | WebSocket stream of lease status events |
Operational
| Method | Path | Auth | Notes |
|---|---|---|---|
GET |
/health |
None | Liveness. Probes chain, backends and DBs, but no verdict ever makes it 503 — poll this from a load balancer |
GET |
/readyz |
None | Deep readiness. Same body; 503 when local bbolt authority is unreadable/withdrawn, no durable inventory baseline matches the configured backend topology, an interrupted inventory sweep still awaits its reporters, or a fenced backend may hold a lease with no placement row. Not for load balancers |
GET |
/metrics |
None | Prometheus metrics |
GET |
/workloads?lease_uuid=<u1>&lease_uuid=<u2>... |
None | Bulk workload metadata lookup by lease UUID (1..MaxLookupUUIDs). Confirmed leases without an unresolved attempt query only their recorded owner; unresolved leases use fleet discovery. Unavailable relevant backends produce warnings. Used by the manifest-admin SPA. |
POST |
/callbacks/provision |
HMAC-SHA256 | Backend → Fred callback (5-min replay window) |
See SECURITY.md for replay protection rationale per endpoint.
Health Check
GET /health # liveness — no verdict returns 503
GET /readyz # deep readiness — also 503 while placement inventory is not ready
Both probe the same things — chain connectivity, every registered backend, and the token-tracker, placement-store and payload-store bbolt DBs — and return the same body. They differ only in how the verdict maps onto the status code:
status |
Meaning | /health |
/readyz |
|---|---|---|---|
healthy |
Every configured probe passed | 200 | 200 |
degraded |
A remote, shared dependency is impaired (chain, or one or more backends). Existing workloads keep serving and exact callbacks plus safely evidenced reconciliation work continue. After inventory bootstrap, the reconciler may place genuinely new recordless PENDING work only on backends that answered both inventories; work pinned to a silent owner, ACTIVE recordless work, and conflicts remain deferred, and an unresolved attempt is only redelivered to its attempted backend. A chain outage halts reconciliation and lease-resolving calls. Both conditions still accept backend callbacks |
200 | 200 |
unhealthy |
A local, process-owned bbolt store is unreadable, placement authority was permanently withdrawn after a path/inode or outcome-unknown commit failure, or placement_inventory is not ready: no durable inventory baseline matches the configured backend topology, an interrupted sweep still awaits both inventories from every backend that could have reported a lost positive, or a fenced backend may hold a lease with no placement row |
200 | 503 |
checks.placement_inventory reports a durable baseline bound to the exact set of
configured backend storage identities. A complete /provisions plus
/retentions projection establishes it; it survives process restarts and
transient incomplete sweeps while that topology is unchanged. A topology
membership change requires identity-bearing responses from the complete
proposed fleet and another complete projection; one temporarily down node does
not revoke an already-established unchanged-topology baseline.
fred_reconciler_sweep_complete is 0 while a sweep is in progress or after an
incomplete/error sweep, and becomes 1 only after the most recently completed
full-fleet inventory was durably projected. It is not a fleet-wide admission
gate. With a valid baseline, a partial sweep issues a typed scope containing
only the backends that answered both inventories. That scope can authorize
genuinely recordless PENDING work on those nodes. It cannot move or retry work
tied to a silent owner, clear an attempt or conflict from silence, or authorize
recordless ACTIVE recovery.
Tenant event dispatch has no per-sweep inventory witness. It instead requires the durable topology baseline, live-routes by backend stats within that topology, and durably records the exact attempted backend before making the call. A later ambiguous result remains pinned regardless of an empty inventory response.
The placement database is bound to the exact configured provider_uuid and is
backup-critical, not a derived inventory cache. Restore it after loss. Only a
genuinely new provider with zero total chain lease history can create its first
authority, using the explicit
placement-preflight -initialize-fresh
workflow after an all-state chain query proves no lease history, an independently
supplied exact backend roster matches configuration, and every configured
backend returns complete, identity-consistent empty inventories. Fresh
initialization is never recovery for a lost placement database. The printed
operator acknowledgement includes the target parent's physical device/inode;
the initializer rejects a rename/recreation between print and initialize and
publishes descriptor-relatively with renameat2(RENAME_NOREPLACE).
Mutation modes that read backend inventory (placement-preflight --prepare / --initialize-fresh, and placement-repair --apply, including conflict repair) require certificate-verified HTTPS for every configured backend, independently of production_mode, and refuse to run while any configured backend is fenced. placement-repair --apply with -attest-restored-backup or -retire-lost-backend reads no backend inventory and has neither requirement. Use tls_ca_file for a private CA or system roots; existing mTLS credentials remain supported. HTTP and tls_skip_verify cannot authorize durable changes because request HMAC does not authenticate inventory responses. Read-only inspection and repair dry-runs may observe development HTTP endpoints; there is no insecure-backend-evidence override.
It is also bound at runtime to the exact regular-file inode opened at
placement_store_db_path. Never copy over, unlink, rename, rotate, or restore
that pathname while providerd is running. A mismatch or outcome-unknown bbolt
commit permanently withdraws authority from that process; putting the old name
back does not heal it. Preserve the evidence and follow the
runtime-authority runbook.
Live backups must use atomic filesystem snapshots, and restores happen only
while stopped.
The mutating offline placement tools likewise bind the mandatory backup
parent's physical identity before remote proof, publish with descriptor-relative
no-replace rename, and retain the exact published inode through mutation and the
final verdict. Re-attestation also binds its SHA-256 bytes, exact length, 0600
mode, and single-link status. Treat BACKUP PUBLISHED, PREPARED:/COMMITTED:, and
OUTCOME UNKNOWN as distinct evidence-preservation outcomes; see the
deployment procedure.
/health is a liveness contract: no dependency verdict makes it 503, because
providerd runs as the single server of its load-balancer pool and the backends'
completion callbacks arrive on the same listener — so removing it from rotation
takes down the tenant API and the callback path that lets a recovering backend
report what it finished (ENG-522). Point load balancers here. Alert on
fred_health_check_healthy and fred_backend_healthy, not on the status code.
Chain and backend probes start together within a shared three-second remote budget, so a slow chain cannot consume the backends' opportunity to answer. Local store validation remains synchronous: the remote deadline cannot interrupt filesystem or database-lock waits. Health stage histograms distinguish these costs; investigate latency separately from the dependency verdict before changing probe timeouts. See the health runbook.
Being unauthenticated, both endpoints do still sit behind the global IP rate limiter
and can return 429; the default budget is far above any sane probe interval.
A check absent from checks means an optional dependency is not configured, not
that it passed. placement_store and placement_inventory are mandatory in
providerd and are therefore always present.
Response:
{
"status": "degraded",
"provider_uuid": "01234567-89ab-cdef-0123-456789abcdef",
"checks": {
"chain": {"status": "healthy"},
"backend:docker-1": {"status": "healthy"},
"backend:docker-2": {"status": "unhealthy", "message": "backend health check failed"},
"backend:docker-3": {"status": "unhealthy", "message": "backend is fenced"},
"token_tracker": {"status": "healthy"},
"placement_store": {"status": "healthy"},
"placement_inventory": {"status": "healthy"},
"payload_store": {"status": "healthy"}
},
"stats": {"in_flight_provisions": 2}
}
stats.in_flight_provisions is the number of provision and restore operations
this process is currently tracking.
Get Lease Connection
GET /v1/leases/{lease_uuid}/connection
Authorization: Bearer <token>
Returns connection details for an active lease from the backend. Requires ADR-036 signed bearer token. See SECURITY.md for token format and signing details.
Response (single instance):
{
"lease_uuid": "...",
"tenant": "manifest1...",
"provider_uuid": "...",
"connection": {
"host": "compute-alpha.example.com",
"fqdn": "a1b2c3d.example.com",
"ports": {
"8080/tcp": {"host_ip": "0.0.0.0", "host_port": 32768},
"443/tcp": {"host_ip": "0.0.0.0", "host_port": 32769}
},
"protocol": "https",
"metadata": {
"region": "us-east-1",
"backend": "kubernetes"
}
}
}
Response (multi-instance lease):
{
"lease_uuid": "...",
"tenant": "manifest1...",
"provider_uuid": "...",
"connection": {
"host": "compute-alpha.example.com",
"fqdn": "0-a1b2c3d.example.com",
"instances": [
{
"instance_index": 0,
"container_id": "abc123",
"image": "nginx:latest",
"status": "running",
"fqdn": "0-a1b2c3d.example.com",
"ports": {"80/tcp": {"host_ip": "0.0.0.0", "host_port": 32768}}
},
{
"instance_index": 1,
"container_id": "def456",
"image": "redis:alpine",
"status": "running",
"fqdn": "1-e5f6789.example.com",
"ports": {"6379/tcp": {"host_ip": "0.0.0.0", "host_port": 32769}}
}
],
"metadata": {"backend": "docker"}
}
}
Fields:
fqdn- Fully qualified domain name for ingress routing (omitted when ingress is not enabled). At the top level (connection.fqdn), this is set directly from the backend or propagated from the first instance's FQDN when no top-level value is provided. Each instance and service may also have its ownfqdn. A top-level or service-level explicit FQDN takes precedence over instance propagation.ports- Map of container port to host binding (e.g., "8080/tcp" → host_port 32768)instances- Array of per-instance details for multi-container leases (each with its own ports and optionalfqdn)services- Map of service name to connection details for stack (multi-service) leases. Each service contains its owninstancesarray and optionalfqdn(propagated from its first instance when not set explicitly).metadata- Additional backend-specific data
Response Codes:
200 OK- Connection details found401 Unauthorized- Invalid signature or token403 Forbidden- Lease does not belong to this tenant404 Not Found- Lease not found or notACTIVEon chain, or not provisioned on its backend500 Internal Server Error- The chain query failed, or the backend returned any error other than not-provisioned while reading connection details503 Service Unavailable- The replay-protection store is unavailable, or durable placement is unusable or unresolved or names a backend Fred no longer knows
Get Lease Status
GET /v1/leases/{lease_uuid}/status
Authorization: Bearer <token>
Returns the current provisioning status of a lease. Useful for checking if provisioning is in progress or complete.
Response:
{
"lease_uuid": "550e8400-e29b-41d4-a716-446655440000",
"tenant": "manifest1abc...",
"provider_uuid": "01234567-89ab-cdef-0123-456789abcdef",
"state": "LEASE_STATE_PENDING",
"requires_payload": true,
"meta_hash_hex": "a1b2c3...",
"payload_received": false,
"provisioning_started": false
}
Fields:
tenant- Tenant address from the authenticated tokenprovider_uuid- Provider UUIDstate- Chain lease state as its protobuf enum name:LEASE_STATE_PENDING,LEASE_STATE_ACTIVE,LEASE_STATE_CLOSED,LEASE_STATE_REJECTED, orLEASE_STATE_EXPIRED. A value this build does not recognize is reported as its number, and an answer from the retained record (see below) reportsLEASE_STATE_UNSPECIFIED.UNSPECIFIEDand unrecognized future values are non-actionable safety states: reconciliation preserves backend state and retries rather than inferring cleanup authority.requires_payload- True if lease has meta_hash (expects payload upload)meta_hash_hex- Expected payload hash in hex (omitted if no meta_hash)payload_received- True if Fred holds a payload for the lease: uploaded, stored by a confirmed update, or rebuilt from the backend's active release (see Restore Lease)provisioning_started- True if provisioning is in progressprovision_status- Backend provision status (omitted if not provisioned). May beretainedfor a closed/expired lease whose data was soft-deleted and is restorable (see retention)fail_count- Lifetime count of recorded failures, whoever caused them (omitted if zero). A diagnostic only: it never decides whether the lease is closedterminal_budget- The backend's consecutive-failure budget (omitted when the backend reports none):consecutive_failuresis the recorded count of consecutive failures of your own workload, andverdictisexhaustedwhen the provider will close the lease for repeated failure, otherwiseretry. It is exhausted only by a third or later consecutive failure that comes at least 30 minutes after the first failure of the streak, so a quick burst of failures (an outage) never closes the lease by itself. Restarts, updates and platform failures never count, and a restart or update you request resets the count. Once the lease has stayed ready for ten minutes, its next failure, restart or update starts a fresh streak. The count and verdict shown are the recorded ones and do not change with time, so a lease that has been healthy for a long time can still show its old count until then. See Repeated failuresreason- Stable, machine-readable failure category; present whenever a failure has been recorded — including areadylease whose last update failed and rolled back to the previous version — and omitted (omitempty) when empty; see Failure Reason Codesmessage- Curated, human-readable failure summary (omitted if empty); no host paths or raw command outputretained_until- RFC3339 retention deadline; present only for retained data with a configured age limit. Omitted when age-based expiry is disabled; other retention policy and capacity limits still apply.items- Restore shape (service_name,sku,quantity) to request when opening the fresh lease to restore into; present only whenretainedrestore_hint- Short human-readable next step for restoring; present only whenretainedpartition- The retention partition key recorded with the retained data; present only whenretainedand a partition was recorded
Leases the chain cannot find:
x/billingnever deletes a lease, so a closed lease normally answers through the chain path above, with its chainstate. If the chain query returns not-found (a lagging or reset RPC node, or a provider pointed at the wrong chain), this endpoint still answers from the retained record, withstateset toLEASE_STATE_UNSPECIFIED. Authorization is then by the retained record's tenant (the signed caller must own it); a cross-tenant caller, or a record that is absent or not retained, gets404.
Response Codes:
200 OK- Status returned401 Unauthorized- Invalid signature or token403 Forbidden- Lease does not belong to this tenant or provider404 Not Found- The chain has no record of the lease, and no backend returned a retained record owned by the caller500 Internal Server Error- The chain query failed
Get Provision Diagnostics
GET /v1/leases/{lease_uuid}/provision
Authorization: Bearer <token>
Returns provision diagnostics for a lease, including status, failure reason, and failure count. Works for both active and non-active leases (e.g., after rejection or closure), falling back to persisted diagnostics when the provision is no longer in memory.
Response:
{
"lease_uuid": "550e8400-e29b-41d4-a716-446655440000",
"tenant": "manifest1abc...",
"provider_uuid": "01234567-89ab-cdef-0123-456789abcdef",
"status": "failed",
"fail_count": 3,
"terminal_budget": {"verdict": "retry", "consecutive_failures": 1},
"reason": "ContainerExited",
"message": "container exited unexpectedly"
}
Fields:
status- Provision status:provisioning,ready,failing,failed,restarting,updating,deprovisioning,retained, orunknown.failingis a transient state between container-death detection and the Failed callback;deprovisioningcovers the container-removal window;retainedmarks a closed/expired lease whose data was soft-deleted and is restorablefail_count- Lifetime count of recorded failures (diagnostic only; it never decides a close)terminal_budget- The consecutive-failure budget, as in Get Lease Status; omitted when the backend reports none or the answer comes from persisted diagnosticsreason- Stable, machine-readable failure category, always present whenstatusisfailed(defaults toUnknownif no specific cause was recorded); see Failure Reason Codesmessage- Curated, human-readable failure summary; may be emptyitems,restore_hint,partition- Present only whenstatusisretained(restore shape, next-step hint, and recorded retention partition); see Get Lease Statusretained_until- RFC3339 retention deadline for retained data with a configured age limit. Omitted when age-based expiry is disabled; other retention policy and capacity limits still apply.
Response Codes:
200 OK- Provision found401 Unauthorized- Invalid signature or token403 Forbidden- Lease does not belong to this tenant404 Not Found- Provision not found (never provisioned or diagnostics expired)500 Internal Server Error- The chain query failed, or a backend returned an error other than not-provisioned and no backend reported the provision503 Service Unavailable- Durable placement is unresolved, so absence cannot be reported safely
Failure Reason Codes
reason is an open, add-only enum (Kubernetes Condition.Reason-shaped): new values may be
added in future releases without notice. Clients must treat any value they don't recognize as
a generic failure and fall back to displaying the human-readable message — never match/branch on
reason with an exhaustive switch that errors on the default case. Adding a new reason is
considered a backward-compatible change.
A ready lease can retain reason and message from a failed maintenance
attempt while its original runtime remains available. Periodic Docker recovery
preserves that cause while the same release and operation remain ready,
including transient healthcheck observations. An actual recovery from failed
to ready, or a successful replacement, can clear the previous cause. Use
status to determine the current runtime state.
The set defined today:
| Reason | Meaning |
|---|---|
ContainerExited |
A container exited unexpectedly (crash, non-zero exit, OOM kill) |
HealthCheckFailed |
A container's health check never passed during startup: it reported unhealthy, or was still not healthy at the startup deadline |
ContainerStartFailed |
The container runtime refused to start a container, which never ran (for example, its entrypoint does not exist in the image) |
ImagePullFailed |
The container image could not be pulled |
Internal |
An internal fred/backend error occurred (not attributable to the tenant's workload) |
RestartFailed |
A tenant-initiated restart failed |
UpdateFailed |
A tenant-initiated manifest update failed (and was rolled back) |
RestoreFailed |
A tenant-initiated restore (redeploy from retained data) failed |
VolumeCleanupExhausted |
Volume cleanup on deprovision failed after exhausting all retry attempts |
CleanupFailed |
Cleanup on deprovision failed (containers or volumes) |
BackendStorageLost |
An operator retired the lease's backend because its storage was irrecoverably lost; the lease is closed (or, if pending, rejected) on chain |
VolumeDeletePending |
A provision was refused because an earlier deletion of the lease's own volume is still finishing on the provider |
VolumeDeletionInProgress |
The lease is closing and its volume is still being deleted by the provider; the close completes when the deletion does |
Unknown |
Read-boundary default: the lease is failed but no specific reason was recorded |
message is a short, human-readable string for display; it contains no host filesystem paths or
raw command/daemon output (that detail is retained operator-side, correlated by lease_uuid, for
support/debugging).
A lease whose backend an operator retired as lost (see DEPLOYMENT.md, "Retiring
a backend whose storage is lost") is answered from its placement record and no
backend is asked. Its status reports provision_status: failed with
reason: BackendStorageLost. Provision, connection, logs, releases, restart,
and update, and a restore that names it as the source, answer:
{"error":"the backend storage holding this lease was irrecoverably lost","code":410,"reason":"backend_storage_lost"}
The data on the lost storage cannot be recovered; do not retry. These answers hold until the lease has ended on chain and a later sweep prunes its placement row; from then on it is answered like any other ended lease.
Get Container Logs
GET /v1/leases/{lease_uuid}/logs?tail=100
Authorization: Bearer <token>
Returns live container logs under <service>/<instance> keys, falling back to
persisted failure logs after removal. When compensation restores the previous
Ready deployment after a failed update or restart, its live keys remain unchanged
and the failed attempt appears under failed/<service>/<instance> keys. A later
deployment removes those older failure entries from the active view. Logs share
a 32 MiB aggregate content budget, with bounded marker and encoding overhead.
Each daemon admits one materialized log response at a time across tenants and
backends, with at most nine admitted requests total. The provider prepares small
authorization and routing results concurrently within those nine slots, then
queues authorized reads in FIFO order for the single response slot. A slow chain
lookup therefore does not hold response memory needed by another authorized read.
Preparation, queueing and retrieval share the original configured request
deadline. A full request queue returns 503 Service Unavailable with
Retry-After: 1; expiry while waiting for the provider response slot returns the
numeric 503 request timeout envelope without a retry header. Requests already
canceled at entry receive the capacity envelope. Backend admission expiry retains its
capacity envelope and retry header.
Admission remains held until both the worker and the final response write finish,
including timeout cleanup; retrying a read does not consume a replay token.
The final response transfer has a separate deadline using the daemon's configured
HTTP write timeout, falling back to the request timeout when no positive write
timeout is configured. A client that does not read, or reads too slowly, can
receive an incomplete response even after 200 OK. Treat the response as
complete only after reading the entire HTTP body without a transport error and
parsing it as the expected JSON response below; otherwise, retry with a smaller
tail. The 32 MiB content budget is unchanged, but it does not guarantee
delivery of a fully escaped response over an arbitrarily slow link.
Query Parameters:
tail- Number of log lines to return per container (default: 100, max: 10000)
Response:
{
"lease_uuid": "550e8400-e29b-41d4-a716-446655440000",
"tenant": "manifest1abc...",
"provider_uuid": "01234567-89ab-cdef-0123-456789abcdef",
"logs": {
"web/0": "2024-01-15 Starting nginx...\nListening on port 80\n",
"db/0": "2024-01-15 Redis ready\n"
}
}
Fields:
logs- Map of service/instance keys to log output;failed/keys identify the compensated attempt
Response Codes:
200 OK- Logs found400 Bad Request- Invalid tail parameter (negative, zero, or exceeds max)401 Unauthorized- Invalid signature or token403 Forbidden- Lease does not belong to this tenant404 Not Found- Provision not found (never provisioned or logs expired)500 Internal Server Error- The chain query failed, or the backend returned another error or an invalid log response503 Service Unavailable- The log queue is full, the admission wait expires, or read capacity is exhausted (Retry-After: 1); backend routing is unavailable, or durable placement is unusable or unresolved
Upload Payload
The payload is a deployment manifest in JSON format. See the Manifest Guide for the full schema (single-service and stack formats, validation rules, examples). A formal JSON Schema is available for client-side validation.
POST /v1/leases/{lease_uuid}/data
Authorization: Bearer <token>
Content-Type: application/octet-stream
<raw payload bytes>
Upload deployment configuration for a lease that was created with a meta_hash. The payload's SHA-256 is checked against the on-chain hash before provisioning starts. Fred does not parse the manifest at upload: a malformed manifest whose hash matches is accepted and fails later, when the backend validates it during provisioning. Requires a payload-specific ADR-036 token that includes the meta_hash field. See SECURITY.md for token details.
Response Codes:
202 Accepted- Payload received, provisioning started400 Bad Request- Invalid lease UUID, the lease is billing-only (withreason: billing_only, whatever its state), the lease has nometa_hash, the body could not be read (including a body overmax_request_body_size), the body is empty, or its SHA-256 does not matchmeta_hash401 Unauthorized- Invalid signature or token, or the token's lease UUID ormeta_hashdoes not match the lease403 Forbidden- Lease does not belong to this tenant or provider404 Not Found- Lease not found, or not PENDING and not billing-only409 Conflict- The payload was not stored: a payload is already stored for this lease, or the payload store is not configured, is closed, or failed the write. A store failure is therefore also reported as409500 Internal Server Error- The chain query failed, or publishing the payload event failed; the stored payload is removed so the upload can be retried503 Service Unavailable(rarely504 Gateway Timeout) - The request, including the body upload, did not complete within the provider's request timeout
Restart Lease
POST /v1/leases/{lease_uuid}/restart
Authorization: Bearer <token>
Idempotency-Key: <canonical UUIDv4>
Restart containers for a lease without changing the manifest. Containers are stopped, removed, and recreated with the same configuration. Volumes are preserved. Allowed from ready or failed state.
The idempotency key is scoped to this lease. Once a command is definitively
settled, retrying the exact same key and command returns its durable result
without starting another replacement; reusing the key for a different command
or update payload returns 409. A
different key also returns 409 while an earlier command is unresolved. Fred
keeps the receipts of the lease's 1,024 most recent restarts and updates, and
pending commands until they are definitively settled; admitting a newer command
evicts the oldest settled receipt, and closing the lease reclaims the rest.
Clients must generate a fresh UUIDv4 for each new logical command and reuse it
only for retries, so a retry within the last 1,024 commands always replays its
result. Never reuse a key for a new command: a key Fred has forgotten may still
name an earlier release on the backend, and is then refused with 409.
Each command carries the time Fred admitted it, and Fred stamps a lease's
commands in strictly increasing order. A backend refuses any restart or update
it no longer has a receipt for and that is not newer than the newest command it
has accepted for the lease, so an arbitrarily late or replayed command can
never run again, restart work twice, or move the provider's desired payload
backward. Such a command settles as 410 Gone with
reason: maintenance_expired; it did not run. Send a new command with a new
key.
For clients written before Idempotency-Key existed, an operator can list
tenant addresses in maintenance_legacy_idempotency_tenants. A restart or update
from a listed tenant may then omit the header: Fred authenticates the request
first, which consumes its single-use signed token, and keys the command by that
token. Each accepted token is one command, as before keys existed, and a
replayed token is refused with 401; a retry therefore needs a new token and is
a new command. Every such request logs a WARN and increments
fred_api_maintenance_legacy_key_total. A malformed or repeated header is still
refused with 400, after the token's signature is validated but before the
token is consumed, and an unlisted tenant that omits the header receives 400.
The provider shares a separate budget of 1,024 pending commands and 64 MiB of
journal content, including 512 bytes of phase-growth allowance per command.
There is no fixed per-tenant concurrency cap. A tenant that already has pending
work can borrow the shared pool while leaving one command and 2 MiB available
for a tenant without pending work. Reaching that reserve returns 429 with
reason: maintenance_capacity_reserved and Retry-After: 1 before recording a
new command. Retry that request after your pending work completes. Exact replay
and settlement of admitted commands remain available. Global exhaustion returns
503; this finite reserve does not guarantee admission for unlimited new tenant
addresses.
For both restart and update, a 503 response can leave a durably admitted
command pending. Even when an open backend circuit blocks its first attempt,
the command remains pending. Fred retries work at startup and every reconciliation_interval
(default 5m), so the operation can execute later without another tenant request.
A different key receives 409 while the command is pending. Retrying the same
key and exact command safely joins recovery and may continue returning 503
while progress is blocked. After the lease closes, a positive chain observation
allows recovery to settle the pending command without starting a replacement.
Fred derives tenant, provider, backend/storage, and callback authority from its prepared placement store; the request supplies none of those routing facts. For an upgraded v0.13 owner, the first complete identity-bearing fleet inventory must establish that durable runtime principal before restart/update is enabled. Once established, an unrelated backend outage does not revoke it.
Response: 202 Accepted
{
"status": "restarting"
}
Response Codes:
202 Accepted- Restart initiated400 Bad Request- Missing, repeated, or non-canonical UUIDv4Idempotency-Key, or a backend validation refusal with a curated diagnostic preserved for exact retries401 Unauthorized- Invalid signature or token403 Forbidden- Lease does not belong to this tenant404 Not Found- The backend reports that the lease is not provisioned409 Conflict- The key conflicts with a prior command, another command or lifecycle operation is in progress for the lease, the lease is notACTIVEon chain, or the backend reports a state that cannot be restarted410 Gonewithreason: backend_storage_lost- The lease's backend was retired because its storage was lost; the lease is being ended on chain410 Gonewithreason: maintenance_expired- The command is older than the lease's retained maintenance history and was not run; send a new command with a new key429 Too Many Requestswithreason: maintenance_capacity_reserved- Shared capacity is reserved for a tenant without pending work; this new command was not recorded. Retry after your pending work completes (Retry-After: 1)500 Internal Server Error- The backend accepted the command but Fred could not yet durably record that acceptance (the pending command remains recoverable), or Fred hit an internal error503 Service Unavailable- Backend dispatch is blocked (for example by an open circuit) or its outcome is uncertain, the backend refused it for capacity, the chain lease could not be read or was not found, the lease has no usable confirmed placement here or its backend is fenced, authentication/routing authority is temporarily unavailable, or a bounded idempotency journal refused admission before side effects. An admitted command can remain pending for automatic recovery as described above
Update Lease
POST /v1/leases/{lease_uuid}/update
Authorization: Bearer <token>
Idempotency-Key: <canonical UUIDv4>
Content-Type: application/json
{
"payload": "<base64-encoded-manifest>"
}
Deploy a new manifest for a lease, replacing containers with a new image/configuration. Fred captures the exact source before replacing it. If replacement fails and Docker effects are settled, bounded compensation can preserve or recreate that source. Unknown effects remain pending; lease close or backend shutdown cancels compensation while retaining completion ownership. Volumes are preserved, but compensation does not undo application writes or database migrations.
The accepted manifest is persisted as pending desired state in the maintenance
journal. HTTP 202 confirms that durable acceptance, while the replay payload
remains the last successfully deployed manifest. Only an authenticated completion
for the exact maintenance ID, lifecycle and storage identity can authorize
promotion. A failed or rolled-back update leaves the previous replay payload
unchanged. The durable journal represents waiting-for-completion and
confirmed-for-payload-write as separate states and in-memory capabilities.
Recovery retries an ambiguous backend delivery using the same command. Once
acceptance is recorded, it waits for the exact completion; after success it
retries only the local payload commit. Repeating the same Idempotency-Key
joins this work. Until the local commit or terminal failure settles, the lease
remains excluded from conflicting maintenance and reprovision. An HTTP 202
is therefore not a promise that asynchronous deployment has succeeded.
If an exact chain observation confirms that the lease has since ended, recovery can settle the accepted command without writing its payload. This lets teardown proceed despite a payload-store failure; the settlement must still be committed to placement storage.
The restart retry contract also applies to updates: an admitted
command returning 503 may execute during automatic recovery, and a different
key receives 409 until the pending command settles.
Because the on-chain meta_hash is set once at lease creation and cannot currently be updated, an updated payload no longer matches it. Fred records each stored payload's own SHA-256 and verifies against that on reprovision; meta_hash is still used for payloads stored before this behavior existed. See ENG-643 for the on-chain update handshake that restores meta_hash as the authoritative reference.
Response: 202 Accepted
{
"status": "updating"
}
Response Codes:
202 Accepted- Update accepted with pending desired state persisted400 Bad Request- Missing/invalidIdempotency-Key, payload, or manifest; curated backend validation diagnostics are preserved for exact retries401 Unauthorized- Invalid signature or token403 Forbidden- Lease does not belong to this tenant404 Not Found- The backend reports that the lease is not provisioned409 Conflict- The key conflicts with a prior command, another command or lifecycle operation is in progress for the lease, the lease is notACTIVEon chain, or the backend reports a state that cannot be updated. An exact retry of an update whose deployment failed also returns409410 Gonewithreason: backend_storage_lost- The lease's backend was retired because its storage was lost; the lease is being ended on chain410 Gonewithreason: maintenance_expired- The command is older than the lease's retained maintenance history and was not run; send a new command with a new key429 Too Many Requestswithreason: maintenance_capacity_reserved- Shared capacity is reserved for a tenant without pending work; this new command was not recorded. Retry after your pending work completes (Retry-After: 1)500 Internal Server Error- The backend accepted the update but Fred could not yet durably record that acceptance or write the confirmed manifest to the provider payload store (the durable pending command remains recoverable), or Fred hit an internal error503 Service Unavailable- Backend dispatch is blocked (for example by an open circuit) or its outcome is uncertain, the backend refused it for capacity, no payload store is configured, the chain lease could not be read or was not found, the lease has no usable confirmed placement here or its backend is fenced, routing/authority is temporarily unavailable, or the bounded provider/backend idempotency journal refused admission before side effects. An admitted command can remain pending for automatic recovery
Restore Lease
POST /v1/leases/{lease_uuid}/restore
Authorization: Bearer <token>
Content-Type: application/json
{
"from_lease_uuid": "<original-closed-lease-uuid>"
}
Restore a soft-deleted lease's retained data into a new lease. The path lease_uuid is the new, fresh PENDING lease the data is adopted into; from_lease_uuid in the body names the original closed/expired lease whose volumes were retained (see retention). Fred resolves the backend that holds the source lease's retained data (restore is same-backend, ENG-333), then re-deploys the retained manifest onto the adopted volumes. Only the item shape must match: the new lease's requested service names and quantities must equal the original's, but its SKU/disk tier MAY differ. A promote (same-or-larger disk tier) satisfies the tier-size check but still requires sufficient backend capacity and the other admission checks; on success, the new disk_mb cap is applied; a demote (smaller disk tier) is allowed only if the retained volume's measured data still fits the new tier's disk_mb cap (the backend runs checkDemoteFit before adopting). A refused demote returns 422 Unprocessable Entity; the JSON body's error message begins retained data exceeds the requested smaller tier (the body's code field is the numeric HTTP status, not a string discriminator).
Fred never receives the retained manifest, so it holds no payload for the new
lease. If the new lease has a meta_hash and later has to be re-provisioned
(for example after a host reboot loses its containers), the reconciler stores
the manifest of the latest active release the owning backend reports on
GET /releases/{lease_uuid} as the lease's payload and re-provisions from it,
onto the same data (ENG-1442). Create the new lease with a meta_hash, as
restore clients do by reusing the source lease's: Fred provisions a lease
without one, with no payload, as soon as it is created.
Admission is safe by construction. Fred acquires ordered lifecycle claims for the source and target, then obtains a store-issued reservation of the exact confirmed source before re-reading and authorizing the target. That reservation fences even inventory captured before the lifecycle claims. Durable target admission atomically transfers the same reservation into the full restore claim while writing the absent target's operation-scoped attempt, before contacting the backend. Failure before transfer releases only the early reservation. Concurrent lifecycle work sharing either lease is rejected before dispatch. The source reservation is process-local and lasts only through the synchronous backend call; the target attempt is durable and is later settled only by its exact operation ID.
Backend acceptance confirms the target, while a contract-conforming synchronous
domain refusal clears it. A bare already_provisioned response does not prove
which callback generation the backend persisted, so Fred retains the new target
attempt until upgraded inventory reports that exact typed generation or its
authenticated callback arrives. A
timeout, transport error,
panic, generic 5xx, malformed error envelope, or unvalidated 503 is ambiguous: Fred
releases the short-lived source reservation but retains the target attempt because
the backend may have accepted the request. An immediate retry of that target will
normally return 409. A positive report confirms it only when the backend also
reports the exact paired typed lifecycle generation; an older typed or legacy
generation remains current while the newer attempt stays recoverable;
a positive report from another backend creates a durable conflict containing
every candidate. Inventory silence never disproves or clears an ambiguous
attempt because the original request could commit after the list response.
Retrying requires such a contract-conforming synchronous refusal or explicit
operator proof and repair. Backend response bodies are not HMAC-authenticated:
their codes establish protocol conformance under the deployment's transport
trust, not cryptographic authorship. Use TLS or an equivalently trusted network
between Fred and its configured backends when an on-path attacker is in scope.
On the Docker backend, the source retention row remains a durable finalizer for
the destination after adoption. While it exists, Provision and Restore of that
destination are rejected. A pre-commit failure hands the source back only after
teardown/re-quarantine, source-quota proof, and exact
failed-operation settlement; an accepted worker failure may therefore stay
restoring until the actor's Failed operation outcome and callback are durable.
If the later source handback must retry, that exact terminal row—not absence—owns
the rollback decision; a missing row fails closed. Reconciliation also holds a
typed exclusive quiescence capability from the exact lease actor across its
decision and substrate work, so queued messages, handlers, workers, terminal
handoff, and actor replacement cannot race rollback. Conversely, an exact
active destination Release is proof the restore committed even if containers
later fail or disappear. With zero survivors, recovery keeps that Release,
reconstructs a conservative Failed destination plus its capacity, and retains
the source finalizer as tenant/provider identity rather than rolling data back.
An exact Succeeded operation plus that immutable finalizer also reconstructs a
missing active Release; Failed plus an exact committed Release is contradictory
authority and fails closed.
Once no Pending or contradictory Failed restore operation remains (an
authorized successor may already have retired Succeeded history), a plain,
identity-preserving Restart may
repair it; Update and custom-domain redeploys remain fenced until that Restart
reaches Ready and reconciliation consumes the finalizer. Close instead hands it
off to a complete durable close intent before teardown.
Response: 202 Accepted
{
"status": "provisioning"
}
Response Codes:
202 Accepted- Restore initiated (the lease then transitions throughrestartingtoready/failed)400 Bad Request- Missing/invalidfrom_lease_uuid, source and target UUIDs are equal, items don't match the retained set, or the target lease is billing-only (withreason: billing_only, whatever its state)401 Unauthorized- Invalid signature or token403 Forbidden- Lease does not belong to this tenant404 Not Found- The source has no placement record, is notCLOSEDorEXPIRED, belongs to another tenant/provider, or its configured backend reports no retained data (including retention that has expired)409 Conflict- Source or target lifecycle work is already in progress, or the target is notPENDING(a billing-only target gets400instead), has an unresolved durable provision/restore attempt, or is not in a restorable state410 Gonewithreason: backend_storage_lost- The source's backend was retired because its storage was lost; its data cannot be restored422 Unprocessable Entity- The retained data exceeds a requested smaller tier'sdisk_mbcap; the response relays the backend's bounded, recognized refusal detail500 Internal Server Error- The restore returned an unexpected or ambiguous backend result, such as a transport error, timeout, generic 5xx, coded already-provisioned response, or unknown refusal code; the durable target attempt is retained until positive evidence confirms it or an operator safely repairs it502 Bad Gateway- The backend rejected the restore with an unusable or off-contract error response503 Service Unavailable- Insufficient resources, an open backend circuit, an unavailable source lease observation, unavailable placement routing/recording/tracking, or a source placement that is unusable, unresolved, or names a backend Fred no longer knows
Restore admission conflicts add an optional reason while preserving the public
API's numeric HTTP-status code and human-readable error:
{"error":"lease is already being provisioned or restored","code":409,"reason":"source_busy"}
reason |
Meaning | Client action |
|---|---|---|
source_busy |
The source is owned by lifecycle work, such as close, or another restore dispatch | Keep the target lease and retry later with a fresh bearer token |
target_busy |
The target has lifecycle work, a tracked operation, or existing durable placement/attempt evidence | Preserve the target and inspect its status; pending recovery may need to finish before another restore can be admitted |
target_not_pending |
An authoritative target chain read found a state other than PENDING |
Do not retry restore into this target; restore requires a fresh PENDING lease |
A generic backend 409 still omits reason: its refusal does not identify
whether the source or target state caused the conflict. Missing or unrecognized
reasons are not evidence that it is safe to cancel the target. Preserve its UUID
and inspect its status. Backend protocol string codes, including
demote_exceeds_tier, are separate from the public API's numeric code field.
Get Release History
GET /v1/leases/{lease_uuid}/releases
Authorization: Bearer <token>
Returns the release (deployment) history for a lease, showing each version that was deployed.
Response:
{
"lease_uuid": "550e8400-e29b-41d4-a716-446655440000",
"tenant": "manifest1abc...",
"provider_uuid": "01234567-89ab-cdef-0123-456789abcdef",
"releases": [
{
"version": 1,
"image": "nginx:1.24",
"status": "superseded",
"created_at": "2024-01-15T10:30:00Z",
"manifest": "<base64-encoded-manifest>"
},
{
"version": 2,
"image": "nginx:1.25",
"status": "active",
"created_at": "2024-01-16T14:00:00Z",
"manifest": "<base64-encoded-manifest>"
}
]
}
Fields:
version- Monotonically increasing version numberimage- Container image used in this releasestatus- Release status:deploying,active,superseded, orfailedcreated_at- When this release was createdreason- Stable, machine-readable failure category (only present on failed releases); see Failure Reason Codesmessage- Curated, human-readable failure summary (only present on failed releases; may be empty)manifest- The manifest payload used for this release
Response Codes:
200 OK- Releases found (may be an empty array)401 Unauthorized- Invalid signature or token403 Forbidden- Lease does not belong to this tenant404 Not Found- Lease not found on chain, or not provisioned on its backend500 Internal Server Error- The chain query failed, or the backend returned an error other than not-provisioned503 Service Unavailable- Backend routing is unavailable, or durable placement is unusable or unresolved
Stream Lease Events (WebSocket)
GET /v1/leases/{lease_uuid}/events
Authorization: Bearer <token>
Opens a WebSocket connection for real-time lease status updates. Events are pushed as JSON frames when the lease transitions between provisioning states (e.g., provisioning, ready, failed, restarting, updating). A retained event is pushed when a closed/expired lease's data is soft-deleted (best-effort, only to currently-connected clients), signalling that the data may be restorable within the grace window. For that event the status field is the enum retained, and the human-readable restore instruction is carried in the error field. A failed event can also carry a failure description in error; other events omit the field.
Authentication: Bearer token via the Authorization header or the ?token= query parameter (since the WebSocket API cannot set custom headers during upgrade). Auth is verified before the WebSocket upgrade, so failures return standard HTTP error responses.
Response: 101 Switching Protocols on successful upgrade
{"lease_uuid":"...","status":"ready","timestamp":"2024-01-15T10:30:00Z"}
{"lease_uuid":"...","status":"restarting","timestamp":"2024-01-15T10:31:00Z"}
{"lease_uuid":"...","status":"ready","timestamp":"2024-01-15T10:31:30Z"}
Behavior:
- Events are delivered as WebSocket JSON frames
- Events are best-effort notifications, not a globally ordered state log. An
exact operation-ID callback remains recoverable after a provider restart only
while the same durable placement Attempt or confirmed lifecycle generation
exists; a nonmatching or replaced ID is ignored, so it cannot publish stale
status. Bundled backends serialize each
lease's durable callback queue: exact provision/restore and maintenance
completions remain FIFO, autonomous lifecycle observations are latest-only,
and synchronous provider application preserves that relative order. A bundled
Docker backend also returns retryable
409 Conflictfor a newer maintenance command while that lease's prior exact maintenance completion is still queued; successful delivery reopens only that lease. Other event sources and custom/v0.13 backends do not carry a global sequence; use the REST status endpoints for current state. - The server sends WebSocket ping frames every 30 seconds; the client must respond with pong within 40 seconds or the connection is closed
- Slow clients that fall behind have events dropped — use the REST endpoints (
/status,/releases) to catch up - The stream is push-only. The server reads at most 512 bytes per client message; any client data message closes the connection with
1008, and a larger message closes it with1009 - The token is checked only at the handshake. Each connection lasts at most one hour, then closes with
1013("max connection lifetime reached"); reconnect with a fresh token - A lease accepts at most 10 concurrent subscriptions and the server 1,000 in total. Past either limit the upgrade still succeeds (
101), then the connection closes at once with1013("too many connections") - The stream also ends when the client disconnects or the server shuts down (
1001)
Response Codes (before upgrade):
101 Switching Protocols- WebSocket connection established400 Bad Request- Invalid lease UUID, or the request is not a valid WebSocket handshake401 Unauthorized- Invalid signature or token403 Forbidden- Lease does not belong to this tenant or provider404 Not Found- Lease not found on chain429 Too Many Requests- Rate limit exceeded500 Internal Server Error- The chain query failed
Provision Callback (Backend -> Fred)
POST /callbacks/provision?operation_id=<uuid>
POST /callbacks/provision?lifecycle_id=<uuid>
Content-Type: application/json
X-Fred-Signature: t=<unix-timestamp>,sha256=<hmac-sha256-hex>
Called by backends to report operation results and later lease-lifecycle
observations. Provision and restore completion URLs carry exactly one
operation_id; their separately persisted lifecycle URLs carry exactly one
lifecycle_id. Both are lowercase, hyphenated, canonical RFC-4122 UUIDv4
values. Preserve the selected URL byte-for-byte: the HMAC covers the complete
request URI, including its query. Requires HMAC-SHA256 authentication via the
X-Fred-Signature header. See SECURITY.md for signing details and replay protection.
Request:
{
"lease_uuid": "...",
"status": "success",
"error": "",
"backend_storage_id": "canonical-uuidv4-for-this-backend-storage",
"backend": "optional-sender-label",
"retained": false
}
Status must be one of "success", "failed", or "deprovisioned". deprovisioned reports a completed teardown and is accepted only on a lifecycle_id URL or the tokenless v0.13 route; on an operation_id URL it is rejected with 400. The bundled backends send it when a lease's close completes.
backend(optional string) — legacy sender metadata used only for bounded metrics when no current operation exists. It need not equal Fred's configured router name and cannot authorize or redirect a typed callback; the HMAC-covered callback URL plus Fred's exact-operation registry or durable lifecycle record select the authoritative backend.maintenance_id(optional canonical UUIDv4 string) — included on the exact durable restart/update completion. The docker-backend also sends one on a custom-domain completion; it names no Fred command, so it settles nothing. Successful update completion authorizes promotion of that command's pending manifest; failed completion discards its promotion. Later runtime-failure observations omit this field. It is HMAC-covered and must match the command under the authorized lifecycle and storage identity. Sending it on anoperation_idURL or withdeprovisionedstatus is rejected with400.maintenance_admitted_at(optional RFC 3339 timestamp in UTC, written withZ; a+00:00offset is rejected with400) — echoes theadmitted_atFred sent with that restart/update; Fred compares the instant. Sending it withoutmaintenance_idis rejected with400. A stamp that differs from the command Fred admitted under thatmaintenance_idcannot settle the command.retained(optional bool) — settrueon adeprovisionedcallback when the backend soft-deleted (retained) the lease's volumes instead of destroying them; with any other status it is rejected with400. Fred uses this to push the optimisticretainednotice to the tenant; the queryable retained status (GET /v1/leases/{uuid}/status) is the durable backstop. Omitted/falsemeans the volumes were destroyed.operation_idin the JSON body, if sent by an older or custom backend, is untrusted metadata and is overwritten at ingress. Only the HMAC-authenticated URL query grants exact-operation authority.lifecycle_idin the JSON body is likewise overwritten. Fred authorizes the authenticated query only when it matches the current durable per-lease lifecycle capability and backend.
Response Codes:
200 OK- Callback synchronously reached a terminal application result, or was terminally ignored as a duplicate/stale exact-operation callback; the backend may advance this lease's durable callback queue400 Bad Request- Malformed JSON, lease UUID, status, or callback capability query, or a field combination rejected above.operation_idandlifecycle_idare mutually exclusive; a present empty, nil, non-v4, non-RFC-variant, uppercase, compact, braced, URN, malformed, or duplicate value is rejected401 Unauthorized- Missing or invalid signature429 Too Many Requests- Callback ingress or verified-storage rate limit exceeded503 Service Unavailable- Callback application is unavailable, not yet started, shutting down, failed, or timed out; keep the delivery durable and retry with backoff
Callbacks have a separate pre-authentication IP budget (100 requests/s, burst
200), independent of tenant routes, plus a post-HMAC budget with the same limits
per verified backend storage identity. Unverified payload identity fields never
spend another backend's authenticated allowance. When the pre-authentication
bucket is exhausted, valid HMAC callbacks can still reach their own storage
bucket, so junk sent directly to the callback route cannot starve a backend
sharing that IP. The legacy single-key mode
shares one authenticated callback bucket. Both limits return Retry-After.
Callback application has a dedicated two-minute deadline. Bundled backends give the complete delivery retry chain two minutes fifteen seconds, so a fresh first attempt normally leaves response time after the provider's application timer returns 503. Quick retries and backoff consume that same deadline; a later attempt may instead be canceled when the sender's remaining budget expires. A backend must retain the FIFO head until a 2xx response; a 503, transport timeout, or lost response is not delivery success. When the shared budget expires, bundled backends defer to their 30-second durable replay loop instead of holding that lease's FIFO lock for consecutive application budgets.
The v0.13.0 upgrade is a stopped cutover, not a rolling upgrade. Drain every old
callback outbox, stop providerd and all backends, install the upgraded binaries
without starting them, rotate to unique per-backend keys, and run each Docker
backend's mandatory read-only -preflight-storage-identity-adoption proof
before taking the cutover backup or sealing storage identity. Then restart
every upgraded backend before the new providerd. Stack-form v0.13 Docker
workloads stay in place; service-name-less pre-stack cohorts are unsupported and
must be resolved before sealing. New backends recover the old operationless callback shape already
embedded in migrated workloads, preserve its tokenless lifecycle route, and
report a non-secret legacy generation in internal inventory. Mandatory offline
preparation migrates the corresponding owner as legacy before the new provider
opens it. A pre-identity callback row prevents current callback-store startup;
it is not carried across the cutover. An absent or rebuilt placement database is
never a recovery path for those workloads. The reverse binary order is not
lifecycle-compatible:
an old backend ignores lifecycle_callback_url and reuses the expired
operation-scoped URL for later observations. The new provider refuses those
with 400 (or 429 once the pre-authentication budget is exhausted), because
they carry no backend_storage_id to select the HMAC key.
The seal covers more than the substrate marker. Docker always binds
callbacks.db, releases.db, and retention.db to the same storage UUID and
to distinct store kinds; retention.db remains required when
retain_on_close: false. K3s binds callbacks.db and releases.db.
Every authoritative database must be an unsymlinked, single-link regular file
with exact mode 0600; initialization and normal startup reject an insecure
restore, and runtime re-attestation withdraws the whole backend lineage on
path/inode, link-count, or permission drift.
Initialization accepts only an all-absent fresh set or a complete, valid,
stopped and drained v0.13 set, records that choice in a crash-resumable pending
anchor, and never completes a mixed set. Before evidence inspection it binds the
physical parent of both markers and every authoritative journal; subsequent I/O
is descriptor-relative, so a parent rename, unmount, or same-path recreation
cannot redirect initialization. Normal startup is verification-only: it will
not create, rebind, or repair any authoritative store. A missing, foreign,
cross-kind, or replaced file fails closed, including during periodic cleanup.
The first terminal journal/substrate failure is sticky backend-wide: every
sibling journal and callback delivery refuses with that cause until restart.
diagnostics.db is not authority and may be recreated while stopped. Its
generic opener still refuses symlinks, hard links, non-regular files, and modes
other than exact 0600, but it is not storage-UUID-bound or continuously
re-attested. Back up and restore the marker pair, complete authoritative-store
set, and substrate together; a
matching complete snapshot retains the same lineage, so fence the original
before starting its restored copy. The preflight's only successful stdout is
ready_for_v0_13_storage_identity_adoption; it fails closed on unresolved
v0.13 restore/deprovision crash windows instead of inventing missing authority.
Only complete stack-form v0.13 cohorts are supported. A service-name-less
pre-stack container, -prev remnant, authorityless migration generation, or
partial topology is rejected before mutation; the upgraded backend has no
in-place converter for those shapes.
See Deployment for the complete cutover,
repair, and rollback procedure.
Idempotency:
If a callback is received for a lease that has already been processed (no longer in-flight),
the server still returns 200 OK. A callback carrying an
operation_id that is no longer current is ignored completely: it cannot publish
status, acknowledge or reject the lease, retire teardown state, or mutate placement.
The current lifecycle_id may publish successful maintenance (ready),
runtime failure (failed), or retained teardown status. It cannot settle a
provision or restore operation or touch chain state, and its placement effects
are limited to two: an exact update completion settles that update command, and
a deprovisioned observation atomically retires the capability. If no matching
confirmed placement owner remains, the capability is teardown-only:
success/failure is a 200 no-op, it cannot be reissued for maintenance, and only
the exact deprovisioned observation may retire it and publish retained status.
Retirement is durable before that best-effort push, so a process crash can lose
the event but cannot resurrect authority; retention status remains queryable.
A stale, missing,
or retired ID is a 200 no-op. Tokenless callbacks are accepted only for durable
owners migrated from v0.13.0 and keep the same limits.
This is an explicit compatibility boundary: the stopped placement preparation
records the old route as a legacy lifecycle capability with no ID, callback
ingress matches a tokenless callback to that owner (the backend keeps its side
as a LegacyRuntimeAuthority in releases.db), and no current provision, restore, maintenance, or reconciliation path
can mint a tokenless operation or lifecycle authority.
Response bodies are not guaranteed for this path, so callers should treat the HTTP status
code as the source of truth.
Backend API Specification
Any backend must implement these HTTP endpoints. This section summarizes the contract; BACKEND_GUIDE.md is the complete implementation guide, including SKU handling, callback signing, storage identity, state management, and reconciliation.
Endpoint Reference
All endpoints except /health, /stats, and /metrics require HMAC-SHA256 signature authentication via the X-Fred-Signature header.
That HMAC authenticates requests sent to a backend. Backend response bodies are not signed; machine-readable response codes are contract signals trusted under the configured transport. TLS or an equivalently trusted private network is required if response forgery by an on-path actor is in scope.
Requests and responses are bound to the backend's sealed storage identity (see Durable Backend Storage Identity):
- Mutating
POSTs go to/_fred/storage/{storage-id}/{operation}; the paths below are their short operation names. Reads and inventories keep the paths below. - Once Fred has pinned the identity, every request also carries it as the
HMAC-covered
backend_storage_idquery parameter. - Every response must carry exactly one
X-Fred-Backend-Storage-IDheader naming that storage UUID. Fred rejects a response whose header is missing, duplicated, or different from the pinned identity.
Required
| Method | Path | Auth | Description |
|---|---|---|---|
POST |
/provision |
HMAC | Create resource (async, callback on completion) |
POST |
/deprovision |
HMAC | Remove resource (idempotent) |
POST |
/restart |
HMAC | Restart containers (async, callback on completion) |
POST |
/update |
HMAC | Deploy new manifest (async, callback on completion) |
POST |
/reconcile_custom_domain |
HMAC | Apply a lease's custom domains (idempotent; called every reconciliation sweep) |
GET |
/info/{uuid} |
HMAC | Connection details (host, ports) |
GET |
/provisions |
HMAC | List all provisions (reconciliation) |
GET |
/provisions/{uuid} |
HMAC | Provision diagnostics (status, errors) |
GET |
/retentions |
HMAC | List leases whose data this backend currently retains (reconciliation, restore affinity) |
GET |
/logs/{uuid} |
HMAC | Container logs |
GET |
/releases/{uuid} |
HMAC | Release history |
GET |
/health |
None | Health check |
Optional
| Method | Path | Auth | Description |
|---|---|---|---|
POST |
/restore |
HMAC | Restore a retained lease's data into a new lease (async, callback on completion; retention support) |
GET |
/stats |
None | Resource capacity and usage (least-loaded routing) |
GET |
/metrics |
None | Prometheus metrics |
POST, GET |
/runtime_refresh |
HMAC | docker-backend only, operator-facing: recreate leases created under an older runtime contract, and read progress. Fred never calls it (docker-backend README) |
A backend without soft-delete/retention support returns an empty list from /retentions and, if it serves /restore, 422 (no retained data). A backend that does not answer /retentions is treated as not having answered the sweep.
POST /provision
Start provisioning a resource (async).
Request:
{
"lease_uuid": "550e8400-e29b-41d4-a716-446655440000",
"tenant": "manifest1abc...",
"provider_uuid": "01234567-89ab-cdef-0123-456789abcdef",
"items": [
{"sku": "a1b2c3d4-e5f6-7890-abcd-1234567890ab", "quantity": 2, "service_name": "web"},
{"sku": "b2c3d4e5-f6a7-8901-bcde-2345678901bc", "quantity": 1, "service_name": "db"}
],
"callback_url": "http://fred.example.com:8080/callbacks/provision?operation_id=550e8400-e29b-41d4-a716-446655440000",
"lifecycle_callback_url": "http://fred.example.com:8080/callbacks/provision?lifecycle_id=550e8400-e29b-41d4-a716-446655440000",
"payload": "<base64-encoded-bytes>",
"payload_hash": "abc123..."
}
Fields:
items- Array of lease items: the on-chain SKU UUID (sku),quantity, and the optionalservice_nameandcustom_domain, as recorded on chain. All items belong to the same provider.callback_url- Exact operation-completion URL containing one canonical UUIDv4operation_id; preserve it byte-for-byte and use it only for this provision result. New tokenless provision and restore operations are rejected before durable admission.lifecycle_callback_url- Typed URL for exact restart/update/custom-domain completion plus autonomous failure and deprovision observations. If omitted, bundled backends derive it from the typed operation URL; if supplied, it must match that derivation exactly. Bundled backends keep maintenance completion non-coalescible even though it uses this lifecycle route.payload- Optional base64-encoded deployment payload: the payload Fred stores for the lease (uploaded for itsmeta_hash, written by a later confirmed update, or, for anACTIVElease with ameta_hashthat Fred holds none for, themanifestof the latestactiverelease fromGET /releases/{lease_uuid}). Absent for a lease without onepayload_hash- Optional hex-encoded SHA-256 hash ofpayload(only present with payload)
Response: 202 Accepted
{
"provision_id": "..."
}
GET /info/{lease_uuid}
Get lease information for a provisioned resource.
Response: 200 OK
{
"host": "10.0.0.1",
"ports": {
"8080/tcp": {"host_ip": "0.0.0.0", "host_port": "32768"},
"443/tcp": {"host_ip": "0.0.0.0", "host_port": "32769"}
},
"protocol": "https",
"metadata": {"region": "us-east-1"}
}
Fields (Fred decodes only these and drops any other field):
host- Hostname or IP for connecting to the resourcefqdn- Fully qualified domain name for ingress routing (omitted when not set)ports- Map of container ports to host bindingsinstances- Array of per-instance details (each may include its ownfqdn)services- Map of service name to per-service details (each may include its ownfqdn)protocol- Connection protocol (e.g., "https", "ssh")metadata- Additional key-value metadata
Put any custom key-value data for tenants in metadata (string values).
Response: 404 Not Found if not provisioned.
POST /deprovision
Deprovision a resource (idempotent).
Request:
{
"lease_uuid": "550e8400-e29b-41d4-a716-446655440000"
}
Response: 200 OK
GET /provisions/{lease_uuid}
Get provision diagnostics for a specific lease.
Response: 200 OK
{
"lease_uuid": "550e8400-e29b-41d4-a716-446655440000",
"provider_uuid": "01234567-89ab-cdef-0123-456789abcdef",
"status": "failed",
"fail_count": 3,
"terminal_budget": {"verdict": "retry", "consecutive_failures": 1},
"reason": "ContainerExited",
"message": "container exited unexpectedly",
"created_at": "2024-01-15T10:30:00Z"
}
terminal_budget is the backend's consecutive-failure verdict; see BACKEND_GUIDE.md.
Response: 404 Not Found if not provisioned.
GET /logs/{lease_uuid}
Get logs for a specific lease. Live entries use <service>/<instance> keys;
failed replacement logs use failed/<service>/<instance> after compensation.
Query Parameters:
tail- Number of log lines per container (default: 100)
Response: 200 OK
{
"web/0": "2024-01-15 Starting nginx...\nListening on port 80\n",
"db/0": "2024-01-15 Redis ready\n"
}
Response: 404 Not Found if not provisioned.
GET /provisions
List all provisions (for reconciliation).
GET /provisions is keyset-paginated. Query params: limit (max page size) and continue (a lease UUID — the continue cursor returned by the previous page). The JSON response carries a top-level continue field set to the last record's lease UUID, omitted once the list is exhausted. An invalid limit or a non-UUID continue returns 400, as does a continue cursor supplied without a positive limit. A limit above the server maximum (5000) is coerced down to it rather than rejected. With no params it returns the full list unpaginated (back-compat). One or more lease_uuid query params return just those records. (ENG-380)
Response:
{
"provisions": [
{
"lease_uuid": "...",
"status": "ready",
"lifecycle_generation": {"kind": "typed", "id": "550e8400-e29b-41d4-a716-446655440000"},
"created_at": "2024-01-15T10:30:00Z"
}
],
"continue": "..."
}
lifecycle_generation is an internal reconciliation field, not part of tenant
lease responses. Bundled backends derive it from the exact callback pair they
persisted and report only unknown, tokenless legacy, canonical typed plus
its UUID, or unusable; they never expose either callback URL. Older and
third-party backends may omit it, which providerd treats as unknown.
POST /restart
Restart containers for a lease without changing the manifest (async).
Request:
{
"lease_uuid": "550e8400-e29b-41d4-a716-446655440000",
"maintenance_id": "6ba7b811-9dad-41d1-80b4-00c04fd430c8",
"callback_url": "http://fred.example.com:8080/callbacks/provision?lifecycle_id=550e8400-e29b-41d4-a716-446655440000",
"admitted_at": "2024-01-16T14:00:00.000001Z"
}
admitted_at is Fred's admission time for the command (RFC 3339, UTC); every
replay of the command sends the same value. Echo it as
maintenance_admitted_at on the completion callback.
Response: 202 Accepted
{
"status": "restarting"
}
Error Responses:
400 Bad Request- Invalid request or validation refusal (validation_codeidentifies a validation refusal;errorcarries its detail)404 Not Found- Lease not provisioned409 Conflict- Invalid state for restart (e.g., already restarting or updating)409 Conflictwithcode: "maintenance_expired"- The command is older than the lease's retained maintenance history and was not run; Fred settles it as expired503 Service Unavailable- Capacity refusal (code: "insufficient_resources"); any other 503 is an availability failure and does not prove refusal
POST /update
Deploy a new manifest for a lease, replacing containers (async).
Request:
{
"lease_uuid": "550e8400-e29b-41d4-a716-446655440000",
"maintenance_id": "6ba7b811-9dad-41d1-80b4-00c04fd430c8",
"callback_url": "http://fred.example.com:8080/callbacks/provision?lifecycle_id=550e8400-e29b-41d4-a716-446655440000",
"payload": "<base64-encoded-manifest>",
"admitted_at": "2024-01-16T14:00:00.000001Z"
}
admitted_at is as for /restart.
Response: 202 Accepted
{
"status": "updating"
}
Error Responses:
400 Bad Request- Invalid request/manifest or validation refusal (validation_codeidentifies a validation refusal;errorcarries its detail)404 Not Found- Lease not provisioned409 Conflict- Invalid state for update409 Conflictwithcode: "maintenance_expired"- The command is older than the lease's retained maintenance history and was not run; Fred settles it as expired503 Service Unavailable- Capacity refusal (code: "insufficient_resources"); any other 503 does not prove refusal
POST /restore
Adopt a soft-deleted lease's retained volumes into a new lease and re-deploy its retained manifest (async). lease_uuid is the new lease; from_lease_uuid is the original retained lease. items must shape-match the retained set.
Request:
{
"lease_uuid": "<new-lease-uuid>",
"from_lease_uuid": "<original-retained-lease-uuid>",
"tenant": "manifest1abc...",
"provider_uuid": "01234567-89ab-cdef-0123-456789abcdef",
"items": [{"sku": "a1b2c3d4-e5f6-7890-abcd-1234567890ab", "quantity": 1, "service_name": "app"}],
"callback_url": "http://fred.example.com:8080/callbacks/provision?operation_id=550e8400-e29b-41d4-a716-446655440000",
"lifecycle_callback_url": "http://fred.example.com:8080/callbacks/provision?lifecycle_id=550e8400-e29b-41d4-a716-446655440000"
}
Response: 202 Accepted
{
"status": "restoring"
}
Error Responses:
400 Bad Request- Missinglease_uuid/from_lease_uuid/callback_url, missing or invalid operation UUID/callback pair, equal source and target UUIDs, or items/manifest validation error409 Conflict- Invalid state for restore, or already provisioned. Both return a JSON{"error": "..."}body; the already-provisioned case additionally setscode: "already_provisioned", so the two are distinguished by the presence of that discriminator422 Unprocessable Entity- No retained data for the source lease (also returned by backends that don't support retention)503 Service Unavailable- Insufficient resources. A synchronous refusal returns{"error":"...","code":"insufficient_resources"}so Fred can classify the exact attempt as clearable under the configured transport's trust boundary. A code-less or unknown-code 503 remains ambiguousErrInsufficientResources; a malformed/non-envelope 503 becomesErrMalformedErrorBody. Neither ambiguous class authorizes substitution.
Fred maps only the backend's bare
422(ErrNotRetained— no retained data, nocode) to a tenant-facing404onPOST /v1/leases/{uuid}/restore. A422carryingcode: "demote_exceeds_tier"(ErrDemoteDataExceedsTier— retained data exceeds the requested smaller tier) is forwarded to the tenant as422, not remapped.
GET /retentions
List the leases whose data this backend currently retains (soft-deleted, awaiting restore or grace-reap). Fred's reconciler polls this on every backend to keep restore routing affinity (a restore is routed to the backend that holds the source data). Backends without retention return an empty list.
Response: 200 OK
{
"retentions": [
{"lease_uuid": "550e8400-e29b-41d4-a716-446655440000"}
]
}
The retentions array is always present ([] when empty, never null).
POST /reconcile_custom_domain
Apply the custom domains currently set on a lease's items (idempotent). Fred
calls it on every reconciliation sweep for each ACTIVE lease whose provision
is not failed, so a backend with nothing to change, or without custom-domain
support, must still return success rather than 404. See
BACKEND_GUIDE.md.
Request:
{
"lease_uuid": "550e8400-e29b-41d4-a716-446655440000",
"items": [{"sku": "a1b2c3d4-e5f6-7890-abcd-1234567890ab", "quantity": 1, "service_name": "web", "custom_domain": "app.example.com"}]
}
Response: 204 No Content (Fred also accepts 202 Accepted)
GET /releases/{lease_uuid}
Get release (deployment) history for a lease.
Response: 200 OK
[
{
"version": 1,
"image": "nginx:1.24",
"status": "superseded",
"created_at": "2024-01-15T10:30:00Z",
"manifest": "<base64-encoded-manifest>"
},
{
"version": 2,
"image": "nginx:1.25",
"status": "active",
"created_at": "2024-01-16T14:00:00Z",
"manifest": "<base64-encoded-manifest>"
}
]
Response: 404 Not Found if not provisioned.
Local Fred Development with the Mock Backend
The mock backend can stand in for a compute backend while testing Fred against a real local chain. It supports concurrent provisions with per-lease callback routing, but it is not itself an end-to-end harness and direct calls to its mutation endpoints bypass Fred's placement and chain invariants.
Note: The mock backend ignores the SKU field entirely - all provisions create identical fake resources regardless of SKU. Connection details are deterministically generated from the lease UUID. For implementing a real backend that interprets SKUs, see BACKEND_GUIDE.md.
1. Start the Mock Backend
# Build mock-backend
make build-mock
# Run with a persisted development storage identity and callback secret.
# Generate the UUID once and keep it with this mock deployment's configuration.
install -d -m 700 .fred-dev
mock_storage_id_file="$PWD/.fred-dev/mock-storage-id"
if [[ ! -e "$mock_storage_id_file" ]]; then
(umask 077; set -o noclobber; uuidgen --random | tr '[:upper:]' '[:lower:]' > "$mock_storage_id_file")
fi
# Generate the private development trust anchor once, then reuse the same pair.
# If only one file exists, restore the pair before proceeding.
if [[ ! -e .fred-dev/mock-tls.crt && ! -e .fred-dev/mock-tls.key ]]; then
(umask 077; openssl req -x509 -newkey rsa:3072 -nodes -days 365 \
-keyout .fred-dev/mock-tls.key -out .fred-dev/mock-tls.crt \
-subj '/CN=localhost' \
-addext 'subjectAltName=DNS:localhost,IP:127.0.0.1' \
-addext 'basicConstraints=critical,CA:TRUE')
fi
export MOCK_BACKEND_TLS_CERT_FILE="$PWD/.fred-dev/mock-tls.crt"
export MOCK_BACKEND_TLS_KEY_FILE="$PWD/.fred-dev/mock-tls.key"
MOCK_BACKEND_STORAGE_ID="$(<"$mock_storage_id_file")" \
MOCK_BACKEND_NAME=mock \
MOCK_BACKEND_CALLBACK_SECRET="test-secret-at-least-32-characters-long" \
./build/mock-backend
# Or with custom loopback settings. MOCK_BACKEND_NAME must exactly match the
# corresponding providerd backends[].name.
MOCK_BACKEND_ADDR=127.0.0.1:9001 \
MOCK_BACKEND_NAME=test-backend \
MOCK_BACKEND_STORAGE_ID="$(<"$mock_storage_id_file")" \
MOCK_BACKEND_DELAY=2s \
MOCK_BACKEND_CALLBACK_SECRET="test-secret-at-least-32-characters-long" \
./build/mock-backend
Environment Variables:
| Variable | Description | Default |
|---|---|---|
MOCK_BACKEND_ADDR |
Listen address; explicitly choosing a non-loopback address exposes the development server to that network | 127.0.0.1:9000 |
MOCK_BACKEND_NAME |
Backend name (in responses) | mock-backend |
MOCK_BACKEND_STORAGE_ID |
Persisted canonical UUIDv4 identifying this mock deployment's in-memory storage lineage | (required) |
MOCK_BACKEND_DELAY |
Simulated provisioning delay | 0s |
MOCK_BACKEND_TLS_SKIP_VERIFY |
Development-only callback TLS verification bypass; independent of inbound backend HTTPS | false |
MOCK_BACKEND_TLS_CERT_FILE |
Inbound HTTPS certificate; set with the matching key for authenticated offline inventories | (optional; requires key) |
MOCK_BACKEND_TLS_KEY_FILE |
Inbound HTTPS private key | (optional; requires certificate) |
MOCK_BACKEND_CALLBACK_SECRET |
Per-backend HMAC secret for authenticating inbound provider requests and signing callbacks; must match the providerd backend entry (required, min 32 bytes) | (required) |
MOCK_BACKEND_CLIENT_TIMEOUT |
HTTP client timeout for outbound callbacks | 10s |
MOCK_BACKEND_READ_TIMEOUT |
HTTP server read timeout | 15s |
MOCK_BACKEND_WRITE_TIMEOUT |
HTTP server write timeout | 15s |
MOCK_BACKEND_IDLE_TIMEOUT |
HTTP server idle timeout | 60s |
Important: the mock backend stores resources only in process memory. Do not restart it while Fred has live placements: a stable storage UUID cannot prove continuity for state the process discarded. Stop Fred first and create a fresh development provider/placement authority (or use a new backend name and storage UUID) after such a reset. Reusing the same UUID is safe only while the mock is empty.
Security Warning: Contract routes require the configured HMAC, and the server binds to loopback by default. The mock still accepts callback destinations from authenticated requests and performs outbound HTTP, keeps authority only in memory, and is not production-hardened. Do not bind it to an untrusted network; rotate the development secret if it is disclosed.
2. Configure Fred
Create a config file that points to the mock backend:
# config-test.yaml
provider_uuid: "550e8400-e29b-41d4-a716-446655440000"
provider_address: "manifest1replace-with-registered-address"
keyring_backend: "test"
keyring_dir: "/absolute/path/to/manifest-home"
key_name: "replace-with-provider-key-name"
chain_id: "replace-with-local-chain-id"
grpc_endpoint: "localhost:9090"
websocket_url: "ws://localhost:26657/websocket"
api_listen_addr: ":8080"
backends:
- name: mock
url: "https://localhost:9000"
tls_ca_file: "/absolute/path/to/fred/.fred-dev/mock-tls.crt"
timeout: 30s
default: true
hmac_secret: "test-secret-at-least-32-characters-long"
callback_base_url: "http://localhost:8080"
placement_store_db_path: "/absolute/path/to/fred/.fred-dev/placements.db"
Every identity and endpoint above is deployment-specific. Use the UUID and address of the same newly registered provider, its signer keyring/name, the actual local chain ID and endpoints, and this checkout's physical absolute path. Replace the provider UUID again in the exact confirmation below. The provider must have zero lease history, the parent directory must exist, and the placement database itself must not already exist. Keep that exact physical parent directory in place: its device/inode is included in the printed confirmation and re-attested before descriptor-relative publication.
3. Initialize the Fresh Placement Authority
Keep providerd stopped and tenant/chain lease-mutation ingress fenced. Leave
the empty mock backend running so its inventory endpoints answer, with no
in-flight mutation or callback delivery. Then run:
fresh_confirmation="$(./build/placement-preflight \
-config config-test.yaml \
-print-fresh-confirmation \
-expected-backends '["mock"]')"
./build/placement-preflight \
-config config-test.yaml \
-initialize-fresh \
-expected-backends '["mock"]' \
-confirm-insecure-chain 'I ACCEPT UNAUTHENTICATED CHAIN EVIDENCE FOR LOCAL DEVELOPMENT' \
-confirm-quiesced "$fresh_confirmation"
Start Fred only if the final output line begins with
INITIALIZED_FOR_CUTOVER:. This local plaintext-chain attestation is never
appropriate for a production deployment.
4. Run Fred
./build/providerd -c config-test.yaml
5. Test the Flow
Check the mock backend health without mutating it. With the TLS files exported in step 1 it serves HTTPS, so verify it against the development certificate:
curl --cacert .fred-dev/mock-tls.crt https://localhost:9000/health
For a real Fred flow, create a PENDING lease on the local chain for this exact
provider and one of its registered SKUs, then use Fred's tenant endpoints with
a valid ADR-036 token. Fred will write the placement attempt before dispatching
to the mock and will settle it from the signed callback. Do not simulate this
by posting directly to /provision; that creates foreign inventory outside
Fred's authority. The repository's integration harnesses are the repeatable
automated E2E path.
Load Testing
Build with go build -o build/loadtest ./cmd/loadtest. Authenticated traffic requires a JSON fixture file naming existing leases owned by the supplied tenant key. The tool neither creates leases nor invents successful backend outcomes. For connection reads:
{"leases":[{"lease_uuid":"6ba7b811-9dad-41d1-80b4-00c04fd430c8"}]}
./build/loadtest -target https://fred.example.com \
-traffic authenticated -scenario connection -fixtures fixtures.json \
-tenant-key-file /private/test-tenant.hex -duration 30s -concurrency 10
The key file contains exactly 64 hexadecimal digits for the existing tenant's secp256k1 private key; protect it with mode 0600. -bech32-prefix defaults to manifest. Tokens use Fred's shared ADR-036 and endpoint-specific signing formats. Connection requests are paced to at most one per lease per second because replay-protected tokens carry second-resolution timestamps; provide a lease pool for greater throughput.
For -scenario payload, include a base64 payload with each selected lease. The decoded bytes must be the exact valid manifest whose SHA-256 is the pending lease's on-chain meta_hash. Repeated delivery measures idempotency/conflict handling; after provisioning changes lease state, further uploads can be refused. mixed requires payload and lease fixtures and uses a 40% upload / 60% connection mix unless callback fixtures are present (then 40% / 50% / 10%).
For -scenario callback, supply callbacks entries containing request_uri and body: use the exact origin-relative callback URI and base64-encode the exact JSON body bytes recorded for the test backend, including its backend name, storage UUID and operation/lifecycle route. Supply that backend's matching -callback-secret (at least 32 bytes). The tool validates the current typed callback format and signs the preserved URI/body; it never substitutes random IDs. Recorded callbacks must still describe real state in the target test deployment. Replaying a settled callback exercises acknowledgement/idempotency, not a new deployment.
Use -traffic rejection without fixtures or credentials to stress deliberately unauthenticated requests; -payload-size applies only to that mode. Results count actual 2xx responses as successes and retain refusal/rate-limit status counts. Redirects are not followed, so credentials and recorded callback facts stay at the selected origin.
Project Structure
cmd/
├── providerd/ # Main daemon entry point
├── mock-backend/ # Mock backend for testing
├── docker-backend/ # Docker container backend
├── k3s-backend/ # K3s container backend
├── placement-preflight/ # Offline placement inspector, v0.13 preparer, and fresh initializer
├── placement-repair/ # Offline placement inspector and exact repair tool
├── lease-token/ # Mints ADR-036 tenant bearer tokens for lease endpoints
└── loadtest/ # Load testing tool (not built by `make all`; `go build ./cmd/loadtest`)
internal/
├── adr036/ # ADR-036 signature verification
├── api/ # HTTP server, handlers, rate limiting
├── auth/ # Shared authentication utilities
├── hmacauth/ # HMAC-SHA256 signing and verification
├── backend/ # Backend client and router
│ ├── client.go # HTTP client for backends (with circuit breaker)
│ ├── router.go # SKU-based routing
│ ├── mock.go # In-memory mock for unit tests
│ ├── shared/ # Cross-backend durable journals, typed settlements, callback sender, and diagnostics
│ ├── docker/ # Docker container backend implementation (actor-per-lease)
│ └── k3s/ # K3s container backend implementation
├── backendidentity/ # Durable backend storage identity and bound HTTP routes
├── callbackurl/ # Validated operation/lifecycle callback URL construction
├── chain/ # gRPC client, WebSocket subscriber, signer
│ └── chaintest/ # Test-only mock chain client (not imported by providerd)
├── config/ # Configuration loading and validation
├── fsidentity/ # Descriptor-bound filesystem identity checks
├── maintenanceid/ # Canonical UUIDv4 maintenance-command identity
├── metrics/ # Prometheus metrics definitions
├── operationid/ # Canonical UUIDv4 provision/restore operation identity
├── placementprobe/ # Read-only backend identity/inventory proof client
├── provisioner/ # Provision lifecycle application and runtime composition
│ ├── manager.go # Composition root, callback admission, and runtime ownership
│ ├── handler_set.go # Internal message adapters to application services
│ ├── orchestrator.go # Thin construction-bound handler event capability
│ ├── callback_service.go # Authenticated callback consequence adapter
│ ├── maintenance/ # Durable restart/update application service
│ ├── restore/ # HTTP-neutral adapter to the atomic restore application
│ ├── operation/ # Opaque capabilities, one-shot Registry, observe/drain facet
│ ├── reconciler.go # Level-triggered reconciliation runtime
│ ├── reconcile_inventory.go # Read-only chain/backend inventory collection
│ ├── reconcile_plan.go # Pure evidence-to-action decision table
│ ├── reconcile_projection.go # Atomic durable inventory projection
│ ├── inflight.go # Manager status/drain facade over RuntimeController
│ ├── handlers.go # Shared transport helpers and lease item extraction
│ ├── ack_batcher.go # Batches lease acknowledgments
│ ├── timeout_checker.go # Detects callback timeouts
│ ├── leaseutil.go # Lease helper utilities
│ ├── topics.go # Internal event topics and stable metric labels
│ ├── payload/ # Lease-lifetime deployment payload storage (bbolt)
│ ├── placement/ # Durable authority plus construction-bound purpose applications
│ ├── bridge.go # Chain events -> Watermill
│ └── interfaces.go # Narrow consumer-owned routing, chain, and placement ports
├── scheduler/ # Periodic withdrawal and credit monitoring
├── strictjson/ # Duplicate/unknown-field rejecting authoritative decoders
├── testutil/ # Test fixtures and helpers
├── tlsconfig/ # TLS config builders for the providerd<->backend hop (mTLS, identity pinning)
├── util/ # Shared utility functions
├── uuidv4/ # Shared zero-invalid canonical UUIDv4 representation
└── watcher/ # Cross-provider event detection
Reconciliation
Fred uses level-triggered reconciliation to ensure consistency between chain state and backend state. It does not require a durable queue of chain events: reconciliation can recover missed edges from current state. Accepted backend effects are different and remain protected by durable attempts, mutation journals, receipts, and callback outboxes.
Close events that encounter a locally proven inventory wait, busy lifecycle, or HTTP circuit refusal transfer a typed retry hint to the manager. A fixed four-worker scheduler coalesces hints by lease, retains at most 1,024 queued or executing hints, and retries after one second with backoff capped at five seconds. Each attempt has a 30-second budget and reacquires current ownership; queuing or dispatching a close never means resources were retained or removed. Only the callback and queried backend state report physical completion. Saturation returns an event error, unknown backend effects remain failures, and shutdown cancels and joins the workers. Periodic reconciliation recovers work after lost events, queue saturation, or process restart.
How It Works
Instead of replaying missed events (edge-triggered), reconciliation queries current state. Before reading provisions, the reconciler calls RefreshState on each backend. That call synchronizes an in-process backend; the standard HTTP client deliberately implements it as a no-op because a remote backend owns its own projection. In the normal separate-process deployment, Docker substrate/WAL recovery runs at docker-backend startup and on its own reconcile_interval (default 5m), independently of providerd's reconciliation_interval.
One ReconciliationSweep binds the inventory collector session, durable Store
fence, and process-local operation boundary for a pass. Projection consumes that
combination once. Its projected value can mint live or terminal-orphan action
capabilities only after a bounded exact chain re-read under the matching lease
claim; execution derives the lease and backend from the capability. The
reconciler therefore cannot combine an observation from one sweep with another
Store revision, Registry claim, backend target, or caller-selected settlement.
This is a level-triggered evidence join, not a hand-rolled distributed FSM; the
existing FSM dependency remains confined to backend-local, per-lease actors.
Chain inventory is completed before BeginSweep because it cannot reveal a
backend owner. The sweep then writes a durable pending marker immediately before
backend inventory reads. A successful endpoint read returns only an opaque,
one-shot receipt after positive lease observations are barred from concurrent
mutation. The sweep consumes one backend's provision and retention receipts
together and owns the identity, refresh, and cross-endpoint classification; a
single successful half can only be rejected as untrusted. Outstanding receipts
cannot be sealed or projected. A newer sweep invalidates older unclaimed action
capabilities, while an action already holding its lease claim is captured as
in-flight by the newer causal boundary.
The two backend endpoints are read sequentially. A lease can therefore appear in both when it closes between reads. After validating the paired storage identity and each endpoint's shape, the collector records that lease only as untrusted positive membership: it cannot issue provision, retention, lifecycle, or absence authority for it. Unchanged sibling leases keep their own validated evidence. The ambiguous lease remains fenced until it is durably quarantined or represented by its unchanged confirmed owner: constructor-issued paired overlap can preserve that sole owner only when the pinned storage identity, generation and principal already account for the row, with no unresolved attempt. Explicit rejection cannot issue this observation. Other quarantines need a later valid sweep or operator repair. This partial inventory cannot establish a new admission baseline or prove an empty backend. Identity, refresh, or malformed endpoint failures still reject the backend's entire response.
After an interrupted sweep or process restart, fresh paired responses from every backend that reported a positive during the interrupted sweep chain, each matching its storage pin, can retire inherited inventory fencing once the projection durably accounts for every positive, including quarantined leases. The Store journals each such reporter durably before its positive can become a lease barrier, so a backend that stayed silent cannot hold the provider fenced. Evidence that cannot be attributed to its reporter (failed refresh, identity mismatch, malformed rows, a lease in both endpoints of one backend, or a rejected response) returns the chain to the whole-topology rule before it is used, as does a marker written before reporter tracking: those need paired responses from every configured backend. This endpoint-coverage proof does not establish a new admission baseline or prove a backend empty.
Chain state Backend inventory Durable placement/attempts
│ │ │
└────────────────────┼────────────────────────────┘
▼
one typed ReconciliationSweep
(inventory session + Store fence + Registry boundary)
│
▼
one-shot durable projection
│
▼
bounded exact chain re-read under lease claim
│
▼
opaque action capability
│
┌───────────────┼────────────────┐
▼ ▼ ▼
provision acknowledge exact teardown
(write-ahead) or reject (positive proof)
Reconciliation Triggers
- Startup: Full reconciliation runs immediately on startup
- Periodic: Runs every
reconciliation_interval(default: 5 minutes) - Cross-provider credit depletion: Triggers withdrawal which may close leases
Billing-only leases
A provider can register a SKU purely for on-chain billing and deliver its
service outside Fred. List such SKUs in billing_only_skus. A lease whose items
all use them is billing-only:
- Fred acknowledges it as soon as the lease-created event arrives, and the reconciler retries on its next sweep if that fails. The acknowledgment reads only the chain: no backend needs to answer, and nothing a backend reports, such as data or a workload an earlier deployment left, holds it back. Acknowledgment makes the lease ACTIVE, so billing starts at once.
- Fred never provisions it, never rejects it, and never closes it for a
provisioning reason, including as lost after a backend retirement.
POST /v1/leases/{lease_uuid}/datarefuses its payload with400, andPOST /v1/leases/{lease_uuid}/restorerefuses to restore into it with400; both carryreason: billing_only, in any lease state, because the request races the acknowledgment. - A SKU can be made billing-only while leases already use it. Fred never retries a deployment still in flight for such a lease, and no failure callback, timeout or lost backend ends the lease. Whatever the earlier deployment left on a backend is torn down when the lease ends.
- Billing applies as for any lease: the provider-wide withdrawal settles it, and the credit check closes it once its tenant's credit is exhausted.
A lease that mixes billing-only and deployable SKUs is not billing-only. It takes the normal path, where the backend refuses the SKU it cannot serve and Fred rejects the lease.
State Matrix
| Chain State | Backend State | Action |
|---|---|---|
| PENDING + meta_hash | Not provisioned, payload not uploaded | Await payload upload |
| PENDING + meta_hash | Not provisioned, payload stored | Start provisioning with the stored payload |
| PENDING (no hash) | Not provisioned | Start provisioning |
| PENDING | Provisioned + ready | Acknowledge lease |
| PENDING | Provisioned + provisioning, restarting, updating, or any other status except ready or failed | Wait - no action |
| PENDING | Provisioned + failed | Reject on chain. The backend's failure callback usually rejects it first, with the curated message (a startup crash reports it within seconds); a sweep that finds it rejects with provisioning failed |
| ACTIVE | Provisioned + provisioning | In-flight re-provision - no lifecycle action; reconcile custom domains. The backend bounds it: a definite startup failure reports failed at once, and an attempt the backend could not settle live is settled failed by its periodic recovery, within one reconcile_interval when one of its containers exited or its cohort is partial, otherwise at provision_timeout |
| ACTIVE | Provisioned + ready | Healthy - no lifecycle action; reconcile custom domains |
| ACTIVE | Provisioned + restarting | In-flight restart - no lifecycle action; reconcile custom domains |
| ACTIVE | Provisioned + updating | In-flight update - no lifecycle action; reconcile custom domains |
| ACTIVE | Provisioned + failed | Anomaly: re-provision, unless the backend reports an exhausted consecutive-failure budget (terminal_budget.verdict = exhausted); then close on-chain (workload failed repeatedly) and deprovision. fail_count never decides |
| ACTIVE | Not provisioned | Anomaly: provision |
| CLOSED/REJECTED/EXPIRED | Provisioned | Orphan candidate: bounded exact chain re-read, then deprovision only if still terminal |
| Not found in the PENDING/ACTIVE sweep | Provisioned | Orphan candidate: exact chain re-read; absence, query failure, UNSPECIFIED, or a future state defers cleanup |
| UNSPECIFIED or unknown future state | Any | Defer — no action; never infer terminality |
| ACTIVE / PENDING | Placement lost with a retired backend | Close / reject on chain (backend storage lost); never provision |
| ACTIVE | Not provisioned, no placement row, and a retirement could not prove every live lease had one | Close on chain as lost; never provision |
| PENDING/ACTIVE | Placement conflict/unusable, or an attempt without valid operation metadata | Defer — no action this sweep |
| PENDING/ACTIVE | Any other unresolved attempt | Redeliver the exact recorded operation to the attempted backend (deferred while that backend is fenced); a definitive refusal clears the attempt for the next sweep |
| PENDING/ACTIVE | Reported by a backend's /retentions |
Defer — provisioning would lay a fresh volume over retained data |
| PENDING/ACTIVE | Positive membership from a rejected inventory endpoint (untrusted_positive) |
Durably quarantine — do not treat the rejected payload as ownership or its removal as absence |
| PENDING/ACTIVE | Positive report disagrees with confirmed placement | Defer — no action this sweep |
| PENDING/ACTIVE | Confirmed owner is not configured | Defer and emit an operator-visible lease error |
| PENDING/ACTIVE | Owning backend did not answer | Defer — no action this sweep |
| PENDING, billing-only | any | Acknowledge lease; never provision |
| ACTIVE, billing-only | any | No action |
A billing-only lease is decided ahead of every other row and needs no
backend's answer: acknowledging it or leaving it alone cannot lay a workload
over data. For other live (PENDING/ACTIVE) chain leases, the
placement-safety rows take precedence over every normal state row. A placement lost with a retired
backend (see DEPLOYMENT.md, "Retiring a backend whose storage is lost") is
terminal, needs no backend's answer, and is decided ahead of them all; the
sweep-wide safety gates can still defer the close to a later sweep. A positive backend report is not
sufficient when the durable record remains unusable, still has an unresolved
attempt, or names a different confirmed owner.
A confirmed owner must remain configured and must answer the sweep. Anything
else is deferred and retried, because acting on a lease Fred cannot place
unambiguously risks re-provisioning it onto a healthy peer and laying an empty
volume over live data. Terminal and not-found provisions are handled by the
separately gated destructive passes below.
A provision the reconciler starts sends the lease's stored payload when it has
one. An ACTIVE lease with a meta_hash whose payload is missing, such as a
restored lease, first gets one: the reconciler stores the manifest of the latest
active release its confirmed owner reports on GET /releases/{lease_uuid}.
If the owner reports none or cannot be read, the lease is retried on the next
sweep, never closed for it. A PENDING lease only ever waits for its tenant's
upload. If the backend definitively refuses that provision
as invalid, Fred rejects a PENDING lease, or closes an ACTIVE one, on chain
with the refusal's reason.
"Reconcile custom domains" means the sweep sends POST /reconcile_custom_domain
to the owning backend of each ACTIVE lease whose provision is not failed. A
failure counts as a lease error
(fred_reconciler_actions_total{action="lease_error"}) and is retried on the
next sweep.
When both the placement record and positive backend report are absent, a durable
baseline for the configured topology proves only that Fred completed its initial
inventory bootstrap; it does not turn silence in the current sweep into a
lease-specific fact. On a later partial sweep, the reconciler may dispatch a
genuinely recordless PENDING lease only through a typed admission scope
containing backends that answered both /provisions and /retentions. A
recordless ACTIVE lease is deferred until a complete current view, and any
confirmed owner, attempt, or conflict stays pinned or quarantined when one of
its candidates is silent.
Exact callbacks, chain acknowledgements, status observation, and the safely
evidenced cleanup passes below can continue. A positive observation from the
attempted backend can confirm an attempt. A contradictory positive observation
is unioned with all durable candidates into conflict quarantine. Inventory
absence never clears an attempt or conflict. Incomplete sweeps report
fred_reconciler_sweep_complete as 0 and increment
fred_reconciler_runs_total{outcome="degraded"}; that gauge is an observation,
not a global write-authority switch.
Overlapping provision/retention membership rejects only that lease's payload.
Missing or inconsistent endpoint identities, malformed responses, and conflicts
with a durable storage pin reject the backend response. In either case, raw
positive membership survives restart as untrusted_positive quarantine.
A sole candidate of that exact kind can self-resolve when a later sealed
observation accounts for that lease across the complete configured topology:
every backend supplies identity-valid paired endpoints, the same backend is its
only trusted reporter, and every peer proves its absence. Ambiguity about other
leases does not prevent this proof. Missing peers, continued ambiguity for this
lease, a different or second reporter, unknown ownership, and ordinary conflicts
cannot self-resolve. Retention evidence cannot settle an unresolved operation
attempt or issue lifecycle authority.
The three passes that delete durable state — orphan deprovision, payload cleanup, placement pruning — keep running on a degraded sweep, scoped to what that sweep can positively account for:
| Pass | What must hold before it deletes |
|---|---|
| Orphan deprovision | The chain, re-read per candidate, reports the lease terminal (CLOSED/REJECTED/EXPIRED) |
| Payload cleanup | The same chain confirmation, for any payload whose lease is absent from the snapshot; the pass reads no backend state at all |
| Placement pruning | The record's own backend answered both /provisions and /retentions, plus the existing on-backend, in-flight, chain-terminal and grace-window gates |
Absence is never evidence. The two lease-list queries are filtered to
PENDING/ACTIVE and are not atomic, so "missing from the sweep" means terminal
or never-known or created seconds ago; and because the ledger never deletes a
lease, a chain with no record of one means a phantom provision, a wrong or reset
chain, or a lagging RPC node. A failed re-read is likewise not absence. Every
such case keeps the state and increments
fred_reconciler_cleanup_skips_total{pass,reason}.
Security
- Tenant Authentication: ADR-036 secp256k1 signatures with 30-second token expiry and low-S normalization
- Replay Protection: Persistent token tracking (bbolt) with fail-closed semantics on selected tenant endpoints, including the
/connectionread;/datauploads use a separate idempotency guard - Callback Authentication: Per-backend HMAC-SHA256 keys; timestamps bound same-endpoint replay to a 5-minute window, while method/URI binding prevents cross-endpoint replay
- Rate Limiting: Tenant routes use a shared per-IP bucket (10 RPS) and a per-tenant bucket (5 RPS). Callbacks have independent pre-authentication ingress and authenticated storage-lineage budgets, so tenant traffic cannot consume callback capacity. Behind a proxy, set
trusted_proxiesso ingress keys on the real client IP - Container Hardening: Drop all capabilities, no-new-privileges, read-only rootfs, PID limits, network isolation
- Input Validation: UUID format checks, URL scheme/host validation, manifest parsing, image allowlisting
- Production Mode: Enforces replay protection, blocks TLS skip-verify, SSRF checks on all URLs
- Constant-Time Comparisons:
hmac.Equalandsubtle.ConstantTimeComparefor all secret comparisons
See SECURITY.md for the full security architecture, authentication flows, replay protection rationale, and known limitations.
Performance
Fred's event processing pipeline has been extensively benchmarked:
| Metric | Result |
|---|---|
| Publishing rate | 147,000 events/sec |
| End-to-end throughput | 56,000+ events/sec |
| Sustained load | 5,000 events/sec (30s, 100% success) |
| 1M event test | 17.7 seconds, 100% processed |
See PERFORMANCE.md for detailed benchmarks, stress test results, and comparison with other solutions.
Documentation
| Audience | Doc |
|---|---|
| Operators | DEPLOYMENT.md — host requirements, filesystem setup, TLS, multi-host, backups, upgrades |
| Operators | OPERATIONS.md — runbook, alert interpretation, tuning, recovery |
| Operators | SECURITY.md — auth, replay protection, hardening |
| Operators | PERFORMANCE.md — benchmarks and capacity planning |
| Tenants | docs/tenant-quickstart.md — end-to-end API walkthrough |
| Tenants | docs/manifest-guide.md — manifest schema and validation rules |
| Tenants | docs/manifest-schema.json — formal JSON Schema |
| Backend developers | BACKEND_GUIDE.md — implementing a third-party backend |
| Fred developers | ARCHITECTURE.md — design decisions, event flow, observability |
| Fred developers | CONTRIBUTING.md — dev setup, tests, code style, PRs |
| Fred developers | internal/backend/docker/README.md — Docker backend internals |
Dependencies
- Go 1.26.10+ (per the
go 1.26.10directive ingo.mod; also usessync.WaitGroup.Go(),testing.B.Loop(),rangeover integers) - Watermill (event routing)
- Cosmos SDK v0.50.14
- CometBFT v0.38.x
- manifest-ledger (for billing/sku types)
License
Licensed under the Apache License, Version 2.0. See LICENSE for the full text.
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
docker-backend
command
docker-backend is an HTTP server that implements the Fred backend protocol for provisioning Docker containers.
|
docker-backend is an HTTP server that implements the Fred backend protocol for provisioning Docker containers. |
|
k3s-backend
command
k3s-backend is an HTTP server that implements the Fred backend protocol for provisioning K3s workloads.
|
k3s-backend is an HTTP server that implements the Fred backend protocol for provisioning K3s workloads. |
|
lease-token
command
Command lease-token mints a Fred tenant bearer token (ADR-036 signed) from a real BIP39 mnemonic, for authenticating to lease endpoints such as POST /v1/leases/{lease_uuid}/restore.
|
Command lease-token mints a Fred tenant bearer token (ADR-036 signed) from a real BIP39 mnemonic, for authenticating to lease endpoints such as POST /v1/leases/{lease_uuid}/restore. |
|
loadtest
command
Package main provides a load testing tool for fred.
|
Package main provides a load testing tool for fred. |
|
mock-backend
command
Mock Backend for Fred
|
Mock Backend for Fred |
|
placement-preflight
command
Command placement-preflight verifies that a stopped v0.13 placement database is eligible for Fred's one-time lifecycle and storage-identity migration.
|
Command placement-preflight verifies that a stopped v0.13 placement database is eligible for Fred's one-time lifecycle and storage-identity migration. |
|
placement-repair
command
Command placement-repair provides read-only offline placement discovery and deliberately narrow, target-bound mutation modes: refusing one exact attempt, resolving one exact conflict, adopting an observed lifecycle generation, attesting a restored backup, and retiring a lost backend.
|
Command placement-repair provides read-only offline placement discovery and deliberately narrow, target-bound mutation modes: refusing one exact attempt, resolving one exact conflict, adopting an observed lifecycle generation, attesting a restored backup, and retiring a lost backend. |
|
providerd
command
|
|
|
internal
|
|
|
adr036
Package adr036 implements ADR-036 off-chain message signing and verification.
|
Package adr036 implements ADR-036 off-chain message signing and verification. |
|
api
Package api provides the HTTP API server for fred.
|
Package api provides the HTTP API server for fred. |
|
auth
Package auth provides the canonical signed-message formats used by Fred's tenant authentication.
|
Package auth provides the canonical signed-message formats used by Fred's tenant authentication. |
|
backend
Package backend provides the interface and client for communicating with provisioning backends.
|
Package backend provides the interface and client for communicating with provisioning backends. |
|
backend/docker
Package docker implements the Backend interface for Docker container provisioning.
|
Package docker implements the Backend interface for Docker container provisioning. |
|
backend/docker/imageexec
Package imageexec admits immutable container images and binds their use to typed Docker and Compose execution sinks.
|
Package imageexec admits immutable container images and binds their use to typed Docker and Compose execution sinks. |
|
backend/docker/imagefetch
Package imagefetch admits registry content before giving Docker any bytes.
|
Package imagefetch admits registry content before giving Docker any bytes. |
|
backend/docker/tenantseccomp
Package tenantseccomp owns the seccomp profile fred applies to every container it creates for a tenant, and every Docker security option that names a seccomp profile.
|
Package tenantseccomp owns the seccomp profile fred applies to every container it creates for a tenant, and every Docker security option that names a seccomp profile. |
|
backend/k3s
Package k3s is the scaffold for Fred's K3s backend.
|
Package k3s is the scaffold for Fred's K3s backend. |
|
backend/shared
Package shared provides backend-agnostic components that can be reused across different backend implementations (Docker, Kubernetes, Nomad, etc.).
|
Package shared provides backend-agnostic components that can be reused across different backend implementations (Docker, Kubernetes, Nomad, etc.). |
|
backend/shared/completion
Package completion owns the bounded completion window of an admitted effect.
|
Package completion owns the bounded completion window of an admitted effect. |
|
backend/shared/imagebudget
Package imagebudget keeps immutable image verification work and physical daemon allocation in separate dimensions across preparation and durable pins.
|
Package imagebudget keeps immutable image verification work and physical daemon allocation in separate dimensions across preparation and durable pins. |
|
backend/shared/leasesm
Package leasesm owns the per-lease state machine and actor, plus the substrate-agnostic seams they consume.
|
Package leasesm owns the per-lease state machine and actor, plus the substrate-agnostic seams they consume. |
|
backend/shared/leasesm/failurecause
Package failurecause attributes one lease workload failure to its cause, for the consecutive-failure terminal budget the lease state machine keeps (ENG-799).
|
Package failurecause attributes one lease workload failure to its cause, for the consecutive-failure terminal budget the lease state machine keeps (ENG-799). |
|
backend/shared/manifest
Package manifest defines the tenant-facing container manifest schema (StackManifest, holding a map of named per-service Manifest entries), JSON parsing, and validation.
|
Package manifest defines the tenant-facing container manifest schema (StackManifest, holding a map of named per-service Manifest entries), JSON parsing, and validation. |
|
backend/shared/substratemutation
Package substratemutation defines the causal protocol between a durable mutation journal and an attested physical-substrate executor.
|
Package substratemutation defines the causal protocol between a durable mutation journal and an attested physical-substrate executor. |
|
backend/shared/workbarrier
Package workbarrier provides a reference-counted barrier whose zero signal is exposed as a real channel, so callers can wait-with-timeout using a plain select without spawning helper goroutines that would leak when the count never drops to zero.
|
Package workbarrier provides a reference-counted barrier whose zero signal is exposed as a real channel, so callers can wait-with-timeout using a plain select without spawning helper goroutines that would leak when the count never drops to zero. |
|
backendidentity
Package backendidentity defines the immutable identity of a backend's durable storage substrate.
|
Package backendidentity defines the immutable identity of a backend's durable storage substrate. |
|
backendname
Package backendname defines the shared syntax for configured and durable backend identifiers.
|
Package backendname defines the shared syntax for configured and durable backend identifiers. |
|
callbackurl
Package callbackurl validates and constructs the static callback URL boundary shared by providerd configuration and callback builders.
|
Package callbackurl validates and constructs the static callback URL boundary shared by providerd configuration and callback builders. |
|
chain
Package chain provides clients for interacting with the Manifest blockchain.
|
Package chain provides clients for interacting with the Manifest blockchain. |
|
chain/chaintest
Package chaintest provides test doubles for the chain package.
|
Package chaintest provides test doubles for the chain package. |
|
config
Package config handles configuration loading and validation for fred.
|
Package config handles configuration loading and validation for fred. |
|
configyaml
Package configyaml decodes one operator configuration document, rejecting unknown struct fields and additional YAML documents instead of ignoring them.
|
Package configyaml decodes one operator configuration document, rejecting unknown struct fields and additional YAML documents instead of ignoring them. |
|
fsidentity
Package fsidentity provides retained Linux directory capabilities for security-sensitive filesystem publication.
|
Package fsidentity provides retained Linux directory capabilities for security-sensitive filesystem publication. |
|
fstree
Package fstree removes and walks directory trees whose shape fred does not control, such as the contents of a tenant volume, with resources that stay bounded however deep or wide the tree is.
|
Package fstree removes and walks directory trees whose shape fred does not control, such as the contents of a tenant volume, with resources that stay bounded however deep or wide the tree is. |
|
fstree/internal/at
Package at is fstree's descriptor layer: the only code that turns an open directory and a name into a syscall.
|
Package at is fstree's descriptor layer: the only code that turns an open directory and a name into a syscall. |
|
healthprobe
Package healthprobe correlates health diagnostics without carrying admission authority or changing a probe's deadline or verdict.
|
Package healthprobe correlates health diagnostics without carrying admission authority or changing a probe's deadline or verdict. |
|
hmacauth
Package hmacauth provides HMAC-SHA256 signing and verification for request authentication between Fred components (Fred ↔ backends).
|
Package hmacauth provides HMAC-SHA256 signing and verification for request authentication between Fred components (Fred ↔ backends). |
|
httpurl
Package httpurl contains validation shared by HTTP URL construction boundaries.
|
Package httpurl contains validation shared by HTTP URL construction boundaries. |
|
maintenanceid
Package maintenanceid defines the caller-issued identity of one restart or update command.
|
Package maintenanceid defines the caller-issued identity of one restart or update command. |
|
metrics
Package metrics provides Prometheus metrics for fred observability.
|
Package metrics provides Prometheus metrics for fred observability. |
|
metrics/background
Package background holds the Prometheus collectors for background-goroutine health — the whole `fred_background_*` family.
|
Package background holds the Prometheus collectors for background-goroutine health — the whole `fred_background_*` family. |
|
operationid
Package operationid defines the identity of one provision or restore operation.
|
Package operationid defines the identity of one provision or restore operation. |
|
placementprobe
Package placementprobe collects complete backend inventory snapshots for offline placement safety tools.
|
Package placementprobe collects complete backend inventory snapshots for offline placement safety tools. |
|
placementsnapshot
Package placementsnapshot takes periodic online snapshots of providerd's placements.db and payloads.db into one operator-chosen directory, so losing the host costs at most one snapshot interval of placement history instead of everything since the last manual backup.
|
Package placementsnapshot takes periodic online snapshots of providerd's placements.db and payloads.db into one operator-chosen directory, so losing the host costs at most one snapshot interval of placement history instead of everything since the last manual backup. |
|
provisioner
Package provisioner implements lease lifecycle application services and their Watermill runtime composition.
|
Package provisioner implements lease lifecycle application services and their Watermill runtime composition. |
|
provisioner/callbackwire
Package callbackwire decodes the exact bytes and route covered by callback authentication.
|
Package callbackwire decodes the exact bytes and route covered by callback authentication. |
|
provisioner/inventory
Package inventory owns the process-local capability that turns two independently collected backend inventory endpoints into negative evidence.
|
Package inventory owns the process-local capability that turns two independently collected backend inventory endpoints into negative evidence. |
|
provisioner/leaseitems
Package leaseitems converts authoritative chain lease items into the backend protocol representation.
|
Package leaseitems converts authoritative chain lease items into the backend protocol representation. |
|
provisioner/lifecycle
Package lifecycle defines the opaque identity carried by observational backend callback URLs.
|
Package lifecycle defines the opaque identity carried by observational backend callback URLs. |
|
provisioner/maintenance
Package maintenance is the authenticated API adapter for the construction-bound placement maintenance application.
|
Package maintenance is the authenticated API adapter for the construction-bound placement maintenance application. |
|
provisioner/operation
Package operation defines the identities and capabilities used to coordinate provisioner lifecycle operations.
|
Package operation defines the identities and capabilities used to coordinate provisioner lifecycle operations. |
|
provisioner/payload
Package payload implements bbolt-backed lease-lifetime storage for tenant deployment payloads.
|
Package payload implements bbolt-backed lease-lifetime storage for tenant deployment payloads. |
|
provisioner/placement
Package placement implements Fred's durable multi-backend placement authority.
|
Package placement implements Fred's durable multi-backend placement authority. |
|
provisioner/restore
Package restore coordinates restoration of a fresh lease from retained data.
|
Package restore coordinates restoration of a fresh lease from retained data. |
|
provisioner/storeauthority
Package storeauthority linearizes providerd-local durable-store commits with terminal withdrawal of the filesystem authority that names those stores.
|
Package storeauthority linearizes providerd-local durable-store commits with terminal withdrawal of the filesystem authority that names those stores. |
|
provisioner/terminalverdict
Package terminalverdict reads a backend's consecutive-failure budget observation (ENG-799) into the only value providerd's failure-budget lease close accepts.
|
Package terminalverdict reads a backend's consecutive-failure budget observation (ENG-799) into the only value providerd's failure-budget lease close accepts. |
|
scheduler
Package scheduler implements periodic withdrawal of accumulated lease fees and credit-depletion monitoring.
|
Package scheduler implements periodic withdrawal of accumulated lease fees and credit-depletion monitoring. |
|
strictjson
Package strictjson provides an exact JSON decoding boundary for durable authority.
|
Package strictjson provides an exact JSON decoding boundary for durable authority. |
|
testsupport/backendclient
Package backendclient constructs identity-bound HTTP backend clients around legacy-shaped fixture servers.
|
Package backendclient constructs identity-bound HTTP backend clients around legacy-shaped fixture servers. |
|
testsupport/placementstore
Package placementstore contains explicit test-fixture construction for the placement authority.
|
Package placementstore contains explicit test-fixture construction for the placement authority. |
|
testutil
Package testutil provides test fixtures and utilities for testing.
|
Package testutil provides test fixtures and utilities for testing. |
|
tlsconfig
Package tlsconfig builds *tls.Config values for Fred's internal providerd <-> backend HTTP hop (ENG-103).
|
Package tlsconfig builds *tls.Config values for Fred's internal providerd <-> backend HTTP hop (ENG-103). |
|
util
Package util provides shared utility functions.
|
Package util provides shared utility functions. |
|
uuidv4
Package uuidv4 contains the shared canonical UUIDv4 wire mechanics used by distinct typed identities.
|
Package uuidv4 contains the shared canonical UUIDv4 wire mechanics used by distinct typed identities. |
|
watcher
Package watcher detects cross-provider credit depletion and triggers our own withdrawal in response.
|
Package watcher detects cross-provider credit depletion and triggers our own withdrawal in response. |