@serve.zone/interfaces 29.0.0 → 30.1.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 +18 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/data/index.d.ts +1 -0
- package/dist_ts/data/index.js +2 -1
- package/dist_ts/data/runtimesession.d.ts +15 -0
- package/dist_ts/data/runtimesession.js +26 -13
- package/dist_ts/data/runtimeterminalreceipt.d.ts +97 -0
- package/dist_ts/data/runtimeterminalreceipt.js +281 -0
- package/dist_ts/requests/image.d.ts +4 -2
- package/dist_ts/requests/runtimesession.d.ts +7 -0
- package/package.json +2 -2
- package/readme.md +59 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/data/index.ts +1 -0
- package/ts/data/runtimesession.ts +38 -12
- package/ts/data/runtimeterminalreceipt.ts +338 -0
- package/ts/requests/image.ts +4 -2
- package/ts/requests/runtimesession.ts +11 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import type { TSha256Digest } from './immutableimage.js';
|
|
2
|
+
import { isSha256Digest } from './immutableimage.js';
|
|
3
|
+
import type { IRuntimeAssignmentReferenceV1, IRuntimeAssignmentV1 } from './runtimeassignment.js';
|
|
4
|
+
import { evaluateRuntimeAssignmentV1, getRuntimeAssignmentV1Reference,
|
|
5
|
+
snapshotRuntimeAssignmentV1 } from './runtimeassignment.js';
|
|
6
|
+
import type { IRuntimeSessionBindingV1, TRuntimeObservationAuthorityUseV1 } from './runtimesession.js';
|
|
7
|
+
import { bindRuntimeReporterSessionV1, snapshotRuntimeSessionBindingV1 } from './runtimesession.js';
|
|
8
|
+
import { canonicalizeStrictJson, createCanonicalJsonSha256Hex } from '../private/canonicaljson.js';
|
|
9
|
+
|
|
10
|
+
/** Exact value-free native terminal evidence; terminal inspection does not collect addresses. */
|
|
11
|
+
export interface IRuntimeAssignmentTerminalEvidenceV1 {
|
|
12
|
+
phase: 'stopped' | 'absent';
|
|
13
|
+
sandboxId: string | null;
|
|
14
|
+
containerId: string | null;
|
|
15
|
+
imageConfigDigest: TSha256Digest | null;
|
|
16
|
+
/** Exactly empty for the qualified native stop/remove path; no route evidence. */
|
|
17
|
+
ipAddresses: string[];
|
|
18
|
+
exitCode: number | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Qualified only when the named Pallet owner has durably joined this exact native
|
|
23
|
+
* operation. Structure and digest alone do not prove that join. Pending, unknown
|
|
24
|
+
* and host-fenced journal entries can never issue this receipt. A new boot permits
|
|
25
|
+
* a new joined terminal operation; it cannot replace the stop/remove chain.
|
|
26
|
+
*
|
|
27
|
+
* This settles only the named Pallet attempt. It grants no readiness, routing,
|
|
28
|
+
* liveness, storage relocation, whole-host, Docker/Coreflow absence, other
|
|
29
|
+
* controller or cross-organization authority. It has no authoritative wall clock.
|
|
30
|
+
*/
|
|
31
|
+
export interface IRuntimeAssignmentTerminalReceiptV1 {
|
|
32
|
+
schemaVersion: 1;
|
|
33
|
+
/** Exact generation 2 stop or generation 3 remove revision. */
|
|
34
|
+
assignment: IRuntimeAssignmentReferenceV1;
|
|
35
|
+
/** Exact immutable generation 1 run revision. */
|
|
36
|
+
runAssignment: IRuntimeAssignmentReferenceV1;
|
|
37
|
+
reporterSessionId: string;
|
|
38
|
+
sequence: 1 | 2;
|
|
39
|
+
operation: {
|
|
40
|
+
id: string;
|
|
41
|
+
action: 'stop' | 'remove';
|
|
42
|
+
bootId: string;
|
|
43
|
+
status: 'completed';
|
|
44
|
+
evidence: IRuntimeAssignmentTerminalEvidenceV1;
|
|
45
|
+
};
|
|
46
|
+
/** Null for stop; the exact accepted stop receipt digest for remove. */
|
|
47
|
+
previousReceiptDigest: TSha256Digest | null;
|
|
48
|
+
digest: TSha256Digest;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface IReportRuntimeAssignmentTerminalReceiptRequestV1 {
|
|
52
|
+
/** Current physical peer binding, even when the immutable receipt is historical. */
|
|
53
|
+
session: IRuntimeSessionBindingV1;
|
|
54
|
+
receipt: IRuntimeAssignmentTerminalReceiptV1;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface IReportRuntimeAssignmentTerminalReceiptResponseV1 {
|
|
58
|
+
/** Exact immutable body after the owning durable receipt transaction. */
|
|
59
|
+
receipt: IRuntimeAssignmentTerminalReceiptV1;
|
|
60
|
+
/** Historical recovery never restores active readiness, routes or liveness. */
|
|
61
|
+
authorityUse: TRuntimeObservationAuthorityUseV1;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type TRuntimeAssignmentTerminalReceiptDecisionV1 =
|
|
65
|
+
| { status: 'accept' | 'replay'; receipt: IRuntimeAssignmentTerminalReceiptV1 }
|
|
66
|
+
| { status: 'reject'; reason: 'invalid' | 'conflict' | 'sequence-gap' | 'predecessor-mismatch' };
|
|
67
|
+
|
|
68
|
+
export const runtimeAssignmentTerminalReceiptV1Contract = Object.freeze({
|
|
69
|
+
digestDomain: 'serve.zone/runtime-assignment-terminal-receipt-v1',
|
|
70
|
+
maximumReceiptCanonicalBytes: 16_384,
|
|
71
|
+
maximumHistoryCanonicalBytes: 98_304,
|
|
72
|
+
maximumReceipts: 2,
|
|
73
|
+
maximumAssignmentRevisions: 3,
|
|
74
|
+
error: 'Runtime terminal receipt contract rejected.',
|
|
75
|
+
} as const);
|
|
76
|
+
|
|
77
|
+
const fail = (): never => { throw new Error(runtimeAssignmentTerminalReceiptV1Contract.error); };
|
|
78
|
+
function requireValue(conditionArg: unknown): asserts conditionArg { if (!conditionArg) fail(); }
|
|
79
|
+
const safe = <T>(operationArg: () => T): T => { try { return operationArg(); } catch { return fail(); } };
|
|
80
|
+
const canonical = (valueArg: unknown): string => canonicalizeStrictJson(valueArg, fail);
|
|
81
|
+
const encoder = new TextEncoder();
|
|
82
|
+
const identifier = (valueArg: unknown): string => {
|
|
83
|
+
requireValue(typeof valueArg === 'string' && /^[A-Za-z0-9][A-Za-z0-9:._-]{0,199}$/.test(valueArg));
|
|
84
|
+
return valueArg;
|
|
85
|
+
};
|
|
86
|
+
const digest = (valueArg: unknown): TSha256Digest => {
|
|
87
|
+
requireValue(typeof valueArg === 'string' && isSha256Digest(valueArg));
|
|
88
|
+
return valueArg;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/** Bounded inert copy before hashing or calling any nested validator. */
|
|
92
|
+
const capture = (valueArg: unknown, maximumBytesArg: number = runtimeAssignmentTerminalReceiptV1Contract.maximumHistoryCanonicalBytes): unknown => safe(() => {
|
|
93
|
+
let nodes = 0;
|
|
94
|
+
let bytes = 0;
|
|
95
|
+
const ancestors = new Set<object>();
|
|
96
|
+
const account = (value: string): void => {
|
|
97
|
+
requireValue(value.length <= maximumBytesArg);
|
|
98
|
+
bytes += encoder.encode(value).byteLength;
|
|
99
|
+
requireValue(bytes <= maximumBytesArg);
|
|
100
|
+
};
|
|
101
|
+
const copy = (value: unknown, depth: number): unknown => {
|
|
102
|
+
requireValue(++nodes <= 4096 && depth <= 12);
|
|
103
|
+
if (typeof value === 'string') { account(value); return value; }
|
|
104
|
+
if (value === null || typeof value === 'boolean' || typeof value === 'number') return value;
|
|
105
|
+
requireValue(value !== null && typeof value === 'object' && !ancestors.has(value));
|
|
106
|
+
const array = Array.isArray(value);
|
|
107
|
+
const prototype = Object.getPrototypeOf(value);
|
|
108
|
+
requireValue(array ? prototype === Array.prototype : prototype === Object.prototype || prototype === null);
|
|
109
|
+
ancestors.add(value);
|
|
110
|
+
try {
|
|
111
|
+
const keys = Reflect.ownKeys(value);
|
|
112
|
+
requireValue(keys.length <= 65);
|
|
113
|
+
if (array) {
|
|
114
|
+
const length = Object.getOwnPropertyDescriptor(value, 'length')?.value;
|
|
115
|
+
requireValue(Number.isSafeInteger(length) && length >= 0 && length <= 64 && keys.length === length + 1);
|
|
116
|
+
const result: unknown[] = [];
|
|
117
|
+
for (let index = 0; index < length; index++) {
|
|
118
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
119
|
+
requireValue(descriptor?.enumerable && Object.hasOwn(descriptor, 'value'));
|
|
120
|
+
result.push(copy(descriptor.value, depth + 1));
|
|
121
|
+
}
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
const result: Record<string, unknown> = Object.create(null);
|
|
125
|
+
for (const key of keys) {
|
|
126
|
+
requireValue(typeof key === 'string'); account(key);
|
|
127
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
128
|
+
requireValue(descriptor?.enumerable && Object.hasOwn(descriptor, 'value'));
|
|
129
|
+
result[key] = copy(descriptor.value, depth + 1);
|
|
130
|
+
}
|
|
131
|
+
return result;
|
|
132
|
+
} finally { ancestors.delete(value); }
|
|
133
|
+
};
|
|
134
|
+
const serialized = canonical(copy(valueArg, 0));
|
|
135
|
+
requireValue(encoder.encode(serialized).byteLength <= maximumBytesArg);
|
|
136
|
+
return JSON.parse(serialized);
|
|
137
|
+
});
|
|
138
|
+
const record = (valueArg: unknown, keysArg: string[]): Record<string, unknown> => {
|
|
139
|
+
requireValue(valueArg !== null && typeof valueArg === 'object' && !Array.isArray(valueArg));
|
|
140
|
+
const keys = Object.keys(valueArg);
|
|
141
|
+
requireValue(keys.length === keysArg.length && keys.every((key) => keysArg.includes(key)));
|
|
142
|
+
return valueArg as Record<string, unknown>;
|
|
143
|
+
};
|
|
144
|
+
const reference = (valueArg: unknown): IRuntimeAssignmentReferenceV1 => {
|
|
145
|
+
const value = record(valueArg, ['assignmentId', 'generation', 'digest']);
|
|
146
|
+
requireValue(typeof value.generation === 'number' && Number.isSafeInteger(value.generation)
|
|
147
|
+
&& value.generation >= 1 && value.generation <= 3);
|
|
148
|
+
return { assignmentId: identifier(value.assignmentId), generation: value.generation, digest: digest(value.digest) };
|
|
149
|
+
};
|
|
150
|
+
const evidence = (valueArg: unknown): IRuntimeAssignmentTerminalEvidenceV1 => {
|
|
151
|
+
const value = record(valueArg, ['phase', 'sandboxId', 'containerId', 'imageConfigDigest', 'ipAddresses', 'exitCode']);
|
|
152
|
+
requireValue(value.phase === 'absent' || value.phase === 'stopped');
|
|
153
|
+
for (const field of ['sandboxId', 'containerId']) requireValue(value[field] === null
|
|
154
|
+
|| (typeof value[field] === 'string' && /^[a-f0-9]{64}$/.test(value[field])));
|
|
155
|
+
requireValue(value.imageConfigDigest === null || (typeof value.imageConfigDigest === 'string'
|
|
156
|
+
&& isSha256Digest(value.imageConfigDigest)));
|
|
157
|
+
requireValue(Array.isArray(value.ipAddresses) && value.ipAddresses.length === 0);
|
|
158
|
+
requireValue(value.exitCode === null || (typeof value.exitCode === 'number' && Number.isSafeInteger(value.exitCode)
|
|
159
|
+
&& value.exitCode >= -2_147_483_648 && value.exitCode <= 2_147_483_647));
|
|
160
|
+
requireValue(value.phase === 'absent'
|
|
161
|
+
? value.sandboxId === null && value.containerId === null && value.imageConfigDigest === null && value.exitCode === null
|
|
162
|
+
: value.sandboxId !== null);
|
|
163
|
+
requireValue(value.containerId !== null || (value.imageConfigDigest === null && value.exitCode === null));
|
|
164
|
+
return value as unknown as IRuntimeAssignmentTerminalEvidenceV1;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
/** Detached exact-schema assertion only; digest, journal provenance and authority remain unverified. */
|
|
168
|
+
export const snapshotRuntimeAssignmentTerminalReceiptV1 = (valueArg: unknown): IRuntimeAssignmentTerminalReceiptV1 => safe(() => {
|
|
169
|
+
const value = record(capture(valueArg, runtimeAssignmentTerminalReceiptV1Contract.maximumReceiptCanonicalBytes),
|
|
170
|
+
['schemaVersion', 'assignment', 'runAssignment', 'reporterSessionId', 'sequence', 'operation', 'previousReceiptDigest', 'digest']);
|
|
171
|
+
const assignment = reference(value.assignment);
|
|
172
|
+
const runAssignment = reference(value.runAssignment);
|
|
173
|
+
const operation = record(value.operation, ['id', 'action', 'bootId', 'status', 'evidence']);
|
|
174
|
+
const terminalEvidence = evidence(operation.evidence);
|
|
175
|
+
requireValue(value.schemaVersion === 1 && (value.sequence === 1 || value.sequence === 2)
|
|
176
|
+
&& operation.status === 'completed' && assignment.generation === value.sequence + 1
|
|
177
|
+
&& runAssignment.generation === 1 && runAssignment.assignmentId === assignment.assignmentId);
|
|
178
|
+
requireValue(typeof operation.bootId === 'string'
|
|
179
|
+
&& /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(operation.bootId));
|
|
180
|
+
requireValue(value.sequence === 1 ? operation.action === 'stop' && value.previousReceiptDigest === null
|
|
181
|
+
: operation.action === 'remove' && terminalEvidence.phase === 'absent' && value.previousReceiptDigest !== null);
|
|
182
|
+
return { schemaVersion: 1, assignment, runAssignment, reporterSessionId: identifier(value.reporterSessionId),
|
|
183
|
+
sequence: value.sequence, operation: { id: identifier(operation.id), action: operation.action as 'stop' | 'remove',
|
|
184
|
+
bootId: operation.bootId, status: 'completed', evidence: terminalEvidence },
|
|
185
|
+
previousReceiptDigest: value.previousReceiptDigest === null ? null : digest(value.previousReceiptDigest), digest: digest(value.digest) };
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
/** Bounded exact receipt history for synchronous persistence assertions; hashes remain unchecked. */
|
|
189
|
+
export const snapshotRuntimeAssignmentTerminalReceiptHistoryV1 = (valueArg: unknown): IRuntimeAssignmentTerminalReceiptV1[] => safe(() => {
|
|
190
|
+
const values = capture(valueArg);
|
|
191
|
+
requireValue(Array.isArray(values) && values.length <= runtimeAssignmentTerminalReceiptV1Contract.maximumReceipts);
|
|
192
|
+
const receipts = values.map(snapshotRuntimeAssignmentTerminalReceiptV1);
|
|
193
|
+
for (const [index, receipt] of receipts.entries()) {
|
|
194
|
+
requireValue(receipt.sequence === index + 1);
|
|
195
|
+
if (index > 0) requireValue(receipt.previousReceiptDigest === receipts[0].digest
|
|
196
|
+
&& canonical(receipt.runAssignment) === canonical(receipts[0].runAssignment)
|
|
197
|
+
&& receipt.operation.id !== receipts[0].operation.id);
|
|
198
|
+
}
|
|
199
|
+
return receipts;
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const assignments = (valueArg: unknown): IRuntimeAssignmentV1[] => {
|
|
203
|
+
const values = capture(valueArg);
|
|
204
|
+
requireValue(Array.isArray(values) && values.length >= 2
|
|
205
|
+
&& values.length <= runtimeAssignmentTerminalReceiptV1Contract.maximumAssignmentRevisions);
|
|
206
|
+
return values.map(snapshotRuntimeAssignmentV1);
|
|
207
|
+
};
|
|
208
|
+
const checkAssignmentHistory = async (historyArg: IRuntimeAssignmentV1[]): Promise<void> => {
|
|
209
|
+
const run = historyArg[0];
|
|
210
|
+
const scope = { controller: run.controller, organizationId: run.organizationId,
|
|
211
|
+
nodeId: run.nodeId, runtimeNamespace: run.runtimeNamespace };
|
|
212
|
+
for (const [index, assignment] of historyArg.entries()) {
|
|
213
|
+
requireValue(assignment.generation === index + 1
|
|
214
|
+
&& assignment.disposition === ['run', 'stop-preserve', 'remove-runtime-preserve-storage'][index]);
|
|
215
|
+
requireValue((await evaluateRuntimeAssignmentV1(assignment, index === 0 ? null : historyArg[index - 1], scope)).status === 'accept');
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
const computeSnapshotDigest = async (receiptArg: IRuntimeAssignmentTerminalReceiptV1): Promise<TSha256Digest> => {
|
|
219
|
+
const { digest: _digest, ...payload } = receiptArg;
|
|
220
|
+
return `sha256:${await createCanonicalJsonSha256Hex(canonical({
|
|
221
|
+
domain: runtimeAssignmentTerminalReceiptV1Contract.digestDomain, payload,
|
|
222
|
+
}), fail)}`;
|
|
223
|
+
};
|
|
224
|
+
/** Domain-separated exact body digest; captures before asynchronous hashing. */
|
|
225
|
+
export const computeRuntimeAssignmentTerminalReceiptV1Digest = async (
|
|
226
|
+
valueArg: IRuntimeAssignmentTerminalReceiptV1,
|
|
227
|
+
): Promise<TSha256Digest> => {
|
|
228
|
+
try { return await computeSnapshotDigest(snapshotRuntimeAssignmentTerminalReceiptV1(valueArg)); } catch { return fail(); }
|
|
229
|
+
};
|
|
230
|
+
const checkReceipt = async (
|
|
231
|
+
receiptArg: IRuntimeAssignmentTerminalReceiptV1, historyArg: IRuntimeAssignmentV1[],
|
|
232
|
+
previousArg: IRuntimeAssignmentTerminalReceiptV1 | null,
|
|
233
|
+
): Promise<void> => {
|
|
234
|
+
requireValue(historyArg.length > receiptArg.sequence
|
|
235
|
+
&& canonical(receiptArg.runAssignment) === canonical(getRuntimeAssignmentV1Reference(historyArg[0]))
|
|
236
|
+
&& canonical(receiptArg.assignment) === canonical(getRuntimeAssignmentV1Reference(historyArg[receiptArg.sequence]))
|
|
237
|
+
&& receiptArg.digest === await computeSnapshotDigest(receiptArg));
|
|
238
|
+
if (receiptArg.sequence === 1) requireValue(previousArg === null);
|
|
239
|
+
else {
|
|
240
|
+
requireValue(previousArg !== null && previousArg.sequence === 1);
|
|
241
|
+
await checkReceipt(previousArg, historyArg, null);
|
|
242
|
+
requireValue(receiptArg.previousReceiptDigest === previousArg.digest && receiptArg.operation.id !== previousArg.operation.id);
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
/** Full immutable run/stop/remove chain and exact prior stop receipt are mandatory for removal. */
|
|
247
|
+
export const validateRuntimeAssignmentTerminalReceiptV1 = async (
|
|
248
|
+
valueArg: unknown, assignmentHistoryArg: unknown, previousReceiptArg: unknown,
|
|
249
|
+
): Promise<string[]> => {
|
|
250
|
+
try {
|
|
251
|
+
const receipt = snapshotRuntimeAssignmentTerminalReceiptV1(valueArg);
|
|
252
|
+
const history = assignments(assignmentHistoryArg);
|
|
253
|
+
const previous = previousReceiptArg === null ? null : snapshotRuntimeAssignmentTerminalReceiptV1(previousReceiptArg);
|
|
254
|
+
await checkAssignmentHistory(history);
|
|
255
|
+
await checkReceipt(receipt, history, previous);
|
|
256
|
+
return [];
|
|
257
|
+
} catch { return [runtimeAssignmentTerminalReceiptV1Contract.error]; }
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Pure bounded CAS decision. Persist under the owning transaction and current
|
|
262
|
+
* credential/peer fence. New acceptance also requires the exact global incumbent;
|
|
263
|
+
* only a previously persisted exact body can replay after the incumbent changes.
|
|
264
|
+
* The helper cannot prove either condition. Retain both bodies after removal.
|
|
265
|
+
*/
|
|
266
|
+
export const evaluateRuntimeAssignmentTerminalReceiptV1 = async (
|
|
267
|
+
valueArg: unknown, acceptedHistoryArg: unknown, assignmentHistoryArg: unknown,
|
|
268
|
+
): Promise<TRuntimeAssignmentTerminalReceiptDecisionV1> => {
|
|
269
|
+
try {
|
|
270
|
+
const receipt = snapshotRuntimeAssignmentTerminalReceiptV1(valueArg);
|
|
271
|
+
const accepted = snapshotRuntimeAssignmentTerminalReceiptHistoryV1(acceptedHistoryArg);
|
|
272
|
+
const history = assignments(assignmentHistoryArg);
|
|
273
|
+
await checkAssignmentHistory(history);
|
|
274
|
+
for (const existing of accepted) await checkReceipt(existing, history, existing.sequence === 1 ? null : accepted[0]);
|
|
275
|
+
if (receipt.digest !== await computeSnapshotDigest(receipt)
|
|
276
|
+
|| history.length <= receipt.sequence
|
|
277
|
+
|| canonical(receipt.runAssignment) !== canonical(getRuntimeAssignmentV1Reference(history[0]))
|
|
278
|
+
|| canonical(receipt.assignment) !== canonical(getRuntimeAssignmentV1Reference(history[receipt.sequence]))) {
|
|
279
|
+
return { status: 'reject', reason: 'invalid' };
|
|
280
|
+
}
|
|
281
|
+
const existing = accepted[receipt.sequence - 1];
|
|
282
|
+
if (existing) return canonical(existing) === canonical(receipt)
|
|
283
|
+
? { status: 'replay', receipt: existing } : { status: 'reject', reason: 'conflict' };
|
|
284
|
+
if (receipt.sequence !== accepted.length + 1) return { status: 'reject', reason: 'sequence-gap' };
|
|
285
|
+
if (receipt.sequence === 2 && (receipt.previousReceiptDigest !== accepted[0].digest
|
|
286
|
+
|| receipt.operation.id === accepted[0].operation.id)) return { status: 'reject', reason: 'predecessor-mismatch' };
|
|
287
|
+
return { status: 'accept', receipt };
|
|
288
|
+
} catch { return { status: 'reject', reason: 'invalid' }; }
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
export const snapshotReportRuntimeAssignmentTerminalReceiptRequestV1 = (
|
|
292
|
+
valueArg: unknown,
|
|
293
|
+
): IReportRuntimeAssignmentTerminalReceiptRequestV1 => safe(() => {
|
|
294
|
+
const value = record(capture(valueArg), ['session', 'receipt']);
|
|
295
|
+
return { session: snapshotRuntimeSessionBindingV1(value.session), receipt: snapshotRuntimeAssignmentTerminalReceiptV1(value.receipt) };
|
|
296
|
+
});
|
|
297
|
+
export const snapshotReportRuntimeAssignmentTerminalReceiptResponseV1 = (
|
|
298
|
+
valueArg: unknown,
|
|
299
|
+
): IReportRuntimeAssignmentTerminalReceiptResponseV1 => safe(() => {
|
|
300
|
+
const value = record(capture(valueArg), ['receipt', 'authorityUse']);
|
|
301
|
+
requireValue(value.authorityUse === 'current-session' || value.authorityUse === 'historical-recovery');
|
|
302
|
+
return { receipt: snapshotRuntimeAssignmentTerminalReceiptV1(value.receipt), authorityUse: value.authorityUse };
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* CURRENT physical session and immutable server-indexed reporter history are
|
|
307
|
+
* trusted owner inputs, never wire claims. The previous stop receipt must also
|
|
308
|
+
* come from the durable owner. This binds content; it does not authenticate,
|
|
309
|
+
* prove the local operation join, advance CAS, or admit a replacement.
|
|
310
|
+
*/
|
|
311
|
+
export const bindReportRuntimeAssignmentTerminalReceiptRequestV1 = async (
|
|
312
|
+
valueArg: unknown, assignmentHistoryArg: unknown, previousReceiptArg: unknown,
|
|
313
|
+
trustedSessionArg: unknown, reporterHistoryArg: unknown,
|
|
314
|
+
): Promise<{ request: IReportRuntimeAssignmentTerminalReceiptRequestV1; authorityUse: TRuntimeObservationAuthorityUseV1 }> => {
|
|
315
|
+
try {
|
|
316
|
+
const request = snapshotReportRuntimeAssignmentTerminalReceiptRequestV1(valueArg);
|
|
317
|
+
const history = assignments(assignmentHistoryArg);
|
|
318
|
+
const previous = previousReceiptArg === null ? null : snapshotRuntimeAssignmentTerminalReceiptV1(previousReceiptArg);
|
|
319
|
+
requireValue(history.length > request.receipt.sequence);
|
|
320
|
+
const binding = bindRuntimeReporterSessionV1(request.session, request.receipt.reporterSessionId,
|
|
321
|
+
history[request.receipt.sequence], trustedSessionArg, reporterHistoryArg);
|
|
322
|
+
await checkAssignmentHistory(history);
|
|
323
|
+
await checkReceipt(request.receipt, history, previous);
|
|
324
|
+
return { request, authorityUse: binding.authorityUse };
|
|
325
|
+
} catch { return fail(); }
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
/** Exact ACK of the immutable outbox body; no rewritten session, operation or chain. */
|
|
329
|
+
export const bindReportRuntimeAssignmentTerminalReceiptResponseV1 = (
|
|
330
|
+
valueArg: unknown, requestArg: unknown,
|
|
331
|
+
): IReportRuntimeAssignmentTerminalReceiptResponseV1 => safe(() => {
|
|
332
|
+
const response = snapshotReportRuntimeAssignmentTerminalReceiptResponseV1(valueArg);
|
|
333
|
+
const request = snapshotReportRuntimeAssignmentTerminalReceiptRequestV1(requestArg);
|
|
334
|
+
requireValue(canonical(response.receipt) === canonical(request.receipt)
|
|
335
|
+
&& response.authorityUse === (request.receipt.reporterSessionId === request.session.reporterSessionId
|
|
336
|
+
? 'current-session' : 'historical-recovery'));
|
|
337
|
+
return response;
|
|
338
|
+
});
|
package/ts/requests/image.ts
CHANGED
|
@@ -59,7 +59,8 @@ export interface IRequest_PushImageVersion extends plugins.typedrequestInterface
|
|
|
59
59
|
identity: userInterfaces.IIdentity;
|
|
60
60
|
imageId: string;
|
|
61
61
|
versionString: string;
|
|
62
|
-
|
|
62
|
+
/** Requester sends archive bytes; TypedHandler receives the opposite endpoint. */
|
|
63
|
+
imageStream: plugins.typedrequestInterfaces.TVirtualStream<'send'>;
|
|
63
64
|
};
|
|
64
65
|
response: {
|
|
65
66
|
allowed: boolean;
|
|
@@ -77,7 +78,8 @@ export interface IRequest_PullImageVersion extends plugins.typedrequestInterface
|
|
|
77
78
|
versionString: string;
|
|
78
79
|
};
|
|
79
80
|
response: {
|
|
80
|
-
|
|
81
|
+
/** Requester receives archive bytes; TypedHandler returns a sender endpoint. */
|
|
82
|
+
imageStream: plugins.typedrequestInterfaces.TVirtualStream<'receive'>;
|
|
81
83
|
};
|
|
82
84
|
}
|
|
83
85
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as plugins from '../plugins.js';
|
|
2
2
|
import type * as data from '../data/runtimesession.js';
|
|
3
|
+
import type * as terminal from '../data/runtimeterminalreceipt.js';
|
|
3
4
|
|
|
4
5
|
/** WSS-only Pallet bootstrap. Derive physical peer/process/idempotency server-side. */
|
|
5
6
|
export interface IReq_Pallet_Cloudly_RegisterPalletRuntimeSessionV1 extends plugins.typedrequestInterfaces.implementsTR<
|
|
@@ -31,6 +32,16 @@ export interface IReq_Pallet_Controller_ReportRuntimeAssignmentObservationV1 ext
|
|
|
31
32
|
response: data.IReportRuntimeAssignmentObservationResponseV1;
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
/** Exact joined terminal-operation receipt; observations and boot changes cannot substitute. */
|
|
36
|
+
export interface IReq_Pallet_Controller_ReportRuntimeAssignmentTerminalReceiptV1 extends plugins.typedrequestInterfaces.implementsTR<
|
|
37
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
38
|
+
IReq_Pallet_Controller_ReportRuntimeAssignmentTerminalReceiptV1
|
|
39
|
+
> {
|
|
40
|
+
method: 'reportRuntimeAssignmentTerminalReceiptV1';
|
|
41
|
+
request: terminal.IReportRuntimeAssignmentTerminalReceiptRequestV1;
|
|
42
|
+
response: terminal.IReportRuntimeAssignmentTerminalReceiptResponseV1;
|
|
43
|
+
}
|
|
44
|
+
|
|
34
45
|
/** Current-peer, exact current assignment authorization; response is secret material. */
|
|
35
46
|
export interface IReq_Pallet_Controller_GetRuntimeRegistryCredentialV1 extends plugins.typedrequestInterfaces.implementsTR<
|
|
36
47
|
plugins.typedrequestInterfaces.ITypedRequest,
|