@serve.zone/interfaces 32.35.0 → 32.36.1
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 +63 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/data/backup.d.ts +7 -1
- package/dist_ts/data/deploymentlegacyimport.d.ts +92 -0
- package/dist_ts/data/deploymentlegacyimport.js +237 -0
- package/dist_ts/data/deploymentoperation.d.ts +63 -38
- package/dist_ts/data/deploymentoperation.js +12 -2
- package/dist_ts/data/deploymentpreflight.d.ts +42 -10
- package/dist_ts/data/deploymentpreflight.js +47 -10
- package/dist_ts/data/immutableimage.d.ts +26 -2
- package/dist_ts/data/immutableimage.js +12 -2
- package/dist_ts/data/index.d.ts +5 -0
- package/dist_ts/data/index.js +6 -1
- package/dist_ts/data/runtimeassignmentexec.d.ts +110 -0
- package/dist_ts/data/runtimeassignmentexec.js +164 -0
- package/dist_ts/data/runtimeassignmentlogs.d.ts +121 -0
- package/dist_ts/data/runtimeassignmentlogs.js +139 -0
- package/dist_ts/data/runtimeassignmentstats.d.ts +60 -0
- package/dist_ts/data/runtimeassignmentstats.js +93 -0
- package/dist_ts/data/runtimelocalvolumearchive.d.ts +103 -0
- package/dist_ts/data/runtimelocalvolumearchive.js +111 -0
- package/dist_ts/data/user.d.ts +6 -1
- package/dist_ts/data/user.js +6 -1
- package/dist_ts/requests/runtimesession.d.ts +81 -0
- package/dist_ts/requests/service.d.ts +51 -0
- package/package.json +1 -1
- package/readme.md +105 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/data/backup.ts +7 -1
- package/ts/data/deploymentlegacyimport.ts +371 -0
- package/ts/data/deploymentoperation.ts +83 -39
- package/ts/data/deploymentpreflight.ts +86 -18
- package/ts/data/immutableimage.ts +42 -3
- package/ts/data/index.ts +5 -0
- package/ts/data/runtimeassignmentexec.ts +271 -0
- package/ts/data/runtimeassignmentlogs.ts +249 -0
- package/ts/data/runtimeassignmentstats.ts +149 -0
- package/ts/data/runtimelocalvolumearchive.ts +207 -0
- package/ts/data/user.ts +10 -0
- package/ts/requests/runtimesession.ts +102 -0
- package/ts/requests/service.ts +66 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import type { IRuntimeAssignment, IRuntimeAssignmentReference } from './runtimeassignment.js';
|
|
2
|
+
import { getRuntimeAssignmentReference, snapshotRuntimeAssignment, validateRuntimeAssignment } from './runtimeassignment.js';
|
|
3
|
+
import type { IRuntimeSessionBinding } from './runtimesession.js';
|
|
4
|
+
import { snapshotRuntimeSessionBinding } from './runtimesession.js';
|
|
5
|
+
import {
|
|
6
|
+
captureNetwork, networkCounter, networkDigest, networkEqual, networkId, networkRecord, requireNetwork,
|
|
7
|
+
} from '../private/networkcontract.js';
|
|
8
|
+
|
|
9
|
+
/** A terminal's size in character cells. */
|
|
10
|
+
export interface IRuntimeTerminalSize {
|
|
11
|
+
columns: number;
|
|
12
|
+
rows: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A controller's request to run one command inside one running attempt's container.
|
|
17
|
+
*
|
|
18
|
+
* `command` is the argument vector the runtime executes directly: no shell parses it unless its
|
|
19
|
+
* first element names one. With `tty` the process runs on a terminal of `terminalSize`, which is
|
|
20
|
+
* stated exactly then; its output arrives on one stream. Without it stdout and stderr arrive on
|
|
21
|
+
* separate streams. Standard input is the optional stream the TypedRequest carries beside this
|
|
22
|
+
* body; its end is the process's end of input.
|
|
23
|
+
*/
|
|
24
|
+
export interface IOpenRuntimeAssignmentExecRequest {
|
|
25
|
+
session: IRuntimeSessionBinding;
|
|
26
|
+
/** The exact current revision of a `run` assignment on this node. */
|
|
27
|
+
assignment: IRuntimeAssignmentReference;
|
|
28
|
+
command: string[];
|
|
29
|
+
tty: boolean;
|
|
30
|
+
terminalSize: IRuntimeTerminalSize | null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The node's answer once the process runs. `execId` names the session in later requests;
|
|
35
|
+
* `idleTimeoutMs` is how long the session may carry no byte in either direction before the node
|
|
36
|
+
* ends it.
|
|
37
|
+
*/
|
|
38
|
+
export interface IOpenRuntimeAssignmentExecResponse {
|
|
39
|
+
execId: string;
|
|
40
|
+
assignment: IRuntimeAssignmentReference;
|
|
41
|
+
tty: boolean;
|
|
42
|
+
idleTimeoutMs: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** A new terminal size for a `tty` session. */
|
|
46
|
+
export interface IResizeRuntimeAssignmentExecRequest {
|
|
47
|
+
session: IRuntimeSessionBinding;
|
|
48
|
+
execId: string;
|
|
49
|
+
terminalSize: IRuntimeTerminalSize;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface IResizeRuntimeAssignmentExecResponse {
|
|
53
|
+
execId: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Ends a session and releases its slot, whether or not its process has exited. */
|
|
57
|
+
export interface ICloseRuntimeAssignmentExecRequest {
|
|
58
|
+
session: IRuntimeSessionBinding;
|
|
59
|
+
execId: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Why the node ended a process before it exited: `closed` by this request, `idle-timeout` after
|
|
64
|
+
* `idleTimeoutMs` without a byte, `container-stopped` because its container stopped.
|
|
65
|
+
*/
|
|
66
|
+
export type TRuntimeAssignmentExecTermination = 'closed' | 'idle-timeout' | 'container-stopped';
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* How the session ended. `exited` carries the process's `exitCode`, 128 plus the signal number
|
|
70
|
+
* for a process a signal ended; `terminated` states why the node ended it and carries no code.
|
|
71
|
+
*/
|
|
72
|
+
export interface ICloseRuntimeAssignmentExecResponse {
|
|
73
|
+
execId: string;
|
|
74
|
+
outcome: 'exited' | 'terminated';
|
|
75
|
+
exitCode: number | null;
|
|
76
|
+
termination: TRuntimeAssignmentExecTermination | null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Exec runs only for a controller on the node's own host (`controller.kind: 'onebox'`, which only a
|
|
81
|
+
* local controller binding carries), and only on the connection that holds its session. A session
|
|
82
|
+
* lives until it is closed, or until the node ends it: after `idleTimeoutMs` without a byte, when
|
|
83
|
+
* its container stops, or when the controller session it was opened under ends, in which case its
|
|
84
|
+
* `execId` is gone with it. An ended session keeps its slot and its outcome until it is closed.
|
|
85
|
+
*/
|
|
86
|
+
export const runtimeAssignmentExecContract = Object.freeze({
|
|
87
|
+
error: 'Runtime assignment exec contract rejected.',
|
|
88
|
+
controllerKind: 'onebox',
|
|
89
|
+
/** Open sessions per node, ended ones not yet closed included; one more is refused. */
|
|
90
|
+
maximumSessions: 4,
|
|
91
|
+
idleTimeoutMs: 900_000,
|
|
92
|
+
maximumCommandArguments: 256,
|
|
93
|
+
/** The UTF-8 bytes of every argument together. */
|
|
94
|
+
maximumCommandBytes: 65_536,
|
|
95
|
+
/** The largest terminal dimension, the width of the runtime's own size field. */
|
|
96
|
+
maximumTerminalDimension: 65_535,
|
|
97
|
+
maximumExitCode: 255,
|
|
98
|
+
} as const);
|
|
99
|
+
|
|
100
|
+
const fail = (): never => { throw new Error(runtimeAssignmentExecContract.error); };
|
|
101
|
+
const safe = <T>(operationArg: () => T): T => { try { return operationArg(); } catch { return fail(); } };
|
|
102
|
+
const encoder = new TextEncoder();
|
|
103
|
+
const terminations = ['closed', 'idle-timeout', 'container-stopped'] as const;
|
|
104
|
+
|
|
105
|
+
const referenceOf = (valueArg: unknown): IRuntimeAssignmentReference => {
|
|
106
|
+
const value = networkRecord(valueArg, ['assignmentId', 'generation', 'digest']);
|
|
107
|
+
return {
|
|
108
|
+
assignmentId: networkId(value.assignmentId), generation: networkCounter(value.generation),
|
|
109
|
+
digest: networkDigest(value.digest),
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
const terminalSizeOf = (valueArg: unknown): IRuntimeTerminalSize => {
|
|
113
|
+
const value = networkRecord(valueArg, ['columns', 'rows']);
|
|
114
|
+
return {
|
|
115
|
+
columns: networkCounter(value.columns, runtimeAssignmentExecContract.maximumTerminalDimension),
|
|
116
|
+
rows: networkCounter(value.rows, runtimeAssignmentExecContract.maximumTerminalDimension),
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
const commandOf = (valueArg: unknown): string[] => {
|
|
120
|
+
requireNetwork(Array.isArray(valueArg) && valueArg.length > 0
|
|
121
|
+
&& valueArg.length <= runtimeAssignmentExecContract.maximumCommandArguments);
|
|
122
|
+
let bytes = 0;
|
|
123
|
+
const command = (valueArg as unknown[]).map((argumentArg) => {
|
|
124
|
+
requireNetwork(typeof argumentArg === 'string' && !argumentArg.includes('\0'));
|
|
125
|
+
bytes += encoder.encode(argumentArg).byteLength;
|
|
126
|
+
return argumentArg;
|
|
127
|
+
});
|
|
128
|
+
requireNetwork(command[0].length > 0 && bytes <= runtimeAssignmentExecContract.maximumCommandBytes);
|
|
129
|
+
return command;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export const snapshotOpenRuntimeAssignmentExecRequest = (
|
|
133
|
+
valueArg: unknown,
|
|
134
|
+
): IOpenRuntimeAssignmentExecRequest => safe(() => {
|
|
135
|
+
const value = networkRecord(captureNetwork(valueArg), ['session', 'assignment', 'command', 'tty', 'terminalSize']);
|
|
136
|
+
requireNetwork(typeof value.tty === 'boolean' && (value.tty === (value.terminalSize !== null)));
|
|
137
|
+
return {
|
|
138
|
+
session: snapshotRuntimeSessionBinding(value.session), assignment: referenceOf(value.assignment),
|
|
139
|
+
command: commandOf(value.command), tty: value.tty,
|
|
140
|
+
terminalSize: value.terminalSize === null ? null : terminalSizeOf(value.terminalSize),
|
|
141
|
+
};
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
export const snapshotOpenRuntimeAssignmentExecResponse = (
|
|
145
|
+
valueArg: unknown,
|
|
146
|
+
): IOpenRuntimeAssignmentExecResponse => safe(() => {
|
|
147
|
+
const value = networkRecord(captureNetwork(valueArg), ['execId', 'assignment', 'tty', 'idleTimeoutMs']);
|
|
148
|
+
requireNetwork(typeof value.tty === 'boolean'
|
|
149
|
+
&& value.idleTimeoutMs === runtimeAssignmentExecContract.idleTimeoutMs);
|
|
150
|
+
return {
|
|
151
|
+
execId: networkId(value.execId), assignment: referenceOf(value.assignment), tty: value.tty,
|
|
152
|
+
idleTimeoutMs: runtimeAssignmentExecContract.idleTimeoutMs,
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
export const snapshotResizeRuntimeAssignmentExecRequest = (
|
|
157
|
+
valueArg: unknown,
|
|
158
|
+
): IResizeRuntimeAssignmentExecRequest => safe(() => {
|
|
159
|
+
const value = networkRecord(captureNetwork(valueArg), ['session', 'execId', 'terminalSize']);
|
|
160
|
+
return {
|
|
161
|
+
session: snapshotRuntimeSessionBinding(value.session), execId: networkId(value.execId),
|
|
162
|
+
terminalSize: terminalSizeOf(value.terminalSize),
|
|
163
|
+
};
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
export const snapshotResizeRuntimeAssignmentExecResponse = (
|
|
167
|
+
valueArg: unknown,
|
|
168
|
+
): IResizeRuntimeAssignmentExecResponse => safe(() => {
|
|
169
|
+
const value = networkRecord(captureNetwork(valueArg), ['execId']);
|
|
170
|
+
return { execId: networkId(value.execId) };
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
export const snapshotCloseRuntimeAssignmentExecRequest = (
|
|
174
|
+
valueArg: unknown,
|
|
175
|
+
): ICloseRuntimeAssignmentExecRequest => safe(() => {
|
|
176
|
+
const value = networkRecord(captureNetwork(valueArg), ['session', 'execId']);
|
|
177
|
+
return { session: snapshotRuntimeSessionBinding(value.session), execId: networkId(value.execId) };
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
export const snapshotCloseRuntimeAssignmentExecResponse = (
|
|
181
|
+
valueArg: unknown,
|
|
182
|
+
): ICloseRuntimeAssignmentExecResponse => safe(() => {
|
|
183
|
+
const value = networkRecord(captureNetwork(valueArg), ['execId', 'outcome', 'exitCode', 'termination']);
|
|
184
|
+
const execId = networkId(value.execId);
|
|
185
|
+
if (value.outcome === 'exited') {
|
|
186
|
+
requireNetwork(typeof value.exitCode === 'number' && Number.isSafeInteger(value.exitCode)
|
|
187
|
+
&& value.exitCode >= 0 && !Object.is(value.exitCode, -0)
|
|
188
|
+
&& value.exitCode <= runtimeAssignmentExecContract.maximumExitCode && value.termination === null);
|
|
189
|
+
return { execId, outcome: 'exited', exitCode: value.exitCode, termination: null };
|
|
190
|
+
}
|
|
191
|
+
requireNetwork(value.outcome === 'terminated' && value.exitCode === null
|
|
192
|
+
&& (terminations as readonly unknown[]).includes(value.termination));
|
|
193
|
+
return {
|
|
194
|
+
execId, outcome: 'terminated', exitCode: null,
|
|
195
|
+
termination: value.termination as TRuntimeAssignmentExecTermination,
|
|
196
|
+
};
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
/** Exec is a local controller's alone; `trustedSessionArg` decides, never a request field. */
|
|
200
|
+
const requireLocalSession = (requestSessionArg: IRuntimeSessionBinding, trustedSessionArg: unknown): void => {
|
|
201
|
+
const trustedSession = safe(() => snapshotRuntimeSessionBinding(trustedSessionArg));
|
|
202
|
+
safe(() => requireNetwork(networkEqual(requestSessionArg, trustedSession)
|
|
203
|
+
&& trustedSession.controller.kind === runtimeAssignmentExecContract.controllerKind));
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Node-side content binding, not authentication. `trustedSessionArg` is the node's CURRENT
|
|
208
|
+
* session, the request must have arrived on that session's own connection, and that session must
|
|
209
|
+
* be a local controller's. `assignmentArg` is the node's current admitted revision of the named
|
|
210
|
+
* assignment: the request must name exactly it, it must be a `run` assignment, and it must belong
|
|
211
|
+
* to the session's controller, node and runtime namespace. Whether its container runs is the
|
|
212
|
+
* node's own check.
|
|
213
|
+
*/
|
|
214
|
+
export const bindOpenRuntimeAssignmentExecRequest = async (
|
|
215
|
+
valueArg: unknown, assignmentArg: unknown, trustedSessionArg: unknown,
|
|
216
|
+
): Promise<{ request: IOpenRuntimeAssignmentExecRequest; assignment: IRuntimeAssignment }> => {
|
|
217
|
+
const request = snapshotOpenRuntimeAssignmentExecRequest(valueArg);
|
|
218
|
+
requireLocalSession(request.session, trustedSessionArg);
|
|
219
|
+
const assignment = safe(() => snapshotRuntimeAssignment(assignmentArg));
|
|
220
|
+
if ((await validateRuntimeAssignment(assignment)).length) fail();
|
|
221
|
+
safe(() => requireNetwork(assignment.disposition === 'run'
|
|
222
|
+
&& networkEqual(assignment.controller, request.session.controller)
|
|
223
|
+
&& assignment.nodeId === request.session.nodeId
|
|
224
|
+
&& assignment.runtimeNamespace === request.session.runtimeNamespace
|
|
225
|
+
&& networkEqual(request.assignment, getRuntimeAssignmentReference(assignment))));
|
|
226
|
+
return { request, assignment };
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
/** Matches an opened session to the request: the same assignment revision and terminal mode. */
|
|
230
|
+
export const bindOpenRuntimeAssignmentExecResponse = (
|
|
231
|
+
valueArg: unknown, requestArg: unknown,
|
|
232
|
+
): IOpenRuntimeAssignmentExecResponse => safe(() => {
|
|
233
|
+
const response = snapshotOpenRuntimeAssignmentExecResponse(valueArg);
|
|
234
|
+
const request = snapshotOpenRuntimeAssignmentExecRequest(requestArg);
|
|
235
|
+
requireNetwork(networkEqual(response.assignment, request.assignment) && response.tty === request.tty);
|
|
236
|
+
return response;
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
/** Node-side content binding of a resize; which sessions exist and are `tty` is the node's check. */
|
|
240
|
+
export const bindResizeRuntimeAssignmentExecRequest = (
|
|
241
|
+
valueArg: unknown, trustedSessionArg: unknown,
|
|
242
|
+
): IResizeRuntimeAssignmentExecRequest => {
|
|
243
|
+
const request = snapshotResizeRuntimeAssignmentExecRequest(valueArg);
|
|
244
|
+
requireLocalSession(request.session, trustedSessionArg);
|
|
245
|
+
return request;
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
export const bindResizeRuntimeAssignmentExecResponse = (
|
|
249
|
+
valueArg: unknown, requestArg: unknown,
|
|
250
|
+
): IResizeRuntimeAssignmentExecResponse => safe(() => {
|
|
251
|
+
const response = snapshotResizeRuntimeAssignmentExecResponse(valueArg);
|
|
252
|
+
requireNetwork(response.execId === snapshotResizeRuntimeAssignmentExecRequest(requestArg).execId);
|
|
253
|
+
return response;
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
/** Node-side content binding of a close; which sessions exist is the node's check. */
|
|
257
|
+
export const bindCloseRuntimeAssignmentExecRequest = (
|
|
258
|
+
valueArg: unknown, trustedSessionArg: unknown,
|
|
259
|
+
): ICloseRuntimeAssignmentExecRequest => {
|
|
260
|
+
const request = snapshotCloseRuntimeAssignmentExecRequest(valueArg);
|
|
261
|
+
requireLocalSession(request.session, trustedSessionArg);
|
|
262
|
+
return request;
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
export const bindCloseRuntimeAssignmentExecResponse = (
|
|
266
|
+
valueArg: unknown, requestArg: unknown,
|
|
267
|
+
): ICloseRuntimeAssignmentExecResponse => safe(() => {
|
|
268
|
+
const response = snapshotCloseRuntimeAssignmentExecResponse(valueArg);
|
|
269
|
+
requireNetwork(response.execId === snapshotCloseRuntimeAssignmentExecRequest(requestArg).execId);
|
|
270
|
+
return response;
|
|
271
|
+
});
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import type { IRuntimeAssignment } from './runtimeassignment.js';
|
|
2
|
+
import { snapshotRuntimeAssignment, validateRuntimeAssignment } from './runtimeassignment.js';
|
|
3
|
+
import type { IRuntimeConfigLogPolicy } from './runtimeconfig.js';
|
|
4
|
+
import type { IRuntimeSessionBinding } from './runtimesession.js';
|
|
5
|
+
import { snapshotRuntimeSessionBinding } from './runtimesession.js';
|
|
6
|
+
import {
|
|
7
|
+
captureNetwork, networkCounter, networkEqual, networkId, networkList, networkRecord, requireNetwork,
|
|
8
|
+
} from '../private/networkcontract.js';
|
|
9
|
+
import { isRuntimeLogPolicy } from '../private/runtimepolicy.js';
|
|
10
|
+
|
|
11
|
+
/** The container output a log line was written to. */
|
|
12
|
+
export type TRuntimeAssignmentLogStream = 'stdout' | 'stderr';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* One piece of one line a container wrote, as the runtime captured it.
|
|
16
|
+
*
|
|
17
|
+
* `text` holds no line terminator and no `\n`; the node decodes the captured bytes as UTF-8 and
|
|
18
|
+
* replaces an invalid sequence with U+FFFD. A line longer than one entry travels as consecutive
|
|
19
|
+
* entries of the same stream: every piece but the last states `partial: true`, and the receiver
|
|
20
|
+
* joins them. The joined line is at most the config's `logs.maximumLineBytes` UTF-8 bytes; the node
|
|
21
|
+
* cuts a longer one there and follows its last piece with a `line-cut` loss of the same stream.
|
|
22
|
+
*/
|
|
23
|
+
export interface IRuntimeAssignmentLogLine {
|
|
24
|
+
kind: 'line';
|
|
25
|
+
stream: TRuntimeAssignmentLogStream;
|
|
26
|
+
/** When the runtime captured the piece, in nanoseconds since the Unix epoch, as decimal digits. */
|
|
27
|
+
timestampNs: string;
|
|
28
|
+
text: string;
|
|
29
|
+
/** The line continues in the next `line` entry of the same stream. */
|
|
30
|
+
partial: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Why output did not reach the controller.
|
|
35
|
+
*
|
|
36
|
+
* - `buffer-overflow`: the node's buffer held `logs.maximumBufferedBytes` unacknowledged bytes and
|
|
37
|
+
* the oldest were dropped to keep capturing;
|
|
38
|
+
* - `line-cut`: the rest of a line beyond `logs.maximumLineBytes`;
|
|
39
|
+
* - `capture-restart`: the node's capture restarted without its buffer, for example after a reboot
|
|
40
|
+
* emptied its disposable capture directory; how much was lost is unknown;
|
|
41
|
+
* - `not-captured`: the container was started without output capture, so none of its output is
|
|
42
|
+
* available; how much there was is unknown.
|
|
43
|
+
*/
|
|
44
|
+
export type TRuntimeAssignmentLogLossReason = 'buffer-overflow' | 'line-cut' | 'capture-restart' | 'not-captured';
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Evidence of output that was lost, in the position of the stream where it was lost.
|
|
48
|
+
*
|
|
49
|
+
* `bytes` and `lines` count exactly what was dropped; both are `null` for `capture-restart` and
|
|
50
|
+
* `not-captured`, whose extent the node cannot know, and both are stated for the other reasons.
|
|
51
|
+
* A `line-cut` counts the cut bytes and no line, because the line itself was delivered; it names
|
|
52
|
+
* the stream of that line. A `buffer-overflow` counts the dropped bytes and the whole lines among
|
|
53
|
+
* them, and names a stream only when every dropped byte came from it.
|
|
54
|
+
*/
|
|
55
|
+
export interface IRuntimeAssignmentLogLoss {
|
|
56
|
+
kind: 'lost';
|
|
57
|
+
reason: TRuntimeAssignmentLogLossReason;
|
|
58
|
+
stream: TRuntimeAssignmentLogStream | null;
|
|
59
|
+
/** When the node detected the loss, in nanoseconds since the Unix epoch, as decimal digits. */
|
|
60
|
+
timestampNs: string;
|
|
61
|
+
bytes: number | null;
|
|
62
|
+
lines: number | null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export type TRuntimeAssignmentLogEntry = IRuntimeAssignmentLogLine | IRuntimeAssignmentLogLoss;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* One delivery of one attempt's captured output, in capture order.
|
|
69
|
+
*
|
|
70
|
+
* `captureId` names one capture of the attempt on its node: the node starts a new capture, with a
|
|
71
|
+
* new id and `sequence` 1, whenever it cannot continue the previous one, and opens it with a
|
|
72
|
+
* `capture-restart` loss. Within a capture `sequence` counts batches from 1 without gaps, and the
|
|
73
|
+
* node sends the next batch only after the controller acknowledged the previous one, so a batch is
|
|
74
|
+
* the one unit of retry. `final` states that no batch follows in this capture: the container is gone
|
|
75
|
+
* and its output is drained. Only a final batch may be empty.
|
|
76
|
+
*/
|
|
77
|
+
export interface IRuntimeAssignmentLogBatch {
|
|
78
|
+
assignmentId: string;
|
|
79
|
+
attemptId: string;
|
|
80
|
+
captureId: string;
|
|
81
|
+
sequence: number;
|
|
82
|
+
entries: TRuntimeAssignmentLogEntry[];
|
|
83
|
+
final: boolean;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Pallet push over its current authenticated session. */
|
|
87
|
+
export interface IReportRuntimeAssignmentLogsRequest {
|
|
88
|
+
session: IRuntimeSessionBinding;
|
|
89
|
+
batch: IRuntimeAssignmentLogBatch;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The controller holds the batch durably. `replay` answers a batch it already holds, which it does
|
|
94
|
+
* not store again. It never refuses a batch for a gap in `sequence`: the node discards only what
|
|
95
|
+
* was acknowledged, so a missing batch can only be the controller's own loss, which it records.
|
|
96
|
+
*/
|
|
97
|
+
export interface IReportRuntimeAssignmentLogsResponse {
|
|
98
|
+
status: 'accepted' | 'replay';
|
|
99
|
+
assignmentId: string;
|
|
100
|
+
captureId: string;
|
|
101
|
+
sequence: number;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export const runtimeAssignmentLogContract = Object.freeze({
|
|
105
|
+
error: 'Runtime assignment log contract rejected.',
|
|
106
|
+
/** Pallet pushes; the controller persists metadata and payloads and acknowledges by sequence. */
|
|
107
|
+
delivery: 'node-pushes-controller-acknowledges-each-batch',
|
|
108
|
+
/** The most entries in one batch. */
|
|
109
|
+
maximumEntriesPerBatch: 1024,
|
|
110
|
+
/** The most UTF-8 bytes of `text` in one entry; a longer line travels as `partial` pieces. */
|
|
111
|
+
maximumEntryTextBytes: 65_536,
|
|
112
|
+
/**
|
|
113
|
+
* The canonical JSON of a whole report is at most one mebibyte, the bound every runtime network
|
|
114
|
+
* contract captures with; the node closes a batch before the next entry would cross it.
|
|
115
|
+
*/
|
|
116
|
+
maximumReportCanonicalBytes: 1_048_576,
|
|
117
|
+
} as const);
|
|
118
|
+
|
|
119
|
+
const fail = (): never => { throw new Error(runtimeAssignmentLogContract.error); };
|
|
120
|
+
const safe = <T>(operationArg: () => T): T => { try { return operationArg(); } catch { return fail(); } };
|
|
121
|
+
const encoder = new TextEncoder();
|
|
122
|
+
const streams = ['stdout', 'stderr'] as const;
|
|
123
|
+
const lossReasons = ['buffer-overflow', 'line-cut', 'capture-restart', 'not-captured'] as const;
|
|
124
|
+
const statuses = ['accepted', 'replay'] as const;
|
|
125
|
+
|
|
126
|
+
const literalOf = <TLiteral extends string>(valueArg: unknown, allowedArg: readonly TLiteral[]): TLiteral => {
|
|
127
|
+
requireNetwork((allowedArg as readonly unknown[]).includes(valueArg));
|
|
128
|
+
return valueArg as TLiteral;
|
|
129
|
+
};
|
|
130
|
+
/** Nanoseconds since the Unix epoch as canonical decimal digits: no sign, no leading zero. */
|
|
131
|
+
const timestampOf = (valueArg: unknown): string => {
|
|
132
|
+
requireNetwork(typeof valueArg === 'string' && /^[1-9][0-9]{0,19}$/.test(valueArg)
|
|
133
|
+
&& BigInt(valueArg) <= 18_446_744_073_709_551_615n);
|
|
134
|
+
return valueArg;
|
|
135
|
+
};
|
|
136
|
+
const countOf = (valueArg: unknown): number => {
|
|
137
|
+
requireNetwork(typeof valueArg === 'number' && Number.isSafeInteger(valueArg) && valueArg >= 0
|
|
138
|
+
&& !Object.is(valueArg, -0));
|
|
139
|
+
return valueArg;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const lineOf = (valueArg: unknown): IRuntimeAssignmentLogLine => {
|
|
143
|
+
const value = networkRecord(valueArg, ['kind', 'stream', 'timestampNs', 'text', 'partial']);
|
|
144
|
+
requireNetwork(value.kind === 'line' && typeof value.text === 'string' && !value.text.includes('\n')
|
|
145
|
+
&& encoder.encode(value.text).byteLength <= runtimeAssignmentLogContract.maximumEntryTextBytes
|
|
146
|
+
&& typeof value.partial === 'boolean');
|
|
147
|
+
return {
|
|
148
|
+
kind: 'line', stream: literalOf(value.stream, streams), timestampNs: timestampOf(value.timestampNs),
|
|
149
|
+
text: value.text, partial: value.partial,
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const lossOf = (valueArg: unknown): IRuntimeAssignmentLogLoss => {
|
|
154
|
+
const value = networkRecord(valueArg, ['kind', 'reason', 'stream', 'timestampNs', 'bytes', 'lines']);
|
|
155
|
+
requireNetwork(value.kind === 'lost');
|
|
156
|
+
const reason = literalOf(value.reason, lossReasons);
|
|
157
|
+
const stream = value.stream === null ? null : literalOf(value.stream, streams);
|
|
158
|
+
const unknownExtent = reason === 'capture-restart' || reason === 'not-captured';
|
|
159
|
+
if (unknownExtent) {
|
|
160
|
+
requireNetwork(value.bytes === null && value.lines === null && stream === null);
|
|
161
|
+
return { kind: 'lost', reason, stream, timestampNs: timestampOf(value.timestampNs), bytes: null, lines: null };
|
|
162
|
+
}
|
|
163
|
+
const bytes = networkCounter(value.bytes);
|
|
164
|
+
const lines = countOf(value.lines);
|
|
165
|
+
requireNetwork(reason !== 'line-cut' || (stream !== null && lines === 0));
|
|
166
|
+
return { kind: 'lost', reason, stream, timestampNs: timestampOf(value.timestampNs), bytes, lines };
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const entryOf = (valueArg: unknown): TRuntimeAssignmentLogEntry => {
|
|
170
|
+
requireNetwork(valueArg !== null && typeof valueArg === 'object');
|
|
171
|
+
return (valueArg as Record<string, unknown>).kind === 'lost' ? lossOf(valueArg) : lineOf(valueArg);
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const batchOf = (valueArg: unknown): IRuntimeAssignmentLogBatch => {
|
|
175
|
+
const value = networkRecord(valueArg, ['assignmentId', 'attemptId', 'captureId', 'sequence', 'entries', 'final']);
|
|
176
|
+
const entries = networkList(value.entries, runtimeAssignmentLogContract.maximumEntriesPerBatch, entryOf);
|
|
177
|
+
requireNetwork(typeof value.final === 'boolean' && (entries.length > 0 || value.final));
|
|
178
|
+
return {
|
|
179
|
+
assignmentId: networkId(value.assignmentId), attemptId: networkId(value.attemptId),
|
|
180
|
+
captureId: networkId(value.captureId), sequence: networkCounter(value.sequence), entries, final: value.final,
|
|
181
|
+
};
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
/** Detached exact-schema snapshot of one batch, bounded like a whole report. */
|
|
185
|
+
export const snapshotRuntimeAssignmentLogBatch = (valueArg: unknown): IRuntimeAssignmentLogBatch =>
|
|
186
|
+
safe(() => batchOf(captureNetwork(valueArg)));
|
|
187
|
+
|
|
188
|
+
export const snapshotReportRuntimeAssignmentLogsRequest = (
|
|
189
|
+
valueArg: unknown,
|
|
190
|
+
): IReportRuntimeAssignmentLogsRequest => safe(() => {
|
|
191
|
+
const value = networkRecord(captureNetwork(valueArg), ['session', 'batch']);
|
|
192
|
+
return { session: snapshotRuntimeSessionBinding(value.session), batch: batchOf(value.batch) };
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
export const snapshotReportRuntimeAssignmentLogsResponse = (
|
|
196
|
+
valueArg: unknown,
|
|
197
|
+
): IReportRuntimeAssignmentLogsResponse => safe(() => {
|
|
198
|
+
const value = networkRecord(captureNetwork(valueArg), ['status', 'assignmentId', 'captureId', 'sequence']);
|
|
199
|
+
return {
|
|
200
|
+
status: literalOf(value.status, statuses), assignmentId: networkId(value.assignmentId),
|
|
201
|
+
captureId: networkId(value.captureId), sequence: networkCounter(value.sequence),
|
|
202
|
+
};
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Controller-side content binding, not authentication. `trustedSessionArg` is the controller's
|
|
207
|
+
* CURRENT session with the node the report arrived on, never read from the request. `assignmentArg`
|
|
208
|
+
* is the controller's own record of the batch's assignment, any generation of it, and
|
|
209
|
+
* `logPolicyArg` the `logs` policy of the config it runs with. The request's session must equal the
|
|
210
|
+
* trusted one, the assignment must be this node's and carry the batch's attempt, and every line
|
|
211
|
+
* the batch holds, its pieces joined, must fit the policy's `maximumLineBytes`. A line whose first
|
|
212
|
+
* pieces travelled in an earlier batch is judged by the pieces this batch holds.
|
|
213
|
+
*/
|
|
214
|
+
export const bindReportRuntimeAssignmentLogsRequest = async (
|
|
215
|
+
valueArg: unknown, assignmentArg: unknown, logPolicyArg: IRuntimeConfigLogPolicy, trustedSessionArg: unknown,
|
|
216
|
+
): Promise<{ request: IReportRuntimeAssignmentLogsRequest; assignment: IRuntimeAssignment }> => {
|
|
217
|
+
const request = snapshotReportRuntimeAssignmentLogsRequest(valueArg);
|
|
218
|
+
const assignment = safe(() => snapshotRuntimeAssignment(assignmentArg));
|
|
219
|
+
const trustedSession = safe(() => snapshotRuntimeSessionBinding(trustedSessionArg));
|
|
220
|
+
if ((await validateRuntimeAssignment(assignment)).length) fail();
|
|
221
|
+
safe(() => {
|
|
222
|
+
requireNetwork(isRuntimeLogPolicy(logPolicyArg));
|
|
223
|
+
requireNetwork(networkEqual(request.session, trustedSession)
|
|
224
|
+
&& networkEqual(assignment.controller, trustedSession.controller)
|
|
225
|
+
&& assignment.nodeId === trustedSession.nodeId
|
|
226
|
+
&& assignment.runtimeNamespace === trustedSession.runtimeNamespace
|
|
227
|
+
&& assignment.assignmentId === request.batch.assignmentId
|
|
228
|
+
&& assignment.attemptId === request.batch.attemptId);
|
|
229
|
+
const joined: Record<TRuntimeAssignmentLogStream, number> = { stdout: 0, stderr: 0 };
|
|
230
|
+
for (const entry of request.batch.entries) {
|
|
231
|
+
if (entry.kind !== 'line') continue;
|
|
232
|
+
joined[entry.stream] += encoder.encode(entry.text).byteLength;
|
|
233
|
+
requireNetwork(joined[entry.stream] <= logPolicyArg.maximumLineBytes);
|
|
234
|
+
if (!entry.partial) joined[entry.stream] = 0;
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
return { request, assignment };
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
/** Matches an acknowledgement to the exact batch that was sent. */
|
|
241
|
+
export const bindReportRuntimeAssignmentLogsResponse = (
|
|
242
|
+
valueArg: unknown, requestArg: unknown,
|
|
243
|
+
): IReportRuntimeAssignmentLogsResponse => safe(() => {
|
|
244
|
+
const response = snapshotReportRuntimeAssignmentLogsResponse(valueArg);
|
|
245
|
+
const { batch } = snapshotReportRuntimeAssignmentLogsRequest(requestArg);
|
|
246
|
+
requireNetwork(response.assignmentId === batch.assignmentId && response.captureId === batch.captureId
|
|
247
|
+
&& response.sequence === batch.sequence);
|
|
248
|
+
return response;
|
|
249
|
+
});
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type { IRuntimeAssignment, IRuntimeAssignmentReference } from './runtimeassignment.js';
|
|
2
|
+
import { getRuntimeAssignmentReference, snapshotRuntimeAssignment, validateRuntimeAssignment } from './runtimeassignment.js';
|
|
3
|
+
import type { IRuntimeSessionBinding } from './runtimesession.js';
|
|
4
|
+
import { snapshotRuntimeSessionBinding } from './runtimesession.js';
|
|
5
|
+
import {
|
|
6
|
+
captureNetwork, networkCounter, networkDigest, networkEqual, networkId, networkRecord, requireNetwork,
|
|
7
|
+
} from '../private/networkcontract.js';
|
|
8
|
+
|
|
9
|
+
/** Bytes the workload's network namespace received and sent since the sandbox started. */
|
|
10
|
+
export interface IRuntimeAssignmentNetworkStats {
|
|
11
|
+
receivedBytes: number;
|
|
12
|
+
transmittedBytes: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* One resource sample of one running attempt, as the runtime reports it.
|
|
17
|
+
*
|
|
18
|
+
* `cpuUsageCoreNanoseconds` is cumulative CPU time across every core since the container started,
|
|
19
|
+
* as decimal digits because it outgrows a JSON number; a reader derives utilisation from two
|
|
20
|
+
* samples and their `timestampNs`. `memoryLimitBytes` is the limit the node enforces, the sealed
|
|
21
|
+
* config's `memoryBytes`. `network` is `null` when the runtime reports no interface of the
|
|
22
|
+
* sandbox, and `writableLayerBytes` is `null` when it reports no usage of the container's writable
|
|
23
|
+
* layer.
|
|
24
|
+
*/
|
|
25
|
+
export interface IRuntimeAssignmentStatsSample {
|
|
26
|
+
/** When the runtime took the sample, in nanoseconds since the Unix epoch, as decimal digits. */
|
|
27
|
+
timestampNs: string;
|
|
28
|
+
cpuUsageCoreNanoseconds: string;
|
|
29
|
+
memoryWorkingSetBytes: number;
|
|
30
|
+
memoryLimitBytes: number;
|
|
31
|
+
network: IRuntimeAssignmentNetworkStats | null;
|
|
32
|
+
writableLayerBytes: number | null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Controller read over the node's current authenticated session. */
|
|
36
|
+
export interface IReadRuntimeAssignmentStatsRequest {
|
|
37
|
+
session: IRuntimeSessionBinding;
|
|
38
|
+
/** The exact current revision of an assignment on this node. */
|
|
39
|
+
assignment: IRuntimeAssignmentReference;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* `sampled` carries one sample; `not-running` states that the attempt has no running container to
|
|
44
|
+
* sample, and carries none.
|
|
45
|
+
*/
|
|
46
|
+
export interface IReadRuntimeAssignmentStatsResponse {
|
|
47
|
+
status: 'sampled' | 'not-running';
|
|
48
|
+
assignment: IRuntimeAssignmentReference;
|
|
49
|
+
sample: IRuntimeAssignmentStatsSample | null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const runtimeAssignmentStatsContract = Object.freeze({
|
|
53
|
+
error: 'Runtime assignment stats contract rejected.',
|
|
54
|
+
/** The node answers one read per assignment at a time and refuses a concurrent one. */
|
|
55
|
+
concurrency: 'one-read-per-assignment',
|
|
56
|
+
} as const);
|
|
57
|
+
|
|
58
|
+
const fail = (): never => { throw new Error(runtimeAssignmentStatsContract.error); };
|
|
59
|
+
const safe = <T>(operationArg: () => T): T => { try { return operationArg(); } catch { return fail(); } };
|
|
60
|
+
|
|
61
|
+
/** An unsigned 64-bit counter as canonical decimal digits: no sign, no leading zero. */
|
|
62
|
+
const decimalOf = (valueArg: unknown): string => {
|
|
63
|
+
requireNetwork(typeof valueArg === 'string' && /^(0|[1-9][0-9]{0,19})$/.test(valueArg)
|
|
64
|
+
&& BigInt(valueArg) <= 18_446_744_073_709_551_615n);
|
|
65
|
+
return valueArg;
|
|
66
|
+
};
|
|
67
|
+
const bytesOf = (valueArg: unknown): number => {
|
|
68
|
+
requireNetwork(typeof valueArg === 'number' && Number.isSafeInteger(valueArg) && valueArg >= 0
|
|
69
|
+
&& !Object.is(valueArg, -0));
|
|
70
|
+
return valueArg;
|
|
71
|
+
};
|
|
72
|
+
const referenceOf = (valueArg: unknown): IRuntimeAssignmentReference => {
|
|
73
|
+
const value = networkRecord(valueArg, ['assignmentId', 'generation', 'digest']);
|
|
74
|
+
return {
|
|
75
|
+
assignmentId: networkId(value.assignmentId), generation: networkCounter(value.generation),
|
|
76
|
+
digest: networkDigest(value.digest),
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const sampleOf = (valueArg: unknown): IRuntimeAssignmentStatsSample => {
|
|
81
|
+
const value = networkRecord(valueArg, ['timestampNs', 'cpuUsageCoreNanoseconds', 'memoryWorkingSetBytes',
|
|
82
|
+
'memoryLimitBytes', 'network', 'writableLayerBytes']);
|
|
83
|
+
const timestampNs = decimalOf(value.timestampNs);
|
|
84
|
+
requireNetwork(timestampNs !== '0');
|
|
85
|
+
let network: IRuntimeAssignmentNetworkStats | null = null;
|
|
86
|
+
if (value.network !== null) {
|
|
87
|
+
const item = networkRecord(value.network, ['receivedBytes', 'transmittedBytes']);
|
|
88
|
+
network = { receivedBytes: bytesOf(item.receivedBytes), transmittedBytes: bytesOf(item.transmittedBytes) };
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
timestampNs,
|
|
92
|
+
cpuUsageCoreNanoseconds: decimalOf(value.cpuUsageCoreNanoseconds),
|
|
93
|
+
memoryWorkingSetBytes: bytesOf(value.memoryWorkingSetBytes),
|
|
94
|
+
memoryLimitBytes: networkCounter(value.memoryLimitBytes),
|
|
95
|
+
network,
|
|
96
|
+
writableLayerBytes: value.writableLayerBytes === null ? null : bytesOf(value.writableLayerBytes),
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const snapshotReadRuntimeAssignmentStatsRequest = (
|
|
101
|
+
valueArg: unknown,
|
|
102
|
+
): IReadRuntimeAssignmentStatsRequest => safe(() => {
|
|
103
|
+
const value = networkRecord(captureNetwork(valueArg), ['session', 'assignment']);
|
|
104
|
+
return { session: snapshotRuntimeSessionBinding(value.session), assignment: referenceOf(value.assignment) };
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
export const snapshotReadRuntimeAssignmentStatsResponse = (
|
|
108
|
+
valueArg: unknown,
|
|
109
|
+
): IReadRuntimeAssignmentStatsResponse => safe(() => {
|
|
110
|
+
const value = networkRecord(captureNetwork(valueArg), ['status', 'assignment', 'sample']);
|
|
111
|
+
requireNetwork((value.status === 'sampled' && value.sample !== null)
|
|
112
|
+
|| (value.status === 'not-running' && value.sample === null));
|
|
113
|
+
return {
|
|
114
|
+
status: value.status as IReadRuntimeAssignmentStatsResponse['status'],
|
|
115
|
+
assignment: referenceOf(value.assignment),
|
|
116
|
+
sample: value.sample === null ? null : sampleOf(value.sample),
|
|
117
|
+
};
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Node-side content binding, not authentication. `trustedSessionArg` is the node's CURRENT
|
|
122
|
+
* session, and the request must have arrived on that session's own connection. `assignmentArg` is
|
|
123
|
+
* the node's current admitted revision of the named assignment: the request must name exactly it,
|
|
124
|
+
* and it must belong to the session's controller, node and runtime namespace.
|
|
125
|
+
*/
|
|
126
|
+
export const bindReadRuntimeAssignmentStatsRequest = async (
|
|
127
|
+
valueArg: unknown, assignmentArg: unknown, trustedSessionArg: unknown,
|
|
128
|
+
): Promise<{ request: IReadRuntimeAssignmentStatsRequest; assignment: IRuntimeAssignment }> => {
|
|
129
|
+
const request = snapshotReadRuntimeAssignmentStatsRequest(valueArg);
|
|
130
|
+
const assignment = safe(() => snapshotRuntimeAssignment(assignmentArg));
|
|
131
|
+
const trustedSession = safe(() => snapshotRuntimeSessionBinding(trustedSessionArg));
|
|
132
|
+
if ((await validateRuntimeAssignment(assignment)).length) fail();
|
|
133
|
+
safe(() => requireNetwork(networkEqual(request.session, trustedSession)
|
|
134
|
+
&& networkEqual(assignment.controller, trustedSession.controller)
|
|
135
|
+
&& assignment.nodeId === trustedSession.nodeId
|
|
136
|
+
&& assignment.runtimeNamespace === trustedSession.runtimeNamespace
|
|
137
|
+
&& networkEqual(request.assignment, getRuntimeAssignmentReference(assignment))));
|
|
138
|
+
return { request, assignment };
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/** Matches a sample to the exact assignment revision the read named. */
|
|
142
|
+
export const bindReadRuntimeAssignmentStatsResponse = (
|
|
143
|
+
valueArg: unknown, requestArg: unknown,
|
|
144
|
+
): IReadRuntimeAssignmentStatsResponse => safe(() => {
|
|
145
|
+
const response = snapshotReadRuntimeAssignmentStatsResponse(valueArg);
|
|
146
|
+
const request = snapshotReadRuntimeAssignmentStatsRequest(requestArg);
|
|
147
|
+
requireNetwork(networkEqual(response.assignment, request.assignment));
|
|
148
|
+
return response;
|
|
149
|
+
});
|