@serve.zone/interfaces
v32.84.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.
data.sparkNodeHostContract defines the node's desired-host route,
/spark/nodes/host, with 1024-byte request and response bounds, authenticated and
judged in the same order as the heartbeat. ISparkNodeHostRequest carries the
Spark bearer and the sender's offer; ISparkNodeDesiredHost answers with the whole
desired value of IClusterNode.data.hostnameIntent — the hostname and its
generation — never a change, and hostname: null with generation 0 states that
Cloudly holds no intent, so Spark leaves the host's hostname as it is. Spark applies
the latest generation and states the outcome of its last attempt as the optional
ISparkNodeReport.hostApply (applied, or failed with a bounded reason) on its
next heartbeat; the node has converged when runtime.hostname states the intent's
hostname. A Spark that sends hostApply raises its minimumPeerVersion so it
reaches only a Cloudly that reads it.
Node Actions
A node action (INodeAction: reboot, serve.zone service update, system upgrade)
that a node runtime executes carries lifecycle (INodeActionLifecycle): the
administrator's confirmation (INodeActionConfirmation: the typed hostname, the
digest of the impact preview they read, when the impact names a Corestore on the
node the consent to restart it, and for an update the fleet Spark target revision
the preview showed, refused target-changed once the target moved), the impact (INodeActionImpact), the fleet
Spark target an update moves the node to, a revision, the current deadline, the
claim, a failure with its remedy and a timeline. A Swarm-era record carries none of
it and travels only in the Swarm-era metrics-sample answer. TNodeActionStatus
keeps the four shipped values (pending is the requested state) and adds
claimed, executing, draining, rebooting, verifying, expired,
cancelled and unverified; data.nodeActionStatusKinds states which are open.
data.sparkNodeActionContracts defines the node runtime's two action routes,
authenticated and judged in the heartbeat's order: offer (/spark/nodes/action)
answers the node's one open action (ISparkNodeActionAssignment) or null, and
transition (/spark/nodes/action/transition) takes one step at the revision the
assignment states — claim with the host's boot id, a forward phase
(sparkNodeActionPhaseOrder), complete (a host upgrade without a reboot) or
fail with one of sparkNodeActionFailureCodes — and answers applied,
replayed (the same step again at the same revision) or a named refusal.
data.admitsSparkNodeActionTransition states which step an action admits.
Cloudly verifies every action from what the node reports
(data.nodeActionVerification): a new boot.id, the target bundle version, or a
completed host upgrade; a heartbeat stating boot.rebootRequired after a host
upgrade fails it reboot-required rather than rebooting. A deadline
(data.nodeActionCompletionBudgetMs, nodeActionContract) that passes ends an
action unverified.
A Spark that executes actions states ISparkNodeReport.boot (the kernel boot id
and whether the host waits for a reboot) and actionExecutors, the canonical list
of action names it executes; Cloudly admits an action only for a node that states
its executor. Both are optional members an earlier reader refuses, so a Spark sends
them only to a Cloudly that reads them, like hostApply.
data.ISparkFleetTarget is the one fleet Spark target: a version and the SHA-256
its release's SHA256SUMS.txt states for install.sh, with a revision, who set
it and when, an append-only history (ISparkFleetTargetChange) and per-node
holds (ISparkNodeTargetHold). data.readSparkNodeTargetStanding places a node
at, behind or ahead of it, held, unreported or untargeted. The admin requests
(getNodeActionPreview, createNodeAction with confirmation,
cancelNodeAction, getSparkFleetTarget, setSparkFleetTarget,
setSparkNodeTargetHold) answer refusals by name from data.nodeActionRefusals
and data.sparkFleetTargetRefusals.
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, resolvers) derives the
service's network suffixes, the single selected search suffix, and the intersection
of external forwarding permissions. resolvers is the complete resolver list of the
protected authority the service is projected under. A service attached to no private
network forwards any public name to the first four of those resolvers in address and
port order; when the authority declares no resolver it has no external resolution
(externalDns: null, so its node answers REFUSED for every name). An attached service
is decided by its networks alone: a network denying external DNS keeps denying its
members whatever the authority declares. 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].
continuesRuntimeNetworkProtection(previous, next) states which successor of a protected authority
only adds. It answers true when next is previous itself, or exactly the next step of its chain
(the same id and controller, next.previous naming previous) in which every egress owner, pool,
resolver and platform endpoint of previous is still present with the same content and the prefixes
still cover every address previous protected, however they are split. A cluster topology change is
such a step: a node joins the egress, or a cluster's hub joins the platform endpoints. The step keeps
its barrier: every egress owner still reports a receipt for next before an allocation binds to it.
Any removal or change, a jump over a step, another chain or another controller answers false. Both
authorities and their digests are validated, and either that does not validate is rejected.
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 node sends one
only in answer to the retirement request of a signed projection (see
Projection Retirement Requests): its egress
ownership is being retired with the withdrawn disposition, and it mints no
positive receipt for the chain afterwards
(runtimeNetworkProtectionContract.withdrawal). Nothing else withdraws. The
node's allocation-pool guard is retained in the kernel when the node process
ends, and its boot unit restores it before networking starts, so a clean stop, a
crash, a reboot or being offline leaves the last receipt current. A reconnect
binds a new reporter session, which the old receipt no longer matches, and the
node's first positive receipt under that session restores 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. There is no generic independent-fence
flag, quorum or expiry: the one way an owner leaves is
Egress Owner Retirement.
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 carried withdrawals. These transport bounds do not assert native
capacity; the node must preflight the actual SmartVPN, Smartnftables and DNS
limits before effectful application.
The packet grants a node projection derives are bounded too, and that bound is
a protocol limit, not a transport bound: grants are never carried in the
projection, and validateRuntimeNetworkProjection refuses a projection whose
grants exceed runtimeNetworkProjectionContract.maximumGrants, 4,096 directed
grants per node projection. The count covers every workload grant (each ordered
pair of distinct members of a private mesh with at least one end on the node)
together with every local public-egress and platform grant. One private network
whose members are all on the node therefore holds at most 64 of them
(64 × 63 = 4,032 workload grants; 65 × 64 = 4,160 is refused), and fewer once
remote members, other networks' pairs or egress and platform grants share the
node's 4,096. The bound also keeps each successor's explicit withdrawals, which
carry every grant it omits and are capped at 4,096, representable. Cloudly and
Pallet both apply it through this package's validation, so lifting the ceiling
is a protocol change across all three, never a different value here alone.
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 a policy document. 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
protocol, hostPort and targetPort, optionally hostPortEnd and symmetric:
the request accepts no hostIp (an omitted address means the node uplink). 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.
Who authors a service's published ports follows the service. A service of an
idp.global organization is that organization's: only a federated sign-in holding
its freshly introspected owner role reads and writes them, and an
infrastructure administrator does not substitute for it. A platform-owned
service (data.IServicePlatformOwnership: organizationId, serviceId, the
designating administrator's actorId, designatedAt) belongs to no idp.global
organization, such as the cluster ingress or a platform call router; only a
verified administrator reads and writes its published ports, as only an
administrator writes its egress. An administrator designates such a service
with designatePlatformOwnedService, withdraws it with
undesignatePlatformOwnedService and reads it with getServicePlatformOwnership
(requests.network.IReq_Admin_Cloudly_DesignatePlatformOwnedService,
IReq_Admin_Cloudly_UndesignatePlatformOwnedService,
IReq_Admin_Cloudly_GetServicePlatformOwnership). Only a platform
organization's service is designated: data.isPlatformOrganizationId is true
exactly for a canonical identifier that carries : (legacy:Service:<id>,
Organization:<id>), and false for an idp.global nanoid, whichever character it
starts with, and for any value that is not a canonical identifier, such as an
empty or whitespace id. Cloudly refuses every other id as
organization-not-platform, and further designation refusals are
service-unknown, service-deleting and conflict
(data.servicePlatformOwnershipRefusals). The designation grants no port, egress
or placement by itself. Withdrawing it leaves the service's published ports as
they are with no writer, because a platform organization has no idp.global
owner, until an administrator designates the service again. A published-port
write staged while the designation changes is refused as the policy write's
conflict.
A published entry with hostPortEnd publishes every port from hostPort to
hostPortEnd inclusive, each to the same port inside the workload, so its
targetPort equals hostPort and a one-port range is written without the member.
symmetric: true makes outbound flows the workload originates from a published
target port leave the node from the matching host port, so a peer sees one address
and port in both directions (SIP, RTP); it grants no egress of its own, and a
projection endpoint carries a symmetric entry only while its publicEgress is true.
Both members are stated only when present, so an entry without them keeps its bytes.
A symmetric entry owns its inside ports: no other entry of the list and protocol,
symmetric or not, publishes to its target port or any port of its range
(findRuntimeNetworkPublishedPortInsideOverlap), which Cloudly refuses as
published-port-symmetric-inside-overlap and the snapshots refuse outright.
The entries of one list are sorted, never overlap within a protocol, number at most
runtimeNetworkPublishedPortContract.maximumEntries (64) and cover at most its
maximumPortsPerService (1024) ports; on one node, no two entries of a protocol
share a port. The port space is split once for Cloudly and Pallet: handoff leases
translate outbound flows to runtimeNetworkHandoffSnatPortRange (49152-65535), and
Cloudly publishes only runtimeNetworkPublishedPortContract.publishablePorts
(1-49151), refusing any other policy as published-port-reserved
(isRuntimeNetworkPublishedPortReserved) and a symmetric entry without public
egress as published-port-symmetric-without-egress. The snapshots do not enforce
the band, so leases and policies written before it stay readable.
A node reads a projection as a whole, so one built against an interfaces release
before these members refuses any projection that carries them, including entries
of endpoints on other nodes. The session offer (IProtocolOffer.interfacesVersion)
is the only statement a node makes about the contract it reads; there is no
separate capability list, so a controller must not issue a ranged or symmetric
entry into a projection for a node whose offer names an earlier release.
Every endpoint's publicEgress and platformEndpointIds come from the same
service policy document. A verified administrator authors them through
getServiceNetworkEgress and setServiceNetworkEgress
(requests.network.IReq_Admin_Cloudly_GetServiceNetworkEgress /
IReq_Admin_Cloudly_SetServiceNetworkEgress): platform endpoints are platform
authority, and only an administrator writes the runtime spec whose attached
network has to state the same values. IServiceNetworkEgress returns
publicEgress and the selected platformEndpointIds (sorted, unique, at most
runtimeNetworkAuthorityContract.maximumPlatformEndpoints) with the document's
revision, or null when a service never had a policy document.
ISetServiceNetworkEgress carries expectedRevision (null for the first
document), a retained mutationId for replay and the exact desired values, and
returns the accepted IServiceNetworkEgress with a replayed flag; Cloudly
carries the published ports forward unchanged, and refuses an id that names no
current platform endpoint. Published ports and egress are two halves of one
document with one revision, so expectedRevision of either request is the
revision the caller read from either answer, and an accepted write of either
advances it.
One document per service holds its published ports and its egress under one revision. The
first accepted write of either (expectedRevision: null) creates it: setServicePublishedPorts
with egress publicEgress: false and platformEndpointIds: [], setServiceNetworkEgress with
no published ports. From then on both reads return it, never null. It is an explicit policy: an
attached spec stating publicEgress: false and platformEndpointIds: [] matches it and reaches
no public address and no platform endpoint; network-policy-missing means the service has no
document at all.
snapshotServiceNetworkEgress and snapshotSetServiceNetworkEgress
check the shapes. An attached runtime spec is admitted only while it states the
document's values (network-policy-mismatch), and nothing else grants a
workload egress: a Corestore endpoint on the node's own host is delivered
locally, but only to a workload that selects it.
getRuntimeNetworkProjectionPacketGrants derives directed member connectivity
and explicit local public/platform egress independently of DNS readiness.
getRuntimeNetworkProjectionPrivateMeshes validates the complete projection and
returns its private meshes (IRuntimeNetworkPrivateMesh): one per
(organizationId, networkId) that some endpoint's service membership attaches,
with the lease references of every such endpoint, local or remote, sorted by
lease id. Meshes are sorted by organization and then network id; a network no
endpoint attaches has no mesh, and an endpoint whose membership attaches no
network is in none. This grouping is the one definition of who may reach whom:
the workload grants of getRuntimeNetworkProjectionPacketGrants are exactly the
ordered pairs of distinct members of each mesh with at least one end on the
projection's node, each pair once however many networks its ends share. A
receiver realizes a mesh from it without deriving membership again. Whether a
member is local is its lease's node scope (the projection's nodeId,
runtimeNamespace and controller), and which local members are attached, or
whether remote members are reachable through a tunnel, is the receiver's own
fact; the mesh grants nothing beyond the pairs above. A projection names each
network id once and only for the organization that owns it, so the key's
organization never separates two meshes of one validated projection; it is kept
so that the grouping holds without that rule.
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.
composeRuntimeNetworkProjectionDnsRecordSets answers the same DNS material
with each network's records defined once (IRuntimeNetworkDnsRecordSets). It
returns one record set (IRuntimeNetworkDnsRecordSet: networkId, dnsSuffix
and records sorted by name) per private network that a selected local source
attaches, and no other, sorted by network id. Per selected source, in projection
order, it returns that source's union view without records
(IRuntimeNetworkDnsSetView: source, sourceAddress, dnsServer, policy)
and networkIds, the sets it serves in policy.networkSuffixes order. The union
of a source's sets is exactly the records of its
composeRuntimeNetworkProjectionDnsViews view, and no name is in two of them,
because a validated projection names every alias FQDN once. The union views
repeat every shared name in every view; the sets hold it once, so the material
grows with sources plus names instead of their product. The composer takes the
same optional selection, validates the same way and refuses the same inputs. Its
answer is a pure function of the validated projection and the selection, in a
stable order; it reads no attachment journal. It names no consumer's
identifiers: a consumer that installs the sets (Pallet, as smartdns recordSets
referenced by recordSetIds) derives its own set ids, for example from the
network's DNS keys.
Both composers read each membership's ready addresses once per projection, so composing one source's view costs work linear in memberships plus endpoints, not their product.
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
(Projection Application Receipts).
A signed grant is not yet a delivered flow. The receiving node enforces a grant only between two workloads attached behind its own router: the packet engine has no one-way grant across the inter-node tunnel, so a grant whose ingress lease and target lease run on different nodes is signed into both nodes' projections and admitted by both, yet neither builds a packet path for it and the flow stays denied. Until cross-node one-way grants exist, a controller may route a cluster ingress route to the target lease address and port only when the grant exists and both leases are placed on the same node.
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.
Projection Retirement Requests
The optional IRuntimeNetworkProjection.retirement
(IRuntimeNetworkProjectionRetirement { disposition: 'withdrawn', protectedAuthority })
is Cloudly's request that a running node withdraw its allocation protection,
because its egress ownership is being retired with the withdrawn disposition.
It travels in the projection because that is the one authenticated, monotonic
channel the node already admits, journals and reads on every pass, and whose
acknowledged head Cloudly already tracks: the controller's signature covers it, so
a node cannot invent it; the projection chain is bound to the controller epoch,
node and runtime namespace, so it cannot be carried elsewhere; and a node's
acknowledged head proves the node admitted it. A retired node never returns
(runtimeNetworkEgressRetirementContract.reentry), so no later chain of the same
node can meet a stale request.
protectedAuthority is the authority current when Cloudly first signed the
request; its id names the receipt chain's authority chain.
validateRuntimeNetworkProjection accepts it only on a cloudly projection and
only naming the projection's own authority chain at a generation the projection
has reached (the current authority itself at the same generation).
admitRuntimeNetworkProjection latches it: it may first appear on any projection,
including the first, and that first appearance names exactly the projection's
current protectedAuthority (an older generation is refused, because the node's
positive receipt under the current authority would then be newer than the request
and the withdrawal could never complete). Afterwards every successor carries it
unchanged, never dropped and never rewritten, so it stays fixed while the
authority chain moves on. Omitted, it keeps the canonical bytes and asks for
nothing; it grants and withdraws no packet flow. Consumers on interfaces earlier
than 32.44.0 read projections with exact keys and refuse one that carries
retirement, so Cloudly sends the request only to nodes that report a compatible
interfaces version.
snapshotRuntimeNetworkProjectionRetirement is the exact-key reader and
runtimeNetworkProjectionRetirementContract states the rules.
The node answers with the null-barrier successor of its protection receipt chain and mints no positive receipt for that chain again. Its allocation-pool guard stays enforced: the answer withdraws eligibility, not protection.
bindRuntimeNetworkProtectionReceiptRetirement(receipt, previous, retirement) is
the receiver's gate for every report from a node whose projections carry the
request, beside admitRuntimeNetworkProtectionReceipt with the same previous
(the receiver's current receipt for the node, or null). The receipt must belong to
the retiring authority chain and previous, when given, must be the same chain's
exact predecessor or the receipt itself. The null answer always passes. A positive
receipt passes only while it can predate the node's admission of the request, so a
receipt the node minted before it saw the request still drains from its outbox
and the withdrawal behind it is never wedged: it names no authority newer than the
request's (a newer one could only come from a projection already carrying the
request), and its predecessor is not a null withdrawal (once the receiver holds
the answer, no positive receipt follows). A positive receipt minted after the
request against the same authority cannot be told apart from one minted before;
the node's obligation is never to mint one.
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 (Projection Application Receipts).
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.
A credential names one projection, but its key pair belongs to the node's managed VPN
membership, which outlives it. Every workload add, removal, readiness change or policy change
signs a new projection generation; none of them changes who the member is.
continuesRuntimeManagedVpnMembership(previous, next) is that rule: next continues the
membership when it is the same projection lineage (id), under a protected authority that
continues the previous one (continuesRuntimeNetworkProtection: the same authority or its next
step that only adds), with the same handoff lease (which names the node scope and carries the
router incarnation), and is previous itself or a later generation. A node joining the egress
