Public release release-6d1a121b0a8a
Source commit: 6d1a121b0a8a636aac95998fe5d15c24c78eb7d0 Public tree identity: sha256:2bc22807f5b838286678b65d3f550b8ae4714ae673622041d4c2516a7c5b7926
This commit is contained in:
commit
21f097d9a8
210 changed files with 78649 additions and 0 deletions
57
docs/architecture.md
Normal file
57
docs/architecture.md
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
# Architecture
|
||||
|
||||
Clusterflux separates a control plane from execution nodes.
|
||||
|
||||
## Coordinator
|
||||
|
||||
The coordinator owns identity scope, one-active-process admission, task
|
||||
placement, attempt history, Debug Epoch coordination, VFS metadata, artifact
|
||||
metadata, quotas, and secure relay reservations. Its async main may park and wake
|
||||
without consuming node compute.
|
||||
|
||||
The coordinator does not execute arbitrary native commands or hosted
|
||||
containers. It also does not become a durable artifact store merely because it
|
||||
coordinates a download.
|
||||
|
||||
## Nodes
|
||||
|
||||
A node is an enrolled public-key identity. It reports capabilities and periodic
|
||||
heartbeats. The coordinator derives whether it is live from the last accepted
|
||||
heartbeat; a client-supplied "online" field is not placement authority.
|
||||
|
||||
Nodes resolve bundle environments, execute Wasm tasks, run native commands, and
|
||||
retain artifact bytes. A node must prove the tenant, project, process, task, and
|
||||
key scope on every signed request.
|
||||
|
||||
## Virtual process and tasks
|
||||
|
||||
A Coordinator Project admits one active virtual process. The process contains:
|
||||
|
||||
- one coordinator-hosted async main;
|
||||
- logical task instances created by that main;
|
||||
- one or more attempts for each logical task;
|
||||
- joins tied to the logical task, not to an individual attempt.
|
||||
|
||||
"FailFast" makes a terminal failure visible to the join immediately.
|
||||
"AwaitOperator" keeps the join pending while an operator accepts, cancels, or
|
||||
restarts the failed task. Restart creates a distinct attempt but preserves the
|
||||
logical task identity.
|
||||
|
||||
A terminal process releases the active slot automatically. A task parked for an
|
||||
operator is not terminal and continues to occupy the slot.
|
||||
|
||||
## Durable and live state
|
||||
|
||||
Projects, identities, credentials, permissions, hosted policy records, relay
|
||||
period usage, and relay reservation accounting may be stored in Postgres.
|
||||
Active processes, task placement, Debug Epochs, transient VFS state, relay byte
|
||||
streams, and live node state are bounded in memory and are not reconstructed as
|
||||
running after a coordinator restart. Pre-restart relay reservations are
|
||||
reconciled as abandoned before new links can be issued.
|
||||
|
||||
## Protocol lanes
|
||||
|
||||
Human clients use scoped sessions. Agents and nodes use separate signed
|
||||
public-key identities. Operator actions use a separate administrative
|
||||
credential. Authority comes from the authenticated lane and server-side scope,
|
||||
never from identity fields in an untrusted request body.
|
||||
38
docs/artifacts.md
Normal file
38
docs/artifacts.md
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# Artifacts
|
||||
|
||||
Clusterflux separates artifact metadata from artifact bytes.
|
||||
|
||||
## Publish metadata
|
||||
|
||||
`flush()` makes a VFS artifact visible to downstream tasks and records its
|
||||
digest, size, producer task and attempt, and retaining locations. It does not
|
||||
upload bytes to the coordinator by default.
|
||||
|
||||
Your node retains artifact bytes on a best-effort basis. If every retaining node
|
||||
is lost, stale, revoked, or garbage-collects the bytes, the artifact is
|
||||
unavailable. Clusterflux reports that state instead of inventing durable
|
||||
storage.
|
||||
|
||||
## Move bytes explicitly
|
||||
|
||||
Use `sync()` or an explicit export when you need another node or your own storage
|
||||
system to hold the bytes. Same-node reuse avoids a coordinator transfer.
|
||||
|
||||
## Download
|
||||
|
||||
~~~bash
|
||||
clusterflux artifact list --process <process-id>
|
||||
clusterflux artifact download <artifact-id> --to ./output.bin --max-bytes 67108864
|
||||
~~~
|
||||
|
||||
Before returning a link, the service checks authorization, current retention,
|
||||
source-node liveness, live size, per-project and per-account policy, and relay
|
||||
capacity. It reserves the affordable ingress and egress budget atomically. The
|
||||
scoped link is unguessable, expires, can be revoked, and is bound to the actor,
|
||||
tenant, project, process, artifact, and policy context.
|
||||
|
||||
The reverse stream counts framing, base64 expansion, failed bytes, and abandoned
|
||||
transfers. The CLI verifies the final digest. Hosted policy may impose
|
||||
per-project, per-tenant, and per-account size, concurrency, and period limits.
|
||||
Operators may disable relay traffic as an emergency safety control; one tenant's
|
||||
period usage does not consume a shared customer byte quota.
|
||||
51
docs/debugging.md
Normal file
51
docs/debugging.md
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# Debugging
|
||||
|
||||
The VS Code extension uses the Debug Adapter Protocol and the coordinator's
|
||||
authoritative process, task, attempt, and Debug Epoch APIs.
|
||||
|
||||
## Launch
|
||||
|
||||
Open your project and start "Clusterflux: Launch Virtual Process". The adapter
|
||||
acknowledges launch configuration immediately, then builds and starts the
|
||||
selected entrypoint in the background. You can keep using the debug UI while a
|
||||
long-running process is attached. The adapter replaces its thread view with
|
||||
coordinator task snapshots as they arrive; product mode does not infer threads
|
||||
from completion events or demo fixtures.
|
||||
|
||||
Each task view includes its logical task, attempt ID, definition, state, node,
|
||||
environment, arguments, typed handles, command status, VFS checkpoint, probe
|
||||
source when known, and restart compatibility. Unknown source remains unknown.
|
||||
|
||||
## Breakpoints and Debug Epochs
|
||||
|
||||
When a breakpoint is hit, the coordinator asks every active participant to
|
||||
freeze. A fully frozen epoch is a consistent all-participant view.
|
||||
|
||||
You do not need to configure a breakpoint before launch. Breakpoint changes are
|
||||
sent to the running coordinator, and a stopped event is emitted only after an
|
||||
executing Wasm probe reports a real hit. Pause, continue, inspection, and
|
||||
disconnect requests remain responsive while the adapter observes the runtime in
|
||||
the background. Attach does not fabricate an initial breakpoint.
|
||||
|
||||
If one or more participants cannot freeze within five seconds, the epoch becomes
|
||||
partially frozen when at least one participant did freeze. The adapter warns
|
||||
that running and frozen tasks do not form a consistent global snapshot. You may
|
||||
inspect the acknowledged participants and resume them cleanly. Missing or failed
|
||||
participants remain explicit.
|
||||
|
||||
Stack frames, Wasm locals, task arguments, handles, command status, and output
|
||||
come from runtime acknowledgements. No frame or value is fabricated when the
|
||||
runtime did not report it.
|
||||
|
||||
## Continue and restart
|
||||
|
||||
Continue responds immediately, then resumes only participants that acknowledged
|
||||
the freeze while runtime observation continues in the background.
|
||||
|
||||
Restarting a terminal task rebuilds the bundle and checks its clean entry
|
||||
checkpoint. A compatible restart creates a new attempt under the same logical
|
||||
task identity. Earlier attempt history remains inspectable, and the logical join
|
||||
continues. An active task or an incompatible boundary requires a whole-process
|
||||
restart.
|
||||
|
||||
Source stepping is not simulated. Unsupported stepping fails explicitly.
|
||||
34
docs/environments.md
Normal file
34
docs/environments.md
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# Environments
|
||||
|
||||
Declare environments in the bundle under:
|
||||
|
||||
~~~text
|
||||
envs/<name>/Containerfile
|
||||
envs/<name>/Dockerfile
|
||||
~~~
|
||||
|
||||
Reference one by logical name:
|
||||
|
||||
~~~rust
|
||||
use clusterflux::env;
|
||||
|
||||
let linux = env!("linux");
|
||||
~~~
|
||||
|
||||
Bundle inspection reports every discovered environment and its digest:
|
||||
|
||||
~~~bash
|
||||
clusterflux bundle inspect --project .
|
||||
~~~
|
||||
|
||||
The bundle definition is authoritative for every spawn. The coordinator passes
|
||||
the declared environment identity and digest in the TaskSpec, and the node
|
||||
resolves that exact definition. A same-named local recipe with different bytes
|
||||
is rejected rather than substituted.
|
||||
|
||||
On Linux, container-backed environments use rootless Podman. Clusterflux does
|
||||
not enable privileged containers by default.
|
||||
|
||||
A task may use a bind-mounted local checkout for speed. That source path is
|
||||
non-hermetic. Choose a source snapshot when you need a reproducible input
|
||||
identity independent of the current working tree.
|
||||
125
docs/getting-started.md
Normal file
125
docs/getting-started.md
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
# Getting started
|
||||
|
||||
This guide uses the hosted coordinator. For your own coordinator, complete
|
||||
[Self-hosting](self-hosting.md) first and then return to the node and run steps.
|
||||
|
||||
## 1. Install the commands
|
||||
|
||||
~~~bash
|
||||
cargo install --path crates/clusterflux-cli --bin clusterflux
|
||||
cargo install --path crates/clusterflux-node --bin clusterflux-node
|
||||
cargo install --path crates/clusterflux-dap --bin clusterflux-debug-dap
|
||||
~~~
|
||||
|
||||
Install rootless Podman on each Linux node that will execute container-backed
|
||||
environments.
|
||||
|
||||
## 2. Sign in and select your hosted project
|
||||
|
||||
~~~bash
|
||||
clusterflux login --browser
|
||||
clusterflux auth status
|
||||
clusterflux project list
|
||||
clusterflux project select <hosted-project-id>
|
||||
~~~
|
||||
|
||||
The browser flow is owned by the configured Authentik identity provider. The CLI
|
||||
stores an opaque Clusterflux session, not provider authorization codes or
|
||||
provider tokens. Hosted login creates or links your single project; hosted
|
||||
admission rejects additional project creation.
|
||||
|
||||
## 3. Enroll and start a node
|
||||
|
||||
Create a short-lived grant:
|
||||
|
||||
~~~bash
|
||||
clusterflux node enroll --project-id <hosted-project-id> --json
|
||||
~~~
|
||||
|
||||
Create the local node identity and exchange the grant once:
|
||||
|
||||
~~~bash
|
||||
clusterflux node attach \
|
||||
--project-id <hosted-project-id> \
|
||||
--node workstation \
|
||||
--enrollment-grant "$ENROLLMENT_GRANT" \
|
||||
--json
|
||||
~~~
|
||||
|
||||
Clusterflux creates and stores the node key locally with restricted permissions.
|
||||
The enrollment grant is not needed again. Stop and restart the worker with the
|
||||
same stored identity:
|
||||
|
||||
~~~bash
|
||||
clusterflux-node \
|
||||
--coordinator https://clusterflux.michelpaulissen.com \
|
||||
--tenant "$TENANT" \
|
||||
--project-id <hosted-project-id> \
|
||||
--node workstation \
|
||||
--project-root "$PWD" \
|
||||
--worker \
|
||||
--emit-ready
|
||||
~~~
|
||||
|
||||
Supplying `--public-key` and `CLUSTERFLUX_NODE_PRIVATE_KEY` is an advanced option
|
||||
for nodes whose key material is managed externally. Check server-derived
|
||||
liveness with:
|
||||
|
||||
~~~bash
|
||||
clusterflux node list
|
||||
clusterflux node status workstation
|
||||
~~~
|
||||
|
||||
## 4. Inspect and run a bundle
|
||||
|
||||
A project contains `clusterflux.toml`, Rust workflow source, and any declared
|
||||
environments under `envs/`. Task arguments and handles use portable canonical
|
||||
representations; they are not host pointers or shared process memory.
|
||||
|
||||
~~~bash
|
||||
clusterflux bundle inspect --project .
|
||||
clusterflux run --project . build
|
||||
~~~
|
||||
|
||||
Choose another entrypoint by replacing `build`. Clusterflux rejects an oversized
|
||||
or invalid bundle before it creates the virtual process.
|
||||
|
||||
## 5. Inspect tasks and output
|
||||
|
||||
~~~bash
|
||||
clusterflux process list
|
||||
clusterflux process status
|
||||
clusterflux task list
|
||||
clusterflux logs
|
||||
clusterflux artifact list
|
||||
~~~
|
||||
|
||||
A failed task configured with `AwaitOperator` remains visible as awaiting action.
|
||||
Restart it as a new attempt under the same logical task identity:
|
||||
|
||||
~~~bash
|
||||
clusterflux task restart <task-id> --process <process-id> --yes
|
||||
~~~
|
||||
|
||||
## 6. Debug in VS Code
|
||||
|
||||
Open the project in VS Code and start `Clusterflux: Launch Virtual Process`.
|
||||
Set a breakpoint on a generated probe location and use Threads, Stack,
|
||||
Variables, Continue, Pause, and Restart.
|
||||
|
||||
A fully frozen Debug Epoch gives a consistent all-participant view. If a
|
||||
participant cannot freeze within five seconds, the adapter reports a partial
|
||||
epoch. You may inspect frozen participants, but values across running and frozen
|
||||
tasks are not a consistent global snapshot. Continue resumes only participants
|
||||
that acknowledged the freeze.
|
||||
|
||||
## 7. Download an artifact
|
||||
|
||||
~~~bash
|
||||
clusterflux artifact list --process <process-id>
|
||||
clusterflux artifact download <artifact-id> --to ./output.bin --max-bytes 67108864
|
||||
~~~
|
||||
|
||||
The command opens a scoped, expiring download and verifies the artifact digest.
|
||||
It fails if the retaining node is stale, the bytes were garbage collected, the
|
||||
digest or size changed, or policy cannot reserve the transfer.
|
||||
66
docs/nodes.md
Normal file
66
docs/nodes.md
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
# Nodes
|
||||
|
||||
Your node runs real commands and retains real output bytes. Enroll it once, then
|
||||
restart it with the same public-key identity.
|
||||
|
||||
## Enroll
|
||||
|
||||
~~~bash
|
||||
clusterflux node enroll --project-id <project-id> --json
|
||||
clusterflux node attach \
|
||||
--project-id <project-id> \
|
||||
--node workstation \
|
||||
--enrollment-grant "$ENROLLMENT_GRANT"
|
||||
~~~
|
||||
|
||||
Treat the enrollment grant as a short-lived secret. It is exchanged once and is
|
||||
not a worker credential. By default, `clusterflux node attach` creates and stores
|
||||
a local node key with restricted permissions. Use an explicit `--public-key`
|
||||
with `CLUSTERFLUX_NODE_PRIVATE_KEY` only when external secret management owns the
|
||||
key pair.
|
||||
|
||||
## Run the worker
|
||||
|
||||
~~~bash
|
||||
clusterflux-node \
|
||||
--coordinator https://clusterflux.michelpaulissen.com \
|
||||
--tenant "$TENANT" \
|
||||
--project-id <project-id> \
|
||||
--node workstation \
|
||||
--project-root "$PWD" \
|
||||
--worker \
|
||||
--emit-ready
|
||||
~~~
|
||||
|
||||
Start the worker from the project directory when tasks need a local checkout.
|
||||
The worker reports detected command, container, source, VFS, environment, OS,
|
||||
and architecture capabilities. Use `clusterflux node attach --cap <capability>`
|
||||
only when detection needs an explicit override.
|
||||
|
||||
## Liveness
|
||||
|
||||
~~~bash
|
||||
clusterflux node list
|
||||
clusterflux node status workstation
|
||||
~~~
|
||||
|
||||
The coordinator marks a node stale after its accepted heartbeat age exceeds the
|
||||
configured threshold. Stale nodes are excluded before placement and before a
|
||||
retained-node download link is created.
|
||||
|
||||
## Local source
|
||||
|
||||
A bind-mounted local checkout is fast and avoids transferring the repository,
|
||||
but it is non-hermetic: uncommitted changes, ignored files, and concurrent
|
||||
editor writes can affect the command. Use a content-addressed source snapshot
|
||||
when reproducibility matters.
|
||||
|
||||
## Revoke
|
||||
|
||||
~~~bash
|
||||
clusterflux node revoke workstation
|
||||
~~~
|
||||
|
||||
Revocation removes the node identity and its live descriptor. Subsequent signed
|
||||
requests fail. Artifacts retained only by that node become unavailable unless
|
||||
you explicitly synchronized them elsewhere.
|
||||
49
docs/security.md
Normal file
49
docs/security.md
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# Security
|
||||
|
||||
## Authority
|
||||
|
||||
Clusterflux separates four authority lanes:
|
||||
|
||||
- Client sessions for people.
|
||||
- Agent public-key signatures for non-interactive workflows.
|
||||
- Node public-key signatures for enrolled workers.
|
||||
- Operator credentials for hosted administrative actions.
|
||||
|
||||
The server derives tenant, project, identity, and permission scope from the
|
||||
authenticated lane. Request-body identity fields are not authority.
|
||||
|
||||
## Sessions and keys
|
||||
|
||||
Human login uses the configured identity provider and an opaque, nonce- and
|
||||
PKCE-bound transaction. The CLI never accepts provider claims or authorization
|
||||
codes as a session. Browser-login requests use a dedicated proxy route with
|
||||
per-client rate limiting. The hosted service binds only to loopback and does not
|
||||
use client-supplied forwarding headers as identity or authorization.
|
||||
|
||||
Enrollment grants are short-lived and single-use. Node and agent signatures bind
|
||||
the canonical request body, timestamp, nonce, key fingerprint, and scope.
|
||||
Forged, replayed, expired, revoked, cross-tenant, and body-modified requests
|
||||
fail.
|
||||
|
||||
Keep `.clusterflux/session.json`, node private keys, agent private keys, and
|
||||
operator credentials out of source control. Use protected environment files or
|
||||
your secret manager.
|
||||
|
||||
## Execution
|
||||
|
||||
The coordinator does not run arbitrary native user commands. Nodes execute
|
||||
commands within their reported capabilities. Linux container environments use
|
||||
rootless Podman and avoid privileged defaults.
|
||||
|
||||
Bundle size, canonical argument size, handle count, logs, task history, Debug
|
||||
Epoch state, relay state, and coordinator collections are bounded. Resource and
|
||||
relay reservations happen before unaffordable work or transfer begins.
|
||||
|
||||
## Artifacts
|
||||
|
||||
Artifact links are scoped, expiring, revocable, and bound to live metadata.
|
||||
Digest, size, producer, task attempt, and retaining source are checked. The
|
||||
coordinator retains metadata and bounded relay spool state, not durable artifact
|
||||
bytes by default.
|
||||
|
||||
Report security issues privately using [SECURITY.md](../SECURITY.md).
|
||||
38
docs/self-hosting.md
Normal file
38
docs/self-hosting.md
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# Self-hosting
|
||||
|
||||
Run the public coordinator and node runtime without the hosted website.
|
||||
|
||||
## Start a strict local coordinator
|
||||
|
||||
Supply one scoped bootstrap session through protected service configuration:
|
||||
|
||||
~~~bash
|
||||
CLUSTERFLUX_SELF_HOSTED_SESSION_SECRET="$SELF_HOSTED_SESSION_SECRET" CLUSTERFLUX_SELF_HOSTED_TENANT=my-team CLUSTERFLUX_SELF_HOSTED_PROJECT=my-project CLUSTERFLUX_SELF_HOSTED_USER=me clusterflux-coordinator --listen 127.0.0.1:7999
|
||||
~~~
|
||||
|
||||
Connect the CLI without placing the secret in a process argument:
|
||||
|
||||
~~~bash
|
||||
printf '%s\n' "$SELF_HOSTED_SESSION_SECRET" | clusterflux auth connect-self-hosted --coordinator 127.0.0.1:7999 --tenant my-team --project-id my-project --user me --session-secret-stdin
|
||||
~~~
|
||||
|
||||
The CLI verifies the scope before writing ".clusterflux/session.json". On Unix,
|
||||
the session file uses mode "0600".
|
||||
|
||||
## Attach nodes
|
||||
|
||||
Use the same enrollment and worker flow described in [Nodes](nodes.md), with
|
||||
"--coordinator 127.0.0.1:7999".
|
||||
|
||||
## Network boundary
|
||||
|
||||
The native coordinator transport is plaintext and refuses non-loopback
|
||||
listeners. For another machine, keep the coordinator on loopback and use an
|
||||
authenticated SSH tunnel or deploy a trusted TLS reverse proxy that enforces the
|
||||
same client boundary. Do not expose the native port directly.
|
||||
|
||||
## Administration
|
||||
|
||||
Project, node, process, task, log, artifact, debug, quota, and self-hosted admin
|
||||
operations remain available through the public CLI/API. Authentik is one hosted
|
||||
identity deployment, not a requirement for your coordinator.
|
||||
52
docs/task-abi.md
Normal file
52
docs/task-abi.md
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# Task ABI
|
||||
|
||||
Clusterflux tasks cross a canonical, versioned Wasm boundary.
|
||||
|
||||
## Values
|
||||
|
||||
Small arguments and results use canonical JSON-compatible values. Large or
|
||||
capability-bearing values use typed handles. A structured boundary value carries
|
||||
both its JSON structure and the complete typed handle table required to
|
||||
materialize it.
|
||||
|
||||
Portable handles carry the minimum stable value needed to cross the task ABI,
|
||||
such as an artifact ID, digest, and size. Tenant, project, process, producer,
|
||||
path, retaining location, and authorization state are coordinator-side
|
||||
metadata; they are not portable authority fields supplied by task code. The SDK
|
||||
encodes handles as explicit placeholders and the runtime replaces only
|
||||
placeholders that match the declared type and authoritative coordinator
|
||||
metadata.
|
||||
|
||||
A malformed, missing, extra, cross-scope, or type-mismatched handle fails closed.
|
||||
The runtime does not reconstruct authority from a string path or caller-supplied
|
||||
display field.
|
||||
|
||||
## Tasks
|
||||
|
||||
Use the macros and SDK types:
|
||||
|
||||
~~~rust
|
||||
#[clusterflux::task]
|
||||
async fn compile(input: clusterflux::Artifact) -> Result<clusterflux::Artifact, String> {
|
||||
// Task body
|
||||
}
|
||||
|
||||
#[clusterflux::main]
|
||||
async fn build() -> Result<(), String> {
|
||||
// Spawn and join tasks
|
||||
}
|
||||
~~~
|
||||
|
||||
Each spawn produces a TaskSpec containing the logical task instance,
|
||||
definition, environment identity and digest, canonical arguments, handles, VFS
|
||||
epoch, bundle identity, dispatch ABI, and failure policy.
|
||||
|
||||
Use distinct logical task instances when you spawn the same definition twice.
|
||||
A restart preserves that logical identity and creates a distinct attempt.
|
||||
|
||||
## Failure policy
|
||||
|
||||
"FailFast" is the default. "AwaitOperator" leaves the logical join pending after
|
||||
failure so you can inspect, accept, cancel, or restart the attempt. Side effects
|
||||
outside the VFS boundary are at-least-once across retries; make them idempotent
|
||||
or add your own deduplication key.
|
||||
Loading…
Add table
Add a link
Reference in a new issue