@serve.zone/interfaces 32.18.0 → 32.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,6 +7,8 @@ import { bindRuntimeNetworkHandoffLease, computeRuntimeNetworkProtectedAuthority
7
7
  import type { IPrivateNetwork, IServicePrivateNetworks, IServicePrivateNetworkDnsPolicy } from './privatenetwork.js';
8
8
  import { routerPacketGrantsOf, snapshotRouterProjection,
9
9
  type IRuntimeNetworkRouterPacketGrant, type IRuntimeNetworkRouterProjection } from './networkrouter.js';
10
+ import { hostPacketGrantsOf, snapshotHostProjection,
11
+ type IRuntimeNetworkHostPacketGrant, type IRuntimeNetworkHostProjection } from './networkhost.js';
10
12
  import { buildPrivateNetworkFqdn, composeServicePrivateNetworkDnsPolicy,
11
13
  snapshotPrivateNetwork, snapshotServicePrivateNetworks } from './privatenetwork.js';
12
14
  import { captureNetwork, failNetwork, networkCanonical, networkContains, networkCounter, networkDigest,
@@ -159,6 +161,12 @@ export interface IRuntimeNetworkProjection extends IRuntimeNetworkNodeScope {
159
161
  * projection's canonical bytes and grants no router-origin flows.
160
162
  */
161
163
  router?: IRuntimeNetworkRouterProjection;
164
+ /**
165
+ * Host-origin selections of a `onebox` projection: the node's own host dials exact local workload
166
+ * ports (`IRuntimeNetworkHostPacketGrant`). Omitted, it keeps the canonical bytes and grants no
167
+ * host-origin flow; any other controller kind carrying it is refused.
168
+ */
169
+ host?: IRuntimeNetworkHostProjection;
162
170
  digest: TSha256Digest;
163
171
  }
164
172
 
@@ -313,9 +321,10 @@ export const snapshotRuntimeNetworkProjection = (value: unknown): IRuntimeNetwor
313
321
  const captured = captureNetwork(value);
314
322
  requireNetwork(new TextEncoder().encode(networkCanonical(captured)).length <= runtimeNetworkProjectionContract.maximumCanonicalBytes);
315
323
  const hasRouter = captured !== null && typeof captured === 'object' && Object.hasOwn(captured, 'router');
324
+ const hasHost = captured !== null && typeof captured === 'object' && Object.hasOwn(captured, 'host');
316
325
  const item = networkRecord(captured, ['id', 'generation', 'previous', 'controller', 'nodeId', 'runtimeNamespace',
317
326
  'protectedAuthority', 'allocationAuthorities', 'handoff', 'networks', 'memberships', 'endpoints', 'dns', 'withdrawals', 'retiredLeases', 'retiredHandoffs', 'digest',
318
- ...(hasRouter ? ['router'] : [])]);
327
+ ...(hasRouter ? ['router'] : []), ...(hasHost ? ['host'] : [])]);
319
328
  return { ...networkRevision(item), ...networkScope(item),
320
329
  protectedAuthority: snapshotRuntimeNetworkProtectedAuthority(item.protectedAuthority),
321
330
  allocationAuthorities: networkSorted(networkList(item.allocationAuthorities, 16, snapshotRuntimeNetworkProtectedAuthority), refKey),
@@ -325,6 +334,7 @@ export const snapshotRuntimeNetworkProjection = (value: unknown): IRuntimeNetwor
325
334
  endpoints: networkSorted(networkList(item.endpoints, 256, endpoint), value => value.lease.id), dns: networkDnsWindow(item.dns),
326
335
  withdrawals: networkSorted(networkList(item.withdrawals, 4096, packetGrant), networkCanonical),
327
336
  ...(hasRouter ? { router: snapshotRouterProjection(item.router) } : {}),
337
+ ...(hasHost ? { host: snapshotHostProjection(item.host) } : {}),
328
338
  retiredLeases: sortedReferences(item.retiredLeases), retiredHandoffs: sortedReferences(item.retiredHandoffs), digest: networkDigest(item.digest) };
329
339
  });
330
340
 
@@ -402,6 +412,8 @@ export const validateRuntimeNetworkProjection = async (value: unknown): Promise<
402
412
  const assignments = new Set<string>(), attempts = new Set<string>(), readyReplicas = new Set<string>(), hostPorts = new Set<string>();
403
413
  for (const endpoint of projection.endpoints) {
404
414
  const lease = endpoint.lease, authority = allocationAuthority(lease.protectedAuthority);
415
+ // A controller on the node itself has no other node: every endpoint it projects is local.
416
+ requireNetwork(projection.controller.kind !== 'onebox' || networkSameScope(lease, projection));
405
417
  requireNetwork(networkEqual(lease.controller, projection.controller) && members.has(ownerKey(lease))
406
418
  && !assignments.has(lease.assignmentId) && !attempts.has(attemptKey(lease)));
407
419
  assignments.add(lease.assignmentId); attempts.add(attemptKey(lease));
@@ -430,6 +442,8 @@ export const validateRuntimeNetworkProjection = async (value: unknown): Promise<
430
442
  requireNetwork(projection.withdrawals.every(grant => !grants.has(networkCanonical(grant))));
431
443
  const routerGrants = new Set(routerPacketGrantsOf(projection).map(networkCanonical));
432
444
  requireNetwork((projection.router?.withdrawals ?? []).every(grant => !routerGrants.has(networkCanonical(grant))));
445
+ const hostGrants = new Set(hostPacketGrantsOf(projection).map(networkCanonical));
446
+ requireNetwork((projection.host?.withdrawals ?? []).every(grant => !hostGrants.has(networkCanonical(grant))));
433
447
  requireNetwork(projection.digest === await computeRuntimeNetworkProjectionDigest(projection));
434
448
  return projection;
435
449
  } catch { return failNetwork(); }
@@ -443,6 +457,10 @@ export const getRuntimeNetworkProjectionPacketGrants = async (value: unknown): P
443
457
  export const getRuntimeNetworkProjectionRouterPacketGrants = async (value: unknown): Promise<IRuntimeNetworkRouterPacketGrant[]> =>
444
458
  routerPacketGrantsOf(await validateRuntimeNetworkProjection(value));
445
459
 
460
+ /** Host-origin authority comes only from explicit signed `onebox` selections of local workload ports. */
461
+ export const getRuntimeNetworkProjectionHostPacketGrants = async (value: unknown): Promise<IRuntimeNetworkHostPacketGrant[]> =>
462
+ hostPacketGrantsOf(await validateRuntimeNetworkProjection(value));
463
+
446
464
  /** Complete offline DNS material, including authorized names never previously queried. */
447
465
  /** Select exact local receivers before expansion; all targets still come from
448
466
  * the complete validated projection. Selection supplies no attachment authority. */
@@ -507,7 +525,7 @@ export const admitRuntimeNetworkProjection = async (
507
525
  requireNetwork(networkSameScope(projection, trusted));
508
526
  if (previousInput === null) {
509
527
  requireNetwork(projection.generation === 1 && applied === null && projection.withdrawals.length === 0
510
- && (projection.router?.withdrawals.length ?? 0) === 0
528
+ && (projection.router?.withdrawals.length ?? 0) === 0 && (projection.host?.withdrawals.length ?? 0) === 0
511
529
  && projection.retiredLeases.length === 0 && projection.retiredHandoffs.length === 0);
512
530
  return { status: 'accepted', projection };
513
531
  }
@@ -543,6 +561,12 @@ export const admitRuntimeNetworkProjection = async (
543
561
  for (const grant of previousRouterGrants) requireNetwork(routerGrants.has(networkCanonical(grant)) || routerWithdrawals.has(networkCanonical(grant)));
544
562
  const knownRouterWithdrawals = new Set([...previousRouterGrants, ...(previous.router?.withdrawals ?? [])].map(networkCanonical));
545
563
  requireNetwork((projection.router?.withdrawals ?? []).every(grant => knownRouterWithdrawals.has(networkCanonical(grant))));
564
+ const hostGrants = new Set(hostPacketGrantsOf(projection).map(networkCanonical));
565
+ const hostWithdrawals = new Set((projection.host?.withdrawals ?? []).map(networkCanonical));
566
+ const previousHostGrants = hostPacketGrantsOf(previous);
567
+ for (const grant of previousHostGrants) requireNetwork(hostGrants.has(networkCanonical(grant)) || hostWithdrawals.has(networkCanonical(grant)));
568
+ const knownHostWithdrawals = new Set([...previousHostGrants, ...(previous.host?.withdrawals ?? [])].map(networkCanonical));
569
+ requireNetwork((projection.host?.withdrawals ?? []).every(grant => knownHostWithdrawals.has(networkCanonical(grant))));
546
570
  const knownLeases = [...previous.endpoints.map(endpoint => networkReferenceOf(endpoint.lease)), ...previous.retiredLeases];
547
571
  const knownHandoffs = [...(previous.handoff ? [networkReferenceOf(previous.handoff)] : []), ...previous.retiredHandoffs];
548
572
  requireNetwork(projection.retiredLeases.every(retired => knownLeases.some(known => networkEqual(known, retired)))
@@ -553,6 +577,7 @@ export const admitRuntimeNetworkProjection = async (
553
577
  if (applied === null) {
554
578
  for (const grant of previous.withdrawals) requireNetwork(withdrawn.has(networkCanonical(grant)));
555
579
  for (const grant of previous.router?.withdrawals ?? []) requireNetwork(routerWithdrawals.has(networkCanonical(grant)));
580
+ for (const grant of previous.host?.withdrawals ?? []) requireNetwork(hostWithdrawals.has(networkCanonical(grant)));
556
581
  requireCarried(previous.retiredLeases, projection.retiredLeases); requireCarried(previous.retiredHandoffs, projection.retiredHandoffs);
557
582
  }
558
583
  for (const old of previous.endpoints) {
@@ -4,9 +4,10 @@ import type { IClusterRuntimeReference } from './clusterruntime.js';
4
4
  import { snapshotClusterRuntimeReference } from './clusterruntime.js';
5
5
  import type { IPalletNodeEnrollmentActive } from './nodeenrollment.js';
6
6
  import { snapshotPalletNodeEnrollmentConfirmation } from './nodeenrollment.js';
7
- import type { IRuntimeController } from './runtimeassignment.js';
8
- import { deepFreezeValue } from '../private/canonicaljson.js';
9
- import { readExactDataRecord } from '../private/dataproperties.js';
7
+ import type { IRuntimeAssignment, IRuntimeController } from './runtimeassignment.js';
8
+ import { isCanonicalRegistryHost } from './immutableimage.js';
9
+ import { compareWireTokens, deepFreezeValue } from '../private/canonicaljson.js';
10
+ import { isPlainRecord, readDataElements, readDataProperty, readExactDataRecord } from '../private/dataproperties.js';
10
11
  import { isClusterNodeId } from '../private/nodeidentifier.js';
11
12
  import { networkController, networkId } from '../private/networkcontract.js';
12
13
 
@@ -48,6 +49,70 @@ export const nodeRuntimeBindingContract = Object.freeze({
48
49
  runtimeAuthority: 'fresh-authenticated-controller-session-only',
49
50
  } as const);
50
51
 
52
+ /**
53
+ * Durable routing identity for one Pallet node driven by a controller on the same host.
54
+ *
55
+ * The local counterpart of `INodeRuntimeRoutingBinding`. It names no Cloudly origin and no relay:
56
+ * the controller reaches Pallet over Pallet's root-only Unix socket
57
+ * (`nodeRuntimeLocalControllerContract.socketPath`), whose file permissions authenticate the peer.
58
+ * A node carries a Cloudly binding or a local one, never both. Like the Cloudly binding it grants
59
+ * nothing by itself; runtime authority still comes only from a fresh session on that socket.
60
+ */
61
+ export interface INodeRuntimeLocalRoutingBinding {
62
+ nodeId: string;
63
+ clusterId: string;
64
+ controller: IRuntimeController & { kind: 'onebox' };
65
+ runtimeNamespace: string;
66
+ /**
67
+ * The registries whose pull credential this controller issues, as canonical `host[:port]`,
68
+ * strictly increasing. A local controller has no Cloudly origin to stand for its registry, so a
69
+ * node asks it for a credential only for an image whose `registryHost`, and `pullEndpoint` when
70
+ * one is stated, are both listed here (`isNodeRuntimeLocalRegistryWorkload`).
71
+ */
72
+ registryHosts: string[];
73
+ }
74
+
75
+ /**
76
+ * The credential a node registers its local session with, as the controller may pin it.
77
+ * Pallet generates the bearer and keeps it; the controller only ever learns its hash.
78
+ */
79
+ export interface INodeRuntimeLocalCredentialAcknowledgement {
80
+ generation: number;
81
+ /** Lowercase hex SHA-256 of the bearer's UTF-8 bytes. Sensitive transport field: never log it. */
82
+ tokenHash: string;
83
+ }
84
+
85
+ /** Pallet's answer to a local bind: the exact persisted binding and the credential behind it. */
86
+ export interface INodeRuntimeLocalBindingConfirmation {
87
+ binding: INodeRuntimeLocalRoutingBinding;
88
+ credential: INodeRuntimeLocalCredentialAcknowledgement;
89
+ }
90
+
91
+ /**
92
+ * How a controller on the node's own host reaches Pallet.
93
+ *
94
+ * The controller is the TypedSocket client and Pallet the server, but the request directions of the
95
+ * runtime session family are unchanged: Pallet still registers (`registerPalletRuntimeSession`, fired
96
+ * at the connected controller) and reports, and the controller still applies. The first request on
97
+ * a connection is `bindPalletLocalRuntimeController`: the first bind persists the binding and a
98
+ * freshly generated bearer, every later bind must be the exact same binding and answers the same
99
+ * credential. The controller pins that credential at its first bind and refuses a different one
100
+ * afterwards, because a node that answers another credential for the same binding has lost the
101
+ * state this controller enrolled.
102
+ */
103
+ export const nodeRuntimeLocalControllerContract = Object.freeze({
104
+ socketPath: '/run/serve.zone/pallet/controller.sock',
105
+ socketMode: 0o600,
106
+ /** TypedSocket server URL over the socket: it supplies only the Host header and request path. */
107
+ serverUrl: 'http://localhost',
108
+ transport: 'controller-initiated-typedsocket-on-root-only-unix-socket',
109
+ localMutation: 'first-bind-or-exact-replay',
110
+ exclusivity: 'cloudly-or-local-controller-never-both',
111
+ credential: 'pallet-generated-bearer-controller-pins-hash-at-first-bind',
112
+ registryAuthority: 'listed-registry-hosts-only',
113
+ maximumRegistryHosts: 16,
114
+ } as const);
115
+
51
116
  const fail = (): never => { throw new Error('Node runtime binding contract rejected.'); };
52
117
  function requireValue(conditionArg: unknown): asserts conditionArg {
53
118
  if (!conditionArg) fail();
@@ -78,6 +143,26 @@ const relay = (valueArg: unknown): IClusterRelay => {
78
143
  return { origin: value.origin as string };
79
144
  };
80
145
  const cloudlyOrigin = (valueArg: unknown): string => relay({ origin: valueArg }).origin;
146
+ const registryHosts = (valueArg: unknown): string[] => {
147
+ const elements = readDataElements(valueArg, nodeRuntimeLocalControllerContract.maximumRegistryHosts);
148
+ requireValue(elements !== null && elements.length > 0);
149
+ elements.forEach((hostArg, indexArg) => requireValue(isCanonicalRegistryHost(hostArg)
150
+ && (indexArg === 0 || compareWireTokens(elements[indexArg - 1] as string, hostArg) < 0)));
151
+ return elements as string[];
152
+ };
153
+ const localController = (valueArg: unknown): IRuntimeController & { kind: 'onebox' } => {
154
+ const controller = networkController(record(valueArg, ['kind', 'id', 'epoch']));
155
+ requireValue(controller.kind === 'onebox');
156
+ return { kind: 'onebox', id: controller.id, epoch: controller.epoch };
157
+ };
158
+ const tokenHash = (valueArg: unknown): string => {
159
+ requireValue(typeof valueArg === 'string' && /^[a-f0-9]{64}$/.test(valueArg));
160
+ return valueArg;
161
+ };
162
+ const localCredential = (valueArg: unknown): INodeRuntimeLocalCredentialAcknowledgement => {
163
+ const value = record(valueArg, ['generation', 'tokenHash']);
164
+ return { generation: positive(value.generation), tokenHash: tokenHash(value.tokenHash) };
165
+ };
81
166
  const activeEnrollment = (valueArg: unknown): IPalletNodeEnrollmentActive => {
82
167
  const value = snapshotPalletNodeEnrollmentConfirmation(valueArg);
83
168
  requireValue(value.phase === 'active');
@@ -189,3 +274,78 @@ export const bindPalletNodeRuntimeBindingRead = (
189
274
  && requestedOrigin === active.cloudlyOrigin && requestedNodeId === active.nodeId);
190
275
  return response;
191
276
  };
277
+
278
+ /** Exact detached snapshot of a local routing binding; shape checks are not authentication. */
279
+ export const snapshotNodeRuntimeLocalRoutingBinding = (
280
+ valueArg: unknown,
281
+ ): INodeRuntimeLocalRoutingBinding => {
282
+ try {
283
+ const value = record(valueArg,
284
+ ['nodeId', 'clusterId', 'controller', 'runtimeNamespace', 'registryHosts']);
285
+ return deepFreezeValue({
286
+ nodeId: nodeId(value.nodeId), clusterId: networkId(value.clusterId),
287
+ controller: localController(value.controller), runtimeNamespace: networkId(value.runtimeNamespace),
288
+ registryHosts: registryHosts(value.registryHosts),
289
+ });
290
+ } catch {
291
+ return fail();
292
+ }
293
+ };
294
+
295
+ /** Exact detached snapshot of Pallet's answer to a local bind. */
296
+ export const snapshotNodeRuntimeLocalBindingConfirmation = (
297
+ valueArg: unknown,
298
+ ): INodeRuntimeLocalBindingConfirmation => {
299
+ try {
300
+ const value = record(valueArg, ['binding', 'credential']);
301
+ return deepFreezeValue({ binding: snapshotNodeRuntimeLocalRoutingBinding(value.binding),
302
+ credential: localCredential(value.credential) });
303
+ } catch {
304
+ return fail();
305
+ }
306
+ };
307
+
308
+ /**
309
+ * Controller-side binding of Pallet's answer to the exact binding it sent. `pinnedArg` must come
310
+ * from the controller's own durable state: the credential it pinned at its first bind, or null for
311
+ * that first bind. A different credential for the same binding is refused, never adopted.
312
+ */
313
+ export const bindNodeRuntimeLocalBindingConfirmation = (
314
+ responseArg: unknown,
315
+ requestArg: unknown,
316
+ pinnedArg: unknown,
317
+ ): INodeRuntimeLocalBindingConfirmation => {
318
+ try {
319
+ const response = snapshotNodeRuntimeLocalBindingConfirmation(responseArg);
320
+ const request = snapshotNodeRuntimeLocalRoutingBinding(requestArg);
321
+ requireValue(JSON.stringify(response.binding) === JSON.stringify(request));
322
+ if (pinnedArg !== null) {
323
+ const pinned = localCredential(pinnedArg);
324
+ requireValue(pinned.generation === response.credential.generation
325
+ && pinned.tokenHash === response.credential.tokenHash);
326
+ }
327
+ return response;
328
+ } catch {
329
+ return fail();
330
+ }
331
+ };
332
+
333
+ /**
334
+ * Whether a local controller may issue the pull credential for this workload's image: its
335
+ * `registryHost`, and its `pullEndpoint` when one is stated, are both listed in the binding.
336
+ * The workload is read as a wire value; anything this cannot read is not authorized.
337
+ */
338
+ export const isNodeRuntimeLocalRegistryWorkload = (
339
+ bindingArg: unknown,
340
+ workloadArg: IRuntimeAssignment['workload'],
341
+ ): boolean => {
342
+ try {
343
+ const binding = snapshotNodeRuntimeLocalRoutingBinding(bindingArg);
344
+ const listed = (propertyArg: { value: unknown } | null): boolean => propertyArg !== null
345
+ && typeof propertyArg.value === 'string' && binding.registryHosts.includes(propertyArg.value);
346
+ return isPlainRecord(workloadArg) && listed(readDataProperty(workloadArg, 'registryHost'))
347
+ && (!Object.hasOwn(workloadArg, 'pullEndpoint') || listed(readDataProperty(workloadArg, 'pullEndpoint')));
348
+ } catch {
349
+ return false;
350
+ }
351
+ };
@@ -11,7 +11,8 @@ import {
11
11
  import { negotiateProtocol, readProtocolOffer, type IProtocolOffer } from '../protocol.js';
12
12
  import type { IRuntimeConfig } from './runtimeconfig.js';
13
13
  import { bindRuntimeConfigToAssignment, snapshotRuntimeConfig } from './runtimeconfig.js';
14
- import { canonicalizeStrictJson } from '../private/canonicaljson.js';
14
+ import { snapshotNodeRuntimeLocalBindingConfirmation, snapshotNodeRuntimeLocalRoutingBinding } from './noderuntimebinding.js';
15
+ import { canonicalizeStrictJson, createSha256Hex } from '../private/canonicaljson.js';
15
16
  import { isClusterNodeId } from '../private/nodeidentifier.js';
16
17
 
17
18
  /**
@@ -32,7 +33,11 @@ export interface IRuntimeSessionBinding {
32
33
  runtimeNamespace: string;
33
34
  }
34
35
 
35
- /** Pallet-initiated WSS on its configured Cloudly HTTPS origin; never HTTP RPC. */
36
+ /**
37
+ * Pallet-initiated WSS on its configured Cloudly HTTPS origin; never HTTP RPC. A node bound to a
38
+ * local controller fires the same request at the controller connected to its root-only Unix socket
39
+ * (`nodeRuntimeLocalControllerContract`), with the bearer whose hash its local bind answered.
40
+ */
36
41
  export interface IRegisterPalletRuntimeSessionRequest {
37
42
  nodeId: string;
38
43
  credentialGeneration: number;
@@ -395,6 +400,49 @@ export const bindRegisterPalletRuntimeSessionResponse = (
395
400
  return response;
396
401
  });
397
402
 
403
+ /**
404
+ * Bind a local controller's registration reply to the node's own local routing binding.
405
+ *
406
+ * The counterpart of `bindRegisterPalletRuntimeSessionResponse` for a node bound to a controller on
407
+ * its own host: the answered session must name exactly the binding's controller, node, cluster and
408
+ * runtime namespace, the credential generation sent, and an offer compatible with the one sent.
409
+ * `bindingArg` must come from the node's own durable state, never from the reply.
410
+ */
411
+ export const bindRegisterPalletLocalRuntimeSessionResponse = (
412
+ valueArg: unknown, requestArg: unknown, bindingArg: unknown,
413
+ ): IRegisterPalletRuntimeSessionResponse => safe(() => {
414
+ const response = snapshotRegisterPalletRuntimeSessionResponse(valueArg);
415
+ const request = snapshotRegisterPalletRuntimeSessionRequest(requestArg);
416
+ const binding = snapshotNodeRuntimeLocalRoutingBinding(bindingArg);
417
+ requireValue(canonical(response.session.controller) === canonical(binding.controller)
418
+ && request.nodeId === binding.nodeId && response.session.nodeId === binding.nodeId
419
+ && response.session.clusterId === binding.clusterId
420
+ && response.session.runtimeNamespace === binding.runtimeNamespace
421
+ && response.session.credentialGeneration === request.credentialGeneration
422
+ && negotiateProtocol(request.protocol, response.protocol).compatible);
423
+ return response;
424
+ });
425
+
426
+ /**
427
+ * Controller-side check of a local node's registration against the credential this controller
428
+ * pinned at its first local bind. `confirmationArg` must come from the controller's own durable
429
+ * state, never from the request. This proves the bearer is the one that bind acknowledged; the
430
+ * socket's file permissions remain the peer authentication, and session admission stays the
431
+ * controller's.
432
+ */
433
+ export const bindRegisterPalletLocalRuntimeSessionRequest = async (
434
+ valueArg: unknown, confirmationArg: unknown,
435
+ ): Promise<IRegisterPalletRuntimeSessionRequest> => {
436
+ try {
437
+ const request = snapshotRegisterPalletRuntimeSessionRequest(valueArg);
438
+ const confirmation = snapshotNodeRuntimeLocalBindingConfirmation(confirmationArg);
439
+ requireValue(request.nodeId === confirmation.binding.nodeId
440
+ && request.credentialGeneration === confirmation.credential.generation);
441
+ requireValue(await createSha256Hex(encoder.encode(request.bearer), fail) === confirmation.credential.tokenHash);
442
+ return request;
443
+ } catch { return fail(); }
444
+ };
445
+
398
446
  /** Reject already expired material; issuance policy and current assignment authority remain server-owned. */
399
447
  export const bindGetRuntimeRegistryCredentialResponse = (
400
448
  valueArg: unknown, nowArg: number,
@@ -1,5 +1,6 @@
1
1
  import * as plugins from '../plugins.js';
2
2
  import type * as data from '../data/runtimesession.js';
3
+ import type * as binding from '../data/noderuntimebinding.js';
3
4
  import type * as terminal from '../data/runtimeterminalreceipt.js';
4
5
  import type * as network from '../data/networkdelivery.js';
5
6
  import type * as protection from '../data/networkprotection.js';
@@ -136,6 +137,22 @@ export interface IReq_Pallet_Cloudly_RegisterPalletRuntimeSession extends plugin
136
137
  response: data.IRegisterPalletRuntimeSessionResponse;
137
138
  }
138
139
 
140
+ /**
141
+ * A local controller's first request on Pallet's root-only Unix socket, before any session:
142
+ * first bind or exact replay of the node's local routing binding (`nodeRuntimeLocalControllerContract`).
143
+ * Pallet answers the exact persisted binding and the credential it will register with, then fires
144
+ * `registerPalletRuntimeSession` at this connection. A node bound to Cloudly refuses it. The
145
+ * response carries a token hash: exclude it from hooks and logs.
146
+ */
147
+ export interface IReq_Onebox_Pallet_BindLocalRuntimeController extends plugins.typedrequestInterfaces.implementsTR<
148
+ plugins.typedrequestInterfaces.ITypedRequest,
149
+ IReq_Onebox_Pallet_BindLocalRuntimeController
150
+ > {
151
+ method: 'bindPalletLocalRuntimeController';
152
+ request: binding.INodeRuntimeLocalRoutingBinding;
153
+ response: binding.INodeRuntimeLocalBindingConfirmation;
154
+ }
155
+
139
156
  /** Controller push over the already authenticated Pallet-initiated connection. */
140
157
  export interface IReq_Controller_Pallet_ApplyRuntimeAssignment extends plugins.typedrequestInterfaces.implementsTR<
141
158
  plugins.typedrequestInterfaces.ITypedRequest,