@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/interfaces

Public API

The root export exposes five namespaces:

import { appstore, data, platform, platformservice, 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.
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.

Service creation carries canonical ownership separately from caller-writable service data. requests.service.IRequest_Any_Cloudly_CreateService requires a top-level organizationId, while data.TServiceWritableData excludes the server-managed ownership field. Consumers should use data.validateOrganizationId() to validate the identifier shape and verify the request's ownership authorization separately before persistence.

Secrets v24 Pre-Cutover Contract

Version 24 retains the value-free v23 architecture while replacing the remaining pre-cutover lifecycle and runtime authority gaps. Secret values enter Cloudly only as strict SmartCrypto X25519 envelopes or an explicit bounded server-generation request. Coreflow receives a full digest-verified schema-v2 manifest plus one sealed envelope per pinned SecretVersion through the Node-only runtime export.

This is a contract release before consumer cutover. It does not claim that clean-v2 migration, backup verification, historical secret erasure, or storage cleanup has completed. Those operations remain separately gated and must be proven by their owning services before destructive cleanup.

Breaking removals include:

  • The SecretGroup and SecretBundle data/request modules and request namespaces.
  • getServiceSecretBundlesAsFlatObject and every service/preflight field tied to bundled flattening or aggregate runtime files.
  • Plaintext resolved runtime values, generic platform config/credential maps, credential-bearing Cloudly settings, and serialized object-storage credential references.
  • Hosted-app control-token identities and credential-bearing bootstrap actions.

The current secret contract includes:

  • TSecretValueInput, active-only IActiveSecretRecipientMetadata, the requests.secret.IReq_GetSecretIngressRecipient contract with method getSecretIngressRecipient, and fixed-order create/rotate/App Store context builders.
  • ISecretEnvelopeAdmissionBindingV1 under the Node-only runtime export binds an exact envelope and request context to one recipient generation. The binding is reproducible and does not itself prove admission. Retiring-key retries require a trusted Cloudly mutation receipt created atomically while that recipient was active; caller-supplied admission or issuance timestamps are not part of the contract.
  • getSecretVersionPurgePreflight returns bounded advisory reference pages and complete blocker counts. purgeSecretVersion identifies one exact version and fences the mutation by secret, version, and target revisions. The non-issuable purge-pending lifecycle and revisioned pending/erasing/failed/succeeded operation keep external erasure durable across retries without treating preflight as authorization.
  • Schema-v2 IResolvedSecretManifest contracts, stable Docker resource naming, WorkloadInit map/wrapper helpers, and ISealedResolvedSecretMaterial under @serve.zone/interfaces/runtime.
  • Two-step Coreflow X25519 recipient enrollment and exact recipient lifecycle validators.
  • Mandatory IImmutableContainerInvocationV1 evidence on immutable image deployment plans.

Secrets v24 Runtime Registration And Reporting

The Node-only @serve.zone/interfaces/runtime export adds the live contract that Cloudly must validate before publishing secret-bearing desired state to a Coreflow connection:

  • getSecretRecipientEnrollmentState returns either an exact generation-zero empty state or the complete valid recipient set for the cluster derived from the verified JWT.
  • getCoreflowSecretRuntimeRegistrationExpectation returns either an available v2 expectation or an explicit unavailable reason. The available expectation binds the live reporter session, active recipient, fresh generation-fenced target authority, and active WorkloadInit approval through expectationDigest.
  • Spark sends authenticated, sequenced local Swarm membership observations and, on managers, complete manager snapshots. Cloudly derives scope from the Spark credential, reconciles manager consensus privately, and publishes one fresh single-Swarm target authority or a targetless unavailable state. Structural contract validation does not itself establish manager consensus.
  • TSparkSwarmObservationV2 lets workers report only their local Swarm node ID, because Docker does not expose the Swarm cluster ID to workers. Cloudly may associate that node with a cluster only through authenticated node scope and accepted manager consensus. The v2 transport contract designates /spark/v2/nodes/swarm-observation. A conforming Spark sender must retain one exact request until it receives a request-bound acceptance receipt. A conforming Cloudly consumer must authenticate the node before inspecting replay state, derive cluster scope from that persisted identity, and never register, translate, or accept v1 observations as an authority fallback.
  • computeSparkSwarmObservationV2Digest uses strict canonical JSON and the serve.zone/spark-swarm-observation-v2 domain. A reporter session starts at sequence one. The contract requires a Cloudly consumer to accept only the exact next sequence with a strictly advancing observedAt, while an exact same-sequence/same-digest retry returns the byte-equivalent persisted receipt before age checks. It must reject conflicting, stale, skipped, retired-session, old, future, or non-advancing reports. A reused session ID may be treated as new only after it leaves the bounded retired window, and its sequence-one observation must still advance the permanent timestamp high-water mark. The consumer must persist acceptance state and its receipt in one atomic boundary.
  • validateSparkSwarmObservationV2 validates exact structure and attached snapshots, while validateSparkSwarmObservationV2Request and validateSparkSwarmObservationV2Response validate transport and request binding. None of these functions authenticates a node, persists replay state, establishes manager consensus, or creates runtime authority.
  • WorkloadInit approval binds a clean stable release identity, version-tagged OCI index, exact amd64 and arm64 platform/executable digests, policy generation, and a Cloudly summary of detached Cosign DSSE/SLSA verification. Public shape and digest validators do not verify the signature, public-key trust root, or private Cloudly policy.
  • coreflowSecretRuntimeRegistrationTagId is the sole dedicated TypedSocket tag identifier. Its payload is exactly ICoreflowSecretRuntimeRegistrationV2; cluster scope comes from the verified connection identity rather than the tag.
  • ICoreflowSecretRuntimeRegistrationV2 references the exact expectation, target generation/digest, and WorkloadInit authority. It must cover every Cloudly-node/Swarm-cluster/Swarm-node identity and approved per-platform manifest and installed-executable digest without self-asserting placement or approval.
  • validateCoreflowSecretRuntimeRegistration compares the registration with a trusted expectation built by Cloudly, including the live reporter session. Missing, extra, duplicate, reordered, or mismatched node evidence fails closed. Consumers must discard the registration on transport disconnect, tag removal or replacement, or any live session, placement, artifact, or recipient expectation change before publishing more secret-bearing state.
  • reportSecretDeploymentState reports only applying, applied, drifted, or failed for the manifest and plan revision selected by Cloudly. The validator receives the trusted cluster ID after JWT verification and the trusted live reporter session. Wire-provided manifest scope is checked against, and never replaces, that trusted authority.

The package validates report shape, digest, trusted cluster, and trusted live session; it does not persist replay state or mutate deployment plans. Deployment report consumers must persist an atomic receipt keyed by the verified cluster, reporter session, service, and positive sequence. The report digest uses fixed-order JSON and excludes only the JWT identity and reportDigest. The consumer accepts the exact next sequence once, returns the prior response for a same-digest replay, rejects a different-digest replay, and ensures a plan revision CAS failure consumes neither the sequence nor a receipt. Timestamps are informational and never replace live session, placement, artifact, recipient, sequence, or plan-revision fences.

createWorkloadInitEnvironmentMap now rejects manifests without any launcher-environment delivery. Consumers must bypass WorkloadInit for file-only manifests.

Portable Storage Contracts

App Store templates can declare logical, template-local storageClasses and stable named storageRequests. The same manifest is fulfilled by Onebox or Cloudly without exposing a physical provider:

const storageConfig: appstore.IAppStoreVersionConfig = {
  image: 'example/database:1.0.0',
  port: 5432,
  storageClasses: {
    databaseFast: {
      kind: 'filesystem',
      purpose: 'database',
      required: {
        performanceTier: 'highIops',
        durability: 'persistent',
        hardQuota: true,
        snapshots: 'native',
        encryptedInTransit: true,
      },
    },
    backupCapacity: {
      kind: 'objectStorage',
      purpose: 'backup',
      required: {
        performanceTier: 'capacity',
        durability: 'persistent',
        hardQuota: true,
        encryptedInTransit: true,
      },
    },
  },
  storageRequests: [
    {
      id: 'database-data',
      kind: 'filesystem',
      storageClass: 'databaseFast',
      mountPath: '/var/lib/example',
      accessMode: 'ReadWriteOnce',
      capacity: { request: '20GiB', limit: '40GiB' },
      reclaimPolicy: 'retain',
      protection: { backup: 'required', snapshots: 'native' },
    },
    {
      id: 'backup-archive',
      kind: 'objectStorage',
      storageClass: 'backupCapacity',
      accessMode: 'readWrite',
      capacity: { request: '100GiB', limit: '1TiB' },
      reclaimPolicy: 'retain',
      delivery: {
        type: 'file',
        targetPath: '/run/secrets/backup-archive.json',
        format: 'servezone-object-storage-v1',
        uid: 1000,
        gid: 1000,
        mode: 0o400,
      },
      protection: { versioning: 'required', retentionDays: 30 },
    },
  ],
  requiresFeatures: [
    appstore.appStoreStorageFeatureIds.bindingsV2,
    appstore.appStoreStorageFeatureIds.filesystemV1,
    appstore.appStoreStorageFeatureIds.objectStorageV2,
    appstore.appStoreStorageFeatureIds.objectStorageFileV2,
  ],
};

Capacity quantities are positive integers followed by KiB, MiB, GiB, or TiB. Storage request IDs survive upgrades and restores. Logical class keys express requirements and preferences only; Onebox and Cloudly map them to operator policy independently.

An app may declare multiple objectStorage requests. Each request resolves to its own endpoint, bucket, and value-free credential management scope. Launcher environment delivery uses an explicit key map and file delivery uses one managed JSON Secret at a unique target path, so two bindings cannot share credential destinations accidentally.

platform.storage contains separate capability advertisements and resolved binding/status contracts. Resolved object-storage bindings expose connection metadata plus a service-owned credential management scope and delivery policy, never credential references or values. Filesystem bindings expose the container mount and access mode, never a host path.

Portable manifests and resolved bindings intentionally have no fields for Synology, NFS, Kerberos, Corestore, Kubernetes, Docker drivers, servers, exports, mount options, provider credential values, or local fallback paths. Runtimes must reject unknown manifest fields and unsupported required feature IDs before provisioning. Legacy volumes and platformRequirements.s3 remain deprecated inputs for strict resolver normalization only.

platform.storagemigration defines the provider-neutral cutover contract for a named object-storage binding. Corestore atomically owns and fences the source binding after validating a distinct, unfenced active-object snapshot. Onebox only receives a held candidate, stages that exact candidate, stops the matching workload generation, and attests the quiesced state. The candidate cannot start until destinationBindingStartAuthorized is true. Corestore continues returning consumerAction: 'startDestination' until Onebox submits the mutation-fenced consumer activation request and the acknowledgement becomes durable evidence. Candidate-issued abort tombstones authorize only startSource and never retain the staged candidate binding.

Every migration DTO and status has an exact, versioned runtime normalizer. Unknown fields, provider or pool identifiers, unbounded strings, unsafe integers, stale mutation revisions, identity drift, and lifecycle-inconsistent fields are rejected. Migration-created digests use strict canonical JSON and a bare lowercase 64-hex SHA-256 value; portable golden vectors cover the source snapshot, target request, prepare intent, and candidate binding. Pre-cutover failures may retry or abort; after the durable commit point, recovery can only retry or roll forward. Physical pool IDs, mount details, provider receipts, and publication capabilities remain private.

Primary exports include:

  • IObjectStorageMigrationPrepareRequest and TObjectStorageMigrationStatus for the immutable intent and status journal.
  • IObjectStorageMigrationConsumerQuiesceRequest with IStorageMigrationConsumerQuiesceEvidence for exact candidate staging and source-workload shutdown.
  • IObjectStorageMigrationConsumerActivationRequest with IStorageMigrationConsumerActivationEvidence for durable destination-start acknowledgement.
  • normalizeObjectStorageMigrationStatus, bindObjectStorageMigrationConsumerQuiesceRequest, and bindObjectStorageMigrationConsumerActivationRequest for strict ingress and current-revision mutation fencing.
  • The create*Sha256 helpers for source snapshots, prepare intent, target requests, candidate/active bindings, and persisted staging or activation evidence.

The phase and consumer-action progression is exact:

Phase consumerAction Destination start authorized
preparing, transferring wait No
awaitingConsumerQuiesce stageCandidateAndStop No
finalizing, committing wait No
readyToStart startDestination Yes
cleanupPending, complete none Yes; durable activation evidence is required
aborting wait No
aborted startSource No; only the active source binding may restart

Normalize every status before acting, and bind consumer mutations to that exact status revision:

const status =
  await platform.storagemigration.normalizeObjectStorageMigrationStatus(
    untrustedStatusPayload,
  );

if (status.phase === 'awaitingConsumerQuiesce') {
  const request =
    await platform.storagemigration.bindObjectStorageMigrationConsumerQuiesceRequest(
      untrustedQuiescePayload,
      status,
    );
  await submitQuiesceAcknowledgement(request);
}

if (status.phase === 'readyToStart') {
  if (
    !status.destinationBindingStartAuthorized ||
    status.consumerAction !== 'startDestination'
  ) {
    throw new Error('destination binding is not authorized to start');
  }
  await startWorkload(status.activeBinding);
  const request =
    await platform.storagemigration.bindObjectStorageMigrationConsumerActivationRequest(
      untrustedActivationPayload,
      status,
    );
  await submitActivationAcknowledgement(request);
}

if (status.phase === 'cleanupPending') {
  // Cleanup is reachable only after this durable acknowledgement was accepted.
  const durableActivation = status.consumerActivationEvidence;
}

if (status.phase === 'aborted') {
  if (
    status.destinationBindingStartAuthorized ||
    status.consumerAction !== 'startSource'
  ) {
    throw new Error('invalid aborted migration status');
  }
  await startWorkload(status.activeBinding);
}

Here startWorkload, submitQuiesceAcknowledgement, and submitActivationAcknowledgement are consumer-owned operations, not package exports. Canonical digests are produced from normalized payloads:

const snapshotSha256 =
  await platform.storagemigration.createUnfencedObjectStorageBindingControlSnapshotSha256(
    snapshotDigestPayload,
  );
const migrationSha256 =
  await platform.storagemigration.createObjectStorageMigrationSha256(
    prepareRequest,
  );
const candidateSha256 =
  await platform.storagemigration.createObjectStorageMigrationBindingSha256(
    candidateBinding,
  );
const activationRecordSha256 =
  await platform.storagemigration.createObjectStorageMigrationPersistedActivationSha256(
    activationDigestPayload,
  );

Data Contracts

Use data when you need object shapes that are persisted, exchanged between services, or exposed through the Cloudly API.

import { data } from '@serve.zone/interfaces';

const service: data.IService = {
  id: 'service-api',
  data: {
    name: 'api',
    description: 'Public API service',
    imageId: 'image-api',
    imageVersion: '1.0.0',
    environment: {
      NODE_ENV: 'production',
    },
    serviceCategory: 'workload',
    deploymentStrategy: 'limited-replicas',
    scaleFactor: 2,
    balancingStrategy: 'round-robin',
    targetPorts: [
      {
        name: 'web',
        port: 3000,
        protocol: 'http',
        default: true,
      },
      {
        name: 'ssh',
        port: 2222,
        protocol: 'ssh',
      },
    ],
    ports: {
      web: 3000, // legacy compatibility shorthand during migration
    },
    domains: [
      {
        name: 'api',
        protocol: 'https',
        targetPort: 'web',
      },
    ],
    publicPortMappings: [
      {
        name: 'ssh-public',
        publicPort: 2222,
        targetPort: 'ssh',
        protocol: 'tcp',
        exclusive: true,
      },
    ],
    deploymentIds: [],
  },
};

Common data contracts include:

  • ICluster and IClusterNode for cluster membership and provisioning state.
  • IService, IDeployment, IImage, IRegistryTarget, and IExternalRegistry for workload delivery.
  • Service port contracts including IServiceTargetPort, IServiceDomainRoute, and IServicePublicPortMapping for canonical backend targets, domain target references, and edge/Coretraffic TCP/UDP public exposure.
  • IDomain, IDnsEntry, and traffic contracts for routing and DNS management.
  • Traffic and gateway route contracts including ICoretrafficPortRouteConfig, routing portRoutes, and IGatewayClientRoute client-owned route views. Gateway route intent supports optional match domains, transport, and remoteIngress, plus explicit route priority and managedRouteKind. Ownership can combine hostname with routeRef so a normal route and a path-specific managed route for the same hostname reconcile independently.
  • Value-free ISecretMetadata, ISecretVersionMetadata, and ISecretSetMetadata contracts for operator views, plus schema-v2 exact-version IResolvedSecretManifest contracts for cluster delivery. Manifest helpers bind immutable image rollout and invocation evidence, enforce canonical digests and globally unique launcher/file targets, and track per-cluster desired/applied/previous-accepted rollout state. Platform-provider and system owners are valid metadata owners but are rejected from workload manifests. listSecrets exposes the dedicated targetSecretsRevision CAS fence; every create, rotate, and lifecycle mutation consumes and returns that aggregate owner fence, while setServiceSecretSetAttachments returns the independent secretConfigurationRevision. Generic service writes own neither revision. Purge is a separate exact-version mutation with its own version revision and durable operation; it is not a logical-secret lifecycle action.
  • Mail gateway contracts for domain authorities, address bindings, WorkApp bindings, managed SMTP/API credentials, spool items, delivery journals, and inbound/outbound message payloads.
  • Service-level mail configuration through IService.data.mail, including per-address inbound smtpForward settings and outbound credential metadata. Cloudly settings include dcrouter gateway, SMTP submission, and inbound forward-target keys for reconciling those bindings.
  • Web Push contracts for environment-specific service bindings, public credential state, public VAPID key rotation metadata, privacy-minimal notification signals, and redacted delivery state. Subscription endpoints, browser key material, provider ciphertext, VAPID private keys, and credential secrets are intentionally absent from public binding and status DTOs.
  • Service-level Web Push declaration through IService.data.webPush. Immutable deployment declarations can require the pushnotification platform capability alongside database and object-storage capabilities; this does not turn Web Push into a Corestore resource or volume capability.
  • IUser, JWT-only IIdentityCredential, full IIdentity, and token-related contracts for authentication context. IIdentity extends IIdentityCredential with server-issued user metadata.
  • ICloudlyConfig, ICloudlySettings, status, server, bare-metal, BaseOS, backup, and task execution interfaces for control-plane state.

Shared service port helpers are exported from data so Cloudly, App Store resolution, Coreflow, Coretraffic, Onebox, and dcrouter agree on the same normalization rules:

const normalizedPorts = data.normalizeServicePortConfig(service.data);
const defaultTarget = data.resolveDefaultServiceTargetPort(normalizedPorts.targetPorts);
const webTarget = data.resolveServiceTargetPort(normalizedPorts.targetPorts, 'web');

if (webTarget && data.isHttpServiceTargetProtocol(webTarget.protocol)) {
  // safe to use as a domain route target
}

normalizeServicePortData() returns service data with canonical targetPorts, domain targetPort refs, and publicPortMappings while removing legacy domain port fields from normalized writes.

Hosted-App Authorization and Platform OIDC

An App Store version can declare that it supports platform-managed OpenID Connect. This is a capability declaration only: Onebox and Cloudly keep OIDC disabled until an administrator explicitly enables it for that exact app instance, and they can disable it again without changing the template.

import { appstore } from '@serve.zone/interfaces';

const config: appstore.IAppStoreVersionConfig = {
  image: 'registry.example.com/example/app:1.0.0',
  platformOidc: {
    redirectPath: '/auth/oidc/callback',
    roles: [
      { id: 'admin', label: 'Administrator' },
      { id: 'user', label: 'User' },
    ],
    environmentVariables: {
      issuerUrl: 'SERVEZONE_PLATFORM_OIDC_ISSUER',
      clientId: 'SERVEZONE_PLATFORM_OIDC_CLIENT_ID',
      clientSecret: 'SERVEZONE_PLATFORM_OIDC_CLIENT_SECRET',
      redirectUri: 'SERVEZONE_PLATFORM_OIDC_REDIRECT_URI',
      audience: 'SERVEZONE_PLATFORM_OIDC_AUDIENCE',
    },
    clientAuthenticationMethod: 'client_secret_basic',
  },
};

redirectPath is a canonical callback path on the app's HTTPS origin. Registration validation also requires that canonical app origin explicitly and rejects cross-origin, normalized, query-bearing, fragment-bearing, or duplicate callback URLs. The five environment values are environment-key names, not credentials embedded in the manifest. A host injects the generated client secret through launcher environment delivery and injects the other registration values only while OIDC is enabled.

data.IHostedAppRoleAssignment binds a stable user subject to an immutable appInstanceId. That same app instance is the OIDC client_id, the ID-token aud, and the servezone_app_instance_id claim. Tokens include only the assigned roles for that audience; preferred_username is display metadata and must not be treated as identity authority.

requests.hostedapp exports the shared authorization RPCs used by a host dashboard. getHostedAppAccessConfiguration returns human user summaries, role assignments, and hosted-app summaries. setHostedAppRoleAssignment returns the assignment or null when it is removed, while setHostedAppPlatformOidc returns the current registration state. getHostedAppOidcAuthorization returns the app and role summary for a pending request; completeHostedAppOidcAuthorization and cancelHostedAppOidcAuthorization return the redirect URL. All six requests require a full data.IIdentity.

Validate untrusted manifests, registrations, and claims with:

  • data.validateHostedAppRoleDefinitions
  • data.validateHostedAppPlatformOidcRegistration
  • data.validateHostedAppPlatformOidcClaims
  • appstore.validateAppStorePlatformOidcCapability
  • appstore.validateAppStoreVersionPlatformOidc

Hosted lifecycle RPCs accept only IIdentityCredential. After JWT verification, handlers validate IHostedAppMachineClaims and derive the exact app instance and service from servezone_app_instance_id and servezone_service_id; callers cannot submit those selectors. Bootstrap actions are either a canonical same-origin setupRoute path or a nonsecret message. Control tokens, usernames, passwords, and token URLs are not part of the lifecycle contract. Each server action carries a CAS revision; completion requires its exact ID, current revision, and ready status so a delayed request cannot complete a replacement action.

TypedRequest Contracts

Use requests when registering handlers with @api.global/typedrequest or when creating typed requests through a TypedSocket client.

import { requests } from '@serve.zone/interfaces';

type GetClustersRequest = requests.cluster.IReq_Any_Cloudly_GetClusters;

const methodName: GetClustersRequest['method'] = 'getClusters';

Each request interface follows the same pattern:

interface IExampleRequest {
  method: 'methodName';
  request: Record<string, unknown>;
  response: Record<string, unknown>;
}

requests.config.IRequest_Any_Cloudly_GetClusterConfig accepts data.IIdentityCredential, which contains only the JWT needed for server-side identity resolution. Username/password login and machine-token exchange responses continue to return the full data.IIdentity.

Immutable Deployment Contracts

Cloudly deployment authority is expressed as an exact data.IServiceDeploymentGrant. A service grant applies to one owned existing service. An organization-service-slot grant reserves authority for one exact future service ID in an organization; it is not an organization-wide wildcard. configureServiceDeploymentMachineUser accepts this discriminated grant object instead of separate service and capability fields.

The immutable deployment workflow is:

  1. Reserve the exact service, namespace, registry repository/tag, and route intent with reserveServiceDeployment.
  2. Push the OCI index to the returned exact tag with the authenticated deployer identity.
  3. Promote the authenticated release evidence with promoteServiceImageRelease.
  4. Observe exact rollout and runtime-digest evidence with getServiceDeploymentStatus.
  5. For a greenfield service, expose and verify its public route with promoteServiceDeploymentRoute only after the immutable rollout succeeds.

data.IDeploymentRouteRequest.proxied carries provider-specific DNS proxy intent as an optional boolean. New route declarations should set it explicitly; omission remains valid for persisted historical operations and legacy callers.

data.IServiceDeploymentOperation is the durable revisioned compare-and-set fence for this workflow. Image promotion requires Cloudly-created trusted evidence that binds the operation, actor, repository, exact tag, root digest, and OCI index media type. The service request group also exposes deployment preflight, exact-digest rollback, retry, and cleanup contracts.

Gateway request contracts include getGatewayClientRoutes (requests.gateway.IReq_GetGatewayClientRoutes) for listing owned IGatewayClientRoute[] route views, and syncGatewayClientRoute for idempotently syncing or deleting hostname-owned, routeRef-owned, and combined hostname-plus-routeRef routes. A client can label canonical intent with managedRouteKind: 'letsencrypt-http01-forward' and set a higher priority for a path-specific HTTP-01 route while retaining a separate normal route for the same hostname. Mail request contracts include syncMailAddressBinding, deleteMailAddressBinding, rotateMailCredential, and getMailDeliveryStatus. IReq_GetMailDeliveryStatus looks up a delivery spool item by spoolItemId, returns data.IMailDeliveryStatus, and accepts IMailSubmissionRequestAuth so service-mail credentials can query their own accepted, queued, deferred, delivered, or failed status. Typed outbound messages may set replyTo to one bare ASCII mailbox address; arbitrary Reply-To values do not belong in the custom header bag. Invalid values and typed-field/custom-header conflicts return stable TMailSubmissionErrorCode values. TMailAddressBindingSync.outboundEnabled explicitly controls whether a gateway should maintain a managed outbound SMTP credential for an address binding. Binding credential metadata is public; rotateMailCredential returns the new secret only in its one-time IMailCredentialOneTimeSecret response.

Web Push Contracts

New Web Push integrations use requests.webpush. Control-plane methods and application delivery methods deliberately use different, non-overlapping authentication types:

  • listWebPushBindings, syncWebPushBinding, deleteWebPushBinding, rotateWebPushCredential, and rotateWebPushVapidKey use control-plane identity or gateway API-token authentication.
  • getWebPushServiceStatus, enqueueWebPush, cancelWebPush, and getWebPushDeliveryStatus require a Web Push application credential. The gateway derives the owner exclusively from that credential; application requests cannot submit owner identity.

syncWebPushBinding may return the initial application credential secret once, and rotateWebPushCredential may return its replacement once. Binding and status DTOs contain only public credential and VAPID metadata. enqueueWebPush requires a credential-scoped idempotency key, an opaque application subscription ID, the browser Push API subscription, the VAPID key ID used for that browser subscription, and a privacy-minimal notificationAvailable signal.

import { requests } from '@serve.zone/interfaces';

type EnqueueWebPush = requests.webpush.IReq_EnqueueWebPush;
type WebPushStatus = requests.webpush.IReq_GetWebPushDeliveryStatus;

A delivery state of pushServiceAccepted means only that the remote push service accepted the encrypted request. It does not prove browser receipt, notification display, or user interaction.

Gateway Client Lifecycle and DNS

syncGatewayClientRoute accepts an optional dnsMode. Omission means skip for older clients. observe reports DNS without changing it. reconcile makes the gateway authoritative for the exact route hostname: it claims or replaces manual A, AAAA, and CNAME records, including already-correct manual values. Its optional dns result contains a closed status, retryability, the desired A/AAAA target, overwritten-record evidence, checkedAt, and authoritativeVerifiedAt once the provider or authoritative server confirms the state. Consumers can carry that evidence while retrying public propagation instead of treating an immediate recursive lookup miss as permanent.

Use requests.gateway.IReq_ProvisionGatewayClientCredential to replace an admin/bootstrap token with a first-class gateway-client credential. The admin-authenticated request idempotently upserts a data.IGatewayClient, durably creates a new bound credential, returns its raw value once, and then revokes older credentials bound to that client. It never revokes the bootstrap/admin credential. A successful response is discriminated with success: true and always includes the action, durable client, one-time credential, and revocation count.

import { requests } from '@serve.zone/interfaces';

const provisioning: requests.gateway.IReq_ProvisionGatewayClientCredential['request'] = {
  apiToken: 'admin-bootstrap-token',
  provisioning: {
    id: 'cloudly-main',
    type: 'cloudly',
    name: 'Cloudly main',
    hostnamePatterns: ['*'],
    allowedRouteTargets: [
      {
        host: 'coretraffic.internal',
        ports: [],
        allowAnyPort: true,
      },
    ],
    capabilities: {
      readDomains: true,
      readDnsRecords: true,
      readRoutes: true,
      syncRoutes: true,
      syncDnsRecords: true,
      readMail: true,
      manageMail: true,
      readCertificates: true,
      requestCertificates: true,
    },
  },
};

getGatewayClientContext returns effective live policy. A gatewayClient role necessarily includes the credential ID, bound client ID/type, and policyGeneration; consumers should reject admin/operator or mismatched contexts rather than falling back to a caller-supplied owner ID. getGatewayClientMailOverview provides an owner-scoped domain and recent-message summary. getGatewayClientMailDomainCount derives ownership exclusively from the authenticating gateway credential and returns { count: number } for its distinct configured mail domains. Cloudly therefore uses only dcrouterGatewayApiToken; the former dcrouterOpsApiToken setting is not part of data.ICloudlySettings.

CoreMail contracts

requests.coremail is the shared contract boundary for authenticated workload sessions, Coreflow reconciliation, and the CoreMail-to-dcrouter gateway session. Only the three authentication handshakes carry reusable peer credentials. Subsequent transfer operations may carry a scoped, short-lived, one-time bearer capability, while every subsequent workload request derives tenant, service, binding, capabilities, allowed senders, and the composite credential ID/version identity from the server-owned TypedSocket peer. Successful workload authentication also returns the effective binding state and the exact allowedOperations derived from data.coreMailWorkloadOperationPolicy. Disabled bindings never authenticate; draining bindings permit outbound status plus inbound list/fetch/ack only.

Gateway recipient resolution uses four strict outcomes. accept, defer, and reject are authoritative only for recipients owned by an active binding. unhandled means the CoreMail peer does not own that recipient, so the gateway may continue its next configured resolver. Consumers must apply data.normalizeCoreMailRecipientResolutions() against the exact requested recipient set before acting on a peer response.

Large content never travels inside TypedRequest JSON. Outbound body parts and attachments use prepare/upload/complete operations with short-lived one-time HTTP transfer grants. Inbound delivery uses bounded delivery listing followed by prepare/fetch/complete and an explicit acknowledgement after the workload has processed the exact byte count and SHA-256 digest. data.coreMailLimits defines the 64 KiB control-frame boundary, bounded structured content, the 30 MiB serialized MIME ceiling, a 56 KiB inbound page budget, bounded opaque cursors, transfer deadlines, and five-minute grant lifetime.

Coreflow applies data.ICoreMailDesiredState with a config-epoch compare-and-set fence. It stages the digest-fenced JSON snapshot through a bounded one-time HTTP upload, then applies it by reconciliation ID, so a large binding set never bypasses the 64 KiB control-frame boundary. Desired bindings contain password verifiers and secret references only, never plaintext workload or dcrouter credentials. Use data.normalizeCoreMailDesiredState, data.canonicalizeCoreMailDesiredState, and data.createCoreMailDesiredStateDigest at every producer and consumer boundary. Use data.verifyCoreMailDesiredStateDigest before applying a staged snapshot, data.normalizeCoreMailCredentialVerifier before accepting verifier metadata, data.normalizeCoreMailControlBootstrap for startup authority, and data.normalizeCoreMailGatewayPeerDesiredState for dcrouter peer state. The normalizers reject unknown fields, noncanonical mailboxes, ambiguous active recipient ownership, malformed SHA-256 values, and credential-lifecycle inconsistencies. CoreMail credential verifiers use the versioned argon2id-v1 format and the exact policy exported as data.coreMailCredentialVerifierPolicy.

data.ICoreMailControlBootstrap is installed before ordinary reconciliation. It contains the CoreMail service identity and verifier metadata only. The matching plaintext control credential is delivered exclusively to Coreflow through resolved runtime secrets. data.coreMailRuntimeKeys publishes the canonical environment keys for the verifier-only bootstrap payload and the separate control and gateway secret values; no consumer may derive or embed plaintext material in desired state.

CoreMail schema v2 keeps durable authority at the stable tenant, service, and binding identity while revisions, config epochs, and composite credentialId/version values fence sessions and new actions. Credential versions are authority-wide monotonic and unique even when rotation changes the credential ID. active bindings accept new mail, draining bindings permit existing status and inbound fetch/ack work without accepting new mail, and disabled bindings reject authentication. Cloudly retains a draining binding until pending inbound delivery reaches zero as reported by ICoreMailBindingReconciliationStatus.pendingInboundCount.

data.ICoreMailGatewayPeerDesiredState gives dcrouter the authoritative HTTPS CoreMail transfer origin associated with an authenticated CoreMail service. The same origin is carried in CoreMail desired state and returned by workload and gateway authentication. It must never be inferred from a socket, Host header, TypedSocket tag, or unrestricted peer input. CoreMail compares both authoritative views before handing over a path-only transfer grant.

Inbound delivery pagination uses an opaque CoreMail-owned cursor, is bounded by data.coreMailLimits.inboundPageSize, and returns nextCursor only when another page may exist. Consumers must not construct or parse cursor contents. Cursor signing keys are value-free runtime references with one current and bounded retiring versions; plaintext remains in resolved runtime secrets.

Quota windows are fixed UTC minute/day buckets. A new outbound quota unit is consumed only by the first durable insertion of an idempotency identity, while replays consume none. Pending inbound includes every state except acknowledged. These semantics are exported as data.coreMailQuotaPolicy. Every binding carries finite messagesPerMinute, messagesPerDay, and maxPendingInbound values; omitted or unlimited quotas are not valid schema-v2 desired state. data.coreMailRetentionPolicy retains terminal outbound, acknowledged inbound, and idempotency receipts for 30 days and expired capabilities for 24 hours. Pending inbound is never age-purged.

HTTP transfers use canonical /transfers/<uuid> paths and one-time Bearer tokens. data.coreMailTransferTokenPolicy requires canonical 256-bit base64url token material, while issuedAt and expiresAt prove the exact five-minute lifetime. PUT succeeds with 204 and GET with 200. Content length and type must match the grant; digest integrity is bound by grant metadata and repeated in the completion RPC rather than an optional HTTP digest header. Apply the exported strict normalizers for outbound message descriptors, envelopes, method-specific upload/download grants, submissions, gateway outbound statuses, inbound deliveries, desired state, bootstrap state, and gateway-peer state at their corresponding untrusted request and response boundaries. Normalizer failures throw data.CoreMailContractError; its readonly code defaults to INVALID_REQUEST, while an otherwise valid outbound part that exceeds its kind-specific byte budget reports PAYLOAD_LIMIT_EXCEEDED.

Control and gateway credential rotation is ordered: provision the candidate plaintext through resolved runtime secrets, publish and activate the matching argon2id-v1 verifier, roll or reconnect every affected replica, observe per-task authentication and readiness, then mark the previous verifier retiring with acceptUntil. Remove the previous verifier and secret only after its acceptance window has elapsed and no session uses that composite credential identity. Reconciliation status carries the exact CoreMail task, rollout generation, and image digest so Coreflow can correlate every response with its authoritative current task roster. Apply data.normalizeCoreMailReconciliationStatus before using pending-inbound or composite active-session counts for drain and rotation decisions.

Request groups are exported by product area:

  • requests.admin
  • requests.appstore
  • requests.baremetal
  • requests.baseos
  • requests.backup
  • requests.certificate
  • requests.cluster
  • requests.config
  • requests.coremail
  • requests.corestore
  • requests.deployment
  • requests.dns
  • requests.domain
  • requests.externalRegistry
  • requests.gateway
  • requests.hostedapp
  • requests.identity
  • requests.image
  • requests.inform
  • requests.log
  • requests.mail
  • requests.migration
  • requests.network
  • requests.node
  • requests.platform
  • requests.routing
  • requests.secret
  • requests.server
  • requests.service
  • requests.settings
  • requests.status
  • requests.task
  • requests.version
  • requests.webpush

Secret material response contracts are not exported from the universal browser-facing entrypoint.

The root entrypoint does use @push.rocks/smartcrypto to parse and validate strict ingress envelopes. Browser consumers that import the root contract can therefore include SmartCrypto and its browser-compatible crypto dependencies in their bundle. This package never opens private keys; sealed runtime material contracts remain isolated to the Node-only /runtime subpath.

Node runtimes import the isolated subpath:

import {
  verifySealedResolvedSecretMaterial,
} from '@serve.zone/interfaces/runtime';
import type {
  IReq_GetResolvedSecretMaterial,
  ISecretMaterialExpectation,
  ISealedResolvedSecretMaterial,
} from '@serve.zone/interfaces/runtime';

async function acceptMaterial(
  material: ISealedResolvedSecretMaterial,
  expectation: ISecretMaterialExpectation,
) {
  if (!await verifySealedResolvedSecretMaterial(material, expectation)) {
    throw new Error('secret material does not match its immutable manifest');
  }
}

Runtime material is shaped as { schemaVersion: 1, manifest, entries }, where each entry contains only secretVersionId and a strict X25519 envelope. The helper verifies the canonical schema-v2 manifest digest, envelope context digests, active recipient key, sorted exact one-to-one version coverage, every request fence, and the trusted local organization/cluster expectation. It never decrypts. Organization and cluster are derived from the verified cluster JWT and cannot be selected by request fields.

Launcher environment delivery uses stable szsv-<base32-sha256> Docker resource names and /run/serve.zone/secrets/<resource> source paths. The nonsecret map is written to /run/serve.zone/workloadinit-map-v1.json with mode 0444. Runtime assets under /opt/serve.zone/runtime-assets are read-only, and the wrapper executes workloadinit run --map /run/serve.zone/workloadinit-map-v1.json -- <argv...> without a shell, symlink, or aggregate value file. Resources and maps remain retained while any desired, applied, or previous-accepted manifest references them and are deleted only after Docker confirms rollout or service removal.

Platform Contracts

Use platform for current platform-service capabilities and application-facing platform RPCs.

import { platform } from '@serve.zone/interfaces';

type SendEmailRequest = platform.email.IReq_SendEmail;
type PlatformBinding = platform.IPlatformBinding;

const sendEmailMethod: SendEmailRequest['method'] = 'sendEmail';

Available platform modules:

  • platform.email for transactional email, recipient registration, email status, and email stats.
  • platform.sms for SMS delivery and verification-code delivery.
  • platform.pushnotification is the deprecated legacy device-token push contract. New browser Web Push integrations use requests.webpush.
  • platform.letter for physical letter workflows.
  • platform.ai, platform.database, platform.objectstorage, platform.logging, platform.backup, and platform.sip for infrastructure and application capabilities.
  • platform.storage for provider-neutral storage classes, requests, capabilities, and resolved bindings.
  • platform.storagemigration for fenced object-storage migration intent, status, consumer acknowledgements, canonical digests, and strict normalizers.
  • platform.types provider and binding metadata is value-free. Provider-specific operational config stays adapter-internal, while public DTOs expose typed endpoints and optional credential management scopes only.

Legacy Platformservice Contracts

platformservice is retained for older integrations that still consume the previous namespace layout.

import { platformservice } from '@serve.zone/interfaces';

type LegacySendEmailRequest = platformservice.mta.IRequest_SendEmail;

New code should prefer platform unless it must remain compatible with an active legacy consumer.

Contract Ownership

Only ecosystem-wide public contracts belong in this package. Cloudly-internal implementation details, service-private DTOs, and temporary migration helpers should stay in their owning service until they become real shared contracts.

Good candidates for this package:

  • Types persisted or exchanged across multiple serve.zone services.
  • TypedRequest contracts used by more than one project.
  • SDK-facing interfaces that external consumers should be able to rely on.

Poor candidates for this package:

  • Private implementation details of one service.
  • Runtime helpers or convenience wrappers, unless they are shared contract normalization or validation helpers used by multiple packages.
  • Compatibility aliases without an active consumer.

Development

pnpm install
pnpm run build
pnpm test
pnpm run buildDocs

The package is authored as ESM TypeScript and built with tsbuild tsfolders.

This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the license file.

Please note: The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.

Trademarks

This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.

Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.

Company Information

Task Venture Capital GmbH
Registered at District Court Bremen HRB 35230 HB, Germany

For any legal inquiries or further information, please contact us via email at hello@task.vc.

By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.

S
Description
No description provided
Readme
3.1 MiB
Languages
TypeScript 100%