// Stable typed contract for the bmux cluster plugin.
//
// This first schema models the plugin's existing inventory, health,
// orchestration, pane mutation, and connection-event services. Native
// federation interfaces are added as their implementation phases begin;
// runtime managers and transport policy remain in the implementation crate.
plugin bmux.cluster version 1;
capability CLUSTER_READ = bmux.server_clusters.read;
capability CLUSTER_WRITE = bmux.server_clusters.write;
capability CLUSTER_AUTH = bmux.server_clusters.auth;
// Bootstrap proof is independent of node connectivity credentials; mutation
// additionally requires committed feature/schema activation.
interface cluster-principal-bootstrap-types {
record bootstrap-statement {
schema-version: u16,
cluster-id: string,
membership-term: u64,
membership-leader-id: string,
membership-log-index: u64,
principal-id: uuid,
principal-public-key: string,
command-id: cluster-types.command-id,
}
record voter-approval {
node-id: string,
signature: bytes,
}
record bootstrap-proof {
statement: bootstrap-statement,
principal-signature: bytes,
voter-approvals: list<voter-approval>,
}
}
// Supported capability evidence is distinct from active negotiated protocol.
interface cluster-capability-types {
record capability-report {
report-version: u16,
cluster-id: string,
node-id: string,
credential-serial: string,
membership-term: u64,
membership-leader-id: string,
membership-log-index: u64,
expected-report-revision: u64,
command-id: cluster-types.command-id,
schema-min: u32,
schema-max: u32,
features: list<string>,
signature: bytes,
}
}
interface cluster-types {
enum cluster-host-state {
ready,
degraded,
}
enum cluster-consensus-role {
voter,
observer-edge,
}
record cluster-node-capabilities {
consensus_role: cluster-consensus-role,
worker: bool,
ingress: bool,
}
record cluster-protocol-offer {
wire_epoch: u16,
peer_revision_min: u32,
peer_revision_max: u32,
schema_version_min: u32,
schema_version_max: u32,
plugin_version: string,
features: list<string>,
}
record cluster-negotiated-protocol {
wire_epoch: u16,
peer_revision: u32,
schema_version: u32,
local_plugin_version: string,
remote_plugin_version: string,
features: list<string>,
}
record cluster-identity {
cluster_id: string?,
node_id: string,
public_key: string,
capabilities: cluster-node-capabilities?,
protocol: cluster-protocol-offer,
}
record workspace-id {
value: uuid,
}
record logical-tab-id {
value: uuid,
}
record logical-pane-id {
value: uuid,
}
record execution-id {
value: uuid,
}
record command-id {
value: uuid,
}
record promotion-id {
value: uuid,
}
enum pane-restart-policy {
manual,
never,
on-worker-loss,
}
enum pane-availability {
pending,
ready,
suspect,
unavailable,
reconciling,
replacing,
exited,
failed,
quarantined,
}
record placement-label {
key: string,
value: string,
}
record placement-intent {
explicit_node_id: string?,
required_labels: list<placement-label>,
preferred_labels: list<placement-label>,
}
enum worker-operation-class {
interactive,
lifecycle,
}
record worker-authority {
cluster_id: string,
workspace_id: workspace-id,
pane_id: logical-pane-id,
execution_id: execution-id,
generation: u64,
control_term: u64,
lease_sequence: u64,
operation_class: worker-operation-class,
principal_id: string,
issuer_node_id: string,
audience_node_id: string,
lease_id: uuid,
lease_issued_at_unix_ms: u64,
lease_duration_ms: u64,
lease_signature: bytes,
}
record worker-launch-spec {
program: string?,
args: list<string>,
cwd: string?,
env: list<placement-label>,
cols: u16,
rows: u16,
}
record worker-adoption-spec {
local_session_id: uuid,
local_pane_id: uuid,
}
enum worker-execution-state {
launching,
ready,
exited,
unavailable,
quarantined,
closed,
}
record worker-execution {
authority: worker-authority,
local_session_id: uuid,
local_pane_id: uuid,
state: worker-execution-state,
exit_code: i32?,
output_start: u64,
output_end: u64,
}
record worker-execution-list {
executions: list<worker-execution>,
}
record worker-registry-stats {
execution_count: u32,
retained_output_bytes: u64,
}
record worker-mutation-ack {
execution_id: execution-id,
generation: u64,
applied: bool,
}
record worker-output {
execution_id: execution-id,
generation: u64,
requested_cursor: u64,
retained_start: u64,
next_cursor: u64,
gap: bool,
output_still_pending: bool,
data: bytes,
}
record worker-terminal-snapshot {
execution_id: execution-id,
generation: u64,
cursor: u64,
encoded: bytes,
}
variant worker-service-error {
not-found { execution_id: execution-id },
command-conflict,
stale-generation { expected: u64, received: u64 },
stale-term { expected_minimum: u64, received: u64 },
lease-expired,
authority-mismatch,
cursor-gap { retained_start: u64, requested: u64 },
invalid-request { reason: string },
local-runtime { reason: string },
unavailable { reason: string },
}
enum worker-signal {
interrupt,
terminate,
kill,
hangup,
}
variant worker-launch-result {
ready { execution: worker-execution },
pending { execution: worker-execution },
}
variant worker-query-result {
found { execution: worker-execution },
missing,
}
record attach-action-request {
workspace_id: workspace-id,
command_id: command-id,
principal_id: string,
action: string,
arguments: list<string>,
}
record attach-resize-request {
workspace_id: workspace-id,
command_id: command-id,
execution_id: execution-id,
generation: u64,
principal_id: string,
cols: u16,
rows: u16,
}
record execution-assignment {
node_id: string,
generation: u64,
execution_id: execution-id,
}
record workspace-record {
workspace_id: workspace-id,
name: string?,
revision: u64,
}
record logical-tab-record {
tab_id: logical-tab-id,
workspace_id: workspace-id,
name: string?,
layout_schema_version: u32,
layout: bytes,
revision: u64,
}
record attach-layout-rect {
pane_id: logical-pane-id,
x: u16,
y: u16,
width: u16,
height: u16,
}
record attach-layout {
workspace_id: workspace-id,
tab_id: logical-tab-id,
control_revision: u64,
panes: list<logical-pane-record>,
rects: list<attach-layout-rect>,
}
record feature-floor-state {
control_revision: u64,
read_schema_floor: u16,
write_schema_floor: u16,
activated_features: list<string>,
}
record logical-pane-record {
pane_id: logical-pane-id,
workspace_id: workspace-id,
tab_id: logical-tab-id,
name: string?,
restart_policy: pane-restart-policy,
placement: placement-intent,
availability: pane-availability,
availability_reason: string?,
execution: execution-assignment?,
revision: u64,
}
variant control-command-request {
upsert-member { member: cluster-member },
set-member-state { node_id: string, expected_credential_serial: string, state: cluster-member-state },
create-workspace { workspace_id: workspace-id, name: string? },
rename-workspace { workspace_id: workspace-id, expected_revision: u64, name: string? },
put-tab { tab: logical-tab-record, expected_workspace_revision: u64 },
remove-tab { tab_id: logical-tab-id, expected_workspace_revision: u64 },
put-pane { pane: logical-pane-record, expected_workspace_revision: u64 },
remove-pane { pane_id: logical-pane-id, expected_revision: u64, expected_generation: u64? },
assign-execution { pane_id: logical-pane-id, expected_revision: u64, expected_generation: u64, assignment: execution-assignment, launch_spec: worker-launch-spec? },
set-pane-availability { pane_id: logical-pane-id, expected_revision: u64, assignment: execution-assignment, availability: pane-availability, reason: string? },
complete-workflow { original_command_id: command-id, response: bytes },
prune-dedup { completed_before_unix_ms: u64 },
}
record control-command {
schema_version: u16,
principal_id: string,
command_id: command-id,
issued_at_unix_ms: u64,
request: control-command-request,
}
enum control-resource-kind {
member,
workspace,
tab,
pane,
execution,
workflow,
}
variant control-command-error {
command-id-conflict,
not-found { resource: control-resource-kind, id: string },
already-exists { resource: control-resource-kind, id: string },
revision-conflict { expected: u64, current: u64 },
generation-conflict { expected: u64, current: u64 },
invalid-reference { resource: control-resource-kind, id: string },
invalid-transition { reason: string },
member-inactive { node_id: string },
incompatible-schema { supported: u16, received: u16 },
quorum-required,
not-leader { leader_node_id: string? },
}
record pending-workflow {
principal_id: string,
control_command: control-command,
}
record control-state-view {
schema_version: u16,
cluster_id: string,
revision: u64,
members: list<cluster-member>,
workspaces: list<workspace-record>,
tabs: list<logical-tab-record>,
panes: list<logical-pane-record>,
pending_workflows: list<pending-workflow>,
consistency: control-read-consistency,
}
enum control-read-consistency {
linearizable,
stale,
}
variant control-service-error {
not-initialized,
runtime-unavailable { reason: string },
not-leader { leader_node_id: string?, leader_endpoint: string? },
quorum-unavailable { reason: string },
rejected { error: control-command-error },
internal { reason: string },
}
enum control-workflow-status {
complete,
pending,
}
variant control-command-result {
accepted { payload: bytes },
rejected { error: control-command-error },
}
record control-response {
schema_version: u16,
command_id: command-id,
control_revision: u64,
workflow_status: control-workflow-status,
result: control-command-result,
}
enum cluster-member-state {
active,
revoked,
left,
}
record cluster-member {
cluster_id: string,
node_id: string,
public_key: string,
endpoint: string?,
capabilities: cluster-node-capabilities,
credential_serial: string,
credential_issuer_node_id: string,
credential_issuer_public_key: string,
credential_issued_at_unix_ms: u64,
credential_expires_at_unix_ms: u64,
credential_signature: string,
negotiated_protocol: cluster-negotiated-protocol,
joined_at_unix_ms: u64,
updated_at_unix_ms: u64,
state: cluster-member-state,
}
record cluster-member-list {
cluster_id: string?,
members: list<cluster-member>,
}
record enrollment-token-result {
token: string,
expires_at_unix_ms: u64,
}
enum enrollment-state {
active,
consumed,
revoked,
expired,
}
record enrollment-status {
enrollment_id: string,
request_id: string,
expires_at_unix_ms: u64,
state: enrollment-state,
consumed_by: string?,
}
record enrollment-list {
enrollments: list<enrollment-status>,
}
record enrollment-revocation-result {
enrollment_id: string,
revoked: bool,
}
record member-credential-result {
member: cluster-member,
}
enum member-liveness-state {
local,
reachable,
unreachable,
unchecked,
inactive,
incompatible,
untrusted,
}
record member-status {
member: cluster-member,
liveness: member-liveness-state,
reachable: bool?,
compatible: bool,
trusted: bool,
authenticated_at_unix_ms: u64?,
observed_at_unix_ms: u64,
reason: string?,
}
record membership-status {
identity: cluster-identity,
members: list<member-status>,
}
record credential-rotation-request {
node_id: string,
current_serial: string,
issuer_endpoint: string?,
nonce: string,
issued_at_unix_ms: u64,
expires_at_unix_ms: u64,
signature: string,
}
record cluster-join-result {
identity: cluster-identity,
member: cluster-member,
members: list<cluster-member>,
}
record peer-auth-challenge {
protocol_version: u16,
cluster_id: string,
verifier_node_id: string,
verifier_credential_serial: string,
audience_node_id: string,
nonce: string,
issued_at_unix_ms: u64,
expires_at_unix_ms: u64,
signature: string,
}
record peer-auth-proof {
challenge: peer-auth-challenge,
claimant_node_id: string,
claimant_credential_serial: string,
claimant_signature: string,
}
record authenticated-peer {
cluster_id: string,
node_id: string,
capabilities: cluster-node-capabilities,
credential_serial: string,
authenticated_at_unix_ms: u64,
}
enum consensus-voter-change-action {
add,
remove,
}
record consensus-voter-change-authorization {
cluster_id: string,
action: consensus-voter-change-action,
target_node_id: string,
actor_node_id: string,
signature: bytes,
}
record cluster-leave-request {
leave_id: string,
issuer_endpoint: string?,
cluster_id: string,
node_id: string,
signature: bytes,
}
record cluster-leave-result {
leave_id: string,
node_id: string,
left: bool,
}
record cluster-host-status {
cluster: string,
target: string,
state: cluster-host-state,
reason: string?,
}
record cluster-launch-status {
target: string,
state: cluster-host-state,
reason: string?,
pane_id: string?,
}
record cluster-list-result {
clusters: map<string, list<string>>,
}
record cluster-status-result {
statuses: list<cluster-host-status>,
}
record cluster-up-result {
session_id: string,
statuses: list<cluster-launch-status>,
}
record cluster-pane-mutation-result {
target: string,
old_pane_id: string?,
old_name: string?,
new_pane_id: string,
session_id: string,
}
enum cluster-connection-state {
@default connecting,
ready,
degraded,
retrying,
failed,
}
record cluster-connection-event {
ts_unix_ms: u64,
pane_id: string?,
cluster: string?,
target: string?,
source: string?,
state: cluster-connection-state,
message: string,
}
record raft-rpc-request {
target_node_id: string,
proof: peer-auth-proof,
payload: bytes,
signature: bytes,
}
record raft-decoder-proof {
signature: bytes,
}
record raft-rpc-response {
source_node_id: string,
payload: bytes,
error: string?,
}
record cluster-connection-event-list {
events: list<cluster-connection-event>,
}
}
// This version attests support for retained capability-publication commands and snapshots.
@capability(CLUSTER_AUTH)
@interface-version(1)
interface cluster-raft-decoder {
command probe(request: cluster-types.raft-rpc-request) -> result<cluster-types.raft-decoder-proof, string>;
}
@capability(CLUSTER_READ)
@interface-version(1)
interface cluster-query {
query identity() -> cluster-types.cluster-identity;
query list_clusters() -> cluster-types.cluster-list-result;
query status(selector: string?, doctor: bool?) -> cluster-types.cluster-status-result;
query members() -> cluster-types.cluster-member-list;
query membership_status() -> cluster-types.membership-status;
query enrollments() -> cluster-types.enrollment-list;
}
@capability(CLUSTER_WRITE)
@interface-version(1)
interface cluster-command {
command init(endpoint: string?) -> cluster-types.cluster-identity;
command enrollment_token_create(request_id: string, endpoint: string, ttl_ms: u64?, capabilities: cluster-types.cluster-node-capabilities?) -> cluster-types.enrollment-token-result;
command enrollment_revoke(enrollment_id: string) -> cluster-types.enrollment-revocation-result;
command credential_rotate_prepare() -> cluster-types.credential-rotation-request;
command credential_rotate_accept(request: cluster-types.credential-rotation-request) -> cluster-types.member-credential-result;
command credential_rotate_commit(member: cluster-types.cluster-member) -> cluster-types.member-credential-result;
command member_revoke(node_id: string) -> cluster-types.member-credential-result;
// Internal recovery primitive: converge Raft voters to current signed membership.
command consensus_reconcile() -> cluster-types.cluster-member-list;
// Reconcile a prospective signed membership snapshot before publishing it.
command consensus_reconcile_members(members: list<cluster-types.cluster-member>, authorization: cluster-types.consensus-voter-change-authorization) -> cluster-types.cluster-member-list;
// Safely remove one current voter before publishing leave/revocation state.
command consensus_remove_voter(authorization: cluster-types.consensus-voter-change-authorization) -> cluster-types.cluster-member-list;
command join(token: string, issuer: string, enrollment_result: cluster-types.cluster-join-result) -> cluster-types.cluster-join-result;
command redeem_enrollment(token: string, node_id: string, public_key: string, endpoint: string?, protocol: cluster-types.cluster-protocol-offer, possession_signature: bytes) -> cluster-types.cluster-join-result;
command leave_prepare() -> cluster-types.cluster-leave-request;
command leave(leave_id: string) -> cluster-types.cluster-leave-result;
command accept_leave(leave_id: string, cluster_id: string, node_id: string, signature: bytes) -> cluster-types.cluster-leave-result;
command up(cluster: string, hosts: list<string>) -> cluster-types.cluster-up-result;
command pane_new(host: string, name: string?) -> cluster-types.cluster-pane-mutation-result;
command pane_retry(pane: string?) -> cluster-types.cluster-pane-mutation-result;
command pane_move(pane: string?, host: string) -> cluster-types.cluster-pane-mutation-result;
}
// Proof-bearing refresh is separate from generic membership metadata updates.
// Verification time is assigned by the authoritative runtime, not this contract.
@capability(CLUSTER_AUTH)
@interface-version(1)
interface cluster-protocol-refresh {
command submit(command_id: cluster-types.command-id, replacement: cluster-types.cluster-member, expected_serial: string, expected_revision: u64, node_signature: bytes) -> result<cluster-types.control-response, cluster-types.control-service-error>;
}
@capability(CLUSTER_AUTH)
@interface-version(1)
interface cluster-principal-bootstrap {
command submit(proof: cluster-principal-bootstrap-types.bootstrap-proof, expected_control_revision: u64) -> result<cluster-types.control-response, cluster-types.control-service-error>;
}
// Reports carry node proofs; the authority assigns credential verification time.
@capability(CLUSTER_AUTH)
@interface-version(1)
interface cluster-capability-publication {
command submit(reports: list<cluster-capability-types.capability-report>) -> result<cluster-types.control-response, cluster-types.control-service-error>;
}
@capability(CLUSTER_WRITE)
@interface-version(1)
interface cluster-control-command {
command mutate(request: cluster-types.control-command) -> result<cluster-types.control-response, cluster-types.control-service-error>;
}
@capability(CLUSTER_WRITE)
@interface-version(2)
interface cluster-control-command {
command mutate(request: cluster-types.control-command) -> result<cluster-types.control-response, cluster-types.control-service-error>;
command activate-feature(command_id: cluster-types.command-id, principal_id: string, issued_at_unix_ms: u64, expected_control_revision: u64, read_schema_floor: u16, write_schema_floor: u16, feature: string) -> result<cluster-types.control-response, cluster-types.control-service-error>;
}
@capability(CLUSTER_READ)
@interface-version(1)
interface cluster-control-state {
query read_linearizable() -> result<cluster-types.control-state-view, cluster-types.control-service-error>;
query read_stale() -> result<cluster-types.control-state-view, cluster-types.control-service-error>;
}
@capability(CLUSTER_READ)
@interface-version(2)
interface cluster-control-state {
query read_linearizable() -> result<cluster-types.control-state-view, cluster-types.control-service-error>;
query read_stale() -> result<cluster-types.control-state-view, cluster-types.control-service-error>;
query feature-floor() -> result<cluster-types.feature-floor-state, cluster-types.control-service-error>;
}
@capability(CLUSTER_WRITE)
@interface-version(1)
interface cluster-attach-command {
command action(request: cluster-types.attach-action-request) -> result<cluster-types.control-response, cluster-types.control-service-error>;
command input(workspace_id: cluster-types.workspace-id, command_id: cluster-types.command-id, execution_id: cluster-types.execution-id, generation: u64, principal_id: string, data: bytes) -> result<cluster-types.worker-mutation-ack, cluster-types.worker-service-error>;
command resize(request: cluster-types.attach-resize-request) -> result<cluster-types.worker-mutation-ack, cluster-types.worker-service-error>;
}
@capability(CLUSTER_READ)
@interface-version(1)
interface cluster-attach-state {
query layout(workspace_id: cluster-types.workspace-id, tab_id: cluster-types.logical-tab-id?, columns: u16, rows: u16) -> result<cluster-types.attach-layout, cluster-types.control-service-error>;
query snapshot(workspace_id: cluster-types.workspace-id, execution_id: cluster-types.execution-id, generation: u64) -> result<cluster-types.worker-terminal-snapshot, cluster-types.worker-service-error>;
query output(workspace_id: cluster-types.workspace-id, execution_id: cluster-types.execution-id, generation: u64, cursor: u64, max_bytes: u32) -> result<cluster-types.worker-output, cluster-types.worker-service-error>;
}
@capability(CLUSTER_WRITE)
@interface-version(1)
interface cluster-worker-command {
command launch(command_id: cluster-types.command-id, authority: cluster-types.worker-authority, spec: cluster-types.worker-launch-spec) -> result<cluster-types.worker-launch-result, cluster-types.worker-service-error>;
command adopt(command_id: cluster-types.command-id, authority: cluster-types.worker-authority, spec: cluster-types.worker-adoption-spec) -> result<cluster-types.worker-launch-result, cluster-types.worker-service-error>;
command input(command_id: cluster-types.command-id, authority: cluster-types.worker-authority, data: bytes) -> result<cluster-types.worker-mutation-ack, cluster-types.worker-service-error>;
command resize(command_id: cluster-types.command-id, authority: cluster-types.worker-authority, cols: u16, rows: u16) -> result<cluster-types.worker-mutation-ack, cluster-types.worker-service-error>;
command signal(command_id: cluster-types.command-id, authority: cluster-types.worker-authority, signal: cluster-types.worker-signal) -> result<cluster-types.worker-mutation-ack, cluster-types.worker-service-error>;
command restart(command_id: cluster-types.command-id, authority: cluster-types.worker-authority, spec: cluster-types.worker-launch-spec) -> result<cluster-types.worker-launch-result, cluster-types.worker-service-error>;
command close(command_id: cluster-types.command-id, authority: cluster-types.worker-authority) -> result<cluster-types.worker-mutation-ack, cluster-types.worker-service-error>;
command reconcile(executions: list<cluster-types.worker-execution>) -> result<cluster-types.worker-registry-stats, cluster-types.worker-service-error>;
}
@capability(CLUSTER_READ)
@interface-version(1)
interface cluster-worker-state {
query get(execution_id: cluster-types.execution-id) -> result<cluster-types.worker-query-result, cluster-types.worker-service-error>;
query output(execution_id: cluster-types.execution-id, generation: u64, cursor: u64, max_bytes: u32) -> result<cluster-types.worker-output, cluster-types.worker-service-error>;
query snapshot(execution_id: cluster-types.execution-id, generation: u64) -> result<cluster-types.worker-terminal-snapshot, cluster-types.worker-service-error>;
query inventory() -> cluster-types.worker-execution-list;
}
@capability(CLUSTER_AUTH)
@interface-version(1)
interface cluster-peer-auth {
command challenge(claimant_node_id: string) -> cluster-types.peer-auth-challenge;
command prove(challenge: cluster-types.peer-auth-challenge) -> cluster-types.peer-auth-proof;
command authenticate(proof: cluster-types.peer-auth-proof) -> cluster-types.authenticated-peer;
}
@capability(CLUSTER_AUTH)
@interface-version(1)
interface cluster-raft-rpc {
command append_entries(request: cluster-types.raft-rpc-request) -> cluster-types.raft-rpc-response;
command install_snapshot(request: cluster-types.raft-rpc-request) -> cluster-types.raft-rpc-response;
command vote(request: cluster-types.raft-rpc-request) -> cluster-types.raft-rpc-response;
}
// V2 receivers support retained capability-publication commands and snapshots.
// Senders must not fall back to v1 when this interface is unavailable.
@capability(CLUSTER_AUTH)
@interface-version(2)
interface cluster-raft-rpc {
command append_entries(request: cluster-types.raft-rpc-request) -> cluster-types.raft-rpc-response;
command install_snapshot(request: cluster-types.raft-rpc-request) -> cluster-types.raft-rpc-response;
command vote(request: cluster-types.raft-rpc-request) -> cluster-types.raft-rpc-response;
}
@capability(CLUSTER_READ)
@interface-version(1)
interface cluster-connection-events {
query list() -> cluster-types.cluster-connection-event-list;
}