@serve.zone/interfaces 32.10.1 → 32.12.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.
package/changelog.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## 2026-09-20 - 32.12.0
4
+
5
+ ### Features
6
+
7
+ - Preserve one canonical service on multiple cluster runtime targets. The existing runtime spec,
8
+ status, write, assignment and push shapes stay unchanged; exact-cluster and paged target reads
9
+ expose every target, while the scalar read refuses an ambiguous multi-target result. A guarded
10
+ exact-cluster removal carries only its revision fence, leaving Cloudly to derive held state,
11
+ completed slot settlement and release, and current runtime-phase authority.
12
+
13
+ ## 2026-09-20 - 32.11.0
14
+
15
+ ### Features
16
+
17
+ - Add the Spark-authenticated current node runtime routing read and immutable root-local Pallet
18
+ bind/readback contracts. Both paths bind the routing identity to the exact current Cloudly
19
+ enrollment origin and node. The persisted binding names routing only; current authenticated
20
+ runtime sessions remain the sole workload authority.
21
+
3
22
  ## 2026-09-20 - 32.10.1
4
23
 
5
24
  ### Fixes
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/interfaces',
6
- version: '32.10.1',
6
+ version: '32.12.0',
7
7
  description: 'Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSx3QkFBd0I7SUFDOUIsT0FBTyxFQUFFLFNBQVM7SUFDbEIsV0FBVyxFQUFFLHVGQUF1RjtDQUNyRyxDQUFBIn0=
@@ -21,6 +21,14 @@ export interface IClusterRuntime {
21
21
  /** The administrator or producer that made the change. */
22
22
  changedBy: string;
23
23
  }
24
+ /** Current phase reference without its administrative audit metadata. */
25
+ export interface IClusterRuntimeReference {
26
+ /** The cluster id. */
27
+ id: string;
28
+ phase: TClusterRuntimePhase;
29
+ /** Exact compare-and-swap generation observed with this phase. */
30
+ generation: number;
31
+ }
24
32
  /**
25
33
  * Why a cluster cannot leave `swarm` yet. Each blocker names the node or service it is about, so an
26
34
  * administrator can act on it without a second query.
@@ -36,4 +44,6 @@ export type TClusterRuntimeBlocker = {
36
44
  };
37
45
  /** Structural snapshot for exact persistence; which phase may follow which is the cutover's decision. */
38
46
  export declare const snapshotClusterRuntime: (value: unknown) => IClusterRuntime;
47
+ /** Structural snapshot for node routing reads; it grants no right to move or run a phase. */
48
+ export declare const snapshotClusterRuntimeReference: (value: unknown) => IClusterRuntimeReference;
39
49
  export {};
@@ -8,4 +8,10 @@ export const snapshotClusterRuntime = (value) => producerSafe(() => {
8
8
  return { id: networkId(item.id), phase: item.phase, generation: networkCounter(item.generation),
9
9
  changedAt: networkCounter(item.changedAt), changedBy: networkId(item.changedBy) };
10
10
  });
11
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2x1c3RlcnJ1bnRpbWUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9kYXRhL2NsdXN0ZXJydW50aW1lLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxjQUFjLEVBQUUsY0FBYyxFQUFFLFNBQVMsRUFBRSxhQUFhLEVBQUUsY0FBYyxFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFDekgsT0FBTyxFQUFFLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUV2RSxNQUFNLG9CQUFvQixHQUFHLENBQUMsT0FBTyxFQUFFLFVBQVUsRUFBRSxXQUFXLEVBQUUsUUFBUSxDQUFVLENBQUM7QUFtQ25GLHlHQUF5RztBQUN6RyxNQUFNLENBQUMsTUFBTSxzQkFBc0IsR0FBRyxDQUFDLEtBQWMsRUFBbUIsRUFBRSxDQUFDLFlBQVksQ0FBQyxHQUFHLEVBQUU7SUFDM0YsTUFBTSxJQUFJLEdBQUcsYUFBYSxDQUFDLGNBQWMsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLElBQUksRUFBRSxPQUFPLEVBQUUsWUFBWSxFQUFFLFdBQVcsRUFBRSxXQUFXLENBQUMsQ0FBQyxDQUFDO0lBQzNHLGNBQWMsQ0FBQyxPQUFPLENBQUMsb0JBQW9CLEVBQUUsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUM7SUFDMUQsT0FBTyxFQUFFLEVBQUUsRUFBRSxTQUFTLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxFQUFFLEtBQUssRUFBRSxJQUFJLENBQUMsS0FBSyxFQUFFLFVBQVUsRUFBRSxjQUFjLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQztRQUM3RixTQUFTLEVBQUUsY0FBYyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsRUFBRSxTQUFTLEVBQUUsU0FBUyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsRUFBRSxDQUFDO0FBQ3RGLENBQUMsQ0FBQyxDQUFDIn0=
11
+ /** Structural snapshot for node routing reads; it grants no right to move or run a phase. */
12
+ export const snapshotClusterRuntimeReference = (value) => producerSafe(() => {
13
+ const item = networkRecord(captureNetwork(value), ['id', 'phase', 'generation']);
14
+ requireNetwork(isOneOf(clusterRuntimePhases, item.phase));
15
+ return { id: networkId(item.id), phase: item.phase, generation: networkCounter(item.generation) };
16
+ });
17
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2x1c3RlcnJ1bnRpbWUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9kYXRhL2NsdXN0ZXJydW50aW1lLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxjQUFjLEVBQUUsY0FBYyxFQUFFLFNBQVMsRUFBRSxhQUFhLEVBQUUsY0FBYyxFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFDekgsT0FBTyxFQUFFLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUV2RSxNQUFNLG9CQUFvQixHQUFHLENBQUMsT0FBTyxFQUFFLFVBQVUsRUFBRSxXQUFXLEVBQUUsUUFBUSxDQUFVLENBQUM7QUE0Q25GLHlHQUF5RztBQUN6RyxNQUFNLENBQUMsTUFBTSxzQkFBc0IsR0FBRyxDQUFDLEtBQWMsRUFBbUIsRUFBRSxDQUFDLFlBQVksQ0FBQyxHQUFHLEVBQUU7SUFDM0YsTUFBTSxJQUFJLEdBQUcsYUFBYSxDQUFDLGNBQWMsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLElBQUksRUFBRSxPQUFPLEVBQUUsWUFBWSxFQUFFLFdBQVcsRUFBRSxXQUFXLENBQUMsQ0FBQyxDQUFDO0lBQzNHLGNBQWMsQ0FBQyxPQUFPLENBQUMsb0JBQW9CLEVBQUUsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUM7SUFDMUQsT0FBTyxFQUFFLEVBQUUsRUFBRSxTQUFTLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxFQUFFLEtBQUssRUFBRSxJQUFJLENBQUMsS0FBSyxFQUFFLFVBQVUsRUFBRSxjQUFjLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQztRQUM3RixTQUFTLEVBQUUsY0FBYyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsRUFBRSxTQUFTLEVBQUUsU0FBUyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsRUFBRSxDQUFDO0FBQ3RGLENBQUMsQ0FBQyxDQUFDO0FBRUgsNkZBQTZGO0FBQzdGLE1BQU0sQ0FBQyxNQUFNLCtCQUErQixHQUFHLENBQUMsS0FBYyxFQUE0QixFQUFFLENBQUMsWUFBWSxDQUFDLEdBQUcsRUFBRTtJQUM3RyxNQUFNLElBQUksR0FBRyxhQUFhLENBQUMsY0FBYyxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUMsSUFBSSxFQUFFLE9BQU8sRUFBRSxZQUFZLENBQUMsQ0FBQyxDQUFDO0lBQ2pGLGNBQWMsQ0FBQyxPQUFPLENBQUMsb0JBQW9CLEVBQUUsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUM7SUFDMUQsT0FBTyxFQUFFLEVBQUUsRUFBRSxTQUFTLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxFQUFFLEtBQUssRUFBRSxJQUFJLENBQUMsS0FBSyxFQUFFLFVBQVUsRUFBRSxjQUFjLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQUM7QUFDcEcsQ0FBQyxDQUFDLENBQUMifQ==
@@ -26,6 +26,7 @@ export * from './isolatedrestore.golden.js';
26
26
  export * from './mail.js';
27
27
  export * from './nodecredential.js';
28
28
  export * from './nodeenrollment.js';
29
+ export * from './noderuntimebinding.js';
29
30
  export * from './noderetirement.js';
30
31
  export * from './networkaddressplan.js';
31
32
  export * from './networkauthority.js';
@@ -26,6 +26,7 @@ export * from './isolatedrestore.golden.js';
26
26
  export * from './mail.js';
27
27
  export * from './nodecredential.js';
28
28
  export * from './nodeenrollment.js';
29
+ export * from './noderuntimebinding.js';
29
30
  export * from './noderetirement.js';
30
31
  export * from './networkaddressplan.js';
31
32
  export * from './networkauthority.js';
@@ -60,4 +61,4 @@ export * from './traffic.js';
60
61
  export * from './user.js';
61
62
  export * from './version.js';
62
63
  export * from './webpush.js';
63
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9kYXRhL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLGNBQWMsb0JBQW9CLENBQUM7QUFDbkMsY0FBYyxjQUFjLENBQUM7QUFDN0IsY0FBYyxxQkFBcUIsQ0FBQztBQUNwQyxjQUFjLGlCQUFpQixDQUFDO0FBQ2hDLGNBQWMscUJBQXFCLENBQUM7QUFDcEMsY0FBYyxhQUFhLENBQUM7QUFDNUIsY0FBYyxnQkFBZ0IsQ0FBQztBQUMvQixjQUFjLGVBQWUsQ0FBQztBQUM5QixjQUFjLHVCQUF1QixDQUFDO0FBQ3RDLGNBQWMsaUJBQWlCLENBQUM7QUFDaEMsY0FBYyx5QkFBeUIsQ0FBQztBQUN4QyxjQUFjLDBCQUEwQixDQUFDO0FBQ3pDLGNBQWMsMEJBQTBCLENBQUM7QUFDekMsY0FBYyxnQkFBZ0IsQ0FBQztBQUMvQixjQUFjLFVBQVUsQ0FBQztBQUN6QixjQUFjLGFBQWEsQ0FBQztBQUM1QixjQUFjLGFBQWEsQ0FBQztBQUM1QixjQUFjLFlBQVksQ0FBQztBQUMzQixjQUFjLHVCQUF1QixDQUFDO0FBQ3RDLGNBQWMsY0FBYyxDQUFDO0FBQzdCLGNBQWMsZ0JBQWdCLENBQUM7QUFDL0IsY0FBYyxZQUFZLENBQUM7QUFDM0IsY0FBYyxxQkFBcUIsQ0FBQztBQUNwQyxjQUFjLHNCQUFzQixDQUFDO0FBQ3JDLGNBQWMsNkJBQTZCLENBQUM7QUFDNUMsY0FBYyxXQUFXLENBQUM7QUFDMUIsY0FBYyxxQkFBcUIsQ0FBQztBQUNwQyxjQUFjLHFCQUFxQixDQUFDO0FBQ3BDLGNBQWMscUJBQXFCLENBQUM7QUFDcEMsY0FBYyx5QkFBeUIsQ0FBQztBQUN4QyxjQUFjLHVCQUF1QixDQUFDO0FBQ3RDLGNBQWMsd0JBQXdCLENBQUM7QUFDdkMsY0FBYyxzQkFBc0IsQ0FBQztBQUNyQyxjQUFjLG1CQUFtQixDQUFDO0FBQ2xDLGNBQWMsa0JBQWtCLENBQUM7QUFDakMsY0FBYyx3QkFBd0IsQ0FBQztBQUV2QyxjQUFjLHdCQUF3QixDQUFDO0FBQ3ZDLGNBQWMscUJBQXFCLENBQUM7QUFDcEMsY0FBYyxxQkFBcUIsQ0FBQztBQUNwQyxjQUFjLGVBQWUsQ0FBQztBQUM5QixjQUFjLHdCQUF3QixDQUFDO0FBQ3ZDLGNBQWMsb0JBQW9CLENBQUM7QUFDbkMsY0FBYywwQkFBMEIsQ0FBQztBQUN6QyxjQUFjLHFCQUFxQixDQUFDO0FBQ3BDLGNBQWMsNkJBQTZCLENBQUM7QUFDNUMsY0FBYyxhQUFhLENBQUM7QUFDNUIsY0FBYyxnQkFBZ0IsQ0FBQztBQUMvQixjQUFjLGFBQWEsQ0FBQztBQUM1QixjQUFjLGFBQWEsQ0FBQztBQUM1QixjQUFjLGtCQUFrQixDQUFDO0FBQ2pDLGNBQWMsZUFBZSxDQUFDO0FBQzlCLGNBQWMsc0JBQXNCLENBQUM7QUFDckMsY0FBYyxjQUFjLENBQUM7QUFDN0IsY0FBYyx3QkFBd0IsQ0FBQztBQUN2QyxjQUFjLG1CQUFtQixDQUFDO0FBQ2xDLGNBQWMscUJBQXFCLENBQUM7QUFDcEMsY0FBYyxhQUFhLENBQUM7QUFDNUIsY0FBYyxvQkFBb0IsQ0FBQztBQUNuQyxjQUFjLGNBQWMsQ0FBQztBQUM3QixjQUFjLFdBQVcsQ0FBQztBQUMxQixjQUFjLGNBQWMsQ0FBQztBQUM3QixjQUFjLGNBQWMsQ0FBQyJ9
64
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9kYXRhL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLGNBQWMsb0JBQW9CLENBQUM7QUFDbkMsY0FBYyxjQUFjLENBQUM7QUFDN0IsY0FBYyxxQkFBcUIsQ0FBQztBQUNwQyxjQUFjLGlCQUFpQixDQUFDO0FBQ2hDLGNBQWMscUJBQXFCLENBQUM7QUFDcEMsY0FBYyxhQUFhLENBQUM7QUFDNUIsY0FBYyxnQkFBZ0IsQ0FBQztBQUMvQixjQUFjLGVBQWUsQ0FBQztBQUM5QixjQUFjLHVCQUF1QixDQUFDO0FBQ3RDLGNBQWMsaUJBQWlCLENBQUM7QUFDaEMsY0FBYyx5QkFBeUIsQ0FBQztBQUN4QyxjQUFjLDBCQUEwQixDQUFDO0FBQ3pDLGNBQWMsMEJBQTBCLENBQUM7QUFDekMsY0FBYyxnQkFBZ0IsQ0FBQztBQUMvQixjQUFjLFVBQVUsQ0FBQztBQUN6QixjQUFjLGFBQWEsQ0FBQztBQUM1QixjQUFjLGFBQWEsQ0FBQztBQUM1QixjQUFjLFlBQVksQ0FBQztBQUMzQixjQUFjLHVCQUF1QixDQUFDO0FBQ3RDLGNBQWMsY0FBYyxDQUFDO0FBQzdCLGNBQWMsZ0JBQWdCLENBQUM7QUFDL0IsY0FBYyxZQUFZLENBQUM7QUFDM0IsY0FBYyxxQkFBcUIsQ0FBQztBQUNwQyxjQUFjLHNCQUFzQixDQUFDO0FBQ3JDLGNBQWMsNkJBQTZCLENBQUM7QUFDNUMsY0FBYyxXQUFXLENBQUM7QUFDMUIsY0FBYyxxQkFBcUIsQ0FBQztBQUNwQyxjQUFjLHFCQUFxQixDQUFDO0FBQ3BDLGNBQWMseUJBQXlCLENBQUM7QUFDeEMsY0FBYyxxQkFBcUIsQ0FBQztBQUNwQyxjQUFjLHlCQUF5QixDQUFDO0FBQ3hDLGNBQWMsdUJBQXVCLENBQUM7QUFDdEMsY0FBYyx3QkFBd0IsQ0FBQztBQUN2QyxjQUFjLHNCQUFzQixDQUFDO0FBQ3JDLGNBQWMsbUJBQW1CLENBQUM7QUFDbEMsY0FBYyxrQkFBa0IsQ0FBQztBQUNqQyxjQUFjLHdCQUF3QixDQUFDO0FBRXZDLGNBQWMsd0JBQXdCLENBQUM7QUFDdkMsY0FBYyxxQkFBcUIsQ0FBQztBQUNwQyxjQUFjLHFCQUFxQixDQUFDO0FBQ3BDLGNBQWMsZUFBZSxDQUFDO0FBQzlCLGNBQWMsd0JBQXdCLENBQUM7QUFDdkMsY0FBYyxvQkFBb0IsQ0FBQztBQUNuQyxjQUFjLDBCQUEwQixDQUFDO0FBQ3pDLGNBQWMscUJBQXFCLENBQUM7QUFDcEMsY0FBYyw2QkFBNkIsQ0FBQztBQUM1QyxjQUFjLGFBQWEsQ0FBQztBQUM1QixjQUFjLGdCQUFnQixDQUFDO0FBQy9CLGNBQWMsYUFBYSxDQUFDO0FBQzVCLGNBQWMsYUFBYSxDQUFDO0FBQzVCLGNBQWMsa0JBQWtCLENBQUM7QUFDakMsY0FBYyxlQUFlLENBQUM7QUFDOUIsY0FBYyxzQkFBc0IsQ0FBQztBQUNyQyxjQUFjLGNBQWMsQ0FBQztBQUM3QixjQUFjLHdCQUF3QixDQUFDO0FBQ3ZDLGNBQWMsbUJBQW1CLENBQUM7QUFDbEMsY0FBYyxxQkFBcUIsQ0FBQztBQUNwQyxjQUFjLGFBQWEsQ0FBQztBQUM1QixjQUFjLG9CQUFvQixDQUFDO0FBQ25DLGNBQWMsY0FBYyxDQUFDO0FBQzdCLGNBQWMsV0FBVyxDQUFDO0FBQzFCLGNBQWMsY0FBYyxDQUFDO0FBQzdCLGNBQWMsY0FBYyxDQUFDIn0=
@@ -0,0 +1,64 @@
1
+ import type { IClusterRelay } from './cluster.js';
2
+ import type { IClusterRuntimeReference } from './clusterruntime.js';
3
+ import type { IRuntimeController } from './runtimeassignment.js';
4
+ /**
5
+ * Durable routing identity for one Pallet node.
6
+ *
7
+ * This is not a runtime session, a workload grant or a cluster-phase authority. It only names the
8
+ * Cloudly controller and relay through which the node obtains fresh authenticated session state.
9
+ */
10
+ export interface INodeRuntimeRoutingBinding {
11
+ /** Exact immutable enrollment authority; Pallet compares it with its current active identity. */
12
+ cloudlyOrigin: string;
13
+ nodeId: string;
14
+ clusterId: string;
15
+ controller: IRuntimeController & {
16
+ kind: 'cloudly';
17
+ };
18
+ runtimeNamespace: string;
19
+ relay: IClusterRelay;
20
+ }
21
+ /** Sensitive authenticated read input; never persist, log, put in a URL or return it. */
22
+ export interface INodeRuntimeBindingRequest {
23
+ nodeId: string;
24
+ nodeToken: string;
25
+ }
26
+ /** Current server-owned answer to an authenticated Spark-node read. */
27
+ export interface INodeRuntimeBindingRead {
28
+ binding: INodeRuntimeRoutingBinding;
29
+ runtime: IClusterRuntimeReference;
30
+ /** Generation of the Spark bearer that authenticated this exact answer. */
31
+ credentialGeneration: number;
32
+ /** Current Spark session fence; it is observation metadata, not part of the routing binding. */
33
+ sessionEpoch: number;
34
+ }
35
+ export declare const nodeRuntimeBindingContract: Readonly<{
36
+ readonly localMutation: "first-bind-or-exact-replay";
37
+ readonly persistedAuthority: "routing-only";
38
+ readonly runtimeAuthority: "fresh-authenticated-controller-session-only";
39
+ }>;
40
+ /** Exact detached snapshot; shape checks are not authentication. */
41
+ export declare const snapshotNodeRuntimeRoutingBinding: (valueArg: unknown) => INodeRuntimeRoutingBinding;
42
+ /** Exact detached authenticated-read request; validation never logs or returns the bearer. */
43
+ export declare const snapshotNodeRuntimeBindingRequest: (valueArg: unknown) => INodeRuntimeBindingRequest;
44
+ /** Exact detached current read; its runtime must name the binding's cluster. */
45
+ export declare const snapshotNodeRuntimeBindingRead: (valueArg: unknown) => INodeRuntimeBindingRead;
46
+ /**
47
+ * Bind a server answer to the Spark identity that made the authenticated read.
48
+ * Authentication and the current credential transaction remain Cloudly's responsibility.
49
+ */
50
+ export declare const bindNodeRuntimeBindingRead: (valueArg: unknown, expectedArg: {
51
+ cloudlyOrigin: string;
52
+ nodeId: string;
53
+ credentialGeneration: number;
54
+ }) => INodeRuntimeBindingRead;
55
+ /**
56
+ * Pure content binding for the root-local bind reply. `activeEnrollmentArg` must come from the
57
+ * Pallet owner's current durable state; a historical activation receipt is insufficient.
58
+ */
59
+ export declare const bindPalletNodeRuntimeBinding: (responseArg: unknown, requestArg: unknown, activeEnrollmentArg: unknown) => INodeRuntimeRoutingBinding;
60
+ /**
61
+ * Pure content binding for offline readback. `activeEnrollmentArg` must come from the Pallet
62
+ * owner's current durable state; a historical activation receipt is insufficient.
63
+ */
64
+ export declare const bindPalletNodeRuntimeBindingRead: (responseArg: unknown, requestArg: unknown, activeEnrollmentArg: unknown) => INodeRuntimeRoutingBinding;
@@ -0,0 +1,134 @@
1
+ import { validateClusterRelay } from './cluster.js';
2
+ import { snapshotClusterRuntimeReference } from './clusterruntime.js';
3
+ import { snapshotPalletNodeEnrollmentConfirmation } from './nodeenrollment.js';
4
+ import { deepFreezeValue } from '../private/canonicaljson.js';
5
+ import { readExactDataRecord } from '../private/dataproperties.js';
6
+ import { isClusterNodeId } from '../private/nodeidentifier.js';
7
+ import { networkController, networkId } from '../private/networkcontract.js';
8
+ export const nodeRuntimeBindingContract = Object.freeze({
9
+ localMutation: 'first-bind-or-exact-replay',
10
+ persistedAuthority: 'routing-only',
11
+ runtimeAuthority: 'fresh-authenticated-controller-session-only',
12
+ });
13
+ const fail = () => { throw new Error('Node runtime binding contract rejected.'); };
14
+ function requireValue(conditionArg) {
15
+ if (!conditionArg)
16
+ fail();
17
+ }
18
+ const record = (valueArg, keysArg) => {
19
+ const result = readExactDataRecord(valueArg, keysArg);
20
+ requireValue(result !== null);
21
+ return result;
22
+ };
23
+ const positive = (valueArg) => {
24
+ requireValue(typeof valueArg === 'number' && Number.isSafeInteger(valueArg)
25
+ && valueArg > 0 && !Object.is(valueArg, -0));
26
+ return valueArg;
27
+ };
28
+ const nonNegative = (valueArg) => {
29
+ requireValue(typeof valueArg === 'number' && Number.isSafeInteger(valueArg)
30
+ && valueArg >= 0 && !Object.is(valueArg, -0));
31
+ return valueArg;
32
+ };
33
+ const nodeId = (valueArg) => isClusterNodeId(valueArg) ? valueArg : fail();
34
+ const nodeToken = (valueArg) => {
35
+ requireValue(typeof valueArg === 'string' && /^secureToken[a-f0-9]{64}$/.test(valueArg));
36
+ return valueArg;
37
+ };
38
+ const relay = (valueArg) => {
39
+ const value = record(valueArg, ['origin']);
40
+ requireValue(validateClusterRelay(value).length === 0);
41
+ return { origin: value.origin };
42
+ };
43
+ const cloudlyOrigin = (valueArg) => relay({ origin: valueArg }).origin;
44
+ const activeEnrollment = (valueArg) => {
45
+ const value = snapshotPalletNodeEnrollmentConfirmation(valueArg);
46
+ requireValue(value.phase === 'active');
47
+ return value;
48
+ };
49
+ /** Exact detached snapshot; shape checks are not authentication. */
50
+ export const snapshotNodeRuntimeRoutingBinding = (valueArg) => {
51
+ try {
52
+ const value = record(valueArg, ['cloudlyOrigin', 'nodeId', 'clusterId', 'controller', 'runtimeNamespace', 'relay']);
53
+ const controller = networkController(record(value.controller, ['kind', 'id', 'epoch']));
54
+ requireValue(controller.kind === 'cloudly');
55
+ return deepFreezeValue({
56
+ cloudlyOrigin: cloudlyOrigin(value.cloudlyOrigin),
57
+ nodeId: nodeId(value.nodeId), clusterId: networkId(value.clusterId),
58
+ controller: { kind: 'cloudly', id: controller.id, epoch: controller.epoch },
59
+ runtimeNamespace: networkId(value.runtimeNamespace), relay: relay(value.relay),
60
+ });
61
+ }
62
+ catch {
63
+ return fail();
64
+ }
65
+ };
66
+ /** Exact detached authenticated-read request; validation never logs or returns the bearer. */
67
+ export const snapshotNodeRuntimeBindingRequest = (valueArg) => {
68
+ try {
69
+ const value = record(valueArg, ['nodeId', 'nodeToken']);
70
+ return deepFreezeValue({ nodeId: nodeId(value.nodeId), nodeToken: nodeToken(value.nodeToken) });
71
+ }
72
+ catch {
73
+ return fail();
74
+ }
75
+ };
76
+ /** Exact detached current read; its runtime must name the binding's cluster. */
77
+ export const snapshotNodeRuntimeBindingRead = (valueArg) => {
78
+ try {
79
+ const value = record(valueArg, ['binding', 'runtime', 'credentialGeneration', 'sessionEpoch']);
80
+ const binding = snapshotNodeRuntimeRoutingBinding(value.binding);
81
+ const runtime = snapshotClusterRuntimeReference(value.runtime);
82
+ requireValue(runtime.id === binding.clusterId);
83
+ return deepFreezeValue({ binding, runtime,
84
+ credentialGeneration: positive(value.credentialGeneration),
85
+ sessionEpoch: nonNegative(value.sessionEpoch) });
86
+ }
87
+ catch {
88
+ return fail();
89
+ }
90
+ };
91
+ /**
92
+ * Bind a server answer to the Spark identity that made the authenticated read.
93
+ * Authentication and the current credential transaction remain Cloudly's responsibility.
94
+ */
95
+ export const bindNodeRuntimeBindingRead = (valueArg, expectedArg) => {
96
+ try {
97
+ const expected = record(expectedArg, ['cloudlyOrigin', 'nodeId', 'credentialGeneration']);
98
+ const value = snapshotNodeRuntimeBindingRead(valueArg);
99
+ requireValue(value.binding.cloudlyOrigin === cloudlyOrigin(expected.cloudlyOrigin)
100
+ && value.binding.nodeId === nodeId(expected.nodeId)
101
+ && value.credentialGeneration === positive(expected.credentialGeneration));
102
+ return value;
103
+ }
104
+ catch {
105
+ return fail();
106
+ }
107
+ };
108
+ /**
109
+ * Pure content binding for the root-local bind reply. `activeEnrollmentArg` must come from the
110
+ * Pallet owner's current durable state; a historical activation receipt is insufficient.
111
+ */
112
+ export const bindPalletNodeRuntimeBinding = (responseArg, requestArg, activeEnrollmentArg) => {
113
+ const response = snapshotNodeRuntimeRoutingBinding(responseArg);
114
+ const request = snapshotNodeRuntimeRoutingBinding(requestArg);
115
+ const active = activeEnrollment(activeEnrollmentArg);
116
+ requireValue(request.cloudlyOrigin === active.cloudlyOrigin && request.nodeId === active.nodeId
117
+ && JSON.stringify(response) === JSON.stringify(request));
118
+ return response;
119
+ };
120
+ /**
121
+ * Pure content binding for offline readback. `activeEnrollmentArg` must come from the Pallet
122
+ * owner's current durable state; a historical activation receipt is insufficient.
123
+ */
124
+ export const bindPalletNodeRuntimeBindingRead = (responseArg, requestArg, activeEnrollmentArg) => {
125
+ const request = record(requestArg, ['cloudlyOrigin', 'nodeId']);
126
+ const response = snapshotNodeRuntimeRoutingBinding(responseArg);
127
+ const active = activeEnrollment(activeEnrollmentArg);
128
+ const requestedOrigin = cloudlyOrigin(request.cloudlyOrigin);
129
+ const requestedNodeId = nodeId(request.nodeId);
130
+ requireValue(response.cloudlyOrigin === requestedOrigin && response.nodeId === requestedNodeId
131
+ && requestedOrigin === active.cloudlyOrigin && requestedNodeId === active.nodeId);
132
+ return response;
133
+ };
134
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibm9kZXJ1bnRpbWViaW5kaW5nLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHMvZGF0YS9ub2RlcnVudGltZWJpbmRpbmcudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBTyxFQUFFLG9CQUFvQixFQUFFLE1BQU0sY0FBYyxDQUFDO0FBRXBELE9BQU8sRUFBRSwrQkFBK0IsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBRXRFLE9BQU8sRUFBRSx3Q0FBd0MsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBRS9FLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUM5RCxPQUFPLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSw4QkFBOEIsQ0FBQztBQUNuRSxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sOEJBQThCLENBQUM7QUFDL0QsT0FBTyxFQUFFLGlCQUFpQixFQUFFLFNBQVMsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBa0M3RSxNQUFNLENBQUMsTUFBTSwwQkFBMEIsR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDO0lBQ3RELGFBQWEsRUFBRSw0QkFBNEI7SUFDM0Msa0JBQWtCLEVBQUUsY0FBYztJQUNsQyxnQkFBZ0IsRUFBRSw2Q0FBNkM7Q0FDdkQsQ0FBQyxDQUFDO0FBRVosTUFBTSxJQUFJLEdBQUcsR0FBVSxFQUFFLEdBQUcsTUFBTSxJQUFJLEtBQUssQ0FBQyx5Q0FBeUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQzFGLFNBQVMsWUFBWSxDQUFDLFlBQXFCO0lBQ3pDLElBQUksQ0FBQyxZQUFZO1FBQUUsSUFBSSxFQUFFLENBQUM7QUFDNUIsQ0FBQztBQUNELE1BQU0sTUFBTSxHQUFHLENBQUMsUUFBaUIsRUFBRSxPQUEwQixFQUEyQixFQUFFO0lBQ3hGLE1BQU0sTUFBTSxHQUFHLG1CQUFtQixDQUFDLFFBQVEsRUFBRSxPQUFPLENBQUMsQ0FBQztJQUN0RCxZQUFZLENBQUMsTUFBTSxLQUFLLElBQUks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
@@ -66,6 +66,8 @@ export declare const serviceRuntimeContract: Readonly<{
66
66
  /** A node can run workloads only as an egress member of the protected authority, which admits at most this many. */
67
67
  readonly maximumPinnedNodes: 256;
68
68
  readonly maximumPlatformEndpoints: 64;
69
+ /** The largest `limit` `getServiceRuntimeSpecs` accepts; Cloudly refuses a larger one. */
70
+ readonly maximumSpecPageSize: 256;
69
71
  /** The largest `limit` `getClusterRuntimeAssignments` accepts; Cloudly refuses a larger one. */
70
72
  readonly maximumAssignmentPageSize: 256;
71
73
  }>;
@@ -9,6 +9,8 @@ export const serviceRuntimeContract = Object.freeze({
9
9
  /** A node can run workloads only as an egress member of the protected authority, which admits at most this many. */
10
10
  maximumPinnedNodes: runtimeNetworkAuthorityContract.maximumEgressAuthorities,
11
11
  maximumPlatformEndpoints: runtimeNetworkAuthorityContract.maximumPlatformEndpoints,
12
+ /** The largest `limit` `getServiceRuntimeSpecs` accepts; Cloudly refuses a larger one. */
13
+ maximumSpecPageSize: 256,
12
14
  /** The largest `limit` `getClusterRuntimeAssignments` accepts; Cloudly refuses a larger one. */
13
15
  maximumAssignmentPageSize: 256,
14
16
  });
@@ -92,4 +94,4 @@ export const snapshotServiceRuntimeStatus = (value) => producerSafe(() => {
92
94
  requireNetwork(item.state === 'refused' && isOneOf(serviceRuntimeRefusals, item.reason));
93
95
  return { ...base, state: item.state, reason: item.reason };
94
96
  });
95
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmljZXJ1bnRpbWUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9kYXRhL3NlcnZpY2VydW50aW1lLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUlBLE9BQU8sRUFBRSwrQkFBK0IsRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBQ3hFLE9BQU8sRUFBRSxjQUFjLEVBQUUsZ0JBQWdCLEVBQUUsY0FBYyxFQUFFLFNBQVMsRUFBRSxXQUFXLEVBQUUsYUFBYSxFQUFFLGFBQWEsRUFBRSxhQUFhLEVBQzVILGNBQWMsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBQ3hELE9BQU8sRUFBRSxZQUFZLEVBQUUsT0FBTyxFQUFFLFlBQVksRUFBRSxTQUFTLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUNoRyxPQUFPLEVBQUUsc0JBQXNCLEVBQUUsZ0JBQWdCLEVBQUUsa0JBQWtCLEVBQUUsdUJBQXVCLEVBQUUsa0JBQWtCLEVBQ2hILG9CQUFvQixFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUF3RDVELE1BQU0sQ0FBQyxNQUFNLHNCQUFzQixHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUM7SUFDbEQsc0JBQXNCLEVBQUUsRUFBRTtJQUMxQiwrRkFBK0Y7SUFDL0YsZUFBZSxFQUFFLEVBQUU7SUFDbkIsb0hBQW9IO0lBQ3BILGtCQUFrQixFQUFFLCtCQUErQixDQUFDLHdCQUF3QjtJQUM1RSx3QkFBd0IsRUFBRSwrQkFBK0IsQ0FBQyx3QkFBd0I7SUFDbEYsZ0dBQWdHO0lBQ2hHLHlCQUF5QixFQUFFLEdBQUc7Q0FDdEIsQ0FBQyxDQUFDO0FBRVosTUFBTSw0QkFBNEIsR0FBRyxDQUFDLHNCQUFzQixFQUFFLFVBQVUsRUFBRSxpQkFBaUIsQ0FBVSxDQUFDO0FBQ3RHLE1BQU0sc0JBQXNCLEdBQUc7SUFDN0IseUJBQXlCLEVBQUUsc0JBQXNCLEVBQUUsb0JBQW9CLEVBQUUsc0JBQXNCO0lBQy9GLHFCQUFxQixFQUFFLHFCQUFxQixFQUFFLDJCQUEyQixF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
97
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmljZXJ1bnRpbWUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9kYXRhL3NlcnZpY2VydW50aW1lLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUlBLE9BQU8sRUFBRSwrQkFBK0IsRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBQ3hFLE9BQU8sRUFBRSxjQUFjLEVBQUUsZ0JBQWdCLEVBQUUsY0FBYyxFQUFFLFNBQVMsRUFBRSxXQUFXLEVBQUUsYUFBYSxFQUFFLGFBQWEsRUFBRSxhQUFhLEVBQzVILGNBQWMsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBQ3hELE9BQU8sRUFBRSxZQUFZLEVBQUUsT0FBTyxFQUFFLFlBQVksRUFBRSxTQUFTLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUNoRyxPQUFPLEVBQUUsc0JBQXNCLEVBQUUsZ0JBQWdCLEVBQUUsa0JBQWtCLEVBQUUsdUJBQXVCLEVBQUUsa0JBQWtCLEVBQ2hILG9CQUFvQixFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUF3RDVELE1BQU0sQ0FBQyxNQUFNLHNCQUFzQixHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUM7SUFDbEQsc0JBQXNCLEVBQUUsRUFBRTtJQUMxQiwrRkFBK0Y7SUFDL0YsZUFBZSxFQUFFLEVBQUU7SUFDbkIsb0hBQW9IO0lBQ3BILGtCQUFrQixFQUFFLCtCQUErQixDQUFDLHdCQUF3QjtJQUM1RSx3QkFBd0IsRUFBRSwrQkFBK0IsQ0FBQyx3QkFBd0I7SUFDbEYsMEZBQTBGO0lBQzFGLG1CQUFtQixFQUFFLEdBQUc7SUFDeEIsZ0dBQWdHO0lBQ2hHLHlCQUF5QixFQUFFLEdBQUc7Q0FDdEIsQ0FBQyxDQUFDO0FBRVosTUFBTSw0QkFBNEIsR0FBRyxDQUFDLHNCQUFzQixFQUFFLFVBQVUsRUFBRSxpQkFBaUIsQ0FBVSxDQUFDO0FBQ3RHLE1BQU0sc0JBQXNCLEdBQUc7SUFDN0IseUJBQXlCLEVBQUUsc0JBQXNCLEVBQUUsb0JBQW9CLEVBQUUsc0JBQXNCO0lB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
@@ -6,6 +6,7 @@ import type { IDeployment } from '../data/deployment.js';
6
6
  import type { IIdentity } from '../data/user.js';
7
7
  import type { INodeCredentialMetadata, INodeCredentialMutationPreconditions, INodeCredentialMutationResult, TNodeCredentialPurpose } from '../data/nodecredential.js';
8
8
  import type { INodeRuntimeRetirement } from '../data/noderetirement.js';
9
+ import type { INodeRuntimeBindingRead, INodeRuntimeBindingRequest } from '../data/noderuntimebinding.js';
9
10
  import type { INodeEnrollmentRequest, INodeEnrollmentAcknowledgement, INodeEnrollmentState } from '../data/nodeenrollment.js';
10
11
  /** Node-self adoption preconditions, not an admin or workload RBAC grant. */
11
12
  export interface IReq_Spark_Cloudly_GetNodeEnrollmentState extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Spark_Cloudly_GetNodeEnrollmentState> {
@@ -24,6 +25,16 @@ export interface IReq_Spark_Cloudly_EnrollNode extends plugins.typedrequestInter
24
25
  /** Replays require the exact receipt AND both current credential authorities. */
25
26
  response: INodeEnrollmentAcknowledgement;
26
27
  }
28
+ /**
29
+ * Current Spark-node-authenticated routing read. Cloudly derives every response identity from its
30
+ * node, cluster, runtime-controller, relay and credential records in one consistent read.
31
+ */
32
+ export interface IReq_Spark_Cloudly_GetNodeRuntimeBinding extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Spark_Cloudly_GetNodeRuntimeBinding> {
33
+ method: 'getNodeRuntimeBinding';
34
+ request: INodeRuntimeBindingRequest;
35
+ /** Routing and current phase observation only; this grants no workload or phase authority. */
36
+ response: INodeRuntimeBindingRead;
37
+ }
27
38
  /** Requires verified platform-infrastructure admin authority; returns public data only. */
28
39
  export interface IRequest_Any_Cloudly_GetNodeConfig extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IRequest_Any_Cloudly_GetNodeConfig> {
29
40
  method: 'getNodeConfig';
@@ -1,5 +1,6 @@
1
1
  import * as plugins from '../plugins.js';
2
2
  import type { INodeEnrollment, IPalletNodeEnrollmentActivation, IPalletNodeEnrollmentPreparation, IPalletNodeEnrollmentBound, IPalletNodeEnrollmentActive } from '../data/nodeenrollment.js';
3
+ import type { INodeRuntimeRoutingBinding } from '../data/noderuntimebinding.js';
3
4
  /** Root-authenticated node-local IPC only; never expose on a network router. */
4
5
  export interface IReq_Spark_Pallet_PrepareNodeEnrollment extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Spark_Pallet_PrepareNodeEnrollment> {
5
6
  method: 'preparePalletNodeEnrollment';
@@ -21,3 +22,25 @@ export interface IReq_Spark_Pallet_ActivateNodeEnrollment extends plugins.typedr
21
22
  /** Prove the exact current local identity after activation; a historical receipt is insufficient. */
22
23
  response: IPalletNodeEnrollmentActive;
23
24
  }
25
+ /**
26
+ * First immutable routing bind or exact replay. The local owner rejects unless `cloudlyOrigin` and
27
+ * `nodeId` match its current active enrollment; this never mutates credentials, workload state or runtime phase.
28
+ */
29
+ export interface IReq_Spark_Pallet_BindNodeRuntimeBinding extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Spark_Pallet_BindNodeRuntimeBinding> {
30
+ method: 'bindPalletNodeRuntimeBinding';
31
+ request: INodeRuntimeRoutingBinding;
32
+ /** Exact currently persisted binding, answered only after the durable write or exact replay. */
33
+ response: INodeRuntimeRoutingBinding;
34
+ }
35
+ /**
36
+ * Offline routing readback. The root-local owner rejects an absent binding, an inactive enrollment
37
+ * or a requested origin/node different from both its active enrollment and persisted binding.
38
+ */
39
+ export interface IReq_Spark_Pallet_ReadNodeRuntimeBinding extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Spark_Pallet_ReadNodeRuntimeBinding> {
40
+ method: 'readPalletNodeRuntimeBinding';
41
+ request: {
42
+ cloudlyOrigin: string;
43
+ nodeId: string;
44
+ };
45
+ response: INodeRuntimeRoutingBinding;
46
+ }
@@ -270,9 +270,10 @@ export interface IRequest_Any_Cloudly_DeleteServiceById extends plugins.typedreq
270
270
  /**
271
271
  * Writes the complete runtime spec of one service.
272
272
  *
273
- * `expectedRevision` is the revision the caller read, or `null` when the service has never had a
274
- * spec; any other stored revision refuses the write. Cloudly copies the organization from the
275
- * service and answers with the stored spec at its new revision.
273
+ * `expectedRevision` is the revision the caller read for the target `(serviceId, spec.clusterId)`,
274
+ * or `null` when that target has never had a spec; any other stored revision refuses the write.
275
+ * Cloudly copies the organization from the service and answers with the stored spec at its new
276
+ * target-local revision.
276
277
  */
277
278
  export interface IRequest_Any_Cloudly_SetServiceRuntimeSpec extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IRequest_Any_Cloudly_SetServiceRuntimeSpec> {
278
279
  method: 'setServiceRuntimeSpec';
@@ -287,8 +288,19 @@ export interface IRequest_Any_Cloudly_SetServiceRuntimeSpec extends plugins.type
287
288
  };
288
289
  }
289
290
  /**
290
- * A service's runtime spec and the producer's verdict on it. Both are `null` before the first spec;
291
- * a spec's status is `null` until the producer first decides it.
291
+ * The named refusal data when a scalar runtime-spec read finds more than one cluster target.
292
+ *
293
+ * This travels as `TypedResponseError.errorData`. The condition needs an explicit cluster or the
294
+ * paged read, never a retry of the same ambiguous request.
295
+ */
296
+ export interface IServiceRuntimeSpecAmbiguityErrorData {
297
+ code: 'service-runtime-spec-ambiguous';
298
+ retryable: false;
299
+ }
300
+ /**
301
+ * The only runtime spec of a service and the producer's verdict on it. Zero targets answer both
302
+ * members as `null`; one target answers it, with `status: null` until the producer first decides it.
303
+ * More than one target refuses with `IServiceRuntimeSpecAmbiguityErrorData`; Cloudly never picks one.
292
304
  */
293
305
  export interface IRequest_Any_Cloudly_GetServiceRuntimeSpec extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IRequest_Any_Cloudly_GetServiceRuntimeSpec> {
294
306
  method: 'getServiceRuntimeSpec';
@@ -304,6 +316,63 @@ export interface IRequest_Any_Cloudly_GetServiceRuntimeSpec extends plugins.type
304
316
  status: IServiceRuntimeStatus | null;
305
317
  };
306
318
  }
319
+ /** One service's runtime spec for one exact cluster target, or no such target. */
320
+ export interface IRequest_Any_Cloudly_GetServiceRuntimeSpecForCluster extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IRequest_Any_Cloudly_GetServiceRuntimeSpecForCluster> {
321
+ method: 'getServiceRuntimeSpecForCluster';
322
+ request: {
323
+ identity: IIdentity;
324
+ serviceId: string;
325
+ clusterId: string;
326
+ };
327
+ response: {
328
+ spec: null;
329
+ status: null;
330
+ } | {
331
+ spec: IServiceRuntimeSpec;
332
+ status: IServiceRuntimeStatus | null;
333
+ };
334
+ }
335
+ /**
336
+ * Every cluster target of one service, ordered by cluster id and read a bounded page at a time.
337
+ * `cursor` is opaque and `limit` runs from 1 through `serviceRuntimeContract.maximumSpecPageSize`;
338
+ * Cloudly refuses any other limit. A returned status belongs to the spec beside it.
339
+ */
340
+ export interface IRequest_Any_Cloudly_GetServiceRuntimeSpecs extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IRequest_Any_Cloudly_GetServiceRuntimeSpecs> {
341
+ method: 'getServiceRuntimeSpecs';
342
+ request: {
343
+ identity: IIdentity;
344
+ serviceId: string;
345
+ cursor: string | null;
346
+ limit: number;
347
+ };
348
+ response: {
349
+ entries: Array<{
350
+ spec: IServiceRuntimeSpec;
351
+ status: IServiceRuntimeStatus | null;
352
+ }>;
353
+ nextCursor: string | null;
354
+ };
355
+ }
356
+ /**
357
+ * Removes one exact cluster target at the revision the administrator read.
358
+ *
359
+ * Cloudly derives every safety fact itself and fences them in the removal transaction: the current
360
+ * spec revision equals `expectedRevision`, that spec is held, every slot of this service on this
361
+ * cluster is fully settled and released, and the cluster's current phase still gives the spec
362
+ * producer authority. The caller supplies no phase, settlement, release or authority proof.
363
+ */
364
+ export interface IRequest_Any_Cloudly_RemoveServiceRuntimeSpecForCluster extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IRequest_Any_Cloudly_RemoveServiceRuntimeSpecForCluster> {
365
+ method: 'removeServiceRuntimeSpecForCluster';
366
+ request: {
367
+ identity: IIdentity;
368
+ serviceId: string;
369
+ clusterId: string;
370
+ expectedRevision: number;
371
+ };
372
+ response: {
373
+ removed: true;
374
+ };
375
+ }
307
376
  /** Every current runtime assignment of one service, one per replica slot. */
308
377
  export interface IRequest_Any_Cloudly_GetServiceRuntimeAssignments extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IRequest_Any_Cloudly_GetServiceRuntimeAssignments> {
309
378
  method: 'getServiceRuntimeAssignments';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serve.zone/interfaces",
3
- "version": "32.10.1",
3
+ "version": "32.12.0",
4
4
  "private": false,
5
5
  "description": "Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.",
6
6
  "exports": {
package/readme.md CHANGED
@@ -266,6 +266,37 @@ bounded time/concurrency, root-only directory/socket permissions and drained
266
266
  cleanup. The package supplies no listener, permission checks or persistent state.
267
267
  No bearer, generic rotation operation or database access is part of this IPC API.
268
268
 
269
+ ### Node Runtime Routing Binding
270
+
271
+ `requests.node.getNodeRuntimeBinding { nodeId, nodeToken }` is a current
272
+ Spark-node-authenticated read. Cloudly derives `data.INodeRuntimeBindingRead`
273
+ from its current credential, node, cluster, durable runtime-controller, relay and
274
+ cluster-runtime records. The answer contains the exact routing identity
275
+ `{ cloudlyOrigin, nodeId, clusterId, controller, runtimeNamespace, relay }`, the
276
+ current `{ id, phase, generation }` runtime reference and the Spark credential
277
+ generation and session epoch that authenticated the read.
278
+ `snapshotNodeRuntimeBindingRead` requires the runtime id to equal the routing
279
+ cluster, and `bindNodeRuntimeBindingRead` binds the answer to Spark's exact
280
+ immutable Cloudly origin, node and credential generation. Neither helper
281
+ authenticates the server.
282
+
283
+ `requests.pallet.bindPalletNodeRuntimeBinding` and
284
+ `readPalletNodeRuntimeBinding` belong on the existing root-owned Pallet enrollment
285
+ control socket. Bind is absent-to-present only: the current active Pallet
286
+ enrollment must name the same canonical Cloudly origin and node, an exact replay
287
+ is idempotent and every different origin, node, cluster, controller, namespace
288
+ or relay conflicts. Read requires the requested origin and node to equal both
289
+ the active enrollment and the stored binding; an absent binding rejects. Spark
290
+ applies and reads back the binding before starting `runtime-serve`. On an
291
+ offline reboot it may read the exact persisted routing identity to select the
292
+ relay, but that record grants no workload permission and no right to advance or
293
+ run a cluster phase. Fresh authenticated runtime-session state remains the only
294
+ workload authority.
295
+
296
+ The local operations carry no bearer, signing key, generic configuration field,
297
+ phase mutation or caller-authored permission. `nodeRuntimeBindingContract`
298
+ states the immutable local mutation and routing-only persistence rules.
299
+
269
300
  ### Service Machine Credentials
270
301
 
271
302
  `requests.admin.getServiceMachineCredential` inspects redacted metadata;
@@ -1024,10 +1055,32 @@ an operator's service edit.
1024
1055
  with sorted unique endpoint ids.
1025
1056
 
1026
1057
  `snapshotServiceRuntimeSpec` checks all of that; whether the cluster, the nodes and the endpoints
1027
- exist is Cloudly's. `requests.service.setServiceRuntimeSpec { identity, serviceId, expectedRevision, spec }`
1028
- writes `TServiceRuntimeSpecWritableData`, and Cloudly copies the organization from the service.
1029
- `getServiceRuntimeSpec` answers `{ spec, status }`: both `null` before the first spec, and `status`
1030
- `null` until the producer first decides the spec. A status never comes without its spec.
1058
+ exist is Cloudly's. One canonical service may have one runtime target on each of several clusters.
1059
+ Every public target keeps the same `IServiceRuntimeSpec`: `id` is the exact service id and
1060
+ `clusterId` names that target. Cloudly's private persistence joins those two values; no composite
1061
+ public service id is introduced.
1062
+
1063
+ `requests.service.setServiceRuntimeSpec { identity, serviceId, expectedRevision, spec }` writes
1064
+ `TServiceRuntimeSpecWritableData`, and Cloudly copies the organization from the service. Its
1065
+ compare-and-swap is for `(serviceId, spec.clusterId)`: `null` means that exact target is absent, and
1066
+ each target advances its own revision. The request bytes and public spec shape are unchanged.
1067
+
1068
+ `getServiceRuntimeSpecForCluster { identity, serviceId, clusterId }` reads one exact target.
1069
+ `getServiceRuntimeSpecs { identity, serviceId, cursor, limit }` reads targets in cluster-id order,
1070
+ with an opaque cursor and a limit from 1 through `serviceRuntimeContract.maximumSpecPageSize`.
1071
+ Both reads answer each spec with its producer status, which is `null` until the producer first
1072
+ decides the spec. A status never comes without its spec.
1073
+
1074
+ The original `getServiceRuntimeSpec { identity, serviceId }` remains a scalar compatibility read:
1075
+ zero targets answer `{ spec: null, status: null }`, and one target answers it. More than one target
1076
+ is refused with `IServiceRuntimeSpecAmbiguityErrorData { code: 'service-runtime-spec-ambiguous',
1077
+ retryable: false }` in `TypedResponseError.errorData`; Cloudly never selects a target implicitly.
1078
+
1079
+ `removeServiceRuntimeSpecForCluster { identity, serviceId, clusterId, expectedRevision }` removes
1080
+ one exact target only after Cloudly transactionally derives and fences all authority and safety
1081
+ facts: the revision still matches, the target is held, every target slot is fully settled and
1082
+ released, and the cluster's current phase still belongs to the spec producer. Callers provide no
1083
+ phase, settlement, release or authority booleans or evidence.
1031
1084
 
1032
1085
  `data.IServiceRuntimeStatus { id, specRevision, state, reason, changedAt }` is the producer's verdict
1033
1086
  on one spec revision, and each reason belongs to exactly one state. `converged` and `converging`
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/interfaces',
6
- version: '32.10.1',
6
+ version: '32.12.0',
7
7
  description: 'Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.'
8
8
  }
@@ -27,6 +27,15 @@ export interface IClusterRuntime {
27
27
  changedBy: string;
28
28
  }
29
29
 
30
+ /** Current phase reference without its administrative audit metadata. */
31
+ export interface IClusterRuntimeReference {
32
+ /** The cluster id. */
33
+ id: string;
34
+ phase: TClusterRuntimePhase;
35
+ /** Exact compare-and-swap generation observed with this phase. */
36
+ generation: number;
37
+ }
38
+
30
39
  /**
31
40
  * Why a cluster cannot leave `swarm` yet. Each blocker names the node or service it is about, so an
32
41
  * administrator can act on it without a second query.
@@ -43,3 +52,10 @@ export const snapshotClusterRuntime = (value: unknown): IClusterRuntime => produ
43
52
  return { id: networkId(item.id), phase: item.phase, generation: networkCounter(item.generation),
44
53
  changedAt: networkCounter(item.changedAt), changedBy: networkId(item.changedBy) };
45
54
  });
55
+
56
+ /** Structural snapshot for node routing reads; it grants no right to move or run a phase. */
57
+ export const snapshotClusterRuntimeReference = (value: unknown): IClusterRuntimeReference => producerSafe(() => {
58
+ const item = networkRecord(captureNetwork(value), ['id', 'phase', 'generation']);
59
+ requireNetwork(isOneOf(clusterRuntimePhases, item.phase));
60
+ return { id: networkId(item.id), phase: item.phase, generation: networkCounter(item.generation) };
61
+ });
package/ts/data/index.ts CHANGED
@@ -26,6 +26,7 @@ export * from './isolatedrestore.golden.js';
26
26
  export * from './mail.js';
27
27
  export * from './nodecredential.js';
28
28
  export * from './nodeenrollment.js';
29
+ export * from './noderuntimebinding.js';
29
30
  export * from './noderetirement.js';
30
31
  export * from './networkaddressplan.js';
31
32
  export * from './networkauthority.js';
@@ -0,0 +1,191 @@
1
+ import type { IClusterRelay } from './cluster.js';
2
+ import { validateClusterRelay } from './cluster.js';
3
+ import type { IClusterRuntimeReference } from './clusterruntime.js';
4
+ import { snapshotClusterRuntimeReference } from './clusterruntime.js';
5
+ import type { IPalletNodeEnrollmentActive } from './nodeenrollment.js';
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';
10
+ import { isClusterNodeId } from '../private/nodeidentifier.js';
11
+ import { networkController, networkId } from '../private/networkcontract.js';
12
+
13
+ /**
14
+ * Durable routing identity for one Pallet node.
15
+ *
16
+ * This is not a runtime session, a workload grant or a cluster-phase authority. It only names the
17
+ * Cloudly controller and relay through which the node obtains fresh authenticated session state.
18
+ */
19
+ export interface INodeRuntimeRoutingBinding {
20
+ /** Exact immutable enrollment authority; Pallet compares it with its current active identity. */
21
+ cloudlyOrigin: string;
22
+ nodeId: string;
23
+ clusterId: string;
24
+ controller: IRuntimeController & { kind: 'cloudly' };
25
+ runtimeNamespace: string;
26
+ relay: IClusterRelay;
27
+ }
28
+
29
+ /** Sensitive authenticated read input; never persist, log, put in a URL or return it. */
30
+ export interface INodeRuntimeBindingRequest {
31
+ nodeId: string;
32
+ nodeToken: string;
33
+ }
34
+
35
+ /** Current server-owned answer to an authenticated Spark-node read. */
36
+ export interface INodeRuntimeBindingRead {
37
+ binding: INodeRuntimeRoutingBinding;
38
+ runtime: IClusterRuntimeReference;
39
+ /** Generation of the Spark bearer that authenticated this exact answer. */
40
+ credentialGeneration: number;
41
+ /** Current Spark session fence; it is observation metadata, not part of the routing binding. */
42
+ sessionEpoch: number;
43
+ }
44
+
45
+ export const nodeRuntimeBindingContract = Object.freeze({
46
+ localMutation: 'first-bind-or-exact-replay',
47
+ persistedAuthority: 'routing-only',
48
+ runtimeAuthority: 'fresh-authenticated-controller-session-only',
49
+ } as const);
50
+
51
+ const fail = (): never => { throw new Error('Node runtime binding contract rejected.'); };
52
+ function requireValue(conditionArg: unknown): asserts conditionArg {
53
+ if (!conditionArg) fail();
54
+ }
55
+ const record = (valueArg: unknown, keysArg: readonly string[]): Record<string, unknown> => {
56
+ const result = readExactDataRecord(valueArg, keysArg);
57
+ requireValue(result !== null);
58
+ return result;
59
+ };
60
+ const positive = (valueArg: unknown): number => {
61
+ requireValue(typeof valueArg === 'number' && Number.isSafeInteger(valueArg)
62
+ && valueArg > 0 && !Object.is(valueArg, -0));
63
+ return valueArg;
64
+ };
65
+ const nonNegative = (valueArg: unknown): number => {
66
+ requireValue(typeof valueArg === 'number' && Number.isSafeInteger(valueArg)
67
+ && valueArg >= 0 && !Object.is(valueArg, -0));
68
+ return valueArg;
69
+ };
70
+ const nodeId = (valueArg: unknown): string => isClusterNodeId(valueArg) ? valueArg : fail();
71
+ const nodeToken = (valueArg: unknown): string => {
72
+ requireValue(typeof valueArg === 'string' && /^secureToken[a-f0-9]{64}$/.test(valueArg));
73
+ return valueArg;
74
+ };
75
+ const relay = (valueArg: unknown): IClusterRelay => {
76
+ const value = record(valueArg, ['origin']);
77
+ requireValue(validateClusterRelay(value).length === 0);
78
+ return { origin: value.origin as string };
79
+ };
80
+ const cloudlyOrigin = (valueArg: unknown): string => relay({ origin: valueArg }).origin;
81
+ const activeEnrollment = (valueArg: unknown): IPalletNodeEnrollmentActive => {
82
+ const value = snapshotPalletNodeEnrollmentConfirmation(valueArg);
83
+ requireValue(value.phase === 'active');
84
+ return value;
85
+ };
86
+
87
+ /** Exact detached snapshot; shape checks are not authentication. */
88
+ export const snapshotNodeRuntimeRoutingBinding = (
89
+ valueArg: unknown,
90
+ ): INodeRuntimeRoutingBinding => {
91
+ try {
92
+ const value = record(valueArg,
93
+ ['cloudlyOrigin', 'nodeId', 'clusterId', 'controller', 'runtimeNamespace', 'relay']);
94
+ const controller = networkController(record(value.controller, ['kind', 'id', 'epoch']));
95
+ requireValue(controller.kind === 'cloudly');
96
+ return deepFreezeValue({
97
+ cloudlyOrigin: cloudlyOrigin(value.cloudlyOrigin),
98
+ nodeId: nodeId(value.nodeId), clusterId: networkId(value.clusterId),
99
+ controller: { kind: 'cloudly', id: controller.id, epoch: controller.epoch },
100
+ runtimeNamespace: networkId(value.runtimeNamespace), relay: relay(value.relay),
101
+ });
102
+ } catch {
103
+ return fail();
104
+ }
105
+ };
106
+
107
+ /** Exact detached authenticated-read request; validation never logs or returns the bearer. */
108
+ export const snapshotNodeRuntimeBindingRequest = (
109
+ valueArg: unknown,
110
+ ): INodeRuntimeBindingRequest => {
111
+ try {
112
+ const value = record(valueArg, ['nodeId', 'nodeToken']);
113
+ return deepFreezeValue({ nodeId: nodeId(value.nodeId), nodeToken: nodeToken(value.nodeToken) });
114
+ } catch {
115
+ return fail();
116
+ }
117
+ };
118
+
119
+ /** Exact detached current read; its runtime must name the binding's cluster. */
120
+ export const snapshotNodeRuntimeBindingRead = (
121
+ valueArg: unknown,
122
+ ): INodeRuntimeBindingRead => {
123
+ try {
124
+ const value = record(valueArg,
125
+ ['binding', 'runtime', 'credentialGeneration', 'sessionEpoch']);
126
+ const binding = snapshotNodeRuntimeRoutingBinding(value.binding);
127
+ const runtime = snapshotClusterRuntimeReference(value.runtime);
128
+ requireValue(runtime.id === binding.clusterId);
129
+ return deepFreezeValue({ binding, runtime,
130
+ credentialGeneration: positive(value.credentialGeneration),
131
+ sessionEpoch: nonNegative(value.sessionEpoch) });
132
+ } catch {
133
+ return fail();
134
+ }
135
+ };
136
+
137
+ /**
138
+ * Bind a server answer to the Spark identity that made the authenticated read.
139
+ * Authentication and the current credential transaction remain Cloudly's responsibility.
140
+ */
141
+ export const bindNodeRuntimeBindingRead = (
142
+ valueArg: unknown,
143
+ expectedArg: { cloudlyOrigin: string; nodeId: string; credentialGeneration: number },
144
+ ): INodeRuntimeBindingRead => {
145
+ try {
146
+ const expected = record(expectedArg, ['cloudlyOrigin', 'nodeId', 'credentialGeneration']);
147
+ const value = snapshotNodeRuntimeBindingRead(valueArg);
148
+ requireValue(value.binding.cloudlyOrigin === cloudlyOrigin(expected.cloudlyOrigin)
149
+ && value.binding.nodeId === nodeId(expected.nodeId)
150
+ && value.credentialGeneration === positive(expected.credentialGeneration));
151
+ return value;
152
+ } catch {
153
+ return fail();
154
+ }
155
+ };
156
+
157
+ /**
158
+ * Pure content binding for the root-local bind reply. `activeEnrollmentArg` must come from the
159
+ * Pallet owner's current durable state; a historical activation receipt is insufficient.
160
+ */
161
+ export const bindPalletNodeRuntimeBinding = (
162
+ responseArg: unknown,
163
+ requestArg: unknown,
164
+ activeEnrollmentArg: unknown,
165
+ ): INodeRuntimeRoutingBinding => {
166
+ const response = snapshotNodeRuntimeRoutingBinding(responseArg);
167
+ const request = snapshotNodeRuntimeRoutingBinding(requestArg);
168
+ const active = activeEnrollment(activeEnrollmentArg);
169
+ requireValue(request.cloudlyOrigin === active.cloudlyOrigin && request.nodeId === active.nodeId
170
+ && JSON.stringify(response) === JSON.stringify(request));
171
+ return response;
172
+ };
173
+
174
+ /**
175
+ * Pure content binding for offline readback. `activeEnrollmentArg` must come from the Pallet
176
+ * owner's current durable state; a historical activation receipt is insufficient.
177
+ */
178
+ export const bindPalletNodeRuntimeBindingRead = (
179
+ responseArg: unknown,
180
+ requestArg: unknown,
181
+ activeEnrollmentArg: unknown,
182
+ ): INodeRuntimeRoutingBinding => {
183
+ const request = record(requestArg, ['cloudlyOrigin', 'nodeId']);
184
+ const response = snapshotNodeRuntimeRoutingBinding(responseArg);
185
+ const active = activeEnrollment(activeEnrollmentArg);
186
+ const requestedOrigin = cloudlyOrigin(request.cloudlyOrigin);
187
+ const requestedNodeId = nodeId(request.nodeId);
188
+ requireValue(response.cloudlyOrigin === requestedOrigin && response.nodeId === requestedNodeId
189
+ && requestedOrigin === active.cloudlyOrigin && requestedNodeId === active.nodeId);
190
+ return response;
191
+ };
@@ -70,6 +70,8 @@ export const serviceRuntimeContract = Object.freeze({
70
70
  /** A node can run workloads only as an egress member of the protected authority, which admits at most this many. */
71
71
  maximumPinnedNodes: runtimeNetworkAuthorityContract.maximumEgressAuthorities,
72
72
  maximumPlatformEndpoints: runtimeNetworkAuthorityContract.maximumPlatformEndpoints,
73
+ /** The largest `limit` `getServiceRuntimeSpecs` accepts; Cloudly refuses a larger one. */
74
+ maximumSpecPageSize: 256,
73
75
  /** The largest `limit` `getClusterRuntimeAssignments` accepts; Cloudly refuses a larger one. */
74
76
  maximumAssignmentPageSize: 256,
75
77
  } as const);
@@ -11,6 +11,7 @@ import type {
11
11
  TNodeCredentialPurpose,
12
12
  } from '../data/nodecredential.js';
13
13
  import type { INodeRuntimeRetirement } from '../data/noderetirement.js';
14
+ import type { INodeRuntimeBindingRead, INodeRuntimeBindingRequest } from '../data/noderuntimebinding.js';
14
15
  import type {
15
16
  INodeEnrollmentRequest,
16
17
  INodeEnrollmentAcknowledgement,
@@ -39,6 +40,20 @@ export interface IReq_Spark_Cloudly_EnrollNode extends plugins.typedrequestInter
39
40
  response: INodeEnrollmentAcknowledgement;
40
41
  }
41
42
 
43
+ /**
44
+ * Current Spark-node-authenticated routing read. Cloudly derives every response identity from its
45
+ * node, cluster, runtime-controller, relay and credential records in one consistent read.
46
+ */
47
+ export interface IReq_Spark_Cloudly_GetNodeRuntimeBinding extends plugins.typedrequestInterfaces.implementsTR<
48
+ plugins.typedrequestInterfaces.ITypedRequest,
49
+ IReq_Spark_Cloudly_GetNodeRuntimeBinding
50
+ > {
51
+ method: 'getNodeRuntimeBinding';
52
+ request: INodeRuntimeBindingRequest;
53
+ /** Routing and current phase observation only; this grants no workload or phase authority. */
54
+ response: INodeRuntimeBindingRead;
55
+ }
56
+
42
57
  /** Requires verified platform-infrastructure admin authority; returns public data only. */
43
58
  export interface IRequest_Any_Cloudly_GetNodeConfig extends plugins.typedrequestInterfaces.implementsTR<
44
59
  plugins.typedrequestInterfaces.ITypedRequest,
@@ -3,6 +3,7 @@ import type {
3
3
  INodeEnrollment, IPalletNodeEnrollmentActivation, IPalletNodeEnrollmentPreparation,
4
4
  IPalletNodeEnrollmentBound, IPalletNodeEnrollmentActive,
5
5
  } from '../data/nodeenrollment.js';
6
+ import type { INodeRuntimeRoutingBinding } from '../data/noderuntimebinding.js';
6
7
 
7
8
  /** Root-authenticated node-local IPC only; never expose on a network router. */
8
9
  export interface IReq_Spark_Pallet_PrepareNodeEnrollment extends plugins.typedrequestInterfaces.implementsTR<
@@ -36,3 +37,30 @@ export interface IReq_Spark_Pallet_ActivateNodeEnrollment extends plugins.typedr
36
37
  /** Prove the exact current local identity after activation; a historical receipt is insufficient. */
37
38
  response: IPalletNodeEnrollmentActive;
38
39
  }
40
+
41
+ /**
42
+ * First immutable routing bind or exact replay. The local owner rejects unless `cloudlyOrigin` and
43
+ * `nodeId` match its current active enrollment; this never mutates credentials, workload state or runtime phase.
44
+ */
45
+ export interface IReq_Spark_Pallet_BindNodeRuntimeBinding extends plugins.typedrequestInterfaces.implementsTR<
46
+ plugins.typedrequestInterfaces.ITypedRequest,
47
+ IReq_Spark_Pallet_BindNodeRuntimeBinding
48
+ > {
49
+ method: 'bindPalletNodeRuntimeBinding';
50
+ request: INodeRuntimeRoutingBinding;
51
+ /** Exact currently persisted binding, answered only after the durable write or exact replay. */
52
+ response: INodeRuntimeRoutingBinding;
53
+ }
54
+
55
+ /**
56
+ * Offline routing readback. The root-local owner rejects an absent binding, an inactive enrollment
57
+ * or a requested origin/node different from both its active enrollment and persisted binding.
58
+ */
59
+ export interface IReq_Spark_Pallet_ReadNodeRuntimeBinding extends plugins.typedrequestInterfaces.implementsTR<
60
+ plugins.typedrequestInterfaces.ITypedRequest,
61
+ IReq_Spark_Pallet_ReadNodeRuntimeBinding
62
+ > {
63
+ method: 'readPalletNodeRuntimeBinding';
64
+ request: { cloudlyOrigin: string; nodeId: string };
65
+ response: INodeRuntimeRoutingBinding;
66
+ }
@@ -393,9 +393,10 @@ extends plugins.typedrequestInterfaces.implementsTR<
393
393
  /**
394
394
  * Writes the complete runtime spec of one service.
395
395
  *
396
- * `expectedRevision` is the revision the caller read, or `null` when the service has never had a
397
- * spec; any other stored revision refuses the write. Cloudly copies the organization from the
398
- * service and answers with the stored spec at its new revision.
396
+ * `expectedRevision` is the revision the caller read for the target `(serviceId, spec.clusterId)`,
397
+ * or `null` when that target has never had a spec; any other stored revision refuses the write.
398
+ * Cloudly copies the organization from the service and answers with the stored spec at its new
399
+ * target-local revision.
399
400
  */
400
401
  export interface IRequest_Any_Cloudly_SetServiceRuntimeSpec
401
402
  extends plugins.typedrequestInterfaces.implementsTR<
@@ -415,8 +416,20 @@ extends plugins.typedrequestInterfaces.implementsTR<
415
416
  }
416
417
 
417
418
  /**
418
- * A service's runtime spec and the producer's verdict on it. Both are `null` before the first spec;
419
- * a spec's status is `null` until the producer first decides it.
419
+ * The named refusal data when a scalar runtime-spec read finds more than one cluster target.
420
+ *
421
+ * This travels as `TypedResponseError.errorData`. The condition needs an explicit cluster or the
422
+ * paged read, never a retry of the same ambiguous request.
423
+ */
424
+ export interface IServiceRuntimeSpecAmbiguityErrorData {
425
+ code: 'service-runtime-spec-ambiguous';
426
+ retryable: false;
427
+ }
428
+
429
+ /**
430
+ * The only runtime spec of a service and the producer's verdict on it. Zero targets answer both
431
+ * members as `null`; one target answers it, with `status: null` until the producer first decides it.
432
+ * More than one target refuses with `IServiceRuntimeSpecAmbiguityErrorData`; Cloudly never picks one.
420
433
  */
421
434
  export interface IRequest_Any_Cloudly_GetServiceRuntimeSpec
422
435
  extends plugins.typedrequestInterfaces.implementsTR<
@@ -433,6 +446,71 @@ extends plugins.typedrequestInterfaces.implementsTR<
433
446
  | { spec: IServiceRuntimeSpec; status: IServiceRuntimeStatus | null };
434
447
  }
435
448
 
449
+ /** One service's runtime spec for one exact cluster target, or no such target. */
450
+ export interface IRequest_Any_Cloudly_GetServiceRuntimeSpecForCluster
451
+ extends plugins.typedrequestInterfaces.implementsTR<
452
+ plugins.typedrequestInterfaces.ITypedRequest,
453
+ IRequest_Any_Cloudly_GetServiceRuntimeSpecForCluster
454
+ > {
455
+ method: 'getServiceRuntimeSpecForCluster';
456
+ request: {
457
+ identity: IIdentity;
458
+ serviceId: string;
459
+ clusterId: string;
460
+ };
461
+ response:
462
+ | { spec: null; status: null }
463
+ | { spec: IServiceRuntimeSpec; status: IServiceRuntimeStatus | null };
464
+ }
465
+
466
+ /**
467
+ * Every cluster target of one service, ordered by cluster id and read a bounded page at a time.
468
+ * `cursor` is opaque and `limit` runs from 1 through `serviceRuntimeContract.maximumSpecPageSize`;
469
+ * Cloudly refuses any other limit. A returned status belongs to the spec beside it.
470
+ */
471
+ export interface IRequest_Any_Cloudly_GetServiceRuntimeSpecs
472
+ extends plugins.typedrequestInterfaces.implementsTR<
473
+ plugins.typedrequestInterfaces.ITypedRequest,
474
+ IRequest_Any_Cloudly_GetServiceRuntimeSpecs
475
+ > {
476
+ method: 'getServiceRuntimeSpecs';
477
+ request: {
478
+ identity: IIdentity;
479
+ serviceId: string;
480
+ cursor: string | null;
481
+ limit: number;
482
+ };
483
+ response: {
484
+ entries: Array<{ spec: IServiceRuntimeSpec; status: IServiceRuntimeStatus | null }>;
485
+ nextCursor: string | null;
486
+ };
487
+ }
488
+
489
+ /**
490
+ * Removes one exact cluster target at the revision the administrator read.
491
+ *
492
+ * Cloudly derives every safety fact itself and fences them in the removal transaction: the current
493
+ * spec revision equals `expectedRevision`, that spec is held, every slot of this service on this
494
+ * cluster is fully settled and released, and the cluster's current phase still gives the spec
495
+ * producer authority. The caller supplies no phase, settlement, release or authority proof.
496
+ */
497
+ export interface IRequest_Any_Cloudly_RemoveServiceRuntimeSpecForCluster
498
+ extends plugins.typedrequestInterfaces.implementsTR<
499
+ plugins.typedrequestInterfaces.ITypedRequest,
500
+ IRequest_Any_Cloudly_RemoveServiceRuntimeSpecForCluster
501
+ > {
502
+ method: 'removeServiceRuntimeSpecForCluster';
503
+ request: {
504
+ identity: IIdentity;
505
+ serviceId: string;
506
+ clusterId: string;
507
+ expectedRevision: number;
508
+ };
509
+ response: {
510
+ removed: true;
511
+ };
512
+ }
513
+
436
514
  /** Every current runtime assignment of one service, one per replica slot. */
437
515
  export interface IRequest_Any_Cloudly_GetServiceRuntimeAssignments
438
516
  extends plugins.typedrequestInterfaces.implementsTR<