@serve.zone/interfaces 32.34.0 → 32.36.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.
Files changed (45) hide show
  1. package/changelog.md +71 -0
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/data/clusternode.d.ts +16 -2
  4. package/dist_ts/data/clusternode.js +11 -1
  5. package/dist_ts/data/deploymentlegacyimport.d.ts +92 -0
  6. package/dist_ts/data/deploymentlegacyimport.js +237 -0
  7. package/dist_ts/data/deploymentoperation.d.ts +63 -38
  8. package/dist_ts/data/deploymentoperation.js +12 -2
  9. package/dist_ts/data/deploymentpreflight.d.ts +42 -10
  10. package/dist_ts/data/deploymentpreflight.js +47 -10
  11. package/dist_ts/data/immutableimage.d.ts +26 -2
  12. package/dist_ts/data/immutableimage.js +12 -2
  13. package/dist_ts/data/index.d.ts +5 -0
  14. package/dist_ts/data/index.js +6 -1
  15. package/dist_ts/data/networknode.d.ts +16 -6
  16. package/dist_ts/data/networknode.js +1 -1
  17. package/dist_ts/data/runtimeassignmentexec.d.ts +110 -0
  18. package/dist_ts/data/runtimeassignmentexec.js +164 -0
  19. package/dist_ts/data/runtimeassignmentlogs.d.ts +121 -0
  20. package/dist_ts/data/runtimeassignmentlogs.js +139 -0
  21. package/dist_ts/data/runtimeassignmentstats.d.ts +60 -0
  22. package/dist_ts/data/runtimeassignmentstats.js +93 -0
  23. package/dist_ts/data/runtimelocalvolumearchive.d.ts +103 -0
  24. package/dist_ts/data/runtimelocalvolumearchive.js +111 -0
  25. package/dist_ts/data/user.d.ts +6 -1
  26. package/dist_ts/data/user.js +6 -1
  27. package/dist_ts/requests/runtimesession.d.ts +81 -0
  28. package/dist_ts/requests/service.d.ts +51 -0
  29. package/package.json +2 -2
  30. package/readme.md +109 -1
  31. package/ts/00_commitinfo_data.ts +1 -1
  32. package/ts/data/clusternode.ts +17 -2
  33. package/ts/data/deploymentlegacyimport.ts +371 -0
  34. package/ts/data/deploymentoperation.ts +83 -39
  35. package/ts/data/deploymentpreflight.ts +86 -18
  36. package/ts/data/immutableimage.ts +42 -3
  37. package/ts/data/index.ts +5 -0
  38. package/ts/data/networknode.ts +12 -5
  39. package/ts/data/runtimeassignmentexec.ts +271 -0
  40. package/ts/data/runtimeassignmentlogs.ts +249 -0
  41. package/ts/data/runtimeassignmentstats.ts +149 -0
  42. package/ts/data/runtimelocalvolumearchive.ts +207 -0
  43. package/ts/data/user.ts +10 -0
  44. package/ts/requests/runtimesession.ts +102 -0
  45. package/ts/requests/service.ts +66 -0
@@ -0,0 +1,103 @@
1
+ import type * as plugins from '../plugins.js';
2
+ import type { TSha256Digest } from './immutableimage.js';
3
+ import type { IRuntimeAuthorityReference } from './runtimeassignment.js';
4
+ import type { IRuntimeLocalStorageClaim } from './runtimelocalstorageclaim.js';
5
+ import type { IRuntimeSessionBinding } from './runtimesession.js';
6
+ /**
7
+ * The one archive format a volume travels in: a POSIX.1-2001 pax archive of the volume's tree.
8
+ *
9
+ * The first entry is the volume's root, `./`, and every other name is relative below it. An entry is
10
+ * a directory, a regular file, a symbolic link (stored, never followed), a hard link to an earlier
11
+ * entry of the same archive, or a FIFO. Ownership is numeric: `uid` and `gid`, never user or group
12
+ * names, so the tree means the same on every host. Mode bits, including setuid, setgid and sticky,
13
+ * and the modification time travel exactly. A socket carries no bytes and belongs to the process
14
+ * that created it, so it is omitted; a device node, extended attributes and ACLs are not part of
15
+ * the format, and a volume that holds a device node is refused. The archive ends with the format's
16
+ * two zero blocks, so its length is a whole number of 512-byte blocks.
17
+ */
18
+ export type TRuntimeLocalVolumeArchiveFormat = 'pax';
19
+ /**
20
+ * A local controller reads one volume as an archive. The node refuses while a running container
21
+ * holds the volume: it reads a volume no container holds, or one whose holding container is
22
+ * stopped, which a node never restarts, and no container is granted the volume while it reads.
23
+ */
24
+ export interface IReadRuntimeLocalVolumeArchiveRequest {
25
+ session: IRuntimeSessionBinding;
26
+ /** The reference of the node's current generation of a bound `directory` claim. */
27
+ claim: IRuntimeAuthorityReference;
28
+ }
29
+ /**
30
+ * The archive arrives on the stream the TypedRequest answers beside this body. `holder` states
31
+ * whether the volume was read unheld or under a stopped holder. The stream carries no integrity:
32
+ * the node learns the archive's length and digest only while it writes it.
33
+ */
34
+ export interface IReadRuntimeLocalVolumeArchiveResponse {
35
+ claim: IRuntimeAuthorityReference;
36
+ format: TRuntimeLocalVolumeArchiveFormat;
37
+ holder: 'none' | 'stopped';
38
+ }
39
+ /**
40
+ * A local controller writes one archive as the bytes of an `import` claim that waits in
41
+ * `import-required`: the online form of an operator's offline import. The node receives the stream
42
+ * beside this body, whose integrity must be exactly `byteLength` and `digest`, unpacks it into a
43
+ * private staging tree, and publishes that tree as the volume only after the whole archive
44
+ * verified. The tree keeps the ownership the archive carries, as every imported tree does.
45
+ */
46
+ export interface IWriteRuntimeLocalVolumeArchiveRequest {
47
+ session: IRuntimeSessionBinding;
48
+ claim: IRuntimeAuthorityReference;
49
+ format: TRuntimeLocalVolumeArchiveFormat;
50
+ byteLength: number;
51
+ digest: TSha256Digest;
52
+ }
53
+ /**
54
+ * `receiving` permits sending and confirms nothing more. The stream's acceptance receipt is what
55
+ * confirms that the node published the volume as `ready`; a rejected or aborted stream leaves the
56
+ * claim in `import-required` with nothing published.
57
+ */
58
+ export interface IWriteRuntimeLocalVolumeArchiveResponse {
59
+ claim: IRuntimeAuthorityReference;
60
+ status: 'receiving';
61
+ }
62
+ /**
63
+ * Volume archives are a local controller's alone (`controller.kind: 'onebox'`), on the connection
64
+ * that holds its session, and a node moves one archive per volume at a time: a read or write of a
65
+ * volume that is already moving one is refused.
66
+ */
67
+ export declare const runtimeLocalVolumeArchiveContract: Readonly<{
68
+ readonly error: "Runtime local volume archive contract rejected.";
69
+ readonly controllerKind: "onebox";
70
+ readonly format: "pax";
71
+ readonly entryKinds: readonly ["directory", "file", "symlink", "hardlink", "fifo"];
72
+ readonly omittedEntryKinds: readonly ["socket"];
73
+ readonly ownership: "numeric-uid-gid";
74
+ readonly blockBytes: 512;
75
+ /** The shortest archive: the root directory's header and the two closing zero blocks. */
76
+ readonly minimumArchiveBytes: 1536;
77
+ readonly concurrency: "one-archive-per-volume";
78
+ }>;
79
+ export declare const snapshotReadRuntimeLocalVolumeArchiveRequest: (valueArg: unknown) => IReadRuntimeLocalVolumeArchiveRequest;
80
+ export declare const snapshotReadRuntimeLocalVolumeArchiveResponse: (valueArg: unknown) => IReadRuntimeLocalVolumeArchiveResponse;
81
+ export declare const snapshotWriteRuntimeLocalVolumeArchiveRequest: (valueArg: unknown) => IWriteRuntimeLocalVolumeArchiveRequest;
82
+ export declare const snapshotWriteRuntimeLocalVolumeArchiveResponse: (valueArg: unknown) => IWriteRuntimeLocalVolumeArchiveResponse;
83
+ /** The exact integrity the write's stream must carry: the request's length and digest. */
84
+ export declare const getRuntimeLocalVolumeArchiveIntegrity: (requestArg: unknown) => plugins.typedrequestInterfaces.IVirtualStreamIntegrity;
85
+ /**
86
+ * Node-side content binding, not authentication. `claimArg` is the node's current generation of the
87
+ * named claim and `trustedSessionArg` its CURRENT session, on whose own connection the request must
88
+ * have arrived. Whether the volume is `ready` and who holds it are the node's ledger checks.
89
+ */
90
+ export declare const bindReadRuntimeLocalVolumeArchiveRequest: (valueArg: unknown, claimArg: unknown, trustedSessionArg: unknown) => Promise<{
91
+ request: IReadRuntimeLocalVolumeArchiveRequest;
92
+ claim: IRuntimeLocalStorageClaim;
93
+ }>;
94
+ /**
95
+ * As the read binding, and the claim must be an `import` claim. That it still waits in
96
+ * `import-required` is the node's ledger check.
97
+ */
98
+ export declare const bindWriteRuntimeLocalVolumeArchiveRequest: (valueArg: unknown, claimArg: unknown, trustedSessionArg: unknown) => Promise<{
99
+ request: IWriteRuntimeLocalVolumeArchiveRequest;
100
+ claim: IRuntimeLocalStorageClaim;
101
+ }>;
102
+ export declare const bindReadRuntimeLocalVolumeArchiveResponse: (valueArg: unknown, requestArg: unknown) => IReadRuntimeLocalVolumeArchiveResponse;
103
+ export declare const bindWriteRuntimeLocalVolumeArchiveResponse: (valueArg: unknown, requestArg: unknown) => IWriteRuntimeLocalVolumeArchiveResponse;
@@ -0,0 +1,111 @@
1
+ import { getRuntimeLocalStorageClaimReference, snapshotRuntimeLocalStorageClaim, validateRuntimeLocalStorageClaim, } from './runtimelocalstorageclaim.js';
2
+ import { snapshotRuntimeSessionBinding } from './runtimesession.js';
3
+ import { captureNetwork, networkDigest, networkEqual, networkRecord, networkReference, requireNetwork, } from '../private/networkcontract.js';
4
+ /**
5
+ * Volume archives are a local controller's alone (`controller.kind: 'onebox'`), on the connection
6
+ * that holds its session, and a node moves one archive per volume at a time: a read or write of a
7
+ * volume that is already moving one is refused.
8
+ */
9
+ export const runtimeLocalVolumeArchiveContract = Object.freeze({
10
+ error: 'Runtime local volume archive contract rejected.',
11
+ controllerKind: 'onebox',
12
+ format: 'pax',
13
+ entryKinds: ['directory', 'file', 'symlink', 'hardlink', 'fifo'],
14
+ omittedEntryKinds: ['socket'],
15
+ ownership: 'numeric-uid-gid',
16
+ blockBytes: 512,
17
+ /** The shortest archive: the root directory's header and the two closing zero blocks. */
18
+ minimumArchiveBytes: 1536,
19
+ concurrency: 'one-archive-per-volume',
20
+ });
21
+ const fail = () => { throw new Error(runtimeLocalVolumeArchiveContract.error); };
22
+ const safe = (operationArg) => { try {
23
+ return operationArg();
24
+ }
25
+ catch {
26
+ return fail();
27
+ } };
28
+ const archiveBytesOf = (valueArg) => {
29
+ requireNetwork(typeof valueArg === 'number' && Number.isSafeInteger(valueArg)
30
+ && valueArg >= runtimeLocalVolumeArchiveContract.minimumArchiveBytes
31
+ && valueArg % runtimeLocalVolumeArchiveContract.blockBytes === 0);
32
+ return valueArg;
33
+ };
34
+ export const snapshotReadRuntimeLocalVolumeArchiveRequest = (valueArg) => safe(() => {
35
+ const value = networkRecord(captureNetwork(valueArg), ['session', 'claim']);
36
+ return { session: snapshotRuntimeSessionBinding(value.session), claim: networkReference(value.claim) };
37
+ });
38
+ export const snapshotReadRuntimeLocalVolumeArchiveResponse = (valueArg) => safe(() => {
39
+ const value = networkRecord(captureNetwork(valueArg), ['claim', 'format', 'holder']);
40
+ requireNetwork(value.format === 'pax' && (value.holder === 'none' || value.holder === 'stopped'));
41
+ return { claim: networkReference(value.claim), format: 'pax', holder: value.holder };
42
+ });
43
+ export const snapshotWriteRuntimeLocalVolumeArchiveRequest = (valueArg) => safe(() => {
44
+ const value = networkRecord(captureNetwork(valueArg), ['session', 'claim', 'format', 'byteLength', 'digest']);
45
+ requireNetwork(value.format === 'pax');
46
+ return {
47
+ session: snapshotRuntimeSessionBinding(value.session), claim: networkReference(value.claim), format: 'pax',
48
+ byteLength: archiveBytesOf(value.byteLength), digest: networkDigest(value.digest),
49
+ };
50
+ });
51
+ export const snapshotWriteRuntimeLocalVolumeArchiveResponse = (valueArg) => safe(() => {
52
+ const value = networkRecord(captureNetwork(valueArg), ['claim', 'status']);
53
+ requireNetwork(value.status === 'receiving');
54
+ return { claim: networkReference(value.claim), status: 'receiving' };
55
+ });
56
+ /** The exact integrity the write's stream must carry: the request's length and digest. */
57
+ export const getRuntimeLocalVolumeArchiveIntegrity = (requestArg) => {
58
+ const request = snapshotWriteRuntimeLocalVolumeArchiveRequest(requestArg);
59
+ return { algorithm: 'sha256', byteLength: request.byteLength, digest: request.digest };
60
+ };
61
+ /**
62
+ * The claim a request names must be the node's current generation of a bound `directory` claim of
63
+ * the session's controller, cluster, node and runtime namespace, and the session must be a local
64
+ * controller's current one.
65
+ */
66
+ const bindClaim = async (requestArg, claimArg, trustedSessionArg) => {
67
+ const trustedSession = safe(() => snapshotRuntimeSessionBinding(trustedSessionArg));
68
+ const claim = safe(() => snapshotRuntimeLocalStorageClaim(claimArg));
69
+ if ((await validateRuntimeLocalStorageClaim(claim)).length)
70
+ fail();
71
+ safe(() => requireNetwork(networkEqual(requestArg.session, trustedSession)
72
+ && trustedSession.controller.kind === runtimeLocalVolumeArchiveContract.controllerKind
73
+ && networkEqual(requestArg.claim, getRuntimeLocalStorageClaimReference(claim))
74
+ && claim.lifecycle === 'bound' && claim.source.kind === 'directory'
75
+ && networkEqual(claim.controller, trustedSession.controller)
76
+ && claim.clusterId === trustedSession.clusterId
77
+ && claim.nodeId === trustedSession.nodeId
78
+ && claim.runtimeNamespace === trustedSession.runtimeNamespace));
79
+ return claim;
80
+ };
81
+ /**
82
+ * Node-side content binding, not authentication. `claimArg` is the node's current generation of the
83
+ * named claim and `trustedSessionArg` its CURRENT session, on whose own connection the request must
84
+ * have arrived. Whether the volume is `ready` and who holds it are the node's ledger checks.
85
+ */
86
+ export const bindReadRuntimeLocalVolumeArchiveRequest = async (valueArg, claimArg, trustedSessionArg) => {
87
+ const request = snapshotReadRuntimeLocalVolumeArchiveRequest(valueArg);
88
+ return { request, claim: await bindClaim(request, claimArg, trustedSessionArg) };
89
+ };
90
+ /**
91
+ * As the read binding, and the claim must be an `import` claim. That it still waits in
92
+ * `import-required` is the node's ledger check.
93
+ */
94
+ export const bindWriteRuntimeLocalVolumeArchiveRequest = async (valueArg, claimArg, trustedSessionArg) => {
95
+ const request = snapshotWriteRuntimeLocalVolumeArchiveRequest(valueArg);
96
+ const claim = await bindClaim(request, claimArg, trustedSessionArg);
97
+ if (claim.initialization !== 'import')
98
+ fail();
99
+ return { request, claim };
100
+ };
101
+ export const bindReadRuntimeLocalVolumeArchiveResponse = (valueArg, requestArg) => safe(() => {
102
+ const response = snapshotReadRuntimeLocalVolumeArchiveResponse(valueArg);
103
+ requireNetwork(networkEqual(response.claim, snapshotReadRuntimeLocalVolumeArchiveRequest(requestArg).claim));
104
+ return response;
105
+ });
106
+ export const bindWriteRuntimeLocalVolumeArchiveResponse = (valueArg, requestArg) => safe(() => {
107
+ const response = snapshotWriteRuntimeLocalVolumeArchiveResponse(valueArg);
108
+ requireNetwork(networkEqual(response.claim, snapshotWriteRuntimeLocalVolumeArchiveRequest(requestArg).claim));
109
+ return response;
110
+ });
111
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicnVudGltZWxvY2Fsdm9sdW1lYXJjaGl2ZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL2RhdGEvcnVudGltZWxvY2Fsdm9sdW1lYXJjaGl2ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFJQSxPQUFPLEVBQ0wsb0NBQW9DLEVBQUUsZ0NBQWdDLEVBQUUsZ0NBQWdDLEdBQ3pHLE1BQU0sK0JBQStCLENBQUM7QUFFdkMsT0FBTyxFQUFFLDZCQUE2QixFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFDcEUsT0FBTyxFQUNMLGNBQWMsRUFBRSxhQUFhLEVBQUUsWUFBWSxFQUFFLGFBQWEsRUFBRSxnQkFBZ0IsRUFBRSxjQUFjLEdBQzdGLE1BQU0sK0JBQStCLENBQUM7QUErRHZDOzs7O0dBSUc7QUFDSCxNQUFNLENBQUMsTUFBTSxpQ0FBaUMsR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDO0lBQzdELEtBQUssRUFBRSxpREFBaUQ7SUFDeEQsY0FBYyxFQUFFLFFBQVE7SUFDeEIsTUFBTSxFQUFFLEtBQUs7SUFDYixVQUFVLEVBQUUsQ0FBQyxXQUFXLEVBQUUsTUFBTSxFQUFFLFNBQVMsRUFBRSxVQUFVLEVBQUUsTUFBTSxDQUFDO0lBQ2hFLGlCQUFpQixFQUFFLENBQUMsUUFBUSxDQUFDO0lBQzdCLFNBQVMsRUFBRSxpQkFBaUI7SUFDNUIsVUFBVSxFQUFFLEdBQUc7SUFDZix5RkFBeUY7SUFDekYsbUJBQW1CLEVBQUUsSUFBSTtJQUN6QixXQUFXLEVBQUUsd0JBQXdCO0NBQzdCLENBQUMsQ0FBQztBQUVaLE1BQU0sSUFBSSxHQUFHLEdBQVUsRUFBRSxHQUFHLE1BQU0sSUFBSSxLQUFLLENBQUMsaUNBQWlDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDeEYsTUFBTSxJQUFJLEdBQUcsQ0FBSSxZQUFxQixFQUFLLEVBQUUsR0FBRyxJQUFJLENBQUM7SUFBQyxPQUFPLFlBQVksRUFBRSxDQUFDO0FBQUMsQ0FBQztB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
@@ -3,7 +3,12 @@ export interface IToken {
3
3
  expiresAt: number;
4
4
  assignedRoles: string[];
5
5
  }
6
- export type TServiceDeploymentCapability = 'deployment:preflight' | 'deployment:reserve' | 'deployment:registry-push' | 'deployment:release-read' | 'deployment:promote-image' | 'deployment:adopt-existing' | 'deployment:status' | 'deployment:create-greenfield' | 'deployment:promote-route' | 'deployment:rollback-image' | 'deployment:rollback-route' | 'deployment:retry' | 'deployment:cleanup';
6
+ export type TServiceDeploymentCapability = 'deployment:preflight' | 'deployment:reserve' | 'deployment:registry-push' | 'deployment:release-read' | 'deployment:promote-image' | 'deployment:adopt-existing' | 'deployment:status' | 'deployment:create-greenfield'
7
+ /**
8
+ * Reserve a greenfield successor whose image Cloudly imports from an attested legacy service,
9
+ * and have Cloudly import it. Granted on an organization-service-slot grant only.
10
+ */
11
+ | 'deployment:import-legacy' | 'deployment:promote-route' | 'deployment:rollback-image' | 'deployment:rollback-route' | 'deployment:retry' | 'deployment:cleanup';
7
12
  /**
8
13
  * Server-persisted authority for one logical service. These grants are never
9
14
  * accepted from the identity payload supplied by a caller; Cloudly reloads
@@ -7,6 +7,7 @@ const serviceDeploymentCapabilities = new Set([
7
7
  'deployment:adopt-existing',
8
8
  'deployment:status',
9
9
  'deployment:create-greenfield',
10
+ 'deployment:import-legacy',
10
11
  'deployment:promote-route',
11
12
  'deployment:rollback-image',
12
13
  'deployment:rollback-route',
@@ -54,6 +55,10 @@ export const validateServiceDeploymentGrant = (grantArg) => {
54
55
  && capabilities.includes('deployment:adopt-existing')) {
55
56
  errors.push('deployment:adopt-existing requires an existing-service grant');
56
57
  }
58
+ if (capabilities && grant.scope !== 'organization-service-slot'
59
+ && capabilities.includes('deployment:import-legacy')) {
60
+ errors.push('deployment:import-legacy requires an organization-service-slot grant');
61
+ }
57
62
  return errors;
58
63
  };
59
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXNlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL2RhdGEvdXNlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUE4Q0EsTUFBTSw2QkFBNkIsR0FBRyxJQUFJLEdBQUcsQ0FBK0I7SUFDMUUsc0JBQXNCO0lBQ3RCLG9CQUFvQjtJQUNwQiwwQkFBMEI7SUFDMUIseUJBQXlCO0lBQ3pCLDBCQUEwQjtJQUMxQiwyQkFBMkI7SUFDM0IsbUJBQW1CO0lBQ25CLDhCQUE4QjtJQUM5QiwwQkFBMEI7SUFDMUIsMkJBQTJCO0lBQzNCLDJCQUEyQjtJQUMzQixrQkFBa0I7SUFDbEIsb0JBQW9CO0NBQ3JCLENBQUMsQ0FBQztBQUVILE1BQU0saUNBQWlDLEdBQUcscUNBQXFDLENBQUM7QUFFaEYsTUFBTSxDQUFDLE1BQU0sOEJBQThCLEdBQUcsQ0FDNUMsUUFBaUIsRUFDUCxFQUFFO0lBQ1osTUFBTSxNQUFNLEdBQWEsRUFBRSxDQUFDO0lBQzVCLElBQUksQ0FBQyxRQUFRLElBQUksT0FBTyxRQUFRLEtBQUssUUFBUSxJQUFJLEtBQUssQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQztRQUN6RSxPQUFPLENBQUMseUJBQXlCLENBQUMsQ0FBQztJQUNyQyxDQUFDO0lBQ0QsTUFBTSxLQUFLLEdBQUcsUUFBbUMsQ0FBQztJQUNsRCxJQUFJLEtBQUssQ0FBQyxLQUFLLEtBQUssU0FBUyxJQUFJLEtBQUssQ0FBQyxLQUFLLEtBQUssMkJBQTJCLEVBQUUsQ0FBQztRQUM3RSxNQUFNLENBQUMsSUFBSSxDQUFDLHlFQUF5RSxDQUFDLENBQUM7SUFDekYsQ0FBQztJQUNELElBQUksT0FBTyxLQUFLLENBQUMsY0FBYyxLQUFLLFFBQVE7V0FDdkMsQ0FBQyxpQ0FBaUMsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLGNBQWMsQ0FBQyxFQUFFLENBQUM7UUFDbkUsTUFBTSxDQUFDLElBQUksQ0FBQyx1REFBdUQsQ0FBQyxDQUFDO0lBQ3ZFLENBQUM7SUFDRCxJQUFJLE9BQU8sS0FBSyxDQUFDLFNBQVMsS0FBSyxRQUFRO1dBQ2xDLENBQUMsaUNBQWlDLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxTQUFTLENBQUMsRUFBRSxDQUFDO1FBQzlELE1BQU0sQ0FBQyxJQUFJLENBQUMsa0RBQWtELENBQUMsQ0FBQztJQUNsRSxDQUFDO0lBQ0QsTUFBTSxZQUFZLEdBQUcsS0FBSyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsWUFBWSxDQUFDO1FBQ3BELENBQUMsQ0FBQyxLQUFLLENBQUMsWUFBWTtRQUNwQixDQUFDLENBQUMsU0FBUyxDQUFDO0lBQ2QsSUFBSSxDQUFDLFlBQVk7V0FDWixZQUFZLENBQUMsTUFBTSxHQUFHLENBQUM7V0FDdkIsWUFBWSxDQUFDLE1BQU0sR0FBRyw2QkFBNkIsQ0FBQyxJQUFJO1dBQ3hELElBQUksR0FBRyxDQUFDLFlBQVksQ0FBQyxDQUFDLElBQUksS0FBSyxZQUFZLENBQUMsTUFBTTtXQUNsRCxZQUFZLENBQUMsSUFBSSxDQUFDLENBQUMsYUFBYSxFQUFFLEVBQUUsQ0FBQyxDQUN0QyxPQUFPLGFBQWEsS0FBSyxRQUFRO2VBQzlCLENBQUMsNkJBQTZCLENBQUMsR0FBRyxDQUFDLGFBQTZDLENBQUMsQ0FDckYsQ0FBQyxFQUFFLENBQUM7UUFDTCxNQUFNLENBQUMsSUFBSSxDQUFDLGtGQUFrRixDQUFDLENBQUM7SUFDbEcsQ0FBQztJQUNELElBQUksWUFBWSxJQUFJLEtBQUssQ0FBQyxLQUFLLEtBQUssU0FBUztXQUN4QyxZQUFZLENBQUMsUUFBUSxDQUFDLDhCQUE4QixDQUFDLEVBQUUsQ0FBQztRQUMzRCxNQUFNLENBQUMsSUFBSSxDQUFDLGdFQUFnRSxDQUFDLENBQUM7SUFDaEYsQ0FBQztJQUNELElBQUksWUFBWSxJQUFJLEtBQUssQ0FBQyxLQUFLLEtBQUssMkJBQTJCO1dBQzFELENBQUMsWUFBWSxDQUFDLFFBQVEsQ0FBQyw4QkFBOEIsQ0FBQyxFQUFFLENBQUM7UUFDNUQsTUFBTSxDQUFDLElBQUksQ0FBQyx1RUFBdUUsQ0FBQyxDQUFDO0lBQ3ZGLENBQUM7SUFDRCxJQUFJLFlBQVksSUFBSSxLQUFLLENBQUMsS0FBSyxLQUFLLFNBQVM7V0FDeEMsWUFBWSxDQUFDLFFBQVEsQ0FBQywyQkFBMkIsQ0FBQyxFQUFFLENBQUM7UUFDeEQsTUFBTSxDQUFDLElBQUksQ0FBQyw4REFBOEQsQ0FBQyxDQUFDO0lBQzlFLENBQUM7SUFDRCxPQUFPLE1BQU0sQ0FBQztBQUNoQixDQUFDLENBQUMifQ==
64
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXNlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL2RhdGEvdXNlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFtREEsTUFBTSw2QkFBNkIsR0FBRyxJQUFJLEdBQUcsQ0FBK0I7SUFDMUUsc0JBQXNCO0lBQ3RCLG9CQUFvQjtJQUNwQiwwQkFBMEI7SUFDMUIseUJBQXlCO0lBQ3pCLDBCQUEwQjtJQUMxQiwyQkFBMkI7SUFDM0IsbUJBQW1CO0lBQ25CLDhCQUE4QjtJQUM5QiwwQkFBMEI7SUFDMUIsMEJBQTBCO0lBQzFCLDJCQUEyQjtJQUMzQiwyQkFBMkI7SUFDM0Isa0JBQWtCO0lBQ2xCLG9CQUFvQjtDQUNyQixDQUFDLENBQUM7QUFFSCxNQUFNLGlDQUFpQyxHQUFHLHFDQUFxQyxDQUFDO0FBRWhGLE1BQU0sQ0FBQyxNQUFNLDhCQUE4QixHQUFHLENBQzVDLFFBQWlCLEVBQ1AsRUFBRTtJQUNaLE1BQU0sTUFBTSxHQUFhLEVBQUUsQ0FBQztJQUM1QixJQUFJLENBQUMsUUFBUSxJQUFJLE9BQU8sUUFBUSxLQUFLLFFBQVEsSUFBSSxLQUFLLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQUM7UUFDekUsT0FBTyxDQUFDLHlCQUF5QixDQUFDLENBQUM7SUFDckMsQ0FBQztJQUNELE1BQU0sS0FBSyxHQUFHLFFBQW1DLENBQUM7SUFDbEQsSUFBSSxLQUFLLENBQUMsS0FBSyxLQUFLLFNBQVMsSUFBSSxLQUFLLENBQUMsS0FBSyxLQUFLLDJCQUEyQixFQUFFLENBQUM7UUFDN0UsTUFBTSxDQUFDLElBQUksQ0FBQyx5RUFBeUUsQ0FBQyxDQUFDO0lBQ3pGLENBQUM7SUFDRCxJQUFJLE9BQU8sS0FBSyxDQUFDLGNBQWMsS0FBSyxRQUFRO1dBQ3ZDLENBQUMsaUNBQWlDLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxjQUFjLENBQUMsRUFBRSxDQUFDO1FBQ25FLE1BQU0sQ0FBQyxJQUFJLENBQUMsdURBQXVELENBQUMsQ0FBQztJQUN2RSxDQUFDO0lBQ0QsSUFBSSxPQUFPLEtBQUssQ0FBQyxTQUFTLEtBQUssUUFBUTtXQUNsQyxDQUFDLGlDQUFpQyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsU0FBUyxDQUFDLEVBQUUsQ0FBQztRQUM5RCxNQUFNLENBQUMsSUFBSSxDQUFDLGtEQUFrRCxDQUFDLENBQUM7SUFDbEUsQ0FBQztJQUNELE1BQU0sWUFBWSxHQUFHLEtBQUssQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLFlBQVksQ0FBQztRQUNwRCxDQUFDLENBQUMsS0FBSyxDQUFDLFlBQVk7UUFDcEIsQ0FBQyxDQUFDLFNBQVMsQ0FBQztJQUNkLElBQUksQ0FBQyxZQUFZO1dBQ1osWUFBWSxDQUFDLE1BQU0sR0FBRyxDQUFDO1dBQ3ZCLFlBQVksQ0FBQyxNQUFNLEdBQUcsNkJBQTZCLENBQUMsSUFBSTtXQUN4RCxJQUFJLEdBQUcsQ0FBQyxZQUFZLENBQUMsQ0FBQyxJQUFJLEtBQUssWUFBWSxDQUFDLE1BQU07V0FDbEQsWUFBWSxDQUFDLElBQUksQ0FBQyxDQUFDLGFBQWEsRUFBRSxFQUFFLENBQUMsQ0FDdEMsT0FBTyxhQUFhLEtBQUssUUFBUTtlQUM5QixDQUFDLDZCQUE2QixDQUFDLEdBQUcsQ0FBQyxhQUE2QyxDQUFDLENBQ3JGLENBQUMsRUFBRSxDQUFDO1FBQ0wsTUFBTSxDQUFDLElBQUksQ0FBQyxrRkFBa0YsQ0FBQyxDQUFDO0lBQ2xHLENBQUM7SUFDRCxJQUFJLFlBQVksSUFBSSxLQUFLLENBQUMsS0FBSyxLQUFLLFNBQVM7V0FDeEMsWUFBWSxDQUFDLFFBQVEsQ0FBQyw4QkFBOEIsQ0FBQyxFQUFFLENBQUM7UUFDM0QsTUFBTSxDQUFDLElBQUksQ0FBQyxnRUFBZ0UsQ0FBQyxDQUFDO0lBQ2hGLENBQUM7SUFDRCxJQUFJLFlBQVksSUFBSSxLQUFLLENBQUMsS0FBSyxLQUFLLDJCQUEyQjtXQUMxRCxDQUFDLFlBQVksQ0FBQyxRQUFRLENBQUMsOEJBQThCLENBQUMsRUFBRSxDQUFDO1FBQzVELE1BQU0sQ0FBQyxJQUFJLENBQUMsdUVBQXVFLENBQUMsQ0FBQztJQUN2RixDQUFDO0lBQ0QsSUFBSSxZQUFZLElBQUksS0FBSyxDQUFDLEtBQUssS0FBSyxTQUFTO1dBQ3hDLFlBQVksQ0FBQyxRQUFRLENBQUMsMkJBQTJCLENBQUMsRUFBRSxDQUFDO1FBQ3hELE1BQU0sQ0FBQyxJQUFJLENBQUMsOERBQThELENBQUMsQ0FBQztJQUM5RSxDQUFDO0lBQ0QsSUFBSSxZQUFZLElBQUksS0FBSyxDQUFDLEtBQUssS0FBSywyQkFBMkI7V0FDMUQsWUFBWSxDQUFDLFFBQVEsQ0FBQywwQkFBMEIsQ0FBQyxFQUFFLENBQUM7UUFDdkQsTUFBTSxDQUFDLElBQUksQ0FBQyxzRUFBc0UsQ0FBQyxDQUFDO0lBQ3RGLENBQUM7SUFDRCxPQUFPLE1BQU0sQ0FBQztBQUNoQixDQUFDLENBQUMifQ==
@@ -9,6 +9,10 @@ import type * as assignmentSecret from '../runtime.assignmentsecret.js';
9
9
  import type * as fleet from '../data/fleetcutover.js';
10
10
  import type { IRuntimeStorageClaim } from '../data/runtimestorageclaim.js';
11
11
  import type * as localStorage from '../data/runtimelocalstorageclaim.js';
12
+ import type * as logs from '../data/runtimeassignmentlogs.js';
13
+ import type * as stats from '../data/runtimeassignmentstats.js';
14
+ import type * as exec from '../data/runtimeassignmentexec.js';
15
+ import type * as volumeArchive from '../data/runtimelocalvolumearchive.js';
12
16
  /** Current-peer, exact-projection authorization; response contains secret keys. */
13
17
  export interface IReq_Pallet_Controller_GetRuntimeManagedVpnCredential extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Pallet_Controller_GetRuntimeManagedVpnCredential> {
14
18
  method: 'getRuntimeManagedVpnCredential';
@@ -161,6 +165,83 @@ export interface IReq_Pallet_Controller_ReportRuntimeAssignmentTerminalReceipt e
161
165
  request: terminal.IReportRuntimeAssignmentTerminalReceiptRequest;
162
166
  response: terminal.IReportRuntimeAssignmentTerminalReceiptResponse;
163
167
  }
168
+ /**
169
+ * Pallet pushes one batch of one attempt's captured output over its current session and sends the
170
+ * next only after this one is acknowledged (`runtimeAssignmentLogContract`). The controller owns
171
+ * log persistence; the node's capture files are disposable transport artifacts.
172
+ */
173
+ export interface IReq_Pallet_Controller_ReportRuntimeAssignmentLogs extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Pallet_Controller_ReportRuntimeAssignmentLogs> {
174
+ method: 'reportRuntimeAssignmentLogs';
175
+ request: logs.IReportRuntimeAssignmentLogsRequest;
176
+ response: logs.IReportRuntimeAssignmentLogsResponse;
177
+ }
178
+ /** Controller read of one resource sample of one attempt, over the node's current session. */
179
+ export interface IReq_Controller_Pallet_ReadRuntimeAssignmentStats extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Controller_Pallet_ReadRuntimeAssignmentStats> {
180
+ method: 'readRuntimeAssignmentStats';
181
+ request: stats.IReadRuntimeAssignmentStatsRequest;
182
+ response: stats.IReadRuntimeAssignmentStatsResponse;
183
+ }
184
+ /**
185
+ * A local controller runs one command in one running attempt's container
186
+ * (`runtimeAssignmentExecContract`). The controller sends standard input on `stdin`, or states
187
+ * `null` for none. The node answers once the process runs: `stdout` carries a `tty` session's
188
+ * whole output, and `stderr` is `null` exactly for a `tty` session. The streams outlive this
189
+ * request and end when the process's output ends or the session ends.
190
+ */
191
+ export interface IReq_Onebox_Pallet_OpenRuntimeAssignmentExec extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Onebox_Pallet_OpenRuntimeAssignmentExec> {
192
+ method: 'openRuntimeAssignmentExec';
193
+ request: {
194
+ exec: exec.IOpenRuntimeAssignmentExecRequest;
195
+ /** Requester sends standard input; the TypedHandler receives the opposite endpoint. */
196
+ stdin: plugins.typedrequestInterfaces.TVirtualStream<'send'> | null;
197
+ };
198
+ response: {
199
+ exec: exec.IOpenRuntimeAssignmentExecResponse;
200
+ /** Requester receives output; the TypedHandler returns sender endpoints. */
201
+ stdout: plugins.typedrequestInterfaces.TVirtualStream<'receive'>;
202
+ stderr: plugins.typedrequestInterfaces.TVirtualStream<'receive'> | null;
203
+ };
204
+ }
205
+ /** A local controller resizes the terminal of one `tty` exec session. */
206
+ export interface IReq_Onebox_Pallet_ResizeRuntimeAssignmentExec extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Onebox_Pallet_ResizeRuntimeAssignmentExec> {
207
+ method: 'resizeRuntimeAssignmentExec';
208
+ request: exec.IResizeRuntimeAssignmentExecRequest;
209
+ response: exec.IResizeRuntimeAssignmentExecResponse;
210
+ }
211
+ /** A local controller ends one exec session, releases its slot and learns how it ended. */
212
+ export interface IReq_Onebox_Pallet_CloseRuntimeAssignmentExec extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Onebox_Pallet_CloseRuntimeAssignmentExec> {
213
+ method: 'closeRuntimeAssignmentExec';
214
+ request: exec.ICloseRuntimeAssignmentExecRequest;
215
+ response: exec.ICloseRuntimeAssignmentExecResponse;
216
+ }
217
+ /**
218
+ * A local controller reads one node-local volume as a pax archive
219
+ * (`runtimeLocalVolumeArchiveContract`). The archive arrives on `archive`, which outlives this
220
+ * request; the node's completion of that stream is the end of the archive.
221
+ */
222
+ export interface IReq_Onebox_Pallet_ReadRuntimeLocalVolumeArchive extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Onebox_Pallet_ReadRuntimeLocalVolumeArchive> {
223
+ method: 'readRuntimeLocalVolumeArchive';
224
+ request: volumeArchive.IReadRuntimeLocalVolumeArchiveRequest;
225
+ response: {
226
+ volume: volumeArchive.IReadRuntimeLocalVolumeArchiveResponse;
227
+ /** Requester receives archive bytes; the TypedHandler returns a sender endpoint. */
228
+ archive: plugins.typedrequestInterfaces.TVirtualStream<'receive'>;
229
+ };
230
+ }
231
+ /**
232
+ * A local controller writes one pax archive as the bytes of an `import` claim waiting in
233
+ * `import-required`. The stream's integrity is `getRuntimeLocalVolumeArchiveIntegrity(volume)`;
234
+ * its acceptance receipt confirms that the node published the volume as `ready`.
235
+ */
236
+ export interface IReq_Onebox_Pallet_WriteRuntimeLocalVolumeArchive extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Onebox_Pallet_WriteRuntimeLocalVolumeArchive> {
237
+ method: 'writeRuntimeLocalVolumeArchive';
238
+ request: {
239
+ volume: volumeArchive.IWriteRuntimeLocalVolumeArchiveRequest;
240
+ /** Requester sends archive bytes; the TypedHandler receives the opposite endpoint. */
241
+ archive: plugins.typedrequestInterfaces.TVirtualStream<'send'>;
242
+ };
243
+ response: volumeArchive.IWriteRuntimeLocalVolumeArchiveResponse;
244
+ }
164
245
  /** Current-peer, exact current assignment authorization; response is secret material. */
165
246
  export interface IReq_Pallet_Controller_GetRuntimeRegistryCredential extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Pallet_Controller_GetRuntimeRegistryCredential> {
166
247
  method: 'getRuntimeRegistryCredential';
@@ -9,6 +9,7 @@ import type { IImageRelease } from '../data/immutableimage.js';
9
9
  import type { IDeploymentPreflightReport, IDeploymentPreflightRequestData } from '../data/deploymentpreflight.js';
10
10
  import type { IDeploymentReservationInput, IServiceDeploymentOperation, IServiceDeploymentStatus } from '../data/deploymentoperation.js';
11
11
  import type { TSha256Digest } from '../data/immutableimage.js';
12
+ import type { ILegacyServiceRuntimeAttestation } from '../data/deploymentlegacyimport.js';
12
13
  import type { IProtocolOffer } from '../protocol.js';
13
14
  import type { IServiceRuntimeAssignmentView, IServiceRuntimeSpec, IServiceRuntimeStatus, TServiceRuntimeSpecWritableData } from '../data/serviceruntime.js';
14
15
  import type { IServiceDeploymentAdoptionInput, IServiceDeploymentAdoptionImagePromotionInput, IServiceDeploymentAdoptionImagePromotionResult, IServiceDeploymentAdoptionRolloutInput, IServiceDeploymentAdoptionRolloutResult, IServiceDeploymentAdoptionPreflightInput, IServiceDeploymentAdoptionReport, IServiceDeploymentAdoptionResult } from '../data/deploymentadoption.js';
@@ -221,6 +222,56 @@ export interface IRequest_Any_Cloudly_CleanupServiceDeployment extends plugins.t
221
222
  operation: IServiceDeploymentOperation;
222
223
  };
223
224
  }
225
+ /**
226
+ * Captures durable evidence that a legacy `deployOnPush` service runs exactly the registry root its
227
+ * tag names: every required replica observed healthy on that one digest, with the root's platform
228
+ * manifests read from Cloudly's own registry. Administrators only, and only while the legacy
229
+ * runtime still runs — incomplete evidence is refused, never stored.
230
+ *
231
+ * `expectedRuntimeDigest`, when stated, is an assertion the observed digest must equal. Capturing
232
+ * again for an unchanged digest returns the attestation already stored.
233
+ */
234
+ export interface IRequest_Any_Cloudly_CaptureLegacyServiceRuntimeAttestation extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IRequest_Any_Cloudly_CaptureLegacyServiceRuntimeAttestation> {
235
+ method: 'captureLegacyServiceRuntimeAttestation';
236
+ request: {
237
+ identity: IIdentityCredential;
238
+ legacyServiceId: string;
239
+ expectedRuntimeDigest?: TSha256Digest;
240
+ };
241
+ response: {
242
+ attestation: ILegacyServiceRuntimeAttestation;
243
+ };
244
+ }
245
+ /** Every attestation Cloudly holds for one legacy service, oldest first. Administrators only. */
246
+ export interface IRequest_Any_Cloudly_GetLegacyServiceRuntimeAttestations extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IRequest_Any_Cloudly_GetLegacyServiceRuntimeAttestations> {
247
+ method: 'getLegacyServiceRuntimeAttestations';
248
+ request: {
249
+ identity: IIdentityCredential;
250
+ legacyServiceId: string;
251
+ };
252
+ response: {
253
+ attestations: ILegacyServiceRuntimeAttestation[];
254
+ };
255
+ }
256
+ /**
257
+ * Imports the attested legacy image of a reserved legacy-import operation into the operation's own
258
+ * repository and release tag. Cloudly does it from its own registry — nothing is pushed — and
259
+ * records release evidence of source `cloudly-legacy-import`; the operation then continues exactly
260
+ * like one whose image was pushed. Replaying it after the import returns the recorded release.
261
+ */
262
+ export interface IRequest_Any_Cloudly_ImportServiceDeploymentLegacyImage extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IRequest_Any_Cloudly_ImportServiceDeploymentLegacyImage> {
263
+ method: 'importServiceDeploymentLegacyImage';
264
+ request: {
265
+ identity: IIdentityCredential;
266
+ serviceId: string;
267
+ operationId: string;
268
+ expectedOperationRevision: number;
269
+ };
270
+ response: {
271
+ operation: IServiceDeploymentOperation;
272
+ release: IImageRelease;
273
+ };
274
+ }
224
275
  export interface IRequest_Any_Cloudly_CreateService extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IRequest_Any_Cloudly_CreateService> {
225
276
  method: 'createService';
226
277
  request: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serve.zone/interfaces",
3
- "version": "32.34.0",
3
+ "version": "32.36.0",
4
4
  "private": false,
5
5
  "description": "Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.",
6
6
  "exports": {
@@ -29,7 +29,7 @@
29
29
  "devDependencies": {
30
30
  "@git.zone/tsbuild": "^5.0.0",
31
31
  "@git.zone/tsdoc": "^2.2.2",
32
- "@git.zone/tsrun": "^3.0.1",
32
+ "@git.zone/tsrun": "^3.0.2",
33
33
  "@git.zone/tstest": "6.3.2",
34
34
  "@types/node": "26.6.2"
35
35
  },
package/readme.md CHANGED
@@ -963,7 +963,10 @@ refusal has one type.
963
963
 
964
964
  `requests.network.getRuntimeNetworkNodeReadiness { identity, nodeId }` answers
965
965
  `data.IRuntimeNetworkNodeReadiness`: `ready: true` with nothing missing, or `ready: false` with at
966
- least one `TRuntimeNetworkNodeReadinessGap`. The gaps follow the node producer's order: the node
966
+ least one `TRuntimeNetworkNodeReadinessGap`. The first gap is liveness: `node-observation-stale`
967
+ states that no accepted node report newer than `data.clusterNodeObservationFreshnessMs` (180 s, three
968
+ of Spark's default 60 s reporting intervals) shows the node running, with `lastObservedAt` the time
969
+ Cloudly accepted its last report, or `null` when the node's status is not `online`. The remaining gaps follow the node producer's order: the node
967
970
  joins the protected authority's egress (`egress-pending`), every egress owner reports current
968
971
  protection (`protection-receipts-pending`, naming those `owners`, at least one), a handoff is reserved
969
972
  (`handoff-pending`), the router selection carries it (`selection-pending`), the node's acknowledged
@@ -1224,6 +1227,104 @@ only the named Pallet attempt. They do not authorize storage relocation, prove
1224
1227
  whole-host or Docker/Coreflow absence, grant another controller or organization
1225
1228
  authority, or replace independently qualified readiness and routing evidence.
1226
1229
 
1230
+ ### Runtime Workload Logs, Stats, Exec And Volume Archives
1231
+
1232
+ These requests ride on a node's authenticated runtime session (`requests.runtimesession`). Every
1233
+ request carries `session`, and the receiver compares it with its own current session and with the
1234
+ connection the request arrived on: a serialized session is never authentication. Exec and volume
1235
+ archives belong to a controller on the node's own host alone (`controller.kind: 'onebox'`, which
1236
+ only a local controller binding carries), because the local socket's root-only permissions are
1237
+ what authenticate that controller.
1238
+
1239
+ **Logs.** `IReq_Pallet_Controller_ReportRuntimeAssignmentLogs` (`reportRuntimeAssignmentLogs`)
1240
+ pushes one `IRuntimeAssignmentLogBatch { assignmentId, attemptId, captureId, sequence, entries,
1241
+ final }` from the node to its controller. Logs are the controller's to keep
1242
+ (`IRuntimeConfig.logs`); the node's capture files are disposable transport artifacts, so the node
1243
+ pushes and the controller persists. The node discards a batch only after the controller
1244
+ acknowledged it, and sends the next one only then.
1245
+
1246
+ - `entries` are in capture order. A `line` entry `{ stream, timestampNs, text, partial }` is one
1247
+ piece of one line: `text` holds no `\n`, invalid UTF-8 becomes U+FFFD, and a line longer than
1248
+ one entry (`maximumEntryTextBytes`, 64 KiB) travels as pieces that state `partial: true` up to
1249
+ the last. The joined line is at most the config's `logs.maximumLineBytes`.
1250
+ - A `lost` entry `{ reason, stream, timestampNs, bytes, lines }` is evidence of output that did not
1251
+ arrive, placed where it was lost: `buffer-overflow` (the node's buffer reached
1252
+ `logs.maximumBufferedBytes` and it dropped the oldest bytes), `line-cut` (the rest of a line
1253
+ beyond `maximumLineBytes`, right after that line's last piece), `capture-restart` and
1254
+ `not-captured` (a container started without capture). The last two cannot know how much was
1255
+ lost and state `bytes` and `lines` as `null`; the others count exactly.
1256
+ - `captureId` names one capture of the attempt. `sequence` counts that capture's batches from 1.
1257
+ `final` states that the container is gone and its output drained; only a final batch may be
1258
+ empty. A report is at most 1 024 entries and one mebibyte of canonical JSON, and timestamps are
1259
+ nanoseconds since the Unix epoch as decimal digits.
1260
+
1261
+ The answer `{ status, assignmentId, captureId, sequence }` is `accepted`, or `replay` for a batch
1262
+ the controller already holds. The controller never refuses a batch for a gap in `sequence`: the
1263
+ node only discards what was acknowledged, so a gap is the controller's own loss, which it records.
1264
+ `bindReportRuntimeAssignmentLogsRequest(request, assignment, logPolicy, trustedSession)` checks the
1265
+ session, that the assignment is this node's and carries the batch's attempt, and every line's
1266
+ joined length against the policy. `bindReportRuntimeAssignmentLogsResponse(response, request)`
1267
+ binds the acknowledgement to the batch.
1268
+
1269
+ **Stats.** `IReq_Controller_Pallet_ReadRuntimeAssignmentStats` (`readRuntimeAssignmentStats`)
1270
+ reads one sample of one attempt: `{ session, assignment }` names the exact current revision, and
1271
+ the answer is `{ status: 'sampled', assignment, sample }` or `{ status: 'not-running', assignment,
1272
+ sample: null }`. An `IRuntimeAssignmentStatsSample` states `timestampNs`, the cumulative
1273
+ `cpuUsageCoreNanoseconds` (decimal digits: it outgrows a JSON number; utilisation comes from two
1274
+ samples), `memoryWorkingSetBytes`, `memoryLimitBytes` (the limit the node enforces, the config's
1275
+ `memoryBytes`), `network { receivedBytes, transmittedBytes }` of the sandbox, or `null`, and
1276
+ `writableLayerBytes`, or `null`. Any controller may read stats; a node answers one read per
1277
+ assignment at a time. `bindReadRuntimeAssignmentStatsRequest(request, assignment, trustedSession)`
1278
+ and `bindReadRuntimeAssignmentStatsResponse(response, request)` bind both sides.
1279
+
1280
+ **Exec.** `IReq_Onebox_Pallet_OpenRuntimeAssignmentExec` (`openRuntimeAssignmentExec`) runs one
1281
+ command in the container of a `run` assignment's running attempt. The request is `{ exec: { session,
1282
+ assignment, command, tty, terminalSize }, stdin }`: `command` is the argument vector the runtime
1283
+ executes directly (at most 256 arguments and 64 KiB, no NUL, a non-empty first element),
1284
+ `terminalSize { columns, rows }` is stated exactly for `tty`, and `stdin` is a sending
1285
+ VirtualStream or `null`, whose end is the process's end of input. The answer `{ exec: { execId,
1286
+ assignment, tty, idleTimeoutMs }, stdout, stderr }` returns receiving streams that outlive the
1287
+ request; a `tty` session's whole output is on `stdout` and its `stderr` is `null`.
1288
+ `resizeRuntimeAssignmentExec` changes a `tty` session's terminal size, and
1289
+ `closeRuntimeAssignmentExec` ends a session, releases its slot and answers how it ended: `exited`
1290
+ with `exitCode` (128 plus the signal number for a signalled process) or `terminated` with the
1291
+ `termination` that ended it first, `closed`, `idle-timeout` or `container-stopped`.
1292
+ `runtimeAssignmentExecContract` fixes the bounds: four sessions per node, counting ended ones not
1293
+ yet closed, and 15 minutes without a byte in either direction before the node ends one. A session
1294
+ also ends with the controller session it was opened under, and its `execId` is gone with it.
1295
+ `bindOpenRuntimeAssignmentExecRequest`, `bindResizeRuntimeAssignmentExecRequest` and
1296
+ `bindCloseRuntimeAssignmentExecRequest` take the node's trusted session and refuse any controller
1297
+ but a local one; the response binders match the answer to its request.
1298
+
1299
+ **Volume archives.** A local controller backs up and restores a node-local volume as one POSIX
1300
+ pax archive (`runtimeLocalVolumeArchiveContract`). The first entry is the volume root `./`, and
1301
+ entries are directories, regular files, symbolic links (stored, never followed), hard links to
1302
+ earlier entries and FIFOs, with numeric `uid`/`gid`, exact mode bits and modification times.
1303
+ Sockets are omitted; a device node is refused, and extended attributes and ACLs are not carried.
1304
+ An archive is a whole number of 512-byte blocks and at least 1 536 bytes. A node moves one archive
1305
+ per volume at a time.
1306
+
1307
+ - `readRuntimeLocalVolumeArchive` (`IReq_Onebox_Pallet_ReadRuntimeLocalVolumeArchive`):
1308
+ `{ session, claim }` names the node's current generation of a bound `directory` claim. The node
1309
+ refuses while a running container holds the volume. It reads a volume nobody holds, or one whose
1310
+ holder is stopped, which a node never restarts, and grants the volume to no container meanwhile.
1311
+ The answer `{ volume: { claim, format: 'pax', holder }, archive }` streams the archive. It
1312
+ carries no integrity, because the node learns the length and digest only while writing.
1313
+ - `writeRuntimeLocalVolumeArchive` (`IReq_Onebox_Pallet_WriteRuntimeLocalVolumeArchive`):
1314
+ `{ volume: { session, claim, format, byteLength, digest }, archive }` writes an archive as the
1315
+ bytes of an `import` claim that waits in `import-required`, the online form of the offline
1316
+ import. The stream's integrity must be `getRuntimeLocalVolumeArchiveIntegrity(volume)`. The node
1317
+ unpacks into a private staging tree and publishes it as the volume only after the whole archive
1318
+ verified. The tree keeps the archive's ownership, as every imported tree does. The answer
1319
+ `{ claim, status: 'receiving' }` permits sending; the stream's acceptance receipt confirms the
1320
+ volume is `ready`, and a rejected or aborted stream publishes nothing.
1321
+
1322
+ `bindReadRuntimeLocalVolumeArchiveRequest(request, claim, trustedSession)` and
1323
+ `bindWriteRuntimeLocalVolumeArchiveRequest` require a local controller's current session and the
1324
+ exact reference of a bound `directory` claim of its controller, cluster, node and runtime
1325
+ namespace, an `import` claim for a write. Whether the volume is `ready` or still
1326
+ `import-required`, and who holds it, are the node's ledger checks.
1327
+
1227
1328
  ### Cluster Runtime Phase
1228
1329
 
1229
1330
  `data.IClusterRuntime { id, phase, generation, changedAt, changedBy }` records which runtime owns a
@@ -2993,6 +3094,13 @@ The immutable deployment workflow is:
2993
3094
  4. Observe exact rollout and runtime-digest evidence with `getServiceDeploymentStatus`.
2994
3095
  5. For a greenfield service, expose and verify its public route with `promoteServiceDeploymentRoute` only after the immutable rollout succeeds.
2995
3096
 
3097
+ A legacy `deployOnPush` service without deployable source moves to a greenfield successor by a legacy import, which replaces step 2:
3098
+
3099
+ - While the legacy replicas still run — before its runtime is frozen — an administrator captures the evidence with `captureLegacyServiceRuntimeAttestation` (`requests.service.IRequest_Any_Cloudly_CaptureLegacyServiceRuntimeAttestation`). The stored `data.ILegacyServiceRuntimeAttestation` records every required replica healthy on one digest, the registry root the legacy tag names, and that root's platform manifests; `data.validateLegacyServiceRuntimeAttestation` states its rules. Incomplete evidence is refused, never stored, and there is no substitute once the runtime is stopped. `getLegacyServiceRuntimeAttestations` lists what Cloudly holds for a legacy service; both requests are for administrators only.
3100
+ - The greenfield reservation, and every preflight of it, names `legacyImport` (`data.ILegacyImageImportReference`: legacy service, attestation, asserted source root digest) instead of `sourceRevision`, `sourceDirty` and release evidence. Refusals are the blockers `LEGACY_IMPORT_ATTESTATION_MISSING`, `LEGACY_IMPORT_SOURCE_MISMATCH` and `LEGACY_IMPORT_PLATFORM_MISMATCH`.
3101
+ - Instead of the registry push, the deployer calls `importServiceDeploymentLegacyImage`: Cloudly imports the attested image from its own registry into the reserved tag — an index root as it is, a single platform manifest under a one-descriptor index of the manifest's own family — and records release evidence of source `cloudly-legacy-import` naming the legacy source. Steps 3 to 5 follow unchanged; `selectTrustedImageReleaseEvidence` accepts that evidence only with `evidenceSource: 'cloudly-legacy-import'`.
3102
+ - The reservation and the import need the capability `deployment:import-legacy`, which only an organization-service-slot grant can carry.
3103
+
2996
3104
  `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.
2997
3105
 
2998
3106
  `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.
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/interfaces',
6
- version: '32.34.0',
6
+ version: '32.36.0',
7
7
  description: 'Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.'
8
8
  }
@@ -1700,6 +1700,17 @@ export interface ISparkNodeRuntimeInfo {
1700
1700
  lastError?: string;
1701
1701
  }
1702
1702
 
1703
+ /**
1704
+ * How long one accepted node report stands as evidence that the node is running.
1705
+ *
1706
+ * Spark reports while its node runtime runs and stops when that runtime stops, so a node whose
1707
+ * last accepted report (`IClusterNode.data.lastHealthCheck`) is older than this is not observed
1708
+ * running. Three reporting intervals at Spark's 60 s default: a pass waits one interval after it
1709
+ * settles, a failed pass waits the same interval, and a pass takes at most the transport's 10 s,
1710
+ * so one failed pass between two accepted ones leaves at most about 151 s without a report.
1711
+ */
1712
+ export const clusterNodeObservationFreshnessMs = 180_000;
1713
+
1703
1714
  export interface IClusterNode {
1704
1715
  id: string;
1705
1716
  data: {
@@ -1719,7 +1730,10 @@ export interface IClusterNode {
1719
1730
  nodeType: 'baremetal' | 'vm' | 'container';
1720
1731
 
1721
1732
  /**
1722
- * Current status of the node
1733
+ * Current status of the node.
1734
+ *
1735
+ * Cloudly answers `offline` for a node whose last accepted report is older than
1736
+ * `clusterNodeObservationFreshnessMs`, whatever the last report stated.
1723
1737
  */
1724
1738
  status: 'initializing' | 'online' | 'offline' | 'maintenance';
1725
1739
 
@@ -1734,7 +1748,8 @@ export interface IClusterNode {
1734
1748
  joinedAt: number;
1735
1749
 
1736
1750
  /**
1737
- * Last health check timestamp
1751
+ * When Cloudly accepted the node's last report, on Cloudly's clock; the enrollment time while
1752
+ * the node is `initializing` and has not reported yet.
1738
1753
  */
1739
1754
  lastHealthCheck: number;
1740
1755