@serve.zone/interfaces
v32.29.0
Published
Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.
Maintainers
Readme
@serve.zone/interfaces
@serve.zone/interfaces is the shared TypeScript contract package for the serve.zone ecosystem. It contains the public data shapes and TypedRequest interfaces used by Cloudly, Coreflow, Spark, Coretraffic, platform clients, SDKs, and external integrations to exchange infrastructure state without duplicating DTOs.
Issue Reporting and Security
For reporting bugs, issues, or security vulnerabilities, please visit community.foss.global/. This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a code.foss.global/ account to submit Pull Requests directly.
Install
pnpm add @serve.zone/interfacesPublic API
The root export exposes six namespaces:
import { appstore, data, platform, platformservice, protocol, requests } from '@serve.zone/interfaces';| Namespace | Purpose |
| --- | --- |
| appstore | App Store catalog, manifest, service requirement, and upgrade contracts. |
| data | Durable platform object shapes such as clusters, services, deployments, images, domains, DNS entries, secrets, users, status, settings, backups, registries, BaseOS metadata, and task executions. |
| requests | TypedRequest contracts for Cloudly and serve.zone control-plane RPC methods. |
| protocol | The handshake two peers exchange when a session opens: what each side speaks, the oldest peer it accepts, and the named refusal when they cannot serve one session. |
| platform | Current platform-service contracts for email, SMS, push notifications, letters, AI, databases, object storage, logging, backups, and SIP. |
| platformservice | Legacy platform-service namespace kept for older consumers that still depend on the previous layout. |
This package intentionally has no service implementation logic. It is a stable vocabulary for services that need to agree on payload shape, method names, and response types.
Identifier Vocabularies
Two vocabularies name things in these contracts, and which one a member uses is part of the contract.
| Vocabulary | Rule | Members |
| --- | --- | --- |
| Canonical identifier | ^[A-Za-z0-9][A-Za-z0-9:._-]{0,199}$ — first character alphanumeric, up to 200 characters | organizations, clusters, services, namespaces, sessions, assignments, attempts, authorities, pools, endpoints and every content id derived from them |
| Node id | ^[A-Za-z0-9_-]{1,128}$ — the URL alphabet, a leading - or _ included, up to 128 characters | every member that names a node of a cluster |
A node may be named -abc or _abc: enrollment, the runtime session registration and the Spark
wire admit a node id that begins with a separator, so every member that names the same node admits
one too. data.isClusterNodeId(value) is that rule, and it is the rule at every node-bearing member:
nodeId on a session binding, a registration, an enrollment, a Spark heartbeat, a relay block, an
assignment, a workload lease, a handoff lease, a DNS-lease renewal run, a protection receipt, an
egress authority, a cluster VPN node, an ingress registration, a traffic bucket, a Corestore
inventory and an isolated-restore node control; id on a router incarnation and on a node
retirement; cloudlyNodeId on a cluster runtime target and on a secret runtime target; and
placement.nodeIds on a service runtime spec. A service that stores or forwards a node id
should judge it with the same reader rather than with the canonical one, which would refuse a node
these contracts themselves enrolled and registered.
A replica id is the one composed name: a producer names a replica slot <node id>.<index>, counted
from zero. data.isRuntimeReplicaId(value) reads it by decomposition — the part before the last dot
in the node vocabulary, the index on its own and never zero-padded — and reads a name that carries
no such index, a zero-padded node-a.01 among them, as an opaque slot name in the canonical
identifier vocabulary.
WorkloadInit release verifiers should use the dedicated Node-compatible subpath:
import {
validateWorkloadInitReleaseAttestationStatement,
validateWorkloadInitReleaseIdentity,
workloadInitReleaseContract,
} from '@serve.zone/interfaces/runtime/workloadinit';@serve.zone/interfaces/runtime/workloadinit exposes only the WorkloadInit
release identity, attestation, approval, authority, digest, and validator
contracts. Its runtime dependency graph is limited to the immutable-image digest
validator; unlike @serve.zone/interfaces/runtime, it does not load the general
runtime graph, plugins.js, SmartCrypto, TypedRequest, or Corestore runtime
modules.
Directional Image Streams
Image transfer contracts use @api.global/typedrequest-interfaces 7.1.0 and the
directional VirtualStream protocol used by TypedRequest 8 and TypedSocket 8.
Directions in shared DTOs describe the requesting peer:
| Contract field | Requester endpoint | TypedHandler endpoint |
| --- | --- | --- |
| requests.image.IRequest_PushImageVersion.request.imageStream | TVirtualStream<'send'> | TVirtualStream<'receive'> |
| requests.image.IRequest_PullImageVersion.response.imageStream | TVirtualStream<'receive'> | TVirtualStream<'send'> |
The method names remain pushImageVersion and pullImageVersion. Each stream
carries ordered Uint8Array chunks. TypedRequest reverses stream directions for
the handler automatically; do not reverse the shared declaration yourself.
Consumers must replace the removed undirected IVirtualStream API with explicit
transport-created endpoints. Senders use send() or writable, then close;
receivers use receive() or readable, then explicitly accept() after draining
EOF and completing application storage or delivery. completion confirms the
receiver's accepted receipt, while closed confirms transport cleanup. The
upload response's allowed flag permits sending; it does not confirm storage.
Receiver rejection, aborts and failed completion must reach the caller. Existing
deployment log and shell push-message contracts retain their message shapes.
Human Credential Administration
requests.admin.getHumanCredential and mutateHumanCredential describe human
credential inspection and mutation for recently authenticated administrators.
Operations rotate a password, revoke password login, or revoke existing sessions.
Each successful mutation advances the generation and invalidates older human
UI/API and OCI registry sessions. Upstream OIDC identity bindings remain intact.
Send the expected generation and a stable mutation ID. After a lost response, authenticate again and repeat the same request. A replay returns the original metadata without applying the operation again; inspect current metadata separately. Requests containing passwords must never be logged. Responses contain no passwords or verifiers. Cloudly owns validation, atomic persistence, and session enforcement; these shared contracts alone do not implement an endpoint.
Node Credential Administration
requests.node defines getNodeCredential, rotateNodeCredential and
revokeNodeCredential for verified platform-infrastructure administrators.
getNodeConfig now also requires identity; callers using the previous
unauthenticated request shape must update. Its response remains the public
IClusterNode, never a persisted backend document.
data.INodeCredentialMetadata separates spark and pallet purposes and
exposes lifecycle state, generation, rotation-required status and session epoch,
but no bearer, hash, socket peer or controller process identity. Infrastructure
credentials do not grant organization or workload permissions.
Mutations require the exact generation and session epoch plus a stable mutation ID. Rotation takes the SHA-256 hash of a node-generated CSPRNG credential that the node has already durably retained with that mutation ID. Lost-response retries repeat the complete request and receive the original metadata, never newly minted plaintext. A replay result is historical and cannot authorize a current session. Treat rotation request hashes as sensitive in transport hooks and logs.
These types do not implement authentication, storage, enrollment or endpoints. Cloudly must verify current administrator authority, validate complete inputs, atomically persist the credential transition and immutable actor-bound receipt, and fence subsequent control effects against current durable credential/session authority. Node identity storage and transport integration must be qualified before enabling rotation or Pallet enrollment.
Spark Host Reporting
data.sparkNodeHeartbeatContract defines the HTTPS POST endpoint
/spark/nodes/heartbeat, transport byte limits and protocolRefusalStatus, the
status a refused offer is answered with. Its exact request snapshot contains a
Spark-purpose bearer, the sender's protocol offer and ISparkNodeReport:
fractional host CPU, memory and disk observations, Linux host facts, and the
verified bundle version, source commit and manifest digest. It has no Docker or
container-count fields. Local runtime readiness remains distinct from unverified
workload readiness.
The route reads the bounded body, reads the offer with protocol.readProtocolOffer,
negotiates it against its own sparkNode offer and answers a refusal with
protocolRefusalStatus and an IProtocolRefusal body — all before the node is
authenticated, so a peer of another major is refused by name instead of by a
credential verdict it cannot act on. The receipt carries the controller's own
offer, so an accepted node judges the session it just reported into.
The Swarm-era Spark runtime keeps its own four routes in
data.sparkSwarmNodeContracts, each stating the same three facts as the route
above — heartbeat (/spark/swarm-nodes/heartbeat), metricsSample, actionResult
and swarmObservation, every one of them an { endpoint, maxRequestBytes,
maxResponseBytes } — so one bounded read serves every Spark route. The bounds
differ because the bodies do: a heartbeat states the node's whole runtime
description, a metrics-sample answer carries every action queued for that node, and
one observation carries up to 1024 observed Swarm nodes. That is a second runtime,
not an older version of this one: a worker in coreflow-node mode posts those
bodies and a pallet node posts this one, and the family retires with coreflow
rather than with this release. ISparkSwarmNodeHeartbeatRequest is now owned here,
so Cloudly and Spark no longer carry private copies of it, and every Swarm-era body
— request and answer alike — carries protocol and is judged in the same order.
For fleet cutover, that same observation may carry a bounded local Docker snapshot from the
reporting node and, only when control is available, a manager-visible service/task snapshot. The
existing node-token, reporter-session, sequence, digest and accepted-receipt rules authenticate
both; no separate fleet-report route exists.
Each of the four routes has its validator here, so a server never writes its own
member checks: data.validateSparkSwarmNodeHeartbeatRequest,
data.validateSparkMetricsSampleRequest, data.validateSparkActionResultRequest
and data.validateSparkSwarmObservationRequest. All four answer the same way —
one string per refused member, naming the member, and an empty list for an
admissible body — so a route table can hold them side by side. They read the
envelope first (exact key set, the sender's offer, then the reporter's identity),
then the payload: a heartbeat's metrics and full runtime description including
each described serve.zone service, a sample's four observations and optional
network rates, and an action result whose status is one a node can actually
report, never pending. Bodies are judged member by member rather than as
canonical bytes, because only the observation is digested and the others carry
fractional CPU, memory and rate observations; every level is copied away from the
caller's object first, so an accessor is refused instead of run. None of them
authenticates a node or acts on the body.
Cloudly must authenticate the bearer against current Spark authority and fence
credential generation and session epoch transactionally before recording host
liveness. ISparkNodeHeartbeatReceipt contains only server-derived acceptance
evidence; the sender must match its node ID and credential generation to its exact
active identity. Neither a receipt nor the sender's observation time grants
current authority or workload readiness. Persist only the report and receipt in
IClusterNode.data.sparkNodeReport, never the bearer. Keep legacy reporting
separate. This endpoint does not deliver or acknowledge operator actions.
The snapshot functions reject unknown keys, accessors, malformed identities and nonfinite/out-of-range numbers, then return detached frozen records. Transports own byte limits, timeouts and cancellation. These contracts do not implement a listener, credentials, persistence, freshness or shutdown.
Independent Node Enrollment
requests.node defines getNodeEnrollmentState for current Spark-authenticated
adoption preconditions and enrollNode for atomic Spark/Pallet enrollment. These
contracts do not implement endpoints, authentication, a database or a daemon.
The enrollment proposal binds the canonical HTTPS Cloudly origin, hostname,
enrollment identity, original bootstrap proof hash and an exact ordered pair of
independently prepared Spark/Pallet credential hashes and CAS counters. Fresh Jump
starts both at zero; existing-node adoption rotates current Spark and requires
Pallet authority to be absent. snapshotNodeEnrollment returns a detached,
frozen snapshot; computeNodeEnrollmentDigest hashes strict canonical JSON
under nodeEnrollmentContract.digestDomain, serve.zone/node-enrollment.
Neither helper generates or persists credential material.
Transport proof is excluded from the digest to permit exact lost-response
recovery. Bootstrap proof must hash to the original proof hash and is allowed only
before a receipt exists. Pending-Spark proof must hash to the prepared Spark hash
and is allowed only for an existing exact receipt. verifyNodeEnrollmentProof
checks this binding and mode only: Cloudly must derive receipt state itself and
fence live bootstrap/credential authority in the same owned transaction. Current
Jump codes are 12 random bytes encoded as 16 canonical base64url characters.
bindNodeEnrollmentAcknowledgement checks the complete digest, both owners,
exact next generations and unchanged adopted node ID, returning a detached frozen
acknowledgement. Authenticate Cloudly before accepting it. The acknowledgement is
immutable commit evidence, not ongoing authority or workload readiness. Server
replay must still verify both active credential generations and hashes; reporter
reconnect alone does not invalidate the credential. Spark and Pallet commit their
local activation independently and converge after restart; there is no shared
plaintext vault or cross-local-database ACID claim. Never log proof/request bodies
or hashes, put them in URLs, or expose them through transport hooks.
requests.pallet defines the node-local preparePalletNodeEnrollment,
bindPalletNodeEnrollment and activatePalletNodeEnrollment methods. They
belong only on the protected root-owned Unix control socket, never a network
router. Preparation requests initial Pallet authority (zero generation and
session epoch) and returns only its independently retained credential hash.
snapshotPalletNodeEnrollmentPreparation and
bindPalletNodeEnrollmentPreparation validate that exact exchange.
Local confirmations explicitly distinguish a durable pending binding from the
exact current active Pallet identity. bindPalletNodeEnrollmentBound verifies
the whole enrollment digest and its pre-assignment node ID (null for fresh Jump).
snapshotPalletNodeEnrollmentActivation captures the exact activation request,
rejecting extra wrapper fields as well as malformed nested data.
bindPalletNodeEnrollmentActive takes that complete activation request and
verifies the full Cloudly acknowledgement, including both owners and the assigned
node ID. Both confirmation binders return detached frozen
snapshots captured before hashing. These are pure content checks: authenticate
the local transport separately, and have Pallet prove its current owned state
before issuing a confirmation. A historical receipt is insufficient, and Pallet
activation does not imply Spark activation or workload readiness.
palletNodeEnrollmentContract specifies one four-byte unsigned big-endian
length-prefixed UTF-8 JSON frame per direction, followed by write-half-close, at
most 32768 request bytes and 16384 response bytes excluding the prefix. Implement
the listener with half-open response support, strict EOF/trailing-byte rejection,
bounded time/concurrency, root-only directory/socket permissions and drained
cleanup. The package supplies no listener, permission checks or persistent state.
No bearer, generic rotation operation or database access is part of this IPC API.
Node Runtime Routing Binding
requests.node.getNodeRuntimeBinding { nodeId, nodeToken } is a current
Spark-node-authenticated read. Cloudly derives data.INodeRuntimeBindingRead
from its current credential, node, cluster, durable runtime-controller, relay and
cluster-runtime records. The answer contains the exact routing identity
{ cloudlyOrigin, nodeId, clusterId, controller, runtimeNamespace, relay }, the
current { id, phase, generation } runtime reference and the Spark credential
generation and session epoch that authenticated the read.
snapshotNodeRuntimeBindingRead requires the runtime id to equal the routing
cluster, and bindNodeRuntimeBindingRead binds the answer to Spark's exact
immutable Cloudly origin, node and credential generation. Neither helper
authenticates the server.
requests.pallet.bindPalletNodeRuntimeBinding and
readPalletNodeRuntimeBinding belong on the existing root-owned Pallet enrollment
control socket. Bind is absent-to-present only: the current active Pallet
enrollment must name the same canonical Cloudly origin and node, an exact replay
is idempotent and every different origin, node, cluster, controller, namespace
or relay conflicts. Read requires the requested origin and node to equal both
the active enrollment and the stored binding; an absent binding rejects. Spark
applies and reads back the binding before starting runtime-serve. On an
offline reboot it may read the exact persisted routing identity to select the
relay, but that record grants no workload permission and no right to advance or
run a cluster phase. Fresh authenticated runtime-session state remains the only
workload authority.
The local operations carry no bearer, signing key, generic configuration field,
phase mutation or caller-authored permission. nodeRuntimeBindingContract
states the immutable local mutation and routing-only persistence rules.
A node can instead be driven by a controller on its own host, such as Onebox.
data.INodeRuntimeLocalRoutingBinding is
{ nodeId, clusterId, controller, runtimeNamespace, registryHosts } with a
controller of kind onebox; it names no Cloudly origin and no relay.
nodeRuntimeLocalControllerContract fixes the transport: the controller is the
TypedSocket client on Pallet's root-only Unix socket
/run/serve.zone/pallet/controller.sock (mode 0600, server URL
http://localhost), whose file permissions authenticate the peer. A node carries
a Cloudly binding or a local one, never both, and the local binding, like the
Cloudly one, grants no workload permission by itself.
The first request on a local connection is bindPalletLocalRuntimeController
(see Authenticated Runtime Sessions). It is
first bind or exact replay. Pallet answers data.INodeRuntimeLocalBindingConfirmation:
the exact persisted binding plus the generation and SHA-256 hash of a bearer that
Pallet generates and keeps. bindNodeRuntimeLocalBindingConfirmation binds that
answer to the binding sent and to the credential the controller pinned in its
own durable state at the first bind (null for that first bind). A different
credential for the same binding rejects rather than being adopted, because it
means the node lost the state the controller enrolled.
registryHosts lists, strictly ordered, the canonical host[:port] registries
(isCanonicalRegistryHost) whose pull credential the local controller issues.
isNodeRuntimeLocalRegistryWorkload is the local registry rule: a workload's
registryHost, and its pullEndpoint when one is stated, must both be listed.
Service Machine Credentials
requests.admin.getServiceMachineCredential inspects redacted metadata;
mutateServiceMachineCredential creates, rotates or revokes authority for one
exact existing service and organization. ensure requires absent authority and
a null expected generation. Rotation and revocation require the exact current
generation. Retain the mutation ID and complete input for retry; a receipt replay
returns historical metadata without restoring authority.
IServiceMachineGrant currently grants only platform:session. This is separate
from deployment grants, human membership and infrastructure permissions. The
backend must authenticate each JWT against the current active credential
generation, expiry and exact service ownership. Structural snapshot helpers do
not perform these checks. Credential changes, encrypted service SecretSet
delivery and the replay receipt must commit in the same authorized transaction.
The response contains only the immutable secret/version reference; no bearer or
hash is returned. Revocation removes current delivery from the metadata.
Private Networks
requests.network defines organization-scoped network create/update/retire and
service attachment operations. Mutations require a retained idempotency ID and
an exact expected revision; null means no prior document. Cloudly must authorize
each request against live canonical membership and policy, reserve aliases
transactionally, reject cross-organization attachments, and retain historical
receipts. These contracts do not implement the backend or node enforcement.
data.buildPrivateNetworkFqdn produces
<alias>.n-<26 lowercase base32 characters>.o-<26 lowercase base32 characters>.internal.
from server-owned immutable DNS keys. Aliases are lowercase ASCII labels unique
per network. Display-name edits do not rename DNS. A sole attachment selects its
search suffix; several attachments require an explicit default or no search
suffix. Fully qualified names remain unambiguous across networks. Network policy
permits member-to-member connectivity and explicitly configures or denies external
DNS forwarding. Creating an alias does not publish public DNS, ingress or ports.
data.composeServicePrivateNetworkDnsPolicy(membership, networks) derives the
service's network suffixes, the single selected search suffix, and the intersection
of external forwarding permissions. Supply every attached network exactly once,
with active state and matching canonical organization/DNS keys. Missing, extra,
inactive or inconsistent network definitions are rejected. Forwarding requires
every attachment to permit both the queried public name and the same exact
upstream IP/port; a denied or empty intersection returns externalDns: null.
The default network changes only short-name search. Nested suffix unions are
intersected at DNS label boundaries and canonicalized without dropping permitted
branches; the composed list can contain up to 1,024 suffixes across 16 networks.
This pure helper returns detached policy metadata. Its caller must authenticate
and fence the included membership/network revisions before issuing node authority.
The snapshot helpers validate and detach content; they do not authenticate it, reserve names, prove membership, or report applied state. Packet authority remains until an applied denial or independent fence. DNS snapshots have a separate maximum 15-minute validity, with positive TTLs capped at five seconds and negative TTLs at one second. DNS expiry does not prove packet revocation or permit identity reuse. Retirement and attachment responses describe desired or historical metadata; consumers must separately track outstanding node revocations and readiness.
IRuntimeNetworkProtectedAuthority declares the complete controller-owned IPv4
protection set, disjoint private workload/transit pools, protected resolver and
platform endpoints, and every affected egress authority, including offline nodes.
Its snapshot and digest helpers bind the exact predecessor and all declaration
content. They cannot prove that an operator's inventory is complete. Install the
union of old and new protection on every affected egress owner, or independently
fence that owner, before exposing a newly allocatable pool. Removing an entry from
the declaration is not a revocation acknowledgement. Prefix arrays are sorted
lexically and nonoverlapping; pools and endpoints are sorted by ID; egress owners
are ordered by canonical JSON of [nodeId, runtimeNamespace].
IRuntimeNetworkProtectionReceipt reports a worker's durable, joined native
protection journal separately from projection admission. It binds a complete
protected-authority reference, node/controller scope, authenticated reporter,
boot identity and exact native journal reference to a monotonic receipt chain.
A null nativeBarrier explicitly removes allocation eligibility. A boot or
protection change requires new native journal evidence. These portable values
do not inspect a kernel or authenticate their own producer.
reportRuntimeNetworkProtection uses the current authenticated physical peer.
Its request binder accepts a historical outbox only against receiver-owned
reporter history and identifies that use as historical. The response must match
the exact sent receipt. bindRuntimeNetworkAllocationProtection requires the
current persisted protection receipt and durable session for every declared
egress owner, in canonical owner order. Missing, unavailable, stale-protection
and superseded-session evidence reject. An offline owner cannot be omitted.
The controller must fence all those records together with lease insertion, before
reserving any handoff or workload address. No generic independent-fence flag is
provided; such a mechanism needs its own verified owner. Protection receipts
neither prove workload readiness nor release packet/address/name quarantine.
IRuntimeNetworkHandoffLease binds an immutable node/router incarnation to its
protected-authority reference, transit subnet and peers, protocol-qualified source
ports, nonzero conntrack zone and 128-bit label. The label is exactly 32 lowercase
hex characters. Port ranges are inclusive, sorted by protocol then first port,
and disjoint within each protocol. bindRuntimeNetworkHandoffLease verifies both
digests, scope and transit-pool containment and returns detached values. It does
not authenticate a projection or attest to an actual namespace/interface.
snapshotRuntimeNetworkHandoffLedger verifies at most 256 retained leases for
one node/controller epoch, including quarantined allocations. It rejects host
handoff-port collisions even across router/runtime namespace changes, and zone or
label reuse within one router incarnation. The caller must transactionally supply
the complete retained set, serialize allocation and fence controller-epoch changes.
These are collision checks, not an IPAM allocator or a native capability. No expiry,
empty conntrack dump, process exit or table removal establishes flow drainage or
permits handoff identity reuse. Signed complete projections, live interface proofs
and the separate router/host apply journal remain responsibilities of Cloudly and
Pallet; neither packet admission nor readiness follows from these helpers.
IRuntimeNetworkWorkloadLease is the immutable material referenced by
assignment.workload.network. It binds one execution attempt and router
incarnation to a workload pool, dedicated unbridged linkSubnet, workload
address, exact /32 sourcePrefix, and router-side gateway. The gateway
also supplies dnsServer on port 53, because CRI DNS settings cannot encode a
port. Canonical private point-to-point subnets through /31 are accepted; the
owning CNI implementation must qualify its chosen layout. The source prefix,
link subnet, VPN control prefix and host/router transit subnet have different
roles. bindRuntimeNetworkWorkloadLeaseToAssignment verifies immutable
assignment identity without a circular assignment digest. Membership, aliases,
readiness and projection revisions never change this address material.
IRuntimeNetworkProjection contains complete relevant network definitions,
service memberships and endpoint leases, including remote ready endpoints and
declared services with no replicas. It carries the current protected authority,
exact historical allocation authorities, local handoff, a maximum 15-minute DNS
window and explicit packet/lease/handoff withdrawals. All arrays use the
documented canonical ordering: networks and endpoints by ID, memberships by
canonical [organizationId, serviceId], references by canonical complete
reference, and withdrawals by canonical complete grant. Projections are bounded
to 896 KiB, 128 networks, 256 memberships/endpoints, 16 historical authorities
and 4,096 directed grants. These transport bounds do not assert native capacity;
the node must preflight the actual SmartVPN, Smartnftables and DNS limits before
effectful application.
validateRuntimeNetworkProjection checks complete digest and scope bindings,
pool containment, disjoint link subnets, network/organization keys, aliases,
resolver authorization and ready-replica uniqueness. It does not authenticate
controller inventory, prove observation provenance or authorize allocation reuse.
Cloudly must derive readiness from the current authenticated assignment report,
retain all affected offline authorities and complete its allocation/barrier
transactions before issuance.
An endpoint may publish node ports through the optional
IRuntimeNetworkEndpoint.publishedPorts. Each IRuntimeNetworkPublishedPort
binds one hostPort (1..65535, unique per node and protocol across the
projection) to the workload's targetPort, optionally on an explicit uplink
hostIp (never the wildcard or loopback; binding it to the node's uplink is
Pallet's enforcement, because the projection carries no uplink fact), and carries the Cloudly
authorization reference that permitted it: a published port without that
reference cannot exist, and the projection signature covers every byte of it.
An endpoint that publishes nothing omits the member entirely and keeps its exact
canonical bytes and digest; a present member is never an empty list, so
"publishes nothing" has exactly one encoding.
Operators author that authorization through getServicePublishedPorts and
setServicePublishedPorts, modelled on the private-network membership pair.
IServicePublishedPorts returns the current authorized list with its policy
revision, or null when a service never had one. ISetServicePublishedPorts
returns the accepted IServicePublishedPorts document with its new
revision and a replayed flag, and carries expectedRevision (null for the first document), a retained
mutationId for replay, and IRuntimeNetworkPublishedPortRequest entries of
exactly protocol, hostPort and targetPort: the request accepts no hostIp
(an omitted address means the node uplink) and no ranges. An empty list is a valid
policy meaning the service publishes nothing. Callers never supply an
authorization reference: Cloudly derives each projection entry's reference
from the accepted policy revision and digest, and node-level host-port placement
stays Cloudly's decision.
getRuntimeNetworkProjectionPacketGrants derives directed member connectivity
and explicit local public/platform egress independently of DNS readiness.
composeRuntimeNetworkProjectionDnsViews builds one union view per local
workload source address, with every authorized FQDN and complete ready-replica A
set. An empty array declares NODATA; absent/unauthorized names remain NXDOMAIN,
and IPv4-only service names have AAAA NODATA. The view carries the existing
forwarding intersection and zero or one search suffix. Pallet must prove the
actual sandbox/veth/source binding, block private/bare-name forwarding, clip
TTLs and convert the effective window from getRuntimeNetworkEffectiveDnsWindow (see
DNS Lease Renewals) using its qualified boot/time authority.
These helpers neither cache queries nor renew a lease.
The DNS composer accepts an optional second argument of exact local workload
lease references. Selection happens before view expansion while the complete
validated projection still supplies every eligible local or remote target and
declared empty name. Omit it for all local views or pass [] for none. Unknown,
remote, stale and duplicate references reject. Selection is captured before
asynchronous validation and output retains projection order. This lets Pallet
compose only its currently attached sources without duplicating membership rules;
the references themselves do not prove native attachments or DNS authority.
The optional IRuntimeNetworkProjection.router member carries explicit
router-origin egress selections and withdrawals. A projection that omits it
retains its exact canonical bytes, digest and signature and grants no
router-origin flows. New issuers can select resolverIds and
platformEndpointIds from the current protected inventory; an empty selection
denies all router egress. DNS forwarding policy, workload egress and inventory
presence alone supply no router grant. The whole member is signed with the
projection; it is not an unsigned runtime option.
getRuntimeNetworkProjectionRouterPacketGrants validates the complete projection
and returns up to 96 detached exact grants. Each binds the current local handoff
reference and its router transit source address to a selected destination ID,
IPv4 address, protocol and port. Resolver selection explicitly authorizes both
TCP and UDP at the declared resolver port. Platform selection authorizes only
its declared transport tuple, for example a VPN relay endpoint. Every selected
protocol must have a source-port allocation in that handoff. No public wildcard,
workload source or alternate source address can be supplied in this selection.
Router withdrawals retain these complete grant bodies, including the old handoff and source address. Removing a selection, changing an inventory endpoint or retiring the handoff must explicitly withdraw the old grants. Unknown denials and denials overlapping current grants reject. Up to 4,096 retained withdrawals carry forward until the caller supplies its exact joined application of the preceding projection, with the same receipt boundary as workload withdrawals. These values do not prove packet drainage, native enforcement or allocation reuse. Consumers must compose workload, router and host grants and their separate withdrawals; they must also fence the actual native source, routing and process lifetimes. DNS expiry or readiness changes do not revoke packet authority.
A projection whose controller kind is onebox names a controller that runs on
the node itself, so it has no other node: it refuses every endpoint whose lease
is not placed on the projection's own node and runtime namespace. Projections of
a cloudly controller keep their remote endpoints.
The optional IRuntimeNetworkProjection.host member exists only on onebox
projections; any other controller kind carrying it rejects. It lets the node's
own host namespace, for example Onebox's reverse proxy, dial exact local workload
ports directly, with no loopback publication, no loopback DNAT and no change to
martian filtering. A projection that omits it retains its exact canonical bytes,
digest and signature and grants no host-origin flows. Each ingress entry names
the exact lease reference of a local endpoint of the same projection, a protocol,
the workload's own port and an authorization reference. The authorization is
signed with the projection but is not part of the grant identity, so
re-authorizing an unchanged port neither creates nor withdraws a grant. Entries
are unique and strictly ordered; at most 1,024 are allowed
(runtimeNetworkHostContract).
getRuntimeNetworkProjectionHostPacketGrants validates the complete projection
and returns detached exact grants. Each binds the current handoff reference and
its transit host address as the source to the lease reference, lease IPv4 address,
protocol and exact port as the destination. Unlike router grants, host grants
need no per-protocol source-port allocation in the handoff. Host withdrawals
follow the router rules: removing a selection or retiring the handoff or lease
must explicitly withdraw the old complete grants, unknown denials and denials
overlapping current grants reject, and up to 4,096 retained withdrawals carry
forward until the caller supplies its exact joined application of the preceding
projection.
The optional IRuntimeNetworkProjection.workloadIngress member exists only on
cloudly projections; any other controller kind carrying it rejects. It is how
the cluster ingress workload reaches the workloads it routes to without a
published host port: each grants entry names the exact lease reference of the
ingress workload (source), the exact lease reference of the target workload
(destination), a protocol, the target's own listening port and an
authorization reference. Unlike the full-mesh workload grants, it needs neither
the same organization nor a shared private network, it opens exactly one port,
and it is one-way: nothing flows back except the replies of a connection the
ingress opened. Both leases must be endpoints of the projection, they must
differ, and at least one of them must be placed on the projection's own node.
Entries are unique and strictly ordered by [source.id, destination.id, protocol,
port]; at most 1,024 are allowed (runtimeNetworkWorkloadIngressContract). The
authorization is signed content, not grant identity. A projection that omits the
member retains its exact canonical bytes, digest and signature.
getRuntimeNetworkProjectionWorkloadIngressPacketGrants validates the complete
projection and returns detached IRuntimeNetworkWorkloadIngressPacketGrants
({ source, destination: { lease, protocol, port } }). Their withdrawals follow
the host rules: an omitted grant needs its exact withdrawal, unknown or current
denials reject, and up to 4,096 retained withdrawals carry forward until the
exact joined application of the preceding projection is supplied. Cloudly routes
a cluster ingress route to the target lease address and port once the grant
exists.
The optional IRuntimeNetworkProjection.hostPlatformEndpointIds lists the ids of
the protected authority's platform endpoints whose address the projection's
node's own host carries, such as a relay listener or a Corestore beside the node
on one machine. A node delivers workload flows to those endpoints locally instead
of forwarding them. The list is sorted, unique, never empty when present, and
every id must be a current platform endpoint; omitting it keeps the canonical
bytes and states that no endpoint is local. It grants nothing: a workload still
reaches only the endpoints it selects. The platform endpoint contract itself
({ id, address, protocol, port }) names no node, which is why this statement is
per projection.
admitRuntimeNetworkProjection checks exact predecessor/replay identity and
rejects changed same-generation content, regressed membership/readiness evidence,
changed immutable address/DNS keys, and omitted packet grants without explicit
withdrawal. Pending withdrawals/tombstones must carry forward until the caller
supplies the exact previous projection reference from its own durable joined
application receipt. That reference is never taken from an incoming ACK. Keep
the accepted history and apply journal until native effects and DNS/allocation
quarantine are settled; a complete new snapshot does not erase that history.
IRuntimeNetworkSigningAuthority is separate from human JWT signing. Install
its current revision through the existing authenticated physical connection and
commit it with a durable CAS fence. Newly enrolled nodes may bootstrap the
controller's current key generation; existing trust cannot be reset to do so.
Exact successor revisions rotate the Ed25519 public key; publicKey: null
revokes it. Rotation/revocation stops acceptance under the previous key, and
requires newly signed authority for DNS. It does not deny outstanding packets
or permit allocation reuse.
The Node-only @serve.zone/interfaces/runtime export supplies
createRuntimeNetworkSigningKey, signRuntimeNetworkProjection and
verifyRuntimeNetworkProjection. Signing produces
ISignedRuntimeNetworkProjection { authority, projection, signature }, and the signature
covers the canonical envelope { domain, authority, projection } under the dedicated
serve.zone/runtime-network-projection-signature domain, so it binds the complete
projection and the exact signing-authority revision. Verification
requires receiver-owned trusted authority and node scope; an envelope contains
no key to trust. Private KeyObjects stay process-local; Cloudly persists their
exported material only through its encrypted internal secret store. The dedicated
requests.runtimesession key/projection push contracts bind the current physical
peer. Their response acknowledges durable admission only, never application,
readiness, independent fencing or pool activation.
The getRuntimeManagedVpnCredential method contract in requests.runtimesession
uses IGetRuntimeManagedVpnCredentialRequest and
IGetRuntimeManagedVpnCredentialResponse. Its request carries the exact current
session and signed projection reference. The secret response repeats those
bindings and supplies the hub tuple the node dials, the transport it dials it
over (quic, the one transport a managed hub serves), the managed authority ID,
the Noise server public key, the client keypair and the expiry in Unix
milliseconds. The response states no TLS name: QUIC is dialled on the address
alone. The hub is the node's own cluster's
relay (see Cluster VPN Hub And Network). Keep the
response process-local; never persist, hash, log or embed it in a signed projection.
snapshotGetRuntimeManagedVpnCredentialRequest and its response counterpart
capture closed detached JSON shapes and canonical 32-byte key encodings.
bindGetRuntimeManagedVpnCredentialRequest(request, projection, trustedSession)
validates the complete projection digest and binds node, controller, namespace,
physical-session identity and projection reference. The response binder takes
(response, request, projection, trustedSession, now, renewal = null) and additionally requires the
hub's exact tuple to be selected in router.platformEndpointIds, carrying the protocol of the
credential's transport — udp for quic, as
runtimeNetworkAddressPlanContract.hubTransports states. A relay therefore cannot name itself: the
endpoint has to be one the node's signed projection already selected.
Expiry must be after the caller-qualified time and no later than the referenced
projection's effective DNS window (see DNS Lease Renewals);
acceptance before that window starts rejects.
These helpers perform content validation. Callers authenticate and fence the current physical peer and projection before and after native work, qualify time independently, and retain ownership through revocation and shutdown. The native Noise implementation owns cryptographic validation and authentication. Credential delivery does not prove a connected VPN, TUN ownership, packet enforcement or workload readiness, and does not replace explicit packet withdrawals.
DNS Lease Renewals
A projection's dns window lasts at most fifteen minutes and is signed content, so a node's
private DNS stops when the window ends unless a newer signed statement extends it. Re-issuing the
projection would extend it too, but a node applies every new projection as new network state,
which restarts its data plane. IRuntimeNetworkDnsLeaseRenewal extends the window and nothing
else: it carries the node scope (controller, nodeId, runtimeNamespace), the exact
projection reference it renews, a sequence, a fresh dns { issuedAt, notBefore, expiresAt }
window and its digest. It names the projection by reference, so it never joins the projection
chain and never changes what the node applied. The new shapes carry no schemaVersion and no
version suffix; the installed package version is the protocol version.
issuedAt is the start of the window's slot, not the moment of signing: Cloudly signs renewals
ahead of time on a grid, so the fifteen-minute rule (maximumDnsLeaseMs, the same limit a
projection's window has) is measured from the slot. computeRuntimeNetworkDnsLeaseRenewalDigest
hashes every field except digest under its own domain, and validateRuntimeNetworkDnsLeaseRenewal
checks structure and digest only; a digest authenticates nothing.
The Node-only @serve.zone/interfaces/runtime export signs and verifies renewals with the same
authority and key that sign the controller's projections, under a separate signature domain.
signRuntimeNetworkDnsLeaseRenewal(renewal, authority, privateKey) produces
ISignedRuntimeNetworkDnsLeaseRenewal { authority, renewal, signature }.
verifyRuntimeNetworkDnsLeaseRenewal(signed, trustedAuthority, trustedScope, projectionAuthority)
takes the authority and scope from the node's own stored trust, never from the envelope.
projectionAuthority is the reference of the authority revision that signed the projection the
node admitted, and a renewal verifies only when it was signed under exactly that revision.
Rotation and revocation end renewals exactly as they end projections: nothing signed under an
earlier revision verifies after, and a renewal signed under a rotated revision does not renew a
projection signed under the one before. A renewal never closes a key gap; after rotation, DNS
continues only with a newly signed projection.
admitRuntimeNetworkDnsLeaseRenewal(renewal, currentProjection, previousRenewal | null, trustedScope, now)
is the pure admission decision after verification. The projection and the previous renewal are
the node's own admitted state, and now is its caller-qualified clock, read before the first
await like every other input. It admits a renewal only when:
- the renewal and the projection are in the trusted scope, and the renewal names the current projection's exact reference. Once a successor projection is admitted, every renewal of its predecessor is refused, and a renewal never introduces a projection the node has not admitted;
- its
sequenceis higher than the previous renewal's, with gaps allowed, or equal with byte-identical content, which is answered asreplaywhatevernowis, so re-delivery is idempotent. The same sequence with any other content is refused; - its window is current:
notBefore <= now < expiresAt. Admitting a window that has not opened would displace the coverage the node has now, so a sender delivers a window only once its slot has opened; a renewal refused for arriving early is simply delivered again once it has. An expired window is refused as well; - its window does not move
issuedAtback and ends later than the previous window. Without a previous renewal, the projection's own window is the baseline. Windows need not touch, so a node whose lease lapsed can still admit the next renewal it is given.
The caller stores the admitted renewal in the same transaction as its trust fence, and deletes it
in the transaction that admits a successor projection. Admission is not conversion: the node still
turns the window into a deadline with its qualified clock, and must not use a window before its
notBefore.
requests.runtimesession.IReq_Controller_Pallet_ApplyRuntimeNetworkDnsLeaseRenewal
(applyRuntimeNetworkDnsLeaseRenewal) delivers one renewal as
IApplyRuntimeNetworkDnsLeaseRenewalRequest { session, signed }.
bindApplyRuntimeNetworkDnsLeaseRenewalRequest(request, trustedSession) requires the node's
current physical-session binding and a renewal in that session's scope. It does not care who sent
the request: a cluster relay pushes under the binding the node obtained itself. The answer,
IApplyRuntimeNetworkDnsLeaseRenewalResponse { status, renewal }, repeats
IRuntimeNetworkDnsLeaseRenewalReference { projection, sequence, digest }.
bindApplyRuntimeNetworkDnsLeaseRenewalResponse(response, sentRenewal) compares it with the exact
renewal that was sent. The ACK asserts durable admission only, not that the node serves DNS.
A cluster is outbound-only, so while its relay cannot reach Cloudly nobody can sign a new window.
Cloudly therefore hands the elected relay a run of renewals it signed ahead of time, with
requests.cluster.IReq_Cloudly_Relay_HoldRuntimeNetworkDnsLeaseRenewals
(holdRuntimeNetworkDnsLeaseRenewals):
IHoldRuntimeNetworkDnsLeaseRenewalsRequest { nodeId, projection, renewals } answered by
{ held }. Each request replaces what the relay held for that node, and an empty run holds
nothing. The relay keeps the run in memory only, pushes only the renewal whose window is current
and only while it has no Cloudly session (escrowRelease), and holds no key and no trust.
snapshotHoldRuntimeNetworkDnsLeaseRenewalsRequest checks the rules a relay can evaluate without
the projection itself; the node still measures the first window against its admitted projection,
and every window against its clock, when it admits it. The run must have:
- one node, one projection and one signing authority revision, in one scope;
- strictly ascending sequences, each window extending the one before;
- at most
maximumEscrowedRenewals(32) renewals; - at most
maximumEscrowHorizonMs(four hours) from the first window'sissuedAtto the last window'sexpiresAt. On the intended grid of fifteen-minute windows every ten minutes, the horizon allows 23 windows, so it is the limit that binds.
validateHoldRuntimeNetworkDnsLeaseRenewalsRequest adds every renewal's digest check and
verifies no signature. bindHoldRuntimeNetworkDnsLeaseRenewalsResponse(response, sentRequest)
requires held to equal the number of renewals sent.
A hold is the one call whose outcome the controller cannot read back from its own records, so the
refusal is the whole answer. data.holdRuntimeNetworkDnsLeaseRenewalsRefusals is the frozen list
of names a relay leads its refusal with — escrow-run-invalid, escrow-node-not-carried,
escrow-run-foreign-session, escrow-run-stale, escrow-run-unordered and escrow-cloudly-only
— with data.THoldRuntimeNetworkDnsLeaseRenewalsRefusal as their union. The relay writes
<name>: <reason> and the controller matches the name, so neither side keeps its own copy of the
vocabulary.
The horizon is also the revocation latency: a detached cluster keeps DNS authority until its last
held window ends. So a controller must not release an address or a DNS name before the latest
expiresAt it has signed for that node. Renewals extend DNS only. They grant no packet authority
and cannot introduce new network state, and they do not extend an already-issued VPN credential;
a newly issued one may be bound to the renewed window.
getRuntimeNetworkEffectiveDnsWindow(projection, renewal | null) is the one rule for the window a
node may use: the admitted renewal's if there is one, else the projection's own. renewal is the
bare IRuntimeNetworkDnsLeaseRenewal that admission takes and returns, which is what a node
stores; Cloudly passes signed.renewal. It checks digests and that the renewal names exactly that
projection, but verifies no signature. Controller and node use it for every limit tied to the
window, and bindGetRuntimeManagedVpnCredentialResponse takes the same bare admitted renewal as
an optional last argument, so a newly issued managed-VPN credential may run to the end of the
renewed window instead of the projection's original one.
import { data } from '@serve.zone/interfaces';
import { signRuntimeNetworkDnsLeaseRenewal, verifyRuntimeNetworkDnsLeaseRenewal } from '@serve.zone/interfaces/runtime';
const renewal: data.IRuntimeNetworkDnsLeaseRenewal = {
...scope,
projection: { id: projection.id, generation: projection.generation, digest: projection.digest },
sequence: 1,
dns: { issuedAt: slotStart, notBefore: slotStart, expiresAt: slotStart + 900_000 },
digest: `sha256:${'0'.repeat(64)}` as data.TSha256Digest, // well-formed placeholder, replaced below
};
renewal.digest = await data.computeRuntimeNetworkDnsLeaseRenewalDigest(renewal);
const signed = await signRuntimeNetworkDnsLeaseRenewal(renewal, signingAuthority, privateKey);
// On the node: stored trust, then the node's own admitted state and qualified clock.
const verified = await verifyRuntimeNetworkDnsLeaseRenewal(
signed, storedAuthority, nodeScope, admittedProjectionAuthority);
const { renewal: admittedRenewal } = await data.admitRuntimeNetworkDnsLeaseRenewal(
verified.renewal, admittedProjection, previousRenewal, nodeScope, qualifiedNow);
const window = await data.getRuntimeNetworkEffectiveDnsWindow(admittedProjection, admittedRenewal);Runtime Network Address Plan
data.IRuntimeNetworkAddressPlan is the address space an administrator gives the runtime network,
and the single input Cloudly composes the protected authority from:
{ id: 'address-plan', revision, prefixes, pools, resolvers, platformEndpoints, vpn: { controlPrefix, hubPrefix } }.
Pools, resolvers and platform endpoints use the protected authority's own element shapes.
controlPrefix is the private prefix VPN control addresses are allocated from, and hubPrefix is
the protected prefix every cluster's VPN hub binds inside. The hub endpoints themselves are no
administrator's input: each cluster's relay reports what it bound, and the composition turns that
into platform endpoints, so platformEndpoints never lists one.
snapshotRuntimeNetworkAddressPlan refuses any plan that could describe an authority the authority
contract refuses: it runs snapshotRuntimeNetworkProtectedAuthority over the plan's own sets, so
overlapping pools, more than 16 resolvers and a resolver or endpoint outside prefixes or inside a
pool are all refused by that one validator. On top it requires both VPN prefixes to sit inside
prefixes, to overlap no pool, to hold no resolver or platform endpoint, and never to overlap each
other — control addresses live inside the tunnel, hub endpoints on the underlay. controlPrefix is
additionally a private prefix no longer than /30
(runtimeNetworkAddressPlanContract.maximumControlPrefixLength); no minimum length is stated,
because the private-range rule already keeps it at /8 or longer.
data.runtimeNetworkVpnControlPrefix(value) is that reader on its own, exported because a
cluster's VPN network repeats the prefix its hub binds and has to read it exactly as the plan does.
Every list of a plan is stated in one canonical order, and a plan is never reordered for its
writer: prefixes strictly ascending by the prefix text, and pools, resolvers and
platformEndpoints strictly ascending by id. Both compare as strings, so the order is
lexicographic, not numeric — 100.64.0.0/10 comes before 20.0.0.0/8, and resolver-10 before
resolver-9 — and a value stated twice breaks it as well. A list out of order is the one plan
refusal the contract names: snapshotRuntimeNetworkAddressPlan (and
admitRuntimeNetworkAddressPlanChange through it) throws
data.RuntimeNetworkAddressPlanStructureError, whose violation is
{ field: 'prefixes' | 'pools' | 'resolvers' | 'platformEndpoints', rule: 'order' } and whose
message quotes nothing from the plan. Every other refusal stays the contract's generic rejection.
try {
data.snapshotRuntimeNetworkAddressPlan(plan);
} catch (error) {
if (error instanceof data.RuntimeNetworkAddressPlanStructureError) {
error.violation; // { field: 'prefixes', rule: 'order' }
}
}data.IRuntimeNetworkVpnHub { clusterId, address, quicPort } is one cluster's hub as its relay
bound it. runtimeNetworkVpnHubEndpoints(hub) is the one derivation of what it contributes:
<clusterId>:quic on udp, one endpoint per row of
runtimeNetworkAddressPlanContract.hubTransports. That table has one row, because a managed hub
terminates TLS on its QUIC listener alone and no other transport of it can be dialled over a
protected connection. Every reader calls the derivation — the composition
that seals the authority and the router selection that names which endpoint a node may dial — so
two readers can never derive two ids for one hub.
composeRuntimeNetworkProtectedAuthority(plan, frame, hubs) is the composition itself: the producer
supplies the frame (TRuntimeNetworkProtectedAuthorityFrame: authority id, generation, controller,
previous reference and egress authorities) and the hubs of the clusters that registered one, and
receives the complete authority with its digest, so the validator and the protection producer can
never compose differently. Each hub's address must sit inside vpn.hubPrefix and one cluster is
named once; an empty hubs composes the authority of a runtime network whose clusters have no hub
yet. One hub costs one of the authority's 64 platform endpoints, so 64 clusters fit one
authority.
A single host that carries both a cluster's hub and platform endpoints, for example the relay
listener and a Corestore, holds more than one address. The hub address sits inside vpn.hubPrefix,
as every hub's does. The plan's own platform endpoints may not: they sit on another address of the
same host, inside prefixes, outside every pool and outside both VPN prefixes. Several endpoints may
share that one address on distinct ports, because the authority requires only each
address:port:protocol to be unique. For example, with hubPrefix 192.0.2.0/28, the hub binds
192.0.2.1 and the plan lists the relay listener and Corestore on 192.0.2.17 with two ports. The
node's projection then names those endpoints in hostPlatformEndpointIds.
admitRuntimeNetworkAddressPlanChange(previous | null, next) is the pure decision for a plan write at
the following revision; any other revision is a contract violation. Leases and the managed VPN
outlive a plan revision, so it answers:
unchangedwhen the content equals the stored plan; Cloudly writes nothing;refusedwithplan-vpn-immutablewhen either VPN prefix differs,plan-pool-removedwhen any pool is gone or changed its purpose or prefix, andplan-prefix-shrunkwhen the new prefixes, however split, no longer cover every address the old ones did;acceptedwith the ids of the resolvers and platform endpoints the change removes or changes. An entry that keeps its id but differs in any field counts, so moving it goes through the same reference check as removing it.
TRuntimeNetworkAddressPlanRefusal adds plan-resolver-referenced and plan-endpoint-referenced,
which only Cloudly can decide against live references, and plan-unsorted, which Cloudly answers
for a RuntimeNetworkAddressPlanStructureError with the list as its reference, so every plan
refusal has one type.
requests.network carries setRuntimeNetworkAddressPlan { identity, expectedRevision, plan }
(expectedRevision: null when no plan exists yet) and getRuntimeNetworkAddressPlan, which answers
null until the first plan is set.
Node Network Readiness
requests.network.getRuntimeNetworkNodeReadiness { identity, nodeId } answers
data.IRuntimeNetworkNodeReadiness: ready: true with nothing missing, or ready: false with at
least one TRuntimeNetworkNodeReadinessGap. The gaps follow the node producer's order: the node
joins the protected authority's egress (egress-pending), every egress owner reports current
protection (protection-receipts-pending, naming those owners, at least one), a handoff is reserved
(handoff-pending), the router selection carries it (selection-pending), the node's acknowledged
projection carries it (projection-pending), and the node is a managed VPN member
(vpn-member-pending).
data.IRuntimeRouterIncarnation { id, incarnation } is a node's router incarnation, from which its
handoff and router incarnation ids derive; snapshotRuntimeRouterIncarnation checks the record.
reincarnateRuntimeNetworkRouter { identity, nodeId, expectedIncarnation } advances it, so the node
producer reserves the router a fresh handoff, and answers with the new incarnation.
Node-Bound Runtime Assignments
The data namespace exports IRuntimeAssignment, canonical digest helpers,
evaluateRuntimeAssignment, and assignment observation validators/evaluators
for Cloudly/Onebox controllers and Pallet nodes. These are shared contracts;
they do not install a runtime, schedule containers or expose an RPC endpoint.
Each assignment binds a controller incarnation, organization, stable node and
replica IDs, one immutable execution attempt, image/configuration and opaque
network/storage/secret references. Group and site IDs are nullable topology
provenance. Those workload references are also the authority vocabulary:
data.runtimeWorkloadAuthorities is ['network', 'secrets', 'storage'], and
data.requiredWorkloadAuthorities(workload) names the ones this workload cannot
run without — filtered out of that vocabulary and sorted, so the answer is
duplicate-free and compares byte for byte against the resolvedAuthorities a node
stated when it registered. data.isResolvedWorkloadAuthorities(value) is the same
rule read the other way, for a receiver checking a list it was sent. Canonical is
strictly increasing in the code-unit order of the names themselves — a rule a peer
in another language implements from the vocabulary alone, rather than from the
order this package happens to declare it in — and both directions compare through
that one rule, so neither can drift from the other.
workload.registryHost is the registry that published the image, which is the publication identity
the image release is bound to, and it is never rewritten. workload.pullEndpoint is the optional
host[:port] the node fetches the bytes from instead — a cluster whose relay forwards the registry
states the relay's own origin, so a node pulls inside its cluster and nothing cluster-side dials the
control plane. data.buildRuntimeAssignmentImageReference(workload) is the one place that choice is
made: pullEndpoint ?? registryHost, digest-pinned either way, so the endpoint decides reachability
and never content, and a controller and a node cannot disagree about the reference. Absence stays
absence: an assignment that states no endpoint is different content, and so a different digest, from
one that does.
All fields except the digest itself participate in a domain-separated SHA-256 digest. Digests provide integrity and compare-and-swap identity; authentication must separately establish the trusted controller/node scope.
An attempt starts at generation one with run, then advances through
stop-preserve and remove-runtime-preserve-storage using exact predecessor
generation/digest references. Exact retries return replay; conflicts, stale
revisions, gaps, changed execution specifications and resurrection are rejected.
Consumers must atomically persist the evaluator's detached snapshot before effects,
retain removal tombstones and storage, and serialize ownership of each replica.
Omission causes no change. A new attempt requires separate admission proving any
older exclusive writer stopped or fenced. The supported offline policy is
continue-node-bound; no heartbeat timeout or lease expiry proves a writer absent.
Observations bind the exact assignment, authenticated reporter session, durable
per-assignment sequence and digest. Reconnect does not reset the counter. Observation
history must reference the same assignment revision or its exact predecessor;
receivers process each disposition's observations in order. Session
authorization, clock/freshness policy and durable receipt persistence belong to
the receiving service. Readiness requires matching running image evidence and
run intent; assignment acceptance alone is never readiness. A valid observation
can describe drift (such as a removed runtime while intent remains run), and
does not authorize storage relocation or prove fencing.
Resolved Runtime Configuration
data.IRuntimeConfig seals the public environment, working directory, resource
and identity policies, target ports, readiness timing and log limits together with
the immutable image invocation. `computeRuntimeConfig
