@serve.zone/interfaces 28.4.0 → 29.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +19 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/data/index.d.ts +2 -0
- package/dist_ts/data/index.js +3 -1
- package/dist_ts/data/runtimeconfig.d.ts +132 -0
- package/dist_ts/data/runtimeconfig.js +278 -0
- package/dist_ts/data/runtimesession.d.ts +127 -0
- package/dist_ts/data/runtimesession.js +275 -0
- package/dist_ts/requests/index.d.ts +2 -1
- package/dist_ts/requests/index.js +3 -2
- package/dist_ts/requests/runtimesession.d.ts +32 -0
- package/dist_ts/requests/runtimesession.js +2 -0
- package/package.json +1 -1
- package/readme.md +43 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/data/index.ts +2 -0
- package/ts/data/runtimeconfig.ts +395 -0
- package/ts/data/runtimesession.ts +403 -0
- package/ts/requests/index.ts +2 -0
- package/ts/requests/runtimesession.ts +52 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import type { IIdentityCredential } from './user.js';
|
|
2
|
+
import { isSha256Digest } from './immutableimage.js';
|
|
3
|
+
import type {
|
|
4
|
+
IRuntimeAssignmentObservationV1, IRuntimeAssignmentReferenceV1, IRuntimeAssignmentV1,
|
|
5
|
+
IRuntimeControllerV1,
|
|
6
|
+
} from './runtimeassignment.js';
|
|
7
|
+
import {
|
|
8
|
+
getRuntimeAssignmentV1Reference, snapshotRuntimeAssignmentObservationV1,
|
|
9
|
+
snapshotRuntimeAssignmentV1, validateRuntimeAssignmentObservationV1, validateRuntimeAssignmentV1,
|
|
10
|
+
} from './runtimeassignment.js';
|
|
11
|
+
import type { IRuntimeConfigV1 } from './runtimeconfig.js';
|
|
12
|
+
import { bindRuntimeConfigV1ToAssignment, snapshotRuntimeConfigV1 } from './runtimeconfig.js';
|
|
13
|
+
import { canonicalizeStrictJson } from '../private/canonicaljson.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Server-issued public binding for the current authenticated physical connection.
|
|
17
|
+
* It contains no bearer, peer ID or client-selected authority. A serialized binding
|
|
18
|
+
* is not authentication: every receiver must compare it with its live peer context.
|
|
19
|
+
* Onebox local execution uses the same shape with its own authenticated IPC owner.
|
|
20
|
+
*/
|
|
21
|
+
export interface IRuntimeSessionBindingV1 {
|
|
22
|
+
schemaVersion: 1;
|
|
23
|
+
reporterSessionId: string;
|
|
24
|
+
nodeId: string;
|
|
25
|
+
credentialGeneration: number;
|
|
26
|
+
sessionEpoch: number;
|
|
27
|
+
controllerInstanceId: string;
|
|
28
|
+
controller: IRuntimeControllerV1;
|
|
29
|
+
runtimeNamespace: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Pallet-initiated WSS on its configured Cloudly HTTPS origin; never HTTP RPC. */
|
|
33
|
+
export interface IRegisterPalletRuntimeSessionRequestV1 {
|
|
34
|
+
schemaVersion: 1;
|
|
35
|
+
nodeId: string;
|
|
36
|
+
credentialGeneration: number;
|
|
37
|
+
/** Sensitive ephemeral input; exclude the complete request from hooks and logs. */
|
|
38
|
+
bearer: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface IApplyRuntimeAssignmentRequestV1 {
|
|
42
|
+
session: IRuntimeSessionBindingV1;
|
|
43
|
+
assignment: IRuntimeAssignmentV1;
|
|
44
|
+
config: IRuntimeConfigV1;
|
|
45
|
+
/** Exact removed incumbent for a new attempt; not assignment.previous. */
|
|
46
|
+
predecessor: IRuntimeAssignmentReferenceV1 | null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Durable admission acknowledgement only; it asserts neither effects nor readiness. */
|
|
50
|
+
export interface IApplyRuntimeAssignmentResponseV1 {
|
|
51
|
+
status: 'accepted' | 'replay' | 'historical';
|
|
52
|
+
assignment: IRuntimeAssignmentReferenceV1;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface IReportRuntimeAssignmentObservationRequestV1 {
|
|
56
|
+
/** Always the CURRENT authenticated connection, including after credential rotation. */
|
|
57
|
+
session: IRuntimeSessionBindingV1;
|
|
58
|
+
/** Exact durable outbox body. Never rewrite reporterSessionId, digest or sequence. */
|
|
59
|
+
observation: IRuntimeAssignmentObservationV1;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type TRuntimeObservationAuthorityUseV1 = 'current-session' | 'historical-recovery';
|
|
63
|
+
|
|
64
|
+
export interface IReportRuntimeAssignmentObservationResponseV1 {
|
|
65
|
+
/** Exact request body after a durable sequence/receipt transaction. */
|
|
66
|
+
observation: IRuntimeAssignmentObservationV1;
|
|
67
|
+
/**
|
|
68
|
+
* Historical recovery can advance the durable counter and ACK only. It must not
|
|
69
|
+
* update active readiness, routing or liveness; those require a fresh report
|
|
70
|
+
* from the current session. Neither classification is proof of writer fencing.
|
|
71
|
+
*/
|
|
72
|
+
authorityUse: TRuntimeObservationAuthorityUseV1;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface IGetRuntimeRegistryCredentialRequestV1 {
|
|
76
|
+
session: IRuntimeSessionBindingV1;
|
|
77
|
+
/** The server must authorize this exact CURRENT assignment, including its node. */
|
|
78
|
+
assignment: IRuntimeAssignmentReferenceV1;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Sensitive ephemeral material; never persist, hash, log or include in diagnostics. */
|
|
82
|
+
export interface IGetRuntimeRegistryCredentialResponseV1 {
|
|
83
|
+
username: string;
|
|
84
|
+
password: string;
|
|
85
|
+
expiresAt: number;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Cloudly human/machine JWT registration on a physical TypedSocket; reject HTTP. */
|
|
89
|
+
export interface IRegisterCloudlyClientSessionRequestV1 {
|
|
90
|
+
schemaVersion: 1;
|
|
91
|
+
/** Authenticate the current JWT server-side; do not accept client identity claims. */
|
|
92
|
+
identity: IIdentityCredential;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface IRegisterCloudlyClientSessionResponseV1 {
|
|
96
|
+
/** Server-owned identity tagging completed; never trust a client-authored tag. */
|
|
97
|
+
registered: true;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export const runtimeSessionV1Contract = Object.freeze({
|
|
101
|
+
maximumWireCanonicalBytes: 327_680,
|
|
102
|
+
maximumCredentialBytes: 16_384,
|
|
103
|
+
maximumRegistryPasswordBytes: 8192,
|
|
104
|
+
registerTransport: 'pallet-initiated-wss-on-configured-cloudly-https-origin',
|
|
105
|
+
historicalObservationAuthority: 'durable-counter-and-ack-only',
|
|
106
|
+
error: 'Runtime session contract rejected.',
|
|
107
|
+
} as const);
|
|
108
|
+
|
|
109
|
+
const fail = (): never => { throw new Error(runtimeSessionV1Contract.error); };
|
|
110
|
+
function requireValue(conditionArg: unknown): asserts conditionArg {
|
|
111
|
+
if (!conditionArg) fail();
|
|
112
|
+
}
|
|
113
|
+
const safe = <T>(operationArg: () => T): T => {
|
|
114
|
+
try { return operationArg(); } catch { return fail(); }
|
|
115
|
+
};
|
|
116
|
+
const encoder = new TextEncoder();
|
|
117
|
+
const canonical = (valueArg: unknown): string => canonicalizeStrictJson(valueArg, fail);
|
|
118
|
+
const positive = (valueArg: unknown): number => {
|
|
119
|
+
requireValue(typeof valueArg === 'number' && Number.isSafeInteger(valueArg) && valueArg > 0);
|
|
120
|
+
return valueArg;
|
|
121
|
+
};
|
|
122
|
+
const identifier = (valueArg: unknown): string => {
|
|
123
|
+
requireValue(typeof valueArg === 'string' && /^[A-Za-z0-9][A-Za-z0-9:._-]{0,199}$/.test(valueArg));
|
|
124
|
+
return valueArg;
|
|
125
|
+
};
|
|
126
|
+
const nodeId = (valueArg: unknown): string => {
|
|
127
|
+
requireValue(typeof valueArg === 'string' && /^[A-Za-z0-9_-]{1,128}$/.test(valueArg));
|
|
128
|
+
return valueArg;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/** Bound decoded JSON before canonicalization; copy data descriptors, never getters. */
|
|
132
|
+
const capture = (valueArg: unknown): unknown => safe(() => {
|
|
133
|
+
let nodes = 0;
|
|
134
|
+
let stringBytes = 0;
|
|
135
|
+
const ancestors = new Set<object>();
|
|
136
|
+
const account = (value: string): void => {
|
|
137
|
+
requireValue(value.length <= runtimeSessionV1Contract.maximumWireCanonicalBytes);
|
|
138
|
+
stringBytes += encoder.encode(value).byteLength;
|
|
139
|
+
requireValue(stringBytes <= runtimeSessionV1Contract.maximumWireCanonicalBytes);
|
|
140
|
+
};
|
|
141
|
+
const copy = (value: unknown, depth: number): unknown => {
|
|
142
|
+
requireValue(++nodes <= 10_240 && depth <= 16);
|
|
143
|
+
if (typeof value === 'string') { account(value); return value; }
|
|
144
|
+
if (value === null || typeof value === 'boolean' || typeof value === 'number') return value;
|
|
145
|
+
requireValue(value !== null && typeof value === 'object' && !ancestors.has(value));
|
|
146
|
+
ancestors.add(value);
|
|
147
|
+
try {
|
|
148
|
+
const array = Array.isArray(value);
|
|
149
|
+
const prototype = Object.getPrototypeOf(value);
|
|
150
|
+
requireValue(array ? prototype === Array.prototype : prototype === Object.prototype || prototype === null);
|
|
151
|
+
const keys = Reflect.ownKeys(value);
|
|
152
|
+
requireValue(keys.length <= 257);
|
|
153
|
+
if (array) {
|
|
154
|
+
const length = Object.getOwnPropertyDescriptor(value, 'length')?.value;
|
|
155
|
+
requireValue(Number.isSafeInteger(length) && length >= 0 && length <= 256 && keys.length === length + 1);
|
|
156
|
+
const result: unknown[] = [];
|
|
157
|
+
for (let index = 0; index < length; index++) {
|
|
158
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
159
|
+
requireValue(descriptor?.enumerable && Object.hasOwn(descriptor, 'value'));
|
|
160
|
+
result.push(copy(descriptor.value, depth + 1));
|
|
161
|
+
}
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
const result: Record<string, unknown> = Object.create(null);
|
|
165
|
+
for (const key of keys) {
|
|
166
|
+
requireValue(typeof key === 'string');
|
|
167
|
+
account(key);
|
|
168
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
169
|
+
requireValue(descriptor?.enumerable && Object.hasOwn(descriptor, 'value'));
|
|
170
|
+
result[key] = copy(descriptor.value, depth + 1);
|
|
171
|
+
}
|
|
172
|
+
return result;
|
|
173
|
+
} finally { ancestors.delete(value); }
|
|
174
|
+
};
|
|
175
|
+
const serialized = canonical(copy(valueArg, 0));
|
|
176
|
+
requireValue(encoder.encode(serialized).byteLength <= runtimeSessionV1Contract.maximumWireCanonicalBytes);
|
|
177
|
+
return JSON.parse(serialized);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const record = (valueArg: unknown, keysArg: readonly string[]): Record<string, unknown> => {
|
|
181
|
+
requireValue(valueArg !== null && typeof valueArg === 'object' && !Array.isArray(valueArg));
|
|
182
|
+
const keys = Object.keys(valueArg);
|
|
183
|
+
requireValue(keys.length === keysArg.length && keys.every((key) => keysArg.includes(key)));
|
|
184
|
+
return valueArg as Record<string, unknown>;
|
|
185
|
+
};
|
|
186
|
+
const reference = (valueArg: unknown): IRuntimeAssignmentReferenceV1 => {
|
|
187
|
+
const value = record(valueArg, ['assignmentId', 'generation', 'digest']);
|
|
188
|
+
requireValue(typeof value.digest === 'string' && isSha256Digest(value.digest));
|
|
189
|
+
return { assignmentId: identifier(value.assignmentId), generation: positive(value.generation), digest: value.digest };
|
|
190
|
+
};
|
|
191
|
+
const session = (valueArg: unknown): IRuntimeSessionBindingV1 => {
|
|
192
|
+
const value = record(valueArg, ['schemaVersion', 'reporterSessionId', 'nodeId', 'credentialGeneration',
|
|
193
|
+
'sessionEpoch', 'controllerInstanceId', 'controller', 'runtimeNamespace']);
|
|
194
|
+
const controller = record(value.controller, ['kind', 'id', 'epoch']);
|
|
195
|
+
requireValue(value.schemaVersion === 1 && (controller.kind === 'cloudly' || controller.kind === 'onebox'));
|
|
196
|
+
return {
|
|
197
|
+
schemaVersion: 1, reporterSessionId: identifier(value.reporterSessionId), nodeId: nodeId(value.nodeId),
|
|
198
|
+
credentialGeneration: positive(value.credentialGeneration), sessionEpoch: positive(value.sessionEpoch),
|
|
199
|
+
controllerInstanceId: identifier(value.controllerInstanceId),
|
|
200
|
+
controller: { kind: controller.kind, id: identifier(controller.id), epoch: identifier(controller.epoch) },
|
|
201
|
+
runtimeNamespace: identifier(value.runtimeNamespace),
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
const sameSession = (leftArg: IRuntimeSessionBindingV1, rightArg: IRuntimeSessionBindingV1): boolean =>
|
|
205
|
+
canonical(leftArg) === canonical(rightArg);
|
|
206
|
+
const sameOwner = (leftArg: IRuntimeSessionBindingV1, rightArg: IRuntimeSessionBindingV1): boolean =>
|
|
207
|
+
leftArg.nodeId === rightArg.nodeId && leftArg.runtimeNamespace === rightArg.runtimeNamespace
|
|
208
|
+
&& canonical(leftArg.controller) === canonical(rightArg.controller);
|
|
209
|
+
const matchesAssignment = (sessionArg: IRuntimeSessionBindingV1, assignmentArg: IRuntimeAssignmentV1): boolean =>
|
|
210
|
+
sessionArg.nodeId === assignmentArg.nodeId && sessionArg.runtimeNamespace === assignmentArg.runtimeNamespace
|
|
211
|
+
&& canonical(sessionArg.controller) === canonical(assignmentArg.controller);
|
|
212
|
+
|
|
213
|
+
/** Synchronous detached structural assertion only; never an authentication check. */
|
|
214
|
+
export const snapshotRuntimeSessionBindingV1 = (valueArg: unknown): IRuntimeSessionBindingV1 =>
|
|
215
|
+
safe(() => session(capture(valueArg)));
|
|
216
|
+
|
|
217
|
+
export const snapshotRegisterPalletRuntimeSessionRequestV1 = (
|
|
218
|
+
valueArg: unknown,
|
|
219
|
+
): IRegisterPalletRuntimeSessionRequestV1 => safe(() => {
|
|
220
|
+
const value = record(capture(valueArg), ['schemaVersion', 'nodeId', 'credentialGeneration', 'bearer']);
|
|
221
|
+
requireValue(value.schemaVersion === 1 && typeof value.bearer === 'string'
|
|
222
|
+
&& /^palletToken[0-9a-f]{64}$/.test(value.bearer));
|
|
223
|
+
return { schemaVersion: 1, nodeId: nodeId(value.nodeId),
|
|
224
|
+
credentialGeneration: positive(value.credentialGeneration), bearer: value.bearer };
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
export const snapshotApplyRuntimeAssignmentRequestV1 = (
|
|
228
|
+
valueArg: unknown,
|
|
229
|
+
): IApplyRuntimeAssignmentRequestV1 => safe(() => {
|
|
230
|
+
const value = record(capture(valueArg), ['session', 'assignment', 'config', 'predecessor']);
|
|
231
|
+
const assignment = snapshotRuntimeAssignmentV1(value.assignment);
|
|
232
|
+
const predecessor = value.predecessor === null ? null : reference(value.predecessor);
|
|
233
|
+
// Assignment revisions use assignment.previous; a replacement names a different attempt.
|
|
234
|
+
requireValue(predecessor === null || predecessor.assignmentId !== assignment.assignmentId);
|
|
235
|
+
return { session: session(value.session), assignment, config: snapshotRuntimeConfigV1(value.config), predecessor };
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
export const snapshotApplyRuntimeAssignmentResponseV1 = (
|
|
239
|
+
valueArg: unknown,
|
|
240
|
+
): IApplyRuntimeAssignmentResponseV1 => safe(() => {
|
|
241
|
+
const value = record(capture(valueArg), ['status', 'assignment']);
|
|
242
|
+
requireValue(value.status === 'accepted' || value.status === 'replay' || value.status === 'historical');
|
|
243
|
+
return { status: value.status, assignment: reference(value.assignment) };
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
export const snapshotReportRuntimeAssignmentObservationRequestV1 = (
|
|
247
|
+
valueArg: unknown,
|
|
248
|
+
): IReportRuntimeAssignmentObservationRequestV1 => safe(() => {
|
|
249
|
+
const value = record(capture(valueArg), ['session', 'observation']);
|
|
250
|
+
return { session: session(value.session), observation: snapshotRuntimeAssignmentObservationV1(value.observation) };
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
export const snapshotReportRuntimeAssignmentObservationResponseV1 = (
|
|
254
|
+
valueArg: unknown,
|
|
255
|
+
): IReportRuntimeAssignmentObservationResponseV1 => safe(() => {
|
|
256
|
+
const value = record(capture(valueArg), ['observation', 'authorityUse']);
|
|
257
|
+
requireValue(value.authorityUse === 'current-session' || value.authorityUse === 'historical-recovery');
|
|
258
|
+
return { observation: snapshotRuntimeAssignmentObservationV1(value.observation), authorityUse: value.authorityUse };
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
export const snapshotGetRuntimeRegistryCredentialRequestV1 = (
|
|
262
|
+
valueArg: unknown,
|
|
263
|
+
): IGetRuntimeRegistryCredentialRequestV1 => safe(() => {
|
|
264
|
+
const value = record(capture(valueArg), ['session', 'assignment']);
|
|
265
|
+
return { session: session(value.session), assignment: reference(value.assignment) };
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
export const snapshotGetRuntimeRegistryCredentialResponseV1 = (
|
|
269
|
+
valueArg: unknown,
|
|
270
|
+
): IGetRuntimeRegistryCredentialResponseV1 => safe(() => {
|
|
271
|
+
const value = record(capture(valueArg), ['username', 'password', 'expiresAt']);
|
|
272
|
+
requireValue(typeof value.username === 'string' && value.username.length > 0
|
|
273
|
+
&& encoder.encode(value.username).byteLength <= 1024 && !/[\s:\u0000-\u001f\u007f]/.test(value.username));
|
|
274
|
+
requireValue(typeof value.password === 'string' && value.password.length > 0
|
|
275
|
+
&& encoder.encode(value.password).byteLength <= runtimeSessionV1Contract.maximumRegistryPasswordBytes
|
|
276
|
+
&& !/[\u0000-\u001f\u007f]/.test(value.password));
|
|
277
|
+
return { username: value.username, password: value.password, expiresAt: positive(value.expiresAt) };
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
export const snapshotRegisterCloudlyClientSessionRequestV1 = (
|
|
281
|
+
valueArg: unknown,
|
|
282
|
+
): IRegisterCloudlyClientSessionRequestV1 => safe(() => {
|
|
283
|
+
const value = record(capture(valueArg), ['schemaVersion', 'identity']);
|
|
284
|
+
const identity = record(value.identity, ['jwt']);
|
|
285
|
+
requireValue(value.schemaVersion === 1 && typeof identity.jwt === 'string'
|
|
286
|
+
&& identity.jwt.length <= runtimeSessionV1Contract.maximumCredentialBytes
|
|
287
|
+
&& /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(identity.jwt));
|
|
288
|
+
return { schemaVersion: 1, identity: { jwt: identity.jwt } };
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
export const snapshotRegisterCloudlyClientSessionResponseV1 = (
|
|
292
|
+
valueArg: unknown,
|
|
293
|
+
): IRegisterCloudlyClientSessionResponseV1 => safe(() => {
|
|
294
|
+
const value = record(capture(valueArg), ['registered']);
|
|
295
|
+
requireValue(value.registered === true);
|
|
296
|
+
return { registered: true };
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
/** Bind the authenticated Cloudly registration reply to the node and credential sent. */
|
|
300
|
+
export const bindRegisterPalletRuntimeSessionResponseV1 = (
|
|
301
|
+
valueArg: unknown, requestArg: unknown,
|
|
302
|
+
): IRuntimeSessionBindingV1 => safe(() => {
|
|
303
|
+
const response = snapshotRuntimeSessionBindingV1(valueArg);
|
|
304
|
+
const request = snapshotRegisterPalletRuntimeSessionRequestV1(requestArg);
|
|
305
|
+
requireValue(response.controller.kind === 'cloudly' && response.nodeId === request.nodeId
|
|
306
|
+
&& response.credentialGeneration === request.credentialGeneration);
|
|
307
|
+
return response;
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
/** Reject already expired material; issuance policy and current assignment authority remain server-owned. */
|
|
311
|
+
export const bindGetRuntimeRegistryCredentialResponseV1 = (
|
|
312
|
+
valueArg: unknown, nowArg: number,
|
|
313
|
+
): IGetRuntimeRegistryCredentialResponseV1 => safe(() => {
|
|
314
|
+
const response = snapshotGetRuntimeRegistryCredentialResponseV1(valueArg);
|
|
315
|
+
requireValue(response.expiresAt > positive(nowArg));
|
|
316
|
+
return response;
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Capture every input before awaiting digest verification. trustedSessionArg must
|
|
321
|
+
* come from the receiving live connection, never from another request field.
|
|
322
|
+
* This binds content only; durable CAS, slot admission and authority remain owners.
|
|
323
|
+
*/
|
|
324
|
+
export const bindApplyRuntimeAssignmentRequestV1 = async (
|
|
325
|
+
valueArg: unknown, trustedSessionArg: unknown,
|
|
326
|
+
): Promise<IApplyRuntimeAssignmentRequestV1> => {
|
|
327
|
+
try {
|
|
328
|
+
const request = snapshotApplyRuntimeAssignmentRequestV1(valueArg);
|
|
329
|
+
const trustedSession = snapshotRuntimeSessionBindingV1(trustedSessionArg);
|
|
330
|
+
requireValue(sameSession(request.session, trustedSession) && matchesAssignment(trustedSession, request.assignment));
|
|
331
|
+
await bindRuntimeConfigV1ToAssignment(request.config, request.assignment);
|
|
332
|
+
return request;
|
|
333
|
+
} catch { return fail(); }
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
/** Match an admission acknowledgement to the exact sent revision, including historical replies. */
|
|
337
|
+
export const bindApplyRuntimeAssignmentResponseV1 = (
|
|
338
|
+
valueArg: unknown, requestArg: unknown,
|
|
339
|
+
): IApplyRuntimeAssignmentResponseV1 => safe(() => {
|
|
340
|
+
const response = snapshotApplyRuntimeAssignmentResponseV1(valueArg);
|
|
341
|
+
const request = snapshotApplyRuntimeAssignmentRequestV1(requestArg);
|
|
342
|
+
requireValue(canonical(response.assignment) === canonical(getRuntimeAssignmentV1Reference(request.assignment)));
|
|
343
|
+
return response;
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* trustedSessionArg is the CURRENT authenticated physical peer. reporterHistoryArg
|
|
348
|
+
* must be server-indexed immutable session history, obtained by the original
|
|
349
|
+
* observation's reporterSessionId; never accept caller-supplied history. All V1
|
|
350
|
+
* runtime sessions are Pallet-purpose sessions, so Spark history cannot qualify.
|
|
351
|
+
*
|
|
352
|
+
* This verifies content and classifies recovery, not authentication, history
|
|
353
|
+
* provenance, sequence admission or current authority. The server must transact
|
|
354
|
+
* receipt/counter acceptance under current credential and peer authority. A
|
|
355
|
+
* historical result MUST NOT restore active readiness, routes or liveness.
|
|
356
|
+
*/
|
|
357
|
+
export const bindReportRuntimeAssignmentObservationRequestV1 = async (
|
|
358
|
+
valueArg: unknown, assignmentArg: unknown, trustedSessionArg: unknown, reporterHistoryArg: unknown,
|
|
359
|
+
): Promise<{ request: IReportRuntimeAssignmentObservationRequestV1; authorityUse: TRuntimeObservationAuthorityUseV1 }> => {
|
|
360
|
+
try {
|
|
361
|
+
const request = snapshotReportRuntimeAssignmentObservationRequestV1(valueArg);
|
|
362
|
+
const assignment = snapshotRuntimeAssignmentV1(capture(assignmentArg));
|
|
363
|
+
const trustedSession = snapshotRuntimeSessionBindingV1(trustedSessionArg);
|
|
364
|
+
const reporterHistory = snapshotRuntimeSessionBindingV1(reporterHistoryArg);
|
|
365
|
+
requireValue(sameSession(request.session, trustedSession) && matchesAssignment(trustedSession, assignment)
|
|
366
|
+
&& request.observation.reporterSessionId === reporterHistory.reporterSessionId
|
|
367
|
+
&& sameOwner(trustedSession, reporterHistory));
|
|
368
|
+
const current = sameSession(reporterHistory, trustedSession);
|
|
369
|
+
requireValue(current || (reporterHistory.reporterSessionId !== trustedSession.reporterSessionId
|
|
370
|
+
&& reporterHistory.sessionEpoch < trustedSession.sessionEpoch
|
|
371
|
+
&& reporterHistory.credentialGeneration <= trustedSession.credentialGeneration));
|
|
372
|
+
requireValue((await validateRuntimeAssignmentObservationV1(request.observation, assignment)).length === 0);
|
|
373
|
+
return { request, authorityUse: current ? 'current-session' : 'historical-recovery' };
|
|
374
|
+
} catch { return fail(); }
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
/** Reject body rewriting and a historical receipt mislabeled as current authority. */
|
|
378
|
+
export const bindReportRuntimeAssignmentObservationResponseV1 = (
|
|
379
|
+
valueArg: unknown, requestArg: unknown,
|
|
380
|
+
): IReportRuntimeAssignmentObservationResponseV1 => safe(() => {
|
|
381
|
+
const response = snapshotReportRuntimeAssignmentObservationResponseV1(valueArg);
|
|
382
|
+
const request = snapshotReportRuntimeAssignmentObservationRequestV1(requestArg);
|
|
383
|
+
requireValue(canonical(response.observation) === canonical(request.observation)
|
|
384
|
+
&& response.authorityUse === (request.observation.reporterSessionId === request.session.reporterSessionId
|
|
385
|
+
? 'current-session' : 'historical-recovery'));
|
|
386
|
+
return response;
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
/** Current assignment lookup and live peer authorization remain mandatory server-owned inputs. */
|
|
390
|
+
export const bindGetRuntimeRegistryCredentialRequestV1 = async (
|
|
391
|
+
valueArg: unknown, assignmentArg: unknown, trustedSessionArg: unknown,
|
|
392
|
+
): Promise<IGetRuntimeRegistryCredentialRequestV1> => {
|
|
393
|
+
try {
|
|
394
|
+
const request = snapshotGetRuntimeRegistryCredentialRequestV1(valueArg);
|
|
395
|
+
const assignment = snapshotRuntimeAssignmentV1(capture(assignmentArg));
|
|
396
|
+
const trustedSession = snapshotRuntimeSessionBindingV1(trustedSessionArg);
|
|
397
|
+
requireValue(sameSession(request.session, trustedSession) && matchesAssignment(trustedSession, assignment)
|
|
398
|
+
&& assignment.disposition === 'run'
|
|
399
|
+
&& canonical(request.assignment) === canonical(getRuntimeAssignmentV1Reference(assignment)));
|
|
400
|
+
requireValue((await validateRuntimeAssignmentV1(assignment)).length === 0);
|
|
401
|
+
return request;
|
|
402
|
+
} catch { return fail(); }
|
|
403
|
+
};
|
package/ts/requests/index.ts
CHANGED
|
@@ -27,6 +27,7 @@ import * as nodeRequests from './node.js';
|
|
|
27
27
|
import * as palletRequests from './pallet.js';
|
|
28
28
|
import * as platformRequests from './platform.js';
|
|
29
29
|
import * as routingRequests from './routing.js';
|
|
30
|
+
import * as runtimeSessionRequests from './runtimesession.js';
|
|
30
31
|
import * as secretRequests from './secret.js';
|
|
31
32
|
import * as serverRequests from './server.js';
|
|
32
33
|
import * as serviceRequests from './service.js';
|
|
@@ -64,6 +65,7 @@ export {
|
|
|
64
65
|
palletRequests as pallet,
|
|
65
66
|
platformRequests as platform,
|
|
66
67
|
routingRequests as routing,
|
|
68
|
+
runtimeSessionRequests as runtimesession,
|
|
67
69
|
secretRequests as secret,
|
|
68
70
|
serverRequests as server,
|
|
69
71
|
serviceRequests as service,
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
import type * as data from '../data/runtimesession.js';
|
|
3
|
+
|
|
4
|
+
/** WSS-only Pallet bootstrap. Derive physical peer/process/idempotency server-side. */
|
|
5
|
+
export interface IReq_Pallet_Cloudly_RegisterPalletRuntimeSessionV1 extends plugins.typedrequestInterfaces.implementsTR<
|
|
6
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
7
|
+
IReq_Pallet_Cloudly_RegisterPalletRuntimeSessionV1
|
|
8
|
+
> {
|
|
9
|
+
method: 'registerPalletRuntimeSessionV1';
|
|
10
|
+
request: data.IRegisterPalletRuntimeSessionRequestV1;
|
|
11
|
+
response: data.IRuntimeSessionBindingV1;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Controller push over the already authenticated Pallet-initiated connection. */
|
|
15
|
+
export interface IReq_Controller_Pallet_ApplyRuntimeAssignmentV1 extends plugins.typedrequestInterfaces.implementsTR<
|
|
16
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
17
|
+
IReq_Controller_Pallet_ApplyRuntimeAssignmentV1
|
|
18
|
+
> {
|
|
19
|
+
method: 'applyRuntimeAssignmentV1';
|
|
20
|
+
request: data.IApplyRuntimeAssignmentRequestV1;
|
|
21
|
+
response: data.IApplyRuntimeAssignmentResponseV1;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Current-peer report, including exact historical outbox recovery after reconnect. */
|
|
25
|
+
export interface IReq_Pallet_Controller_ReportRuntimeAssignmentObservationV1 extends plugins.typedrequestInterfaces.implementsTR<
|
|
26
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
27
|
+
IReq_Pallet_Controller_ReportRuntimeAssignmentObservationV1
|
|
28
|
+
> {
|
|
29
|
+
method: 'reportRuntimeAssignmentObservationV1';
|
|
30
|
+
request: data.IReportRuntimeAssignmentObservationRequestV1;
|
|
31
|
+
response: data.IReportRuntimeAssignmentObservationResponseV1;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Current-peer, exact current assignment authorization; response is secret material. */
|
|
35
|
+
export interface IReq_Pallet_Controller_GetRuntimeRegistryCredentialV1 extends plugins.typedrequestInterfaces.implementsTR<
|
|
36
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
37
|
+
IReq_Pallet_Controller_GetRuntimeRegistryCredentialV1
|
|
38
|
+
> {
|
|
39
|
+
method: 'getRuntimeRegistryCredentialV1';
|
|
40
|
+
request: data.IGetRuntimeRegistryCredentialRequestV1;
|
|
41
|
+
response: data.IGetRuntimeRegistryCredentialResponseV1;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** WSS-only JWT authentication; server-owned identity tag, never client-authored claims. */
|
|
45
|
+
export interface IReq_Any_Cloudly_RegisterCloudlyClientSessionV1 extends plugins.typedrequestInterfaces.implementsTR<
|
|
46
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
47
|
+
IReq_Any_Cloudly_RegisterCloudlyClientSessionV1
|
|
48
|
+
> {
|
|
49
|
+
method: 'registerCloudlyClientSessionV1';
|
|
50
|
+
request: data.IRegisterCloudlyClientSessionRequestV1;
|
|
51
|
+
response: data.IRegisterCloudlyClientSessionResponseV1;
|
|
52
|
+
}
|